package http import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "net/http" "net/http/httptest" "testing" "time" "github.com/influxdata/httprouter" "github.com/influxdata/influxdb" platform "github.com/influxdata/influxdb" "github.com/influxdata/influxdb/inmem" kithttp "github.com/influxdata/influxdb/kit/transport/http" "github.com/influxdata/influxdb/kv" "github.com/influxdata/influxdb/mock" "github.com/influxdata/influxdb/pkg/httpc" platformtesting "github.com/influxdata/influxdb/testing" "go.uber.org/zap" "go.uber.org/zap/zaptest" ) // NewMockBucketBackend returns a BucketBackend with mock services. func NewMockBucketBackend(t *testing.T) *BucketBackend { return &BucketBackend{ log: zaptest.NewLogger(t).With(zap.String("handler", "bucket")), BucketService: mock.NewBucketService(), BucketOperationLogService: mock.NewBucketOperationLogService(), UserResourceMappingService: mock.NewUserResourceMappingService(), LabelService: mock.NewLabelService(), UserService: mock.NewUserService(), OrganizationService: mock.NewOrganizationService(), } } func TestService_handleGetBuckets(t *testing.T) { type fields struct { BucketService platform.BucketService LabelService platform.LabelService } type args struct { queryParams map[string][]string } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "get all buckets", fields: fields{ &mock.BucketService{ FindBucketsFn: func(ctx context.Context, filter platform.BucketFilter, opts ...platform.FindOptions) ([]*platform.Bucket, int, error) { return []*platform.Bucket{ { ID: platformtesting.MustIDBase16("0b501e7e557ab1ed"), Name: "hello", OrgID: platformtesting.MustIDBase16("50f7ba1150f7ba11"), RetentionPeriod: 2 * time.Second, }, { ID: platformtesting.MustIDBase16("c0175f0077a77005"), Name: "example", OrgID: platformtesting.MustIDBase16("7e55e118dbabb1ed"), RetentionPeriod: 24 * time.Hour, }, }, 2, nil }, }, &mock.LabelService{ FindResourceLabelsFn: func(ctx context.Context, f platform.LabelMappingFilter) ([]*platform.Label, error) { labels := []*platform.Label{ { ID: platformtesting.MustIDBase16("fc3dc670a4be9b9a"), Name: "label", Properties: map[string]string{ "color": "fff000", }, }, } return labels, nil }, }, }, args: args{ map[string][]string{ "limit": {"1"}, }, }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "self": "/api/v2/buckets?descending=false&limit=1&offset=0", "next": "/api/v2/buckets?descending=false&limit=1&offset=1" }, "buckets": [ { "links": { "org": "/api/v2/orgs/50f7ba1150f7ba11", "self": "/api/v2/buckets/0b501e7e557ab1ed", "logs": "/api/v2/buckets/0b501e7e557ab1ed/logs", "labels": "/api/v2/buckets/0b501e7e557ab1ed/labels", "owners": "/api/v2/buckets/0b501e7e557ab1ed/owners", "members": "/api/v2/buckets/0b501e7e557ab1ed/members", "write": "/api/v2/write?org=50f7ba1150f7ba11&bucket=0b501e7e557ab1ed" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "0b501e7e557ab1ed", "orgID": "50f7ba1150f7ba11", "type": "user", "name": "hello", "retentionRules": [{"type": "expire", "everySeconds": 2}], "labels": [ { "id": "fc3dc670a4be9b9a", "name": "label", "properties": { "color": "fff000" } } ] }, { "links": { "org": "/api/v2/orgs/7e55e118dbabb1ed", "self": "/api/v2/buckets/c0175f0077a77005", "logs": "/api/v2/buckets/c0175f0077a77005/logs", "labels": "/api/v2/buckets/c0175f0077a77005/labels", "members": "/api/v2/buckets/c0175f0077a77005/members", "owners": "/api/v2/buckets/c0175f0077a77005/owners", "write": "/api/v2/write?org=7e55e118dbabb1ed&bucket=c0175f0077a77005" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "c0175f0077a77005", "orgID": "7e55e118dbabb1ed", "type": "user", "name": "example", "retentionRules": [{"type": "expire", "everySeconds": 86400}], "labels": [ { "id": "fc3dc670a4be9b9a", "name": "label", "properties": { "color": "fff000" } } ] } ] } `, }, }, { name: "get all buckets when there are none", fields: fields{ &mock.BucketService{ FindBucketsFn: func(ctx context.Context, filter platform.BucketFilter, opts ...platform.FindOptions) ([]*platform.Bucket, int, error) { return []*platform.Bucket{}, 0, nil }, }, &mock.LabelService{}, }, args: args{ map[string][]string{ "limit": {"1"}, }, }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "self": "/api/v2/buckets?descending=false&limit=1&offset=0" }, "buckets": [] }`, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.BucketService = tt.fields.BucketService bucketBackend.LabelService = tt.fields.LabelService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) r := httptest.NewRequest("GET", "http://any.url", nil) qp := r.URL.Query() for k, vs := range tt.args.queryParams { for _, v := range vs { qp.Add(k, v) } } r.URL.RawQuery = qp.Encode() w := httptest.NewRecorder() h.handleGetBuckets(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handleGetBuckets() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handleGetBuckets() = %v, want %v", tt.name, content, tt.wants.contentType) } if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil || tt.wants.body != "" && !eq { t.Errorf("%q. handleGetBuckets() = ***%v***", tt.name, diff) } }) } } func TestService_handleGetBucket(t *testing.T) { type fields struct { BucketService platform.BucketService } type args struct { id string } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "get a bucket by id", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id platform.ID) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), OrgID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", RetentionPeriod: 30 * time.Second, }, nil } return nil, fmt.Errorf("not found") }, }, }, args: args{ id: "020f755c3c082000", }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "org": "/api/v2/orgs/020f755c3c082000", "self": "/api/v2/buckets/020f755c3c082000", "logs": "/api/v2/buckets/020f755c3c082000/logs", "labels": "/api/v2/buckets/020f755c3c082000/labels", "members": "/api/v2/buckets/020f755c3c082000/members", "owners": "/api/v2/buckets/020f755c3c082000/owners", "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "020f755c3c082000", "orgID": "020f755c3c082000", "type": "user", "name": "hello", "retentionRules": [{"type": "expire", "everySeconds": 30}], "labels": [] } `, }, }, { name: "not found", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id platform.ID) (*platform.Bucket, error) { return nil, &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, }, }, args: args{ id: "020f755c3c082000", }, wants: wants{ statusCode: http.StatusNotFound, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) bucketBackend.BucketService = tt.fields.BucketService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) r := httptest.NewRequest("GET", "http://any.url", nil) r = r.WithContext(context.WithValue( context.Background(), httprouter.ParamsKey, httprouter.Params{ { Key: "id", Value: tt.args.id, }, })) w := httptest.NewRecorder() h.handleGetBucket(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) t.Logf(res.Header.Get("X-Influx-Error")) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handleGetBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handleGetBucket() = %v, want %v", tt.name, content, tt.wants.contentType) } if tt.wants.body != "" { if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handleGetBucket(). error unmarshaling json %v", tt.name, err) } else if !eq { t.Errorf("%q. handleGetBucket() = ***%s***", tt.name, diff) } } }) } } func TestService_handlePostBucket(t *testing.T) { type fields struct { BucketService platform.BucketService OrganizationService platform.OrganizationService } type args struct { bucket *platform.Bucket } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "create a new bucket", fields: fields{ BucketService: &mock.BucketService{ CreateBucketFn: func(ctx context.Context, c *platform.Bucket) error { c.ID = platformtesting.MustIDBase16("020f755c3c082000") return nil }, }, OrganizationService: &mock.OrganizationService{ FindOrganizationF: func(ctx context.Context, f platform.OrganizationFilter) (*platform.Organization, error) { return &platform.Organization{ID: platformtesting.MustIDBase16("6f626f7274697320")}, nil }, }, }, args: args{ bucket: &platform.Bucket{ Name: "hello", OrgID: platformtesting.MustIDBase16("6f626f7274697320"), }, }, wants: wants{ statusCode: http.StatusCreated, contentType: "application/json; charset=utf-8", body: ` { "links": { "org": "/api/v2/orgs/6f626f7274697320", "self": "/api/v2/buckets/020f755c3c082000", "logs": "/api/v2/buckets/020f755c3c082000/logs", "labels": "/api/v2/buckets/020f755c3c082000/labels", "members": "/api/v2/buckets/020f755c3c082000/members", "owners": "/api/v2/buckets/020f755c3c082000/owners", "write": "/api/v2/write?org=6f626f7274697320&bucket=020f755c3c082000" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "020f755c3c082000", "orgID": "6f626f7274697320", "type": "user", "name": "hello", "retentionRules": [], "labels": [] } `, }, }, { name: "create a new bucket with invalid name", fields: fields{ BucketService: &mock.BucketService{ CreateBucketFn: func(ctx context.Context, c *platform.Bucket) error { c.ID = platformtesting.MustIDBase16("020f755c3c082000") return nil }, }, OrganizationService: &mock.OrganizationService{ FindOrganizationF: func(ctx context.Context, f platform.OrganizationFilter) (*platform.Organization, error) { return &platform.Organization{ID: platformtesting.MustIDBase16("6f626f7274697320")}, nil }, }, }, args: args{ bucket: &platform.Bucket{ Name: "_hello", OrgID: platformtesting.MustIDBase16("6f626f7274697320"), }, }, wants: wants{ statusCode: http.StatusUnprocessableEntity, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) bucketBackend.BucketService = tt.fields.BucketService bucketBackend.OrganizationService = tt.fields.OrganizationService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) b, err := json.Marshal(newBucket(tt.args.bucket)) if err != nil { t.Fatalf("failed to unmarshal bucket: %v", err) } r := httptest.NewRequest("GET", "http://any.url?org=30", bytes.NewReader(b)) w := httptest.NewRecorder() h.handlePostBucket(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handlePostBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handlePostBucket() = %v, want %v", tt.name, content, tt.wants.contentType) } if tt.wants.body != "" { if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handlePostBucket(). error unmarshaling json %v", tt.name, err) } else if !eq { t.Errorf("%q. handlePostBucket() = ***%s***", tt.name, diff) } } }) } } func TestService_handleDeleteBucket(t *testing.T) { type fields struct { BucketService platform.BucketService } type args struct { id string } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "remove a bucket by id", fields: fields{ &mock.BucketService{ DeleteBucketFn: func(ctx context.Context, id platform.ID) error { if id == platformtesting.MustIDBase16("020f755c3c082000") { return nil } return fmt.Errorf("wrong id") }, }, }, args: args{ id: "020f755c3c082000", }, wants: wants{ statusCode: http.StatusNoContent, }, }, { name: "bucket not found", fields: fields{ &mock.BucketService{ DeleteBucketFn: func(ctx context.Context, id platform.ID) error { return &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, }, }, args: args{ id: "020f755c3c082000", }, wants: wants{ statusCode: http.StatusNotFound, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) bucketBackend.BucketService = tt.fields.BucketService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) r := httptest.NewRequest("GET", "http://any.url", nil) r = r.WithContext(context.WithValue( context.Background(), httprouter.ParamsKey, httprouter.Params{ { Key: "id", Value: tt.args.id, }, })) w := httptest.NewRecorder() h.handleDeleteBucket(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handleDeleteBucket() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handleDeleteBucket() = %v, want %v", tt.name, content, tt.wants.contentType) } if tt.wants.body != "" { if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handleDeleteBucket(). error unmarshaling json %v", tt.name, err) } else if !eq { t.Errorf("%q. handleDeleteBucket() = ***%s***", tt.name, diff) } } }) } } func TestService_handlePatchBucket(t *testing.T) { type fields struct { BucketService platform.BucketService } type args struct { id string name string retention time.Duration } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "update a bucket name and retention", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), }, nil }, UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { d := &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), } if upd.Name != nil { d.Name = *upd.Name } if upd.RetentionPeriod != nil { d.RetentionPeriod = *upd.RetentionPeriod } return d, nil } return nil, fmt.Errorf("not found") }, }, }, args: args{ id: "020f755c3c082000", name: "example", retention: 2 * time.Second, }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "org": "/api/v2/orgs/020f755c3c082000", "self": "/api/v2/buckets/020f755c3c082000", "logs": "/api/v2/buckets/020f755c3c082000/logs", "labels": "/api/v2/buckets/020f755c3c082000/labels", "members": "/api/v2/buckets/020f755c3c082000/members", "owners": "/api/v2/buckets/020f755c3c082000/owners", "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "020f755c3c082000", "orgID": "020f755c3c082000", "type": "user", "name": "example", "retentionRules": [{"type": "expire", "everySeconds": 2}], "labels": [] } `, }, }, { name: "update a bucket name invalid", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), }, nil } return nil, fmt.Errorf("not found") }, UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), }, nil } return nil, fmt.Errorf("not found") }, }, }, args: args{ id: "020f755c3c082000", name: "_example", }, wants: wants{ statusCode: http.StatusBadRequest, }, }, { name: "bucket not found", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { return nil, &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { return nil, &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, }, }, args: args{ id: "020f755c3c082000", name: "hello", retention: time.Second, }, wants: wants{ statusCode: http.StatusNotFound, }, }, { name: "update bucket to no retention and new name", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), }, nil } return nil, fmt.Errorf("not found") }, UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { d := &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), } if upd.Name != nil { d.Name = *upd.Name } if upd.RetentionPeriod != nil { d.RetentionPeriod = *upd.RetentionPeriod } return d, nil } return nil, fmt.Errorf("not found") }, }, }, args: args{ id: "020f755c3c082000", name: "bucket with no retention", retention: 0, }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "org": "/api/v2/orgs/020f755c3c082000", "self": "/api/v2/buckets/020f755c3c082000", "logs": "/api/v2/buckets/020f755c3c082000/logs", "labels": "/api/v2/buckets/020f755c3c082000/labels", "members": "/api/v2/buckets/020f755c3c082000/members", "owners": "/api/v2/buckets/020f755c3c082000/owners", "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "020f755c3c082000", "orgID": "020f755c3c082000", "type": "user", "name": "bucket with no retention", "retentionRules": [], "labels": [] } `, }, }, { name: "update retention policy to 'nothing'", fields: fields{ &mock.BucketService{ UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { d := &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "b1", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), } if upd.Name != nil { d.Name = *upd.Name } if upd.RetentionPeriod != nil { d.RetentionPeriod = *upd.RetentionPeriod } return d, nil } return nil, &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, }, }, args: args{ id: "020f755c3c082000", retention: 0, }, wants: wants{ statusCode: http.StatusOK, contentType: "application/json; charset=utf-8", body: ` { "links": { "org": "/api/v2/orgs/020f755c3c082000", "self": "/api/v2/buckets/020f755c3c082000", "logs": "/api/v2/buckets/020f755c3c082000/logs", "labels": "/api/v2/buckets/020f755c3c082000/labels", "members": "/api/v2/buckets/020f755c3c082000/members", "owners": "/api/v2/buckets/020f755c3c082000/owners", "write": "/api/v2/write?org=020f755c3c082000&bucket=020f755c3c082000" }, "createdAt": "0001-01-01T00:00:00Z", "updatedAt": "0001-01-01T00:00:00Z", "id": "020f755c3c082000", "orgID": "020f755c3c082000", "type": "user", "name": "b1", "retentionRules": [], "labels": [] } `, }, }, { name: "update a bucket name with invalid retention policy is an error", fields: fields{ &mock.BucketService{ FindBucketByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Bucket, error) { return &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), }, nil }, UpdateBucketFn: func(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) { if id == platformtesting.MustIDBase16("020f755c3c082000") { d := &platform.Bucket{ ID: platformtesting.MustIDBase16("020f755c3c082000"), Name: "hello", OrgID: platformtesting.MustIDBase16("020f755c3c082000"), } if upd.Name != nil { d.Name = *upd.Name } if upd.RetentionPeriod != nil { d.RetentionPeriod = *upd.RetentionPeriod } return d, nil } return nil, &platform.Error{ Code: platform.ENotFound, Msg: "bucket not found", } }, }, }, args: args{ id: "020f755c3c082000", name: "example", retention: -10, }, wants: wants{ statusCode: http.StatusUnprocessableEntity, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) bucketBackend.BucketService = tt.fields.BucketService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) upd := platform.BucketUpdate{} if tt.args.name != "" { upd.Name = &tt.args.name } if tt.args.retention != 0 { upd.RetentionPeriod = &tt.args.retention } b, err := json.Marshal(newBucketUpdate(&upd)) if err != nil { t.Fatalf("failed to unmarshal bucket update: %v", err) } r := httptest.NewRequest("GET", "http://any.url", bytes.NewReader(b)) r = r.WithContext(context.WithValue( context.Background(), httprouter.ParamsKey, httprouter.Params{ { Key: "id", Value: tt.args.id, }, })) w := httptest.NewRecorder() h.handlePatchBucket(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handlePatchBucket() = %v, want %v %v", tt.name, res.StatusCode, tt.wants.statusCode, w.Header()) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handlePatchBucket() = %v, want %v", tt.name, content, tt.wants.contentType) } if tt.wants.body != "" { if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handlePatchBucket(). error unmarshaling json %v", tt.name, err) } else if !eq { t.Errorf("%q. handlePatchBucket() = ***%s***", tt.name, diff) } } }) } } func TestService_handlePostBucketMember(t *testing.T) { type fields struct { UserService platform.UserService } type args struct { bucketID string user *platform.User } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "add a bucket member", fields: fields{ UserService: &mock.UserService{ FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { return &platform.User{ ID: id, Name: "name", Status: platform.Active, }, nil }, }, }, args: args{ bucketID: "020f755c3c082000", user: &platform.User{ ID: platformtesting.MustIDBase16("6f626f7274697320"), }, }, wants: wants{ statusCode: http.StatusCreated, contentType: "application/json; charset=utf-8", body: ` { "links": { "logs": "/api/v2/users/6f626f7274697320/logs", "self": "/api/v2/users/6f626f7274697320" }, "role": "member", "id": "6f626f7274697320", "name": "name", "status": "active" } `, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.UserService = tt.fields.UserService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) b, err := json.Marshal(tt.args.user) if err != nil { t.Fatalf("failed to marshal user: %v", err) } path := fmt.Sprintf("/api/v2/buckets/%s/members", tt.args.bucketID) r := httptest.NewRequest("POST", path, bytes.NewReader(b)) w := httptest.NewRecorder() h.ServeHTTP(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handlePostBucketMember() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handlePostBucketMember() = %v, want %v", tt.name, content, tt.wants.contentType) } if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handlePostBucketMember(). error unmarshaling json %v", tt.name, err) } else if tt.wants.body != "" && !eq { t.Errorf("%q. handlePostBucketMember() = ***%s***", tt.name, diff) } }) } } func TestService_handlePostBucketOwner(t *testing.T) { type fields struct { UserService platform.UserService } type args struct { bucketID string user *platform.User } type wants struct { statusCode int contentType string body string } tests := []struct { name string fields fields args args wants wants }{ { name: "add a bucket owner", fields: fields{ UserService: &mock.UserService{ FindUserByIDFn: func(ctx context.Context, id platform.ID) (*platform.User, error) { return &platform.User{ ID: id, Name: "name", Status: platform.Active, }, nil }, }, }, args: args{ bucketID: "020f755c3c082000", user: &platform.User{ ID: platformtesting.MustIDBase16("6f626f7274697320"), }, }, wants: wants{ statusCode: http.StatusCreated, contentType: "application/json; charset=utf-8", body: ` { "links": { "logs": "/api/v2/users/6f626f7274697320/logs", "self": "/api/v2/users/6f626f7274697320" }, "role": "owner", "id": "6f626f7274697320", "name": "name", "status": "active" } `, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bucketBackend := NewMockBucketBackend(t) bucketBackend.UserService = tt.fields.UserService h := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) b, err := json.Marshal(tt.args.user) if err != nil { t.Fatalf("failed to marshal user: %v", err) } path := fmt.Sprintf("/api/v2/buckets/%s/owners", tt.args.bucketID) r := httptest.NewRequest("POST", path, bytes.NewReader(b)) w := httptest.NewRecorder() h.ServeHTTP(w, r) res := w.Result() content := res.Header.Get("Content-Type") body, _ := ioutil.ReadAll(res.Body) if res.StatusCode != tt.wants.statusCode { t.Errorf("%q. handlePostBucketOwner() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode) } if tt.wants.contentType != "" && content != tt.wants.contentType { t.Errorf("%q. handlePostBucketOwner() = %v, want %v", tt.name, content, tt.wants.contentType) } if eq, diff, err := jsonEqual(string(body), tt.wants.body); err != nil { t.Errorf("%q, handlePostBucketOwner(). error unmarshaling json %v", tt.name, err) } else if tt.wants.body != "" && !eq { t.Errorf("%q. handlePostBucketOwner() = ***%s***", tt.name, diff) } }) } } func initBucketService(f platformtesting.BucketFields, t *testing.T) (platform.BucketService, string, func()) { svc := kv.NewService(zaptest.NewLogger(t), inmem.NewKVStore()) svc.IDGenerator = f.IDGenerator svc.OrgBucketIDs = f.OrgBucketIDs svc.TimeGenerator = f.TimeGenerator if f.TimeGenerator == nil { svc.TimeGenerator = platform.RealTimeGenerator{} } ctx := context.Background() if err := svc.Initialize(ctx); err != nil { t.Fatal(err) } for _, o := range f.Organizations { if err := svc.PutOrganization(ctx, o); err != nil { t.Fatalf("failed to populate organizations") } } for _, b := range f.Buckets { if err := svc.PutBucket(ctx, b); err != nil { t.Fatalf("failed to populate buckets") } } bucketBackend := NewMockBucketBackend(t) bucketBackend.HTTPErrorHandler = kithttp.ErrorHandler(0) bucketBackend.BucketService = svc bucketBackend.OrganizationService = svc handler := NewBucketHandler(zaptest.NewLogger(t), bucketBackend) server := httptest.NewServer(handler) client := BucketService{ Client: mustNewHTTPClient(t, server.URL, ""), } done := server.Close return &client, "", done } func TestBucketService(t *testing.T) { platformtesting.BucketService(initBucketService, t) } func mustNewHTTPClient(t *testing.T, addr, token string) *httpc.Client { t.Helper() httpClient, err := NewHTTPClient(addr, token, false) if err != nil { t.Fatal(err) } return httpClient }