feat(http): accept label id for template post

pull/13322/head
Kelvin Wang 2019-04-11 11:17:05 -04:00
parent 6e499049c6
commit 92e82884a6
13 changed files with 249 additions and 58 deletions

View File

@ -63,7 +63,7 @@ func (c *Client) findLabelByID(ctx context.Context, tx *bolt.Tx, id influxdb.ID)
if len(v) == 0 {
return nil, &influxdb.Error{
Code: influxdb.ENotFound,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
}
}

View File

@ -58,7 +58,7 @@ type DocumentIndex interface {
FindOrganizationByName(n string) (ID, error)
FindOrganizationByID(id ID) error
FindLabelByName(n string) (ID, error)
FindLabelByID(id ID) error
AddDocumentLabel(docID, labelID ID) error
RemoveDocumentLabel(docID, labelID ID) error
@ -222,11 +222,11 @@ func TokenAuthorizedWithOrgID(a *Authorization, orgID ID) func(ID, DocumentIndex
}
// WithLabel adds a label to the documents where it is applied.
func WithLabel(label string) func(ID, DocumentIndex) error {
func WithLabel(lid ID) func(ID, DocumentIndex) error {
return func(id ID, idx DocumentIndex) error {
// TODO(desa): turns out that labels are application global, at somepoint we'll
// want to scope these by org. We should add auth at that point.
lid, err := idx.FindLabelByName(label)
err := idx.FindLabelByID(lid)
if err != nil {
return err
}
@ -236,11 +236,11 @@ func WithLabel(label string) func(ID, DocumentIndex) error {
}
// WithoutLabel removes a label to the documents where it is applied.
func WithoutLabel(label string) func(ID, DocumentIndex) error {
func WithoutLabel(lid ID) func(ID, DocumentIndex) error {
return func(id ID, idx DocumentIndex) error {
// TODO(desa): turns out that labels are application global, at somepoint we'll
// want to scope these by org. We should add auth at that point.
lid, err := idx.FindLabelByName(label)
err := idx.FindLabelByID(lid)
if err != nil {
return err
}

View File

@ -112,7 +112,6 @@ func (h *DocumentHandler) handlePostDocument(w http.ResponseWriter, r *http.Requ
EncodeError(ctx, err, w)
return
}
s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace)
if err != nil {
EncodeError(ctx, err, w)
@ -149,16 +148,20 @@ func (h *DocumentHandler) handlePostDocument(w http.ResponseWriter, r *http.Requ
type postDocumentRequest struct {
*influxdb.Document
Namespace string `json:"-"`
Org string `json:"org"`
OrgID influxdb.ID `json:"orgID,omitempty"`
Labels []string `json:"labels"` // TODO(desa): should this be IDs or strings?
Namespace string `json:"-"`
Org string `json:"org"`
OrgID influxdb.ID `json:"orgID,omitempty"`
Labels []influxdb.ID `json:"labels"`
}
func decodePostDocumentRequest(ctx context.Context, r *http.Request) (*postDocumentRequest, error) {
req := &postDocumentRequest{}
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
return nil, err
return nil, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "document body error",
Err: err,
}
}
if req.Document == nil {

View File

@ -22,6 +22,8 @@ var (
doc2ID = influxtesting.MustIDBase16("020f755c3c082011")
doc3ID = influxtesting.MustIDBase16("020f755c3c082012")
doc4ID = influxtesting.MustIDBase16("020f755c3c082013")
doc5ID = influxtesting.MustIDBase16("020f755c3c082014")
doc6ID = influxtesting.MustIDBase16("020f755c3c082015")
user1ID = influxtesting.MustIDBase16("020f755c3c082001")
label1ID = influxtesting.MustIDBase16("020f755c3c082300")
label2ID = influxtesting.MustIDBase16("020f755c3c082301")
@ -76,6 +78,31 @@ var (
},
Content: "content4",
}
doc5 = influxdb.Document{
ID: doc5ID,
Meta: influxdb.DocumentMeta{
Name: "doc5",
},
Content: "content5",
}
doc5JSON, _ = json.Marshal(doc5)
doc6 = influxdb.Document{
ID: doc6ID,
Meta: influxdb.DocumentMeta{
Name: "doc6",
},
Content: "content6",
}
doc6JSON, _ = json.Marshal(
postDocumentRequest{
Document: &doc6,
Labels: []influxdb.ID{
label1ID,
label2ID,
},
},
)
docs = []*influxdb.Document{
&doc1,
&doc2,
@ -700,3 +727,185 @@ func TestService_handleGetDocuments(t *testing.T) {
})
}
}
func TestService_handlePostDocuments(t *testing.T) {
type fields struct {
DocumentService influxdb.DocumentService
LabelService influxdb.LabelService
}
type args struct {
body *bytes.Buffer
queryParams map[string][]string
authorizer influxdb.Authorizer
}
type wants struct {
statusCode int
contentType string
body string
}
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
name: "blank body",
fields: fields{
DocumentService: &mock.DocumentService{},
LabelService: &mock.LabelService{},
},
args: args{
body: bytes.NewBuffer([]byte{}),
queryParams: map[string][]string{
"org": []string{"org1"},
},
authorizer: &influxdb.Session{UserID: user1ID},
},
wants: wants{
statusCode: http.StatusBadRequest,
contentType: "application/json; charset=utf-8",
body: `{"code":"invalid","error":"EOF","message": "document body error"}`,
},
},
{
name: "empty json",
fields: fields{
DocumentService: &mock.DocumentService{},
LabelService: &mock.LabelService{},
},
args: args{
body: bytes.NewBuffer([]byte(`{}`)),
queryParams: map[string][]string{
"org": []string{"org1"},
},
authorizer: &influxdb.Session{UserID: user1ID},
},
wants: wants{
statusCode: http.StatusBadRequest,
contentType: "application/json; charset=utf-8",
body: `{"code":"invalid","message": "missing document body"}`,
},
},
{
name: "without label",
fields: fields{
DocumentService: &mock.DocumentService{
FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) {
return &mock.DocumentStore{
CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error {
return nil
},
}, nil
},
},
LabelService: &mock.LabelService{},
},
args: args{
body: bytes.NewBuffer(doc5JSON),
queryParams: map[string][]string{
"org": []string{"org1"},
},
authorizer: &influxdb.Session{UserID: user1ID},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json; charset=utf-8",
body: `{
"content": "content5",
"id": "020f755c3c082014",
"links": {
"self": "/api/v2/documents/template/020f755c3c082014"
},
"meta": {
"name": "doc5"
}}`,
},
},
{
name: "with label",
fields: fields{
DocumentService: &mock.DocumentService{
FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) {
return &mock.DocumentStore{
CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error {
d.Labels = []*influxdb.Label{&label1, &label2}
return nil
},
}, nil
},
},
LabelService: &mock.LabelService{
FindLabelByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.Label, error) {
if id == label1ID {
return &label1, nil
}
return &label2, nil
},
},
},
args: args{
body: bytes.NewBuffer(doc6JSON),
queryParams: map[string][]string{
"org": []string{"org1"},
},
authorizer: &influxdb.Session{UserID: user1ID},
},
wants: wants{
statusCode: http.StatusCreated,
contentType: "application/json; charset=utf-8",
body: `{
"content": "content6",
"id": "020f755c3c082015",
"links": {
"self": "/api/v2/documents/template/020f755c3c082015"
},
"labels": [{
"id": "020f755c3c082300",
"name": "l1"
},
{
"id": "020f755c3c082301",
"name": "l2"
}],
"meta": {
"name": "doc6"
}}`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
documentBackend := NewMockDocumentBackend()
documentBackend.DocumentService = tt.fields.DocumentService
documentBackend.LabelService = tt.fields.LabelService
h := NewDocumentHandler(documentBackend)
r := httptest.NewRequest("POST", "http://any.url", tt.args.body)
r = r.WithContext(pcontext.SetAuthorizer(r.Context(), tt.args.authorizer))
r = r.WithContext(context.WithValue(r.Context(),
httprouter.ParamsKey,
httprouter.Params{
{
Key: "ns",
Value: "template",
},
}))
w := httptest.NewRecorder()
h.handlePostDocument(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. handlePostDocument() = %v, want %v", tt.name, res.StatusCode, tt.wants.statusCode)
}
if tt.wants.contentType != "" && content != tt.wants.contentType {
t.Errorf("%q. handlePostDocument() = %v, want %v", tt.name, content, tt.wants.contentType)
}
if eq, diff, _ := jsonEqual(string(body), tt.wants.body); !eq {
t.Errorf("%q. handlePostDocument() = ***%s***", tt.name, diff)
}
})
}
}

View File

@ -200,7 +200,7 @@ func TestService_handleGetLabel(t *testing.T) {
FindLabelByIDFn: func(ctx context.Context, id platform.ID) (*platform.Label, error) {
return nil, &platform.Error{
Code: platform.ENotFound,
Err: platform.ErrLabelNotFound,
Msg: platform.ErrLabelNotFound,
}
},
},
@ -382,7 +382,7 @@ func TestService_handleDeleteLabel(t *testing.T) {
DeleteLabelFn: func(ctx context.Context, id platform.ID) error {
return &platform.Error{
Code: platform.ENotFound,
Err: platform.ErrLabelNotFound,
Msg: platform.ErrLabelNotFound,
}
},
},
@ -514,7 +514,7 @@ func TestService_handlePatchLabel(t *testing.T) {
UpdateLabelFn: func(ctx context.Context, id platform.ID, upd platform.LabelUpdate) (*platform.Label, error) {
return nil, &platform.Error{
Code: platform.ENotFound,
Err: platform.ErrLabelNotFound,
Msg: platform.ErrLabelNotFound,
}
},
},

View File

@ -7059,7 +7059,7 @@ components:
description: must specify one of orgID and org
labels:
type: array
description: this is an array of label strings that will be added as labels to the document
description: this is an array of label IDs that will be added as labels to the document
items:
type: string
required:

View File

@ -13,7 +13,7 @@ func (s *Service) loadLabel(ctx context.Context, id influxdb.ID) (*influxdb.Labe
if !ok {
return nil, &influxdb.Error{
Code: influxdb.ENotFound,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
}
}
@ -160,7 +160,7 @@ func (s *Service) UpdateLabel(ctx context.Context, id influxdb.ID, upd influxdb.
return nil, &influxdb.Error{
Code: influxdb.ENotFound,
Op: OpPrefix + influxdb.OpUpdateLabel,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
}
}
@ -207,7 +207,7 @@ func (s *Service) DeleteLabel(ctx context.Context, id influxdb.ID) error {
return &influxdb.Error{
Code: influxdb.ENotFound,
Op: OpPrefix + influxdb.OpDeleteLabel,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
}
}

View File

@ -108,12 +108,12 @@ func (s *DocumentStore) CreateDocument(ctx context.Context, d *influxdb.Document
writable: true,
}
for _, opt := range opts {
if err := opt(d.ID, idx); err != nil {
if err = opt(d.ID, idx); err != nil {
return err
}
}
if err := s.decorateDocumentWithLabels(ctx, tx, d); err != nil {
if err = s.decorateDocumentWithLabels(ctx, tx, d); err != nil {
return err
}
@ -157,31 +157,10 @@ func (i *DocumentIndex) RemoveDocumentLabel(docID, labelID influxdb.ID) error {
return nil
}
// FindLabelByName retrieves a label by name.
func (i *DocumentIndex) FindLabelByName(name string) (influxdb.ID, error) {
// TODO(desa): this should be scoped by organization eventually. As of now labels are
// global to the application.
m := influxdb.LabelFilter{
Name: name,
}
ls, err := i.service.findLabels(i.ctx, i.tx, m)
if err != nil {
return influxdb.InvalidID(), err
}
if len(ls) == 0 {
return influxdb.InvalidID(), &influxdb.Error{
Code: influxdb.ENotFound,
Msg: "label not found",
}
}
if len(ls) > 1 {
return influxdb.InvalidID(), &influxdb.Error{
Code: influxdb.EInternal,
Msg: "found multiple labels matching the name provided",
}
}
return ls[0].ID, nil
// FindLabelByID retrieves a label by id.
func (i *DocumentIndex) FindLabelByID(id influxdb.ID) error {
_, err := i.service.findLabelByID(i.ctx, i.tx, id)
return err
}
// AddDocumentOwner creates a urm for the document id and owner id provided.

View File

@ -65,7 +65,7 @@ func (s *Service) findLabelByID(ctx context.Context, tx Tx, id influxdb.ID) (*in
if IsNotFound(err) {
return nil, &influxdb.Error{
Code: influxdb.ENotFound,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
}
}

View File

@ -5,7 +5,7 @@ import (
)
// ErrLabelNotFound is the error for a missing Label.
const ErrLabelNotFound = ChronografError("label not found")
const ErrLabelNotFound = "label not found"
const (
OpFindLabels = "FindLabels"

View File

@ -34,7 +34,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
l1 := &influxdb.Label{Name: "l1"}
l2 := &influxdb.Label{Name: "l2"}
mustCreateLabels(ctx, svc, l1, l2)
lBad := &influxdb.Label{Name: "bad"}
lBad := &influxdb.Label{ID: MustIDBase16(oneID), Name: "bad"}
o1 := &influxdb.Organization{Name: "foo"}
o2 := &influxdb.Organization{Name: "bar"}
@ -66,7 +66,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"v1": "v1",
},
}
if err := s.CreateDocument(ctx, d1, influxdb.AuthorizedWithOrg(s1, o1.Name), influxdb.WithLabel(l1.Name)); err != nil {
if err := s.CreateDocument(ctx, d1, influxdb.AuthorizedWithOrg(s1, o1.Name), influxdb.WithLabel(l1.ID)); err != nil {
t.Errorf("failed to create document: %v", err)
}
})
@ -80,7 +80,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"i2": "i2",
},
}
if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o1.Name), influxdb.WithLabel(l2.Name)); err == nil {
if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o1.Name), influxdb.WithLabel(l2.ID)); err == nil {
t.Fatalf("should not have be authorized to create document")
}
@ -119,7 +119,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"k4": "v4",
},
}
err = s.CreateDocument(ctx, d4, influxdb.WithLabel(lBad.Name))
err = s.CreateDocument(ctx, d4, influxdb.WithLabel(lBad.ID))
ErrorsEqual(t, err, &influxdb.Error{
Code: influxdb.ENotFound,
Msg: "label not found",

View File

@ -300,7 +300,7 @@ func FindLabelByID(
err: &influxdb.Error{
Code: influxdb.ENotFound,
Op: influxdb.OpFindLabelByID,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
},
},
},
@ -490,7 +490,7 @@ func UpdateLabel(
err: &influxdb.Error{
Code: influxdb.ENotFound,
Op: influxdb.OpUpdateLabel,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
},
},
},
@ -587,7 +587,7 @@ func DeleteLabel(
err: &influxdb.Error{
Code: influxdb.ENotFound,
Op: influxdb.OpDeleteLabel,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
},
},
},
@ -675,7 +675,7 @@ func CreateLabelMapping(
err: &influxdb.Error{
Code: influxdb.ENotFound,
Op: influxdb.OpDeleteLabel,
Err: influxdb.ErrLabelNotFound,
Msg: influxdb.ErrLabelNotFound,
},
},
},

View File

@ -28,12 +28,12 @@ func ErrorsEqual(t *testing.T, actual, expected error) {
}
if platform.ErrorCode(expected) != platform.ErrorCode(actual) {
t.Logf("\nexpected: %v\nactual %v\n\n", expected, actual)
t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual)
t.Errorf("expected error code %q but received %q", platform.ErrorCode(expected), platform.ErrorCode(actual))
}
if platform.ErrorMessage(expected) != platform.ErrorMessage(actual) {
t.Logf("\nexpected: %v\nactual %v\n\n", expected, actual)
t.Logf("\nexpected: %v\nactual: %v\n\n", expected, actual)
t.Errorf("expected error message %q but received %q", platform.ErrorMessage(expected), platform.ErrorMessage(actual))
}
}