influxdb/http/bucket_test.go

1228 lines
33 KiB
Go

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"
"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 = 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.StatusBadRequest,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bucketBackend := NewMockBucketBackend(t)
bucketBackend.HTTPErrorHandler = 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 = 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 = 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 = 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, ""),
OpPrefix: inmem.OpPrefix,
}
done := server.Close
return &client, inmem.OpPrefix, 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
}