From da3349108c65bb97e7953a1febb8f4a5b8706aee Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 11 May 2020 14:39:55 +0200 Subject: [PATCH 01/17] Add ListTwins test Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_test.go | 70 ++++++++++++++++++++++++++++++++- twins/service.go | 15 +++---- twins/twins.go | 1 - 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/twins/api/http/endpoint_test.go b/twins/api/http/endpoint_test.go index f853b5ed47..7000d85082 100644 --- a/twins/api/http/endpoint_test.go +++ b/twins/api/http/endpoint_test.go @@ -378,6 +378,67 @@ func TestViewTwin(t *testing.T) { } } +func TestListTwins(t *testing.T) { + svc := newService(map[string]string{token: email}) + ts := newServer(svc) + defer ts.Close() + + data := []twinRes{} + for i := 0; i < 100; i++ { + name := fmt.Sprintf("%s-%d", twinName, i) + twin := twins.Twin{ + Owner: email, + Name: name, + } + tw, err := svc.AddTwin(context.Background(), token, twin, twins.Definition{}) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + twres := twinRes{ + Owner: tw.Owner, + ID: tw.ID, + Name: tw.Name, + Revision: tw.Revision, + Created: tw.Created, + Updated: tw.Updated, + Definitions: tw.Definitions, + Metadata: tw.Metadata, + } + data = append(data, twres) + } + + twinsURL := fmt.Sprintf("%s/twins", ts.URL) + queryFmt := "%s?offset=%d&limit=%d" + cases := []struct { + desc string + auth string + status int + url string + res []twinRes + }{ + { + desc: "get a list of twins", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, twinsURL, 0, 1), + res: data[0:1], + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: tc.url, + token: tc.auth, + } + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resData twinsPageRes + json.NewDecoder(res.Body).Decode(&resData) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + assert.ElementsMatch(t, tc.res, resData.Twins, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData.Twins)) + } +} + func TestRemoveTwin(t *testing.T) { svc := newService(map[string]string{token: email}) ts := newServer(svc) @@ -447,11 +508,18 @@ type twinReq struct { type twinRes struct { Owner string `json:"owner"` - Name string `json:"name,omitempty"` ID string `json:"id"` + Name string `json:"name,omitempty"` Revision int `json:"revision"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` Definitions []twins.Definition `json:"definitions"` Metadata map[string]interface{} `json:"metadata,omitempty"` } + +type twinsPageRes struct { + Twins []twinRes `json:"twins"` + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` +} diff --git a/twins/service.go b/twins/service.go index 0bff4ea955..e0e262e0f5 100644 --- a/twins/service.go +++ b/twins/service.go @@ -131,8 +131,9 @@ func (ts *twinsService) AddTwin(ctx context.Context, token string, twin Twin, de twin.Owner = res.GetValue() - twin.Created = time.Now() - twin.Updated = time.Now() + t := time.Now().Round(0) + twin.Created = t + twin.Updated = t if def.Attributes == nil { def.Attributes = []Attribute{} @@ -141,7 +142,7 @@ func (ts *twinsService) AddTwin(ctx context.Context, token string, twin Twin, de def.Delta = millisec } - def.Created = time.Now() + def.Created = time.Now().Round(0) def.ID = 0 twin.Definitions = append(twin.Definitions, def) @@ -180,7 +181,7 @@ func (ts *twinsService) UpdateTwin(ctx context.Context, token string, twin Twin, if len(def.Attributes) > 0 { revision = true - def.Created = time.Now() + def.Created = time.Now().Round(0) def.ID = tw.Definitions[len(tw.Definitions)-1].ID + 1 tw.Definitions = append(tw.Definitions, def) } @@ -194,7 +195,7 @@ func (ts *twinsService) UpdateTwin(ctx context.Context, token string, twin Twin, return ErrMalformedEntity } - tw.Updated = time.Now() + tw.Updated = time.Now().Round(0) tw.Revision++ if err := ts.twins.Update(ctx, tw); err != nil { @@ -349,7 +350,7 @@ func prepareState(st *State, tw *Twin, rec senml.Record, msg *messaging.Message) if recNano == 0 || delta > float64(def.Delta) { action = save st.ID++ - st.Created = time.Now() + st.Created = time.Now().Round(0) if recNano != 0 { st.Created = recTime } @@ -413,7 +414,7 @@ func (ts *twinsService) publish(twinID *string, err *error, succOp, failOp strin Subtopic: op, Payload: pl, Publisher: publisher, - Created: time.Now().UnixNano(), + Created: time.Now().Round(0).UnixNano(), } if err := ts.publisher.Publish(msg.Channel, msg); err != nil { diff --git a/twins/twins.go b/twins/twins.go index 4fedeaa7e2..1689b03279 100644 --- a/twins/twins.go +++ b/twins/twins.go @@ -45,7 +45,6 @@ type PageMetadata struct { Total uint64 Offset uint64 Limit uint64 - Name string } // Page contains page related metadata as well as a list of twins that From 57fd3dee9abb177b0c2eeaea81abd69651db0454 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 11 May 2020 16:27:37 +0200 Subject: [PATCH 02/17] Remove monotonic time from twins, definitions and attributes creation and update Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_test.go | 121 +++++++++++++++++++++++++++++++- twins/mocks/twins.go | 3 + twins/service.go | 4 +- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/twins/api/http/endpoint_test.go b/twins/api/http/endpoint_test.go index 7000d85082..42a707256d 100644 --- a/twins/api/http/endpoint_test.go +++ b/twins/api/http/endpoint_test.go @@ -418,8 +418,127 @@ func TestListTwins(t *testing.T) { desc: "get a list of twins", auth: token, status: http.StatusOK, + url: fmt.Sprintf(queryFmt, twinsURL, 0, 5), + res: data[0:5], + }, + { + desc: "get a list of twins with invalid token", + auth: wrongValue, + status: http.StatusForbidden, + url: fmt.Sprintf(queryFmt, twinsURL, 0, 1), + res: nil, + }, + { + desc: "get a list of twins with empty token", + auth: "", + status: http.StatusForbidden, url: fmt.Sprintf(queryFmt, twinsURL, 0, 1), - res: data[0:1], + res: nil, + }, + { + desc: "get a list of twins with valid offset and limit", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, twinsURL, 25, 40), + res: data[25:65], + }, + { + desc: "get a list of twins with offset + limit > total", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, twinsURL, 91, 20), + res: data[91:], + }, + { + desc: "get a list of twins with negative offset", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, twinsURL, -1, 5), + res: nil, + }, + { + desc: "get a list of twins with negative limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, twinsURL, 1, -5), + res: nil, + }, + { + desc: "get a list of twins with zero limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, twinsURL, 1, 0), + res: nil, + }, + { + desc: "get a list of twins with limit greater than max", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s?offset=%d&limit=%d", twinsURL, 0, 110), + res: nil, + }, + { + desc: "get a list of twins with invalid offset", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s%s", twinsURL, "?offset=e&limit=5"), + res: nil, + }, + { + desc: "get a list of twins with invalid limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s%s", twinsURL, "?offset=5&limit=e"), + res: nil, + }, + { + desc: "get a list of twins without offset", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?limit=%d", twinsURL, 5), + res: data[0:5], + }, + { + desc: "get a list of twins without limit", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?offset=%d", twinsURL, 1), + res: data[1:11], + }, + { + desc: "get a list of twins with invalid number of params", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s%s", twinsURL, "?offset=4&limit=4&limit=5&offset=5"), + res: nil, + }, + { + desc: "get a list of twins with redundant query params", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", twinsURL, 0, 5), + res: data[0:5], + }, + { + desc: "get a list of twins with default URL", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s%s", twinsURL, ""), + res: data[0:10], + }, + { + desc: "get a list of things filtering with invalid name", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", twinsURL, 0, 5, invalidName), + res: nil, + }, + { + desc: "get a list of things filtering with valid name", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", twinsURL, 2, 1, twinName+"-2"), + res: data[2:3], }, } diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index 69e4f6179f..0be7423c08 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -102,6 +102,9 @@ func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offs if (uint64)(len(items)) >= limit { break } + if len(name) > 0 && v.Name != name { + continue + } if !strings.HasPrefix(k, prefix) { continue } diff --git a/twins/service.go b/twins/service.go index e0e262e0f5..0a9f1f1e30 100644 --- a/twins/service.go +++ b/twins/service.go @@ -350,7 +350,7 @@ func prepareState(st *State, tw *Twin, rec senml.Record, msg *messaging.Message) if recNano == 0 || delta > float64(def.Delta) { action = save st.ID++ - st.Created = time.Now().Round(0) + st.Created = time.Now() if recNano != 0 { st.Created = recTime } @@ -414,7 +414,7 @@ func (ts *twinsService) publish(twinID *string, err *error, succOp, failOp strin Subtopic: op, Payload: pl, Publisher: publisher, - Created: time.Now().Round(0).UnixNano(), + Created: time.Now().UnixNano(), } if err := ts.publisher.Publish(msg.Channel, msg); err != nil { From e72e3361eb3c761f35428b0f4577d82666fa0890 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 11 May 2020 17:44:52 +0200 Subject: [PATCH 03/17] Separate twins and states endpoint tests in two files Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 60 +++++++++++++++++++ ...ndpoint_test.go => endpoint_twins_test.go} | 12 ++-- 2 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 twins/api/http/endpoint_states_test.go rename twins/api/http/{endpoint_test.go => endpoint_twins_test.go} (99%) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go new file mode 100644 index 0000000000..de447e66c7 --- /dev/null +++ b/twins/api/http/endpoint_states_test.go @@ -0,0 +1,60 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package http_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/mainflux/mainflux/twins" + "github.com/mainflux/mainflux/twins/mocks" + "github.com/stretchr/testify/require" +) + +func TestListStates(t *testing.T) { + svc := newService(map[string]string{token: email}) + ts := newServer(svc) + sr := mocks.NewStateRepository() + defer ts.Close() + + twin := twins.Twin{ + Owner: email, + } + def := twins.Definition{} + stw, _ := svc.AddTwin(context.Background(), token, twin, def) + + data := []stateRes{} + for i := 0; i < 100; i++ { + st := twins.State{ + TwinID: stw.ID, + ID: int64(i), + Definition: 0, + Created: time.Now().Round(0), + } + err := sr.Save(context.TODO(), st) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + stres := stateRes{ + TwinID: st.TwinID, + ID: st.ID, + Definition: st.Definition, + Created: st.Created, + } + data = append(data, stres) + } +} + +type stateRes struct { + TwinID string `json:"twin_id"` + ID int64 `json:"id"` + Definition int `json:"definition"` + Created time.Time `json:"created"` + Payload map[string]interface{} `json:"payload"` +} + +type statesPageRes struct { + pageRes + States []stateRes `json:"states"` +} diff --git a/twins/api/http/endpoint_test.go b/twins/api/http/endpoint_twins_test.go similarity index 99% rename from twins/api/http/endpoint_test.go rename to twins/api/http/endpoint_twins_test.go index 42a707256d..8692abda8a 100644 --- a/twins/api/http/endpoint_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -636,9 +636,13 @@ type twinRes struct { Metadata map[string]interface{} `json:"metadata,omitempty"` } +type pageRes struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` +} + type twinsPageRes struct { - Twins []twinRes `json:"twins"` - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` + pageRes + Twins []twinRes `json:"twins"` } From 0deaaa155e993c49fca4aa8d4a5b291526b1c966 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Tue, 12 May 2020 11:15:06 +0200 Subject: [PATCH 04/17] Add state generation helper funcs to state endpoint tests Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 153 +++++++++++++++++++++---- twins/mocks/states.go | 5 +- 2 files changed, 132 insertions(+), 26 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index de447e66c7..c005347448 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -5,56 +5,159 @@ package http_test import ( "context" + "encoding/json" "fmt" + "math" + "math/rand" + "net/http" "testing" "time" + "github.com/mainflux/mainflux/messaging" "github.com/mainflux/mainflux/twins" - "github.com/mainflux/mainflux/twins/mocks" + "github.com/mainflux/senml" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/mainflux/mainflux/twins/uuid" +) + +const ( + millisec = 1e6 + nanosec = 1e9 + + attrName1 = "temperature" + attrSubtopic1 = "engine" + attrName2 = "humidity" + attrSubtopic2 = "chassis" ) +type stateRes struct { + TwinID string `json:"twin_id"` + ID int64 `json:"id"` + Definition int `json:"definition"` + Created time.Time `json:"created"` + Payload map[string]interface{} `json:"payload"` +} + +type statesPageRes struct { + pageRes + States []stateRes `json:"states"` +} + func TestListStates(t *testing.T) { svc := newService(map[string]string{token: email}) ts := newServer(svc) - sr := mocks.NewStateRepository() defer ts.Close() twin := twins.Twin{ Owner: email, } - def := twins.Definition{} - stw, _ := svc.AddTwin(context.Background(), token, twin, def) + attr1 := createAttribute(attrName1, attrSubtopic1) + attr2 := createAttribute(attrName2, attrSubtopic2) + def := twins.Definition{ + Attributes: []twins.Attribute{ + attr1, + attr2, + }, + } + tw, _ := svc.AddTwin(context.Background(), token, twin, def) + + recs := createSenML(100, "temperature") + message := createMessage(attr1, recs) + err := svc.SaveStates(message) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) data := []stateRes{} - for i := 0; i < 100; i++ { - st := twins.State{ - TwinID: stw.ID, - ID: int64(i), - Definition: 0, - Created: time.Now().Round(0), + twDef := tw.Definitions[len(tw.Definitions)-1].ID + for i := 0; i < len(recs); i++ { + rec := recs[i] + + recSec := rec.BaseTime + rec.Time + sec, dec := math.Modf(recSec) + recTime := time.Unix(int64(sec), int64(dec*nanosec)).Round(0) + + pl := map[string]interface{}{ + rec.BaseName: rec.Value, } - err := sr.Save(context.TODO(), st) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + stres := stateRes{ - TwinID: st.TwinID, - ID: st.ID, - Definition: st.Definition, - Created: st.Created, + TwinID: tw.ID, + ID: int64(i), + Definition: twDef, + Created: recTime, + Payload: pl, } data = append(data, stres) } + + statesURL := fmt.Sprintf("%s/states/%s", ts.URL, tw.ID) + queryFmt := "%s?offset=%d&limit=%d" + cases := []struct { + desc string + auth string + status int + url string + res []stateRes + }{ + { + desc: "get a list of states", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, statesURL, 0, 5), + res: data[0:5], + }, + } + + for _, tc := range cases { + req := testRequest{ + client: ts.Client(), + method: http.MethodGet, + url: tc.url, + token: tc.auth, + } + res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resData statesPageRes + json.NewDecoder(res.Body).Decode(&resData) + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) + assert.ElementsMatch(t, tc.res, resData.States, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData.States)) + } } -type stateRes struct { - TwinID string `json:"twin_id"` - ID int64 `json:"id"` - Definition int `json:"definition"` - Created time.Time `json:"created"` - Payload map[string]interface{} `json:"payload"` +func createAttribute(name, subtopic string) twins.Attribute { + id, _ := uuid.New().ID() + return twins.Attribute{ + Name: name, + Channel: id, + Subtopic: subtopic, + PersistState: true, + } } -type statesPageRes struct { - pageRes - States []stateRes `json:"states"` +func createSenML(n int, bn string) []senml.Record { + var recs []senml.Record + bt := time.Now().Round(0).UnixNano() + for i := 0; i < n; i++ { + t := 10e-6 * float64(i) + v := rand.Float64() + rec := senml.Record{ + BaseName: bn, + BaseTime: float64(bt), + Time: t, + Value: &v, + } + recs = append(recs, rec) + } + return recs +} + +func createMessage(attr twins.Attribute, recs []senml.Record) *messaging.Message { + mRecs, _ := json.Marshal(recs) + return &messaging.Message{ + Channel: attr.Channel, + Subtopic: attr.Subtopic, + Payload: mRecs, + Publisher: "twins", + } } diff --git a/twins/mocks/states.go b/twins/mocks/states.go index f59f39f12b..776b315a0e 100644 --- a/twins/mocks/states.go +++ b/twins/mocks/states.go @@ -107,5 +107,8 @@ func (srm *stateRepositoryMock) RetrieveLast(ctx context.Context, id string) (tw return items[i].ID < items[j].ID }) - return items[len(items)-1], nil + if len(items) > 0 { + return items[len(items)-1], nil + } + return twins.State{}, nil } From 5e2668fc3e842349c1cb811e53bcd106c0772a92 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Tue, 12 May 2020 16:01:02 +0200 Subject: [PATCH 05/17] Add createStateResponse() to states test Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 56 ++++++++++++-------------- twins/mocks/states.go | 8 ++-- twins/service.go | 2 + 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index c005347448..bbf69cb750 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "math" - "math/rand" "net/http" "testing" "time" @@ -23,8 +22,7 @@ import ( ) const ( - millisec = 1e6 - nanosec = 1e9 + nanosec = 1e9 attrName1 = "temperature" attrSubtopic1 = "engine" @@ -63,32 +61,15 @@ func TestListStates(t *testing.T) { } tw, _ := svc.AddTwin(context.Background(), token, twin, def) - recs := createSenML(100, "temperature") + recs := createSenML(100, attrName1) message := createMessage(attr1, recs) err := svc.SaveStates(message) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) data := []stateRes{} - twDef := tw.Definitions[len(tw.Definitions)-1].ID for i := 0; i < len(recs); i++ { - rec := recs[i] - - recSec := rec.BaseTime + rec.Time - sec, dec := math.Modf(recSec) - recTime := time.Unix(int64(sec), int64(dec*nanosec)).Round(0) - - pl := map[string]interface{}{ - rec.BaseName: rec.Value, - } - - stres := stateRes{ - TwinID: tw.ID, - ID: int64(i), - Definition: twDef, - Created: recTime, - Payload: pl, - } - data = append(data, stres) + res := createStateResponse(i, tw, recs[i]) + data = append(data, res) } statesURL := fmt.Sprintf("%s/states/%s", ts.URL, tw.ID) @@ -104,8 +85,8 @@ func TestListStates(t *testing.T) { desc: "get a list of states", auth: token, status: http.StatusOK, - url: fmt.Sprintf(queryFmt, statesURL, 0, 5), - res: data[0:5], + url: fmt.Sprintf(queryFmt, statesURL, 0, 1), + res: data[0:1], }, } @@ -117,6 +98,7 @@ func TestListStates(t *testing.T) { token: tc.auth, } res, err := req.make() + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) var resData statesPageRes json.NewDecoder(res.Body).Decode(&resData) @@ -137,15 +119,13 @@ func createAttribute(name, subtopic string) twins.Attribute { func createSenML(n int, bn string) []senml.Record { var recs []senml.Record - bt := time.Now().Round(0).UnixNano() + bt := time.Now().Unix() for i := 0; i < n; i++ { - t := 10e-6 * float64(i) - v := rand.Float64() rec := senml.Record{ BaseName: bn, BaseTime: float64(bt), - Time: t, - Value: &v, + Time: float64(i), + Value: nil, } recs = append(recs, rec) } @@ -153,6 +133,8 @@ func createSenML(n int, bn string) []senml.Record { } func createMessage(attr twins.Attribute, recs []senml.Record) *messaging.Message { + fmt.Printf("%+v\n", recs[0]) // output for debug + mRecs, _ := json.Marshal(recs) return &messaging.Message{ Channel: attr.Channel, @@ -161,3 +143,17 @@ func createMessage(attr twins.Attribute, recs []senml.Record) *messaging.Message Publisher: "twins", } } + +func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes { + recSec := rec.BaseTime + rec.Time + sec, dec := math.Modf(recSec) + recTime := time.Unix(int64(sec), int64(dec*nanosec)) + + return stateRes{ + TwinID: tw.ID, + ID: int64(id), + Definition: tw.Definitions[len(tw.Definitions)-1].ID, + Created: recTime, + Payload: map[string]interface{}{rec.BaseName: nil}, + } +} diff --git a/twins/mocks/states.go b/twins/mocks/states.go index 776b315a0e..6b3beca7b1 100644 --- a/twins/mocks/states.go +++ b/twins/mocks/states.go @@ -66,16 +66,16 @@ func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset uint64, // This obscure way to examine map keys is enforced by the key structure in mocks/commons.go prefix := fmt.Sprintf("%s-", id) for k, v := range srm.states { + if (uint64)(len(items)) >= limit { + break + } if !strings.HasPrefix(k, prefix) { continue } id := uint64(v.ID) - if id > offset && id < limit { + if id >= offset && id < offset+limit { items = append(items, v) } - if (uint64)(len(items)) >= limit { - break - } } sort.SliceStable(items, func(i, j int) bool { diff --git a/twins/service.go b/twins/service.go index 0a9f1f1e30..fccd3a1334 100644 --- a/twins/service.go +++ b/twins/service.go @@ -325,6 +325,7 @@ func prepareState(st *State, tw *Twin, rec senml.Record, msg *messaging.Message) if st.Payload == nil { st.Payload = make(map[string]interface{}) + st.ID-- // st.ID == -1; state is incremented on save -> zero-based index } else { for k := range st.Payload { idx := findAttribute(k, def.Attributes) @@ -357,6 +358,7 @@ func prepareState(st *State, tw *Twin, rec senml.Record, msg *messaging.Message) } val := findValue(rec) st.Payload[attr.Name] = val + break } } From bc3ba6c176c421650ceca95c5f50a95c6bb4e8a5 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Tue, 12 May 2020 17:10:44 +0200 Subject: [PATCH 06/17] Add states test cases Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 106 +++++++++++++++++++++++-- twins/api/http/endpoint_twins_test.go | 49 +++++------- 2 files changed, 122 insertions(+), 33 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index bbf69cb750..7af06e5ed2 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -72,7 +72,7 @@ func TestListStates(t *testing.T) { data = append(data, res) } - statesURL := fmt.Sprintf("%s/states/%s", ts.URL, tw.ID) + baseURL := fmt.Sprintf("%s/states/%s", ts.URL, tw.ID) queryFmt := "%s?offset=%d&limit=%d" cases := []struct { desc string @@ -85,8 +85,106 @@ func TestListStates(t *testing.T) { desc: "get a list of states", auth: token, status: http.StatusOK, - url: fmt.Sprintf(queryFmt, statesURL, 0, 1), - res: data[0:1], + url: baseURL, + res: data[0:10], + }, + { + desc: "get a list of states with with valid offset and limit", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, baseURL, 20, 15), + res: data[20:35], + }, + { + desc: "get a list of states with invalid token", + auth: wrongValue, + status: http.StatusForbidden, + url: fmt.Sprintf(queryFmt, baseURL, 0, 5), + res: nil, + }, + { + desc: "get a list of states with empty token", + auth: "", + status: http.StatusForbidden, + url: fmt.Sprintf(queryFmt, baseURL, 0, 5), + res: nil, + }, + { + desc: "get a list of states with with + limit > total", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf(queryFmt, baseURL, 91, 20), + res: data[91:], + }, + { + desc: "get a list of states with negative offset", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, baseURL, -1, 5), + res: nil, + }, + { + desc: "get a list of states with negative limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, baseURL, 0, -5), + res: nil, + }, + { + desc: "get a list of states with zero limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, baseURL, 0, 0), + res: nil, + }, + { + desc: "get a list of states with limit greater than max", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf(queryFmt, baseURL, 0, 110), + res: nil, + }, + { + desc: "get a list of states with invalid offset", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s?offset=invalid&limit=%d", baseURL, 15), + res: nil, + }, + { + desc: "get a list of states with invalid limit", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s?offset=%d&limit=invalid", baseURL, 0), + res: nil, + }, + { + desc: "get a list of states without offset", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?limit=%d", baseURL, 15), + res: data[0:15], + }, + { + desc: "get a list of states without limit", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?offset=%d", baseURL, 14), + res: data[14:24], + }, + { + desc: "get a list of states with invalid number of params", + auth: token, + status: http.StatusBadRequest, + url: fmt.Sprintf("%s%s", baseURL, "?offset=4&limit=4&limit=5&offset=5"), + res: nil, + }, + { + desc: "get a list of states with redundant query params", + auth: token, + status: http.StatusOK, + url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5), + res: data[0:5], }, } @@ -133,8 +231,6 @@ func createSenML(n int, bn string) []senml.Record { } func createMessage(attr twins.Attribute, recs []senml.Record) *messaging.Message { - fmt.Printf("%+v\n", recs[0]) // output for debug - mRecs, _ := json.Marshal(recs) return &messaging.Message{ Channel: attr.Channel, diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 8692abda8a..4affb838dd 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -405,7 +405,7 @@ func TestListTwins(t *testing.T) { data = append(data, twres) } - twinsURL := fmt.Sprintf("%s/twins", ts.URL) + baseURL := fmt.Sprintf("%s/twins", ts.URL) queryFmt := "%s?offset=%d&limit=%d" cases := []struct { desc string @@ -418,126 +418,119 @@ func TestListTwins(t *testing.T) { desc: "get a list of twins", auth: token, status: http.StatusOK, - url: fmt.Sprintf(queryFmt, twinsURL, 0, 5), - res: data[0:5], + url: baseURL, + res: data[0:10], }, { desc: "get a list of twins with invalid token", auth: wrongValue, status: http.StatusForbidden, - url: fmt.Sprintf(queryFmt, twinsURL, 0, 1), + url: fmt.Sprintf(queryFmt, baseURL, 0, 1), res: nil, }, { desc: "get a list of twins with empty token", auth: "", status: http.StatusForbidden, - url: fmt.Sprintf(queryFmt, twinsURL, 0, 1), + url: fmt.Sprintf(queryFmt, baseURL, 0, 1), res: nil, }, { desc: "get a list of twins with valid offset and limit", auth: token, status: http.StatusOK, - url: fmt.Sprintf(queryFmt, twinsURL, 25, 40), + url: fmt.Sprintf(queryFmt, baseURL, 25, 40), res: data[25:65], }, { desc: "get a list of twins with offset + limit > total", auth: token, status: http.StatusOK, - url: fmt.Sprintf(queryFmt, twinsURL, 91, 20), + url: fmt.Sprintf(queryFmt, baseURL, 91, 20), res: data[91:], }, { desc: "get a list of twins with negative offset", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf(queryFmt, twinsURL, -1, 5), + url: fmt.Sprintf(queryFmt, baseURL, -1, 5), res: nil, }, { desc: "get a list of twins with negative limit", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf(queryFmt, twinsURL, 1, -5), + url: fmt.Sprintf(queryFmt, baseURL, 1, -5), res: nil, }, { desc: "get a list of twins with zero limit", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf(queryFmt, twinsURL, 1, 0), + url: fmt.Sprintf(queryFmt, baseURL, 1, 0), res: nil, }, { desc: "get a list of twins with limit greater than max", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf("%s?offset=%d&limit=%d", twinsURL, 0, 110), + url: fmt.Sprintf("%s?offset=%d&limit=%d", baseURL, 0, 110), res: nil, }, { desc: "get a list of twins with invalid offset", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf("%s%s", twinsURL, "?offset=e&limit=5"), + url: fmt.Sprintf("%s%s", baseURL, "?offset=e&limit=5"), res: nil, }, { desc: "get a list of twins with invalid limit", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf("%s%s", twinsURL, "?offset=5&limit=e"), + url: fmt.Sprintf("%s%s", baseURL, "?offset=5&limit=e"), res: nil, }, { desc: "get a list of twins without offset", auth: token, status: http.StatusOK, - url: fmt.Sprintf("%s?limit=%d", twinsURL, 5), + url: fmt.Sprintf("%s?limit=%d", baseURL, 5), res: data[0:5], }, { desc: "get a list of twins without limit", auth: token, status: http.StatusOK, - url: fmt.Sprintf("%s?offset=%d", twinsURL, 1), + url: fmt.Sprintf("%s?offset=%d", baseURL, 1), res: data[1:11], }, { desc: "get a list of twins with invalid number of params", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf("%s%s", twinsURL, "?offset=4&limit=4&limit=5&offset=5"), + url: fmt.Sprintf("%s%s", baseURL, "?offset=4&limit=4&limit=5&offset=5"), res: nil, }, { desc: "get a list of twins with redundant query params", auth: token, status: http.StatusOK, - url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", twinsURL, 0, 5), + url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5), res: data[0:5], }, { - desc: "get a list of twins with default URL", - auth: token, - status: http.StatusOK, - url: fmt.Sprintf("%s%s", twinsURL, ""), - res: data[0:10], - }, - { - desc: "get a list of things filtering with invalid name", + desc: "get a list of twins filtering with invalid name", auth: token, status: http.StatusBadRequest, - url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", twinsURL, 0, 5, invalidName), + url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", baseURL, 0, 5, invalidName), res: nil, }, { - desc: "get a list of things filtering with valid name", + desc: "get a list of twins filtering with valid name", auth: token, status: http.StatusOK, - url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", twinsURL, 2, 1, twinName+"-2"), + url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", baseURL, 2, 1, twinName+"-2"), res: data[2:3], }, } From 29f3a7638cc0c9ebc7535bf93fd44667e30e9644 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Wed, 13 May 2020 12:27:03 +0200 Subject: [PATCH 07/17] Simplify RetrieveAll twins and states methods Signed-off-by: Darko Draskovic --- twins/mocks/states.go | 7 ++----- twins/mocks/twins.go | 8 +++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/twins/mocks/states.go b/twins/mocks/states.go index 6b3beca7b1..e742963a67 100644 --- a/twins/mocks/states.go +++ b/twins/mocks/states.go @@ -5,7 +5,6 @@ package mocks import ( "context" - "fmt" "sort" "strings" "sync" @@ -53,7 +52,7 @@ func (srm *stateRepositoryMock) Count(ctx context.Context, tw twins.Twin) (int64 return int64(len(srm.states)), nil } -func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset uint64, limit uint64, id string) (twins.StatesPage, error) { +func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset uint64, limit uint64, twinID string) (twins.StatesPage, error) { srm.mu.Lock() defer srm.mu.Unlock() @@ -63,13 +62,11 @@ func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset uint64, return twins.StatesPage{}, nil } - // This obscure way to examine map keys is enforced by the key structure in mocks/commons.go - prefix := fmt.Sprintf("%s-", id) for k, v := range srm.states { if (uint64)(len(items)) >= limit { break } - if !strings.HasPrefix(k, prefix) { + if !strings.HasPrefix(k, twinID) { continue } id := uint64(v.ID) diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index 0be7423c08..b2aaa72846 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -5,7 +5,6 @@ package mocks import ( "context" - "fmt" "sort" "strconv" "strings" @@ -96,8 +95,6 @@ func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offs return twins.Page{}, nil } - // This obscure way to examine map keys is enforced by the key structure in mocks/commons.go - prefix := fmt.Sprintf("%s-", owner) for k, v := range trm.twins { if (uint64)(len(items)) >= limit { break @@ -105,12 +102,12 @@ func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offs if len(name) > 0 && v.Name != name { continue } - if !strings.HasPrefix(k, prefix) { + if !strings.HasPrefix(k, owner) { continue } suffix := string(v.ID[len(u4Pref):]) id, _ := strconv.ParseUint(suffix, 10, 64) - if id > offset && id <= uint64(offset+limit) { + if id > offset && id <= offset+limit { items = append(items, v) } } @@ -138,6 +135,7 @@ func (trm *twinRepositoryMock) Remove(ctx context.Context, id string) error { for k, v := range trm.twins { if id == v.ID { delete(trm.twins, k) + return nil } } From ae07f3de7252e9a308ff26c3f93a0f5a60778ae1 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Wed, 13 May 2020 12:48:48 +0200 Subject: [PATCH 08/17] Add service.go to mocks Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 3 ++- twins/api/http/endpoint_twins_test.go | 20 +++++--------------- twins/mocks/service.go | 15 +++++++++++++++ twins/service_test.go | 20 +++++--------------- 4 files changed, 27 insertions(+), 31 deletions(-) create mode 100644 twins/mocks/service.go diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 7af06e5ed2..8765989867 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/mainflux/mainflux/twins/mocks" "github.com/mainflux/mainflux/twins/uuid" ) @@ -44,7 +45,7 @@ type statesPageRes struct { } func TestListStates(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 4affb838dd..8ed85f0b9f 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -61,16 +61,6 @@ func (tr testRequest) make() (*http.Response, error) { return tr.client.Do(req) } -func newService(tokens map[string]string) twins.Service { - auth := mocks.NewAuthNServiceClient(tokens) - twinsRepo := mocks.NewTwinRepository() - statesRepo := mocks.NewStateRepository() - idp := mocks.NewIdentityProvider() - subs := map[string]string{"chanID": "chanID"} - broker := mocks.New(subs) - return twins.New(broker, auth, twinsRepo, statesRepo, idp, "chanID", nil) -} - func newServer(svc twins.Service) *httptest.Server { mux := httpapi.MakeHandler(mocktracer.New(), svc) return httptest.NewServer(mux) @@ -82,7 +72,7 @@ func toJSON(data interface{}) string { } func TestAddTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -185,7 +175,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -297,7 +287,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -379,7 +369,7 @@ func TestViewTwin(t *testing.T) { } func TestListTwins(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -552,7 +542,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() diff --git a/twins/mocks/service.go b/twins/mocks/service.go new file mode 100644 index 0000000000..fa64e00e3f --- /dev/null +++ b/twins/mocks/service.go @@ -0,0 +1,15 @@ +package mocks + +import ( + "github.com/mainflux/mainflux/twins" +) + +func NewService(tokens map[string]string) twins.Service { + auth := NewAuthNServiceClient(tokens) + twinsRepo := NewTwinRepository() + statesRepo := NewStateRepository() + idp := NewIdentityProvider() + subs := map[string]string{"chanID": "chanID"} + broker := New(subs) + return twins.New(broker, auth, twinsRepo, statesRepo, idp, "chanID", nil) +} diff --git a/twins/service_test.go b/twins/service_test.go index 73adee6dd1..21829f3276 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -23,18 +23,8 @@ const ( natsURL = "nats://localhost:4222" ) -func newService(tokens map[string]string) twins.Service { - auth := mocks.NewAuthNServiceClient(tokens) - twinsRepo := mocks.NewTwinRepository() - statesRepo := mocks.NewStateRepository() - idp := mocks.NewIdentityProvider() - subs := map[string]string{"chanID": "chanID"} - broker := mocks.New(subs) - return twins.New(broker, auth, twinsRepo, statesRepo, idp, "chanID", nil) -} - func TestAddTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} @@ -65,7 +55,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} other := twins.Twin{} def := twins.Definition{} @@ -109,7 +99,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) @@ -144,7 +134,7 @@ func TestViewTwin(t *testing.T) { } func TestListTwins(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{Name: twinName, Owner: email} def := twins.Definition{} m := make(map[string]interface{}) @@ -202,7 +192,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := newService(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) From 1232d3f74ae7ddaba94857bde88dd253404f613c Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Wed, 13 May 2020 14:39:42 +0200 Subject: [PATCH 09/17] Rename mocks.NewService to mocks.New Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 13 ++++++------- twins/api/http/endpoint_twins_test.go | 14 +++++++------- twins/mocks/service.go | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 8765989867..24e5532f57 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -45,7 +45,7 @@ type statesPageRes struct { } func TestListStates(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -90,7 +90,7 @@ func TestListStates(t *testing.T) { res: data[0:10], }, { - desc: "get a list of states with with valid offset and limit", + desc: "get a list of states with valid offset and limit", auth: token, status: http.StatusOK, url: fmt.Sprintf(queryFmt, baseURL, 20, 15), @@ -111,7 +111,7 @@ func TestListStates(t *testing.T) { res: nil, }, { - desc: "get a list of states with with + limit > total", + desc: "get a list of states with + limit > total", auth: token, status: http.StatusOK, url: fmt.Sprintf(queryFmt, baseURL, 91, 20), @@ -174,14 +174,14 @@ func TestListStates(t *testing.T) { res: data[14:24], }, { - desc: "get a list of states with invalid number of params", + desc: "get a list of states with invalid number of parameters", auth: token, status: http.StatusBadRequest, url: fmt.Sprintf("%s%s", baseURL, "?offset=4&limit=4&limit=5&offset=5"), res: nil, }, { - desc: "get a list of states with redundant query params", + desc: "get a list of states with redundant query parameters", auth: token, status: http.StatusOK, url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5), @@ -218,11 +218,10 @@ func createAttribute(name, subtopic string) twins.Attribute { func createSenML(n int, bn string) []senml.Record { var recs []senml.Record - bt := time.Now().Unix() for i := 0; i < n; i++ { rec := senml.Record{ BaseName: bn, - BaseTime: float64(bt), + BaseTime: float64(time.Now().UnixNano()) / float64(1e9), Time: float64(i), Value: nil, } diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 8ed85f0b9f..977db88903 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -72,7 +72,7 @@ func toJSON(data interface{}) string { } func TestAddTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -175,7 +175,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -287,7 +287,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -369,7 +369,7 @@ func TestViewTwin(t *testing.T) { } func TestListTwins(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -496,14 +496,14 @@ func TestListTwins(t *testing.T) { res: data[1:11], }, { - desc: "get a list of twins with invalid number of params", + desc: "get a list of twins with invalid number of parameters", auth: token, status: http.StatusBadRequest, url: fmt.Sprintf("%s%s", baseURL, "?offset=4&limit=4&limit=5&offset=5"), res: nil, }, { - desc: "get a list of twins with redundant query params", + desc: "get a list of twins with redundant query parameters", auth: token, status: http.StatusOK, url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5), @@ -542,7 +542,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() diff --git a/twins/mocks/service.go b/twins/mocks/service.go index fa64e00e3f..09b18d0d6b 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -4,7 +4,7 @@ import ( "github.com/mainflux/mainflux/twins" ) -func NewService(tokens map[string]string) twins.Service { +func New(tokens map[string]string) twins.Service { auth := NewAuthNServiceClient(tokens) twinsRepo := NewTwinRepository() statesRepo := NewStateRepository() From a801337c0a83b8cd4f4085051f28f37ab1358b44 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Wed, 13 May 2020 14:47:26 +0200 Subject: [PATCH 10/17] Add error checking to endpoint state tests Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 32 ++++++++++++++++---------- twins/api/http/endpoint_twins_test.go | 8 ++++--- twins/doc.go | 2 +- twins/mocks/messages.go | 2 +- twins/mocks/service.go | 2 +- twins/service_test.go | 10 ++++---- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 24e5532f57..1a111ede65 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -29,6 +29,8 @@ const ( attrSubtopic1 = "engine" attrName2 = "humidity" attrSubtopic2 = "chassis" + + publisher = "twins" ) type stateRes struct { @@ -60,14 +62,16 @@ func TestListStates(t *testing.T) { attr2, }, } - tw, _ := svc.AddTwin(context.Background(), token, twin, def) + tw, err := svc.AddTwin(context.Background(), token, twin, def) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) recs := createSenML(100, attrName1) - message := createMessage(attr1, recs) - err := svc.SaveStates(message) + message, err := createMessage(attr1, recs) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + err = svc.SaveStates(message) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - data := []stateRes{} + var data []stateRes for i := 0; i < len(recs); i++ { res := createStateResponse(i, tw, recs[i]) data = append(data, res) @@ -111,7 +115,7 @@ func TestListStates(t *testing.T) { res: nil, }, { - desc: "get a list of states with + limit > total", + desc: "get a list of states with + limit > total", auth: token, status: http.StatusOK, url: fmt.Sprintf(queryFmt, baseURL, 91, 20), @@ -221,23 +225,27 @@ func createSenML(n int, bn string) []senml.Record { for i := 0; i < n; i++ { rec := senml.Record{ BaseName: bn, - BaseTime: float64(time.Now().UnixNano()) / float64(1e9), - Time: float64(i), - Value: nil, + BaseTime: float64(time.Now().Unix()), + // BaseTime: float64(time.Now().UnixNano()) / float64(1e9), + Time: float64(i), + Value: nil, } recs = append(recs, rec) } return recs } -func createMessage(attr twins.Attribute, recs []senml.Record) *messaging.Message { - mRecs, _ := json.Marshal(recs) +func createMessage(attr twins.Attribute, recs []senml.Record) (*messaging.Message, error) { + mRecs, err := json.Marshal(recs) + if err != nil { + return nil, err + } return &messaging.Message{ Channel: attr.Channel, Subtopic: attr.Subtopic, Payload: mRecs, - Publisher: "twins", - } + Publisher: publisher, + }, nil } func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes { diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 977db88903..2789fed72c 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -181,7 +181,8 @@ func TestUpdateTwin(t *testing.T) { twin := twins.Twin{} def := twins.Definition{} - stw, _ := svc.AddTwin(context.Background(), token, twin, def) + stw, err := svc.AddTwin(context.Background(), token, twin, def) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) twin.Name = twinName data := toJSON(twin) @@ -373,7 +374,7 @@ func TestListTwins(t *testing.T) { ts := newServer(svc) defer ts.Close() - data := []twinRes{} + var data []twinRes for i := 0; i < 100; i++ { name := fmt.Sprintf("%s-%d", twinName, i) twin := twins.Twin{ @@ -548,7 +549,8 @@ func TestRemoveTwin(t *testing.T) { def := twins.Definition{} twin := twins.Twin{} - stw, _ := svc.AddTwin(context.Background(), token, twin, def) + stw, err := svc.AddTwin(context.Background(), token, twin, def) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { desc string diff --git a/twins/doc.go b/twins/doc.go index f850aaa508..4816cd0d9c 100644 --- a/twins/doc.go +++ b/twins/doc.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // Package twins contains the domain concept definitions needed to support -// Mainflux twins service functionality. Twin is a semantic representation of a +// Mainflux twins service functionality. Twin is a digital representation of a // real world data system consisting of data producers and consumers. It stores // the sequence of attribute based definitions of a data system and refers to a // time series of definition based states that store the system historical data. diff --git a/twins/mocks/messages.go b/twins/mocks/messages.go index 43611b33c7..b2d4134956 100644 --- a/twins/mocks/messages.go +++ b/twins/mocks/messages.go @@ -15,7 +15,7 @@ type mockBroker struct { } // New returns mock message publisher. -func New(sub map[string]string) messaging.Publisher { +func NewBroker(sub map[string]string) messaging.Publisher { return &mockBroker{ subscriptions: sub, } diff --git a/twins/mocks/service.go b/twins/mocks/service.go index 09b18d0d6b..2b7d440a90 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -10,6 +10,6 @@ func New(tokens map[string]string) twins.Service { statesRepo := NewStateRepository() idp := NewIdentityProvider() subs := map[string]string{"chanID": "chanID"} - broker := New(subs) + broker := NewBroker(subs) return twins.New(broker, auth, twinsRepo, statesRepo, idp, "chanID", nil) } diff --git a/twins/service_test.go b/twins/service_test.go index 21829f3276..b4399f356c 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -24,7 +24,7 @@ const ( ) func TestAddTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} @@ -55,7 +55,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) twin := twins.Twin{} other := twins.Twin{} def := twins.Definition{} @@ -99,7 +99,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) @@ -134,7 +134,7 @@ func TestViewTwin(t *testing.T) { } func TestListTwins(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) twin := twins.Twin{Name: twinName, Owner: email} def := twins.Definition{} m := make(map[string]interface{}) @@ -192,7 +192,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := mocks.NewService(map[string]string{token: email}) + svc := mocks.New(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) From 668af9554a4264d220dbac3131fde7a02457abcf Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Thu, 14 May 2020 13:22:10 +0200 Subject: [PATCH 11/17] Fix method comment Signed-off-by: Darko Draskovic --- twins/mocks/messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twins/mocks/messages.go b/twins/mocks/messages.go index b2d4134956..6eba9e02e8 100644 --- a/twins/mocks/messages.go +++ b/twins/mocks/messages.go @@ -14,7 +14,7 @@ type mockBroker struct { subscriptions map[string]string } -// New returns mock message publisher. +// NewBroker returns mock message publisher. func NewBroker(sub map[string]string) messaging.Publisher { return &mockBroker{ subscriptions: sub, From 1b40ddda6efe5dc5a0623ccb9a1eb5eedb2501c8 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Thu, 14 May 2020 14:54:55 +0200 Subject: [PATCH 12/17] Add json response decode success check Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 8 ++++++-- twins/api/http/endpoint_twins_test.go | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 1a111ede65..9fd18ba7ad 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -201,10 +201,14 @@ func TestListStates(t *testing.T) { token: tc.auth, } res, err := req.make() - assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resData statesPageRes - json.NewDecoder(res.Body).Decode(&resData) + if tc.res != nil { + err = json.NewDecoder(res.Body).Decode(&resData) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + } + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) assert.ElementsMatch(t, tc.res, resData.States, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData.States)) } diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 2789fed72c..0e6b0ef259 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -535,8 +535,13 @@ func TestListTwins(t *testing.T) { } res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + var resData twinsPageRes - json.NewDecoder(res.Body).Decode(&resData) + if tc.res != nil { + err = json.NewDecoder(res.Body).Decode(&resData) + assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + } + assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) assert.ElementsMatch(t, tc.res, resData.Twins, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData.Twins)) } From 8e2c6012a03b9e04cbc54a6287fc47fe42429c6f Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Fri, 15 May 2020 18:27:36 +0200 Subject: [PATCH 13/17] Remove created and updated fields from twin and state res Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 21 ++++++++--------- twins/api/http/endpoint_twins_test.go | 32 +++++++++++--------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 9fd18ba7ad..8214e6bd83 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -7,7 +7,6 @@ import ( "context" "encoding/json" "fmt" - "math" "net/http" "testing" "time" @@ -34,11 +33,11 @@ const ( ) type stateRes struct { - TwinID string `json:"twin_id"` - ID int64 `json:"id"` - Definition int `json:"definition"` - Created time.Time `json:"created"` - Payload map[string]interface{} `json:"payload"` + TwinID string `json:"twin_id"` + ID int64 `json:"id"` + Definition int `json:"definition"` + // Created time.Time `json:"created"` + Payload map[string]interface{} `json:"payload"` } type statesPageRes struct { @@ -253,15 +252,15 @@ func createMessage(attr twins.Attribute, recs []senml.Record) (*messaging.Messag } func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes { - recSec := rec.BaseTime + rec.Time - sec, dec := math.Modf(recSec) - recTime := time.Unix(int64(sec), int64(dec*nanosec)) + // recSec := rec.BaseTime + rec.Time + // sec, dec := math.Modf(recSec) + // recTime := time.Unix(int64(sec), int64(dec*nanosec)) return stateRes{ TwinID: tw.ID, ID: int64(id), Definition: tw.Definitions[len(tw.Definitions)-1].ID, - Created: recTime, - Payload: map[string]interface{}{rec.BaseName: nil}, + // Created: recTime, + Payload: map[string]interface{}{rec.BaseName: nil}, } } diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 0e6b0ef259..1e2d68bcaf 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -8,13 +8,11 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "strconv" "strings" "testing" - "time" "github.com/mainflux/mainflux/twins" httpapi "github.com/mainflux/mainflux/twins/api/http" @@ -302,54 +300,51 @@ func TestViewTwin(t *testing.T) { Name: stw.Name, ID: stw.ID, Revision: stw.Revision, - Created: stw.Created, - Updated: stw.Updated, Definitions: stw.Definitions, Metadata: stw.Metadata, } - data := toJSON(twres) cases := []struct { desc string id string auth string status int - res string + res twinRes }{ { desc: "view existing twin", id: stw.ID, auth: token, status: http.StatusOK, - res: data, + res: twres, }, { desc: "view non-existent twin", id: strconv.FormatUint(wrongID, 10), auth: token, status: http.StatusNotFound, - res: "", + res: twinRes{}, }, { desc: "view twin by passing invalid token", id: stw.ID, auth: wrongValue, status: http.StatusForbidden, - res: "", + res: twinRes{}, }, { desc: "view twin by passing empty id", id: "", auth: token, status: http.StatusBadRequest, - res: "", + res: twinRes{}, }, { desc: "view twin by passing empty token", id: stw.ID, auth: "", status: http.StatusForbidden, - res: "", + res: twinRes{}, }, } @@ -363,9 +358,14 @@ func TestViewTwin(t *testing.T) { res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - body, err := ioutil.ReadAll(res.Body) - data := strings.Trim(string(body), "\n") - assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data)) + + var resData twinRes + err = json.NewDecoder(res.Body).Decode(&resData) + assert.Equal(t, tc.res, resData, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData)) + + // body, err := ioutil.ReadAll(res.Body) + // data := strings.Trim(string(body), "\n") + // assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data)) } } @@ -388,8 +388,6 @@ func TestListTwins(t *testing.T) { ID: tw.ID, Name: tw.Name, Revision: tw.Revision, - Created: tw.Created, - Updated: tw.Updated, Definitions: tw.Definitions, Metadata: tw.Metadata, } @@ -620,8 +618,6 @@ type twinRes struct { ID string `json:"id"` Name string `json:"name,omitempty"` Revision int `json:"revision"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` Definitions []twins.Definition `json:"definitions"` Metadata map[string]interface{} `json:"metadata,omitempty"` } From 41ca3e48828e3a3b3864957c255f6ea802a4302c Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Fri, 15 May 2020 19:34:02 +0200 Subject: [PATCH 14/17] Remove definition fields from twin req and res Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 23 +++---- twins/api/http/endpoint_twins_test.go | 88 ++++++++++++-------------- twins/mocks/service.go | 3 +- twins/service_test.go | 10 +-- 4 files changed, 55 insertions(+), 69 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 8214e6bd83..9b7735426f 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -33,11 +33,10 @@ const ( ) type stateRes struct { - TwinID string `json:"twin_id"` - ID int64 `json:"id"` - Definition int `json:"definition"` - // Created time.Time `json:"created"` - Payload map[string]interface{} `json:"payload"` + TwinID string `json:"twin_id"` + ID int64 `json:"id"` + Definition int `json:"definition"` + Payload map[string]interface{} `json:"payload"` } type statesPageRes struct { @@ -46,7 +45,7 @@ type statesPageRes struct { } func TestListStates(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -229,9 +228,8 @@ func createSenML(n int, bn string) []senml.Record { rec := senml.Record{ BaseName: bn, BaseTime: float64(time.Now().Unix()), - // BaseTime: float64(time.Now().UnixNano()) / float64(1e9), - Time: float64(i), - Value: nil, + Time: float64(i), + Value: nil, } recs = append(recs, rec) } @@ -252,15 +250,10 @@ func createMessage(attr twins.Attribute, recs []senml.Record) (*messaging.Messag } func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes { - // recSec := rec.BaseTime + rec.Time - // sec, dec := math.Modf(recSec) - // recTime := time.Unix(int64(sec), int64(dec*nanosec)) - return stateRes{ TwinID: tw.ID, ID: int64(id), Definition: tw.Definitions[len(tw.Definitions)-1].ID, - // Created: recTime, - Payload: map[string]interface{}{rec.BaseName: nil}, + Payload: map[string]interface{}{rec.BaseName: nil}, } } diff --git a/twins/api/http/endpoint_twins_test.go b/twins/api/http/endpoint_twins_test.go index 1e2d68bcaf..537e529e91 100644 --- a/twins/api/http/endpoint_twins_test.go +++ b/twins/api/http/endpoint_twins_test.go @@ -36,6 +36,31 @@ const ( var invalidName = strings.Repeat("m", maxNameSize+1) +type twinReq struct { + token string + Name string `json:"name,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type twinRes struct { + Owner string `json:"owner"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Revision int `json:"revision"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type pageRes struct { + Total uint64 `json:"total"` + Offset uint64 `json:"offset"` + Limit uint64 `json:"limit"` +} + +type twinsPageRes struct { + pageRes + Twins []twinRes `json:"twins"` +} + type testRequest struct { client *http.Client method string @@ -70,7 +95,7 @@ func toJSON(data interface{}) string { } func TestAddTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -173,7 +198,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -286,7 +311,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -296,12 +321,11 @@ func TestViewTwin(t *testing.T) { require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) twres := twinRes{ - Owner: stw.Owner, - Name: stw.Name, - ID: stw.ID, - Revision: stw.Revision, - Definitions: stw.Definitions, - Metadata: stw.Metadata, + Owner: stw.Owner, + Name: stw.Name, + ID: stw.ID, + Revision: stw.Revision, + Metadata: stw.Metadata, } cases := []struct { @@ -362,15 +386,11 @@ func TestViewTwin(t *testing.T) { var resData twinRes err = json.NewDecoder(res.Body).Decode(&resData) assert.Equal(t, tc.res, resData, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData)) - - // body, err := ioutil.ReadAll(res.Body) - // data := strings.Trim(string(body), "\n") - // assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data)) } } func TestListTwins(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -384,12 +404,11 @@ func TestListTwins(t *testing.T) { tw, err := svc.AddTwin(context.Background(), token, twin, twins.Definition{}) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) twres := twinRes{ - Owner: tw.Owner, - ID: tw.ID, - Name: tw.Name, - Revision: tw.Revision, - Definitions: tw.Definitions, - Metadata: tw.Metadata, + Owner: tw.Owner, + ID: tw.ID, + Name: tw.Name, + Revision: tw.Revision, + Metadata: tw.Metadata, } data = append(data, twres) } @@ -546,7 +565,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) ts := newServer(svc) defer ts.Close() @@ -605,30 +624,3 @@ func TestRemoveTwin(t *testing.T) { assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) } } - -type twinReq struct { - token string - Name string `json:"name,omitempty"` - Definition twins.Definition `json:"definition,omitempty"` - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -type twinRes struct { - Owner string `json:"owner"` - ID string `json:"id"` - Name string `json:"name,omitempty"` - Revision int `json:"revision"` - Definitions []twins.Definition `json:"definitions"` - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -type pageRes struct { - Total uint64 `json:"total"` - Offset uint64 `json:"offset"` - Limit uint64 `json:"limit"` -} - -type twinsPageRes struct { - pageRes - Twins []twinRes `json:"twins"` -} diff --git a/twins/mocks/service.go b/twins/mocks/service.go index 2b7d440a90..d06cc2728d 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -4,7 +4,8 @@ import ( "github.com/mainflux/mainflux/twins" ) -func New(tokens map[string]string) twins.Service { +// NewService use mock dependencies to create real twins service +func NewService(tokens map[string]string) twins.Service { auth := NewAuthNServiceClient(tokens) twinsRepo := NewTwinRepository() statesRepo := NewStateRepository() diff --git a/twins/service_test.go b/twins/service_test.go index b4399f356c..21829f3276 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -24,7 +24,7 @@ const ( ) func TestAddTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} @@ -55,7 +55,7 @@ func TestAddTwin(t *testing.T) { } func TestUpdateTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} other := twins.Twin{} def := twins.Definition{} @@ -99,7 +99,7 @@ func TestUpdateTwin(t *testing.T) { } func TestViewTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) @@ -134,7 +134,7 @@ func TestViewTwin(t *testing.T) { } func TestListTwins(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{Name: twinName, Owner: email} def := twins.Definition{} m := make(map[string]interface{}) @@ -192,7 +192,7 @@ func TestListTwins(t *testing.T) { } func TestRemoveTwin(t *testing.T) { - svc := mocks.New(map[string]string{token: email}) + svc := mocks.NewService(map[string]string{token: email}) twin := twins.Twin{} def := twins.Definition{} saved, err := svc.AddTwin(context.Background(), token, twin, def) From 7a0f2821b7253680754ea49dedce50bfc084a0f0 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 18 May 2020 15:20:50 +0200 Subject: [PATCH 15/17] Add Create funcs to mocks package Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 54 ++--------------------- twins/mocks/service.go | 55 ++++++++++++++++++++++++ twins/mocks/states.go | 8 ++-- twins/mocks/twins.go | 8 ++-- twins/service.go | 10 ++--- twins/service_test.go | 59 ++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 63 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index 9b7735426f..ac788e7dcf 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -9,16 +9,13 @@ import ( "fmt" "net/http" "testing" - "time" - "github.com/mainflux/mainflux/messaging" "github.com/mainflux/mainflux/twins" "github.com/mainflux/senml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mainflux/mainflux/twins/mocks" - "github.com/mainflux/mainflux/twins/uuid" ) const ( @@ -52,19 +49,13 @@ func TestListStates(t *testing.T) { twin := twins.Twin{ Owner: email, } - attr1 := createAttribute(attrName1, attrSubtopic1) - attr2 := createAttribute(attrName2, attrSubtopic2) - def := twins.Definition{ - Attributes: []twins.Attribute{ - attr1, - attr2, - }, - } + def := mocks.CreateDefinition([]string{attrName1, attrName2}, []string{attrSubtopic1, attrSubtopic2}) tw, err := svc.AddTwin(context.Background(), token, twin, def) + attr := def.Attributes[0] require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - recs := createSenML(100, attrName1) - message, err := createMessage(attr1, recs) + recs := mocks.CreateSenML(100, attrName1) + message, err := mocks.CreateMessage(attr, recs) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) err = svc.SaveStates(message) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -212,43 +203,6 @@ func TestListStates(t *testing.T) { } } -func createAttribute(name, subtopic string) twins.Attribute { - id, _ := uuid.New().ID() - return twins.Attribute{ - Name: name, - Channel: id, - Subtopic: subtopic, - PersistState: true, - } -} - -func createSenML(n int, bn string) []senml.Record { - var recs []senml.Record - for i := 0; i < n; i++ { - rec := senml.Record{ - BaseName: bn, - BaseTime: float64(time.Now().Unix()), - Time: float64(i), - Value: nil, - } - recs = append(recs, rec) - } - return recs -} - -func createMessage(attr twins.Attribute, recs []senml.Record) (*messaging.Message, error) { - mRecs, err := json.Marshal(recs) - if err != nil { - return nil, err - } - return &messaging.Message{ - Channel: attr.Channel, - Subtopic: attr.Subtopic, - Payload: mRecs, - Publisher: publisher, - }, nil -} - func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes { return stateRes{ TwinID: tw.ID, diff --git a/twins/mocks/service.go b/twins/mocks/service.go index d06cc2728d..d52e464394 100644 --- a/twins/mocks/service.go +++ b/twins/mocks/service.go @@ -1,7 +1,17 @@ package mocks import ( + "encoding/json" + "time" + + "github.com/mainflux/mainflux/messaging" "github.com/mainflux/mainflux/twins" + "github.com/mainflux/mainflux/twins/uuid" + "github.com/mainflux/senml" +) + +const ( + publisher = "twins" ) // NewService use mock dependencies to create real twins service @@ -14,3 +24,48 @@ func NewService(tokens map[string]string) twins.Service { broker := NewBroker(subs) return twins.New(broker, auth, twinsRepo, statesRepo, idp, "chanID", nil) } + +// CreateDefinition creates twin definition +func CreateDefinition(names []string, subtopics []string) twins.Definition { + var def twins.Definition + for i, v := range names { + id, _ := uuid.New().ID() + attr := twins.Attribute{ + Name: v, + Channel: id, + Subtopic: subtopics[i], + PersistState: true, + } + def.Attributes = append(def.Attributes, attr) + } + return def +} + +// CreateSenML creates SenML record array +func CreateSenML(n int, bn string) []senml.Record { + var recs []senml.Record + for i := 0; i < n; i++ { + rec := senml.Record{ + BaseName: bn, + BaseTime: float64(time.Now().Unix()), + Time: float64(i), + Value: nil, + } + recs = append(recs, rec) + } + return recs +} + +// CreateMessage creates Mainflux message using SenML record array +func CreateMessage(attr twins.Attribute, recs []senml.Record) (*messaging.Message, error) { + mRecs, err := json.Marshal(recs) + if err != nil { + return nil, err + } + return &messaging.Message{ + Channel: attr.Channel, + Subtopic: attr.Subtopic, + Payload: mRecs, + Publisher: publisher, + }, nil +} diff --git a/twins/mocks/states.go b/twins/mocks/states.go index e742963a67..708174f8b2 100644 --- a/twins/mocks/states.go +++ b/twins/mocks/states.go @@ -15,9 +15,8 @@ import ( var _ twins.StateRepository = (*stateRepositoryMock)(nil) type stateRepositoryMock struct { - mu sync.Mutex - counter uint64 - states map[string]twins.State + mu sync.Mutex + states map[string]twins.State } // NewStateRepository creates in-memory twin repository. @@ -79,10 +78,11 @@ func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset uint64, return items[i].ID < items[j].ID }) + total := uint64(len(srm.states)) page := twins.StatesPage{ States: items, PageMetadata: twins.PageMetadata{ - Total: srm.counter, + Total: total, Offset: offset, Limit: limit, }, diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index b2aaa72846..045a4e144b 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -16,9 +16,8 @@ import ( var _ twins.TwinRepository = (*twinRepositoryMock)(nil) type twinRepositoryMock struct { - mu sync.Mutex - counter uint64 - twins map[string]twins.Twin + mu sync.Mutex + twins map[string]twins.Twin } // NewTwinRepository creates in-memory twin repository. @@ -116,10 +115,11 @@ func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offs return items[i].ID < items[j].ID }) + total := uint64(len(trm.twins)) page := twins.Page{ Twins: items, PageMetadata: twins.PageMetadata{ - Total: trm.counter, + Total: total, Offset: offset, Limit: limit, }, diff --git a/twins/service.go b/twins/service.go index fccd3a1334..a52b06ce4b 100644 --- a/twins/service.go +++ b/twins/service.go @@ -131,7 +131,7 @@ func (ts *twinsService) AddTwin(ctx context.Context, token string, twin Twin, de twin.Owner = res.GetValue() - t := time.Now().Round(0) + t := time.Now() twin.Created = t twin.Updated = t @@ -142,7 +142,7 @@ func (ts *twinsService) AddTwin(ctx context.Context, token string, twin Twin, de def.Delta = millisec } - def.Created = time.Now().Round(0) + def.Created = time.Now() def.ID = 0 twin.Definitions = append(twin.Definitions, def) @@ -181,7 +181,7 @@ func (ts *twinsService) UpdateTwin(ctx context.Context, token string, twin Twin, if len(def.Attributes) > 0 { revision = true - def.Created = time.Now().Round(0) + def.Created = time.Now() def.ID = tw.Definitions[len(tw.Definitions)-1].ID + 1 tw.Definitions = append(tw.Definitions, def) } @@ -195,7 +195,7 @@ func (ts *twinsService) UpdateTwin(ctx context.Context, token string, twin Twin, return ErrMalformedEntity } - tw.Updated = time.Now().Round(0) + tw.Updated = time.Now() tw.Revision++ if err := ts.twins.Update(ctx, tw); err != nil { @@ -325,7 +325,7 @@ func prepareState(st *State, tw *Twin, rec senml.Record, msg *messaging.Message) if st.Payload == nil { st.Payload = make(map[string]interface{}) - st.ID-- // st.ID == -1; state is incremented on save -> zero-based index + st.ID = -1 // state is incremented on save -> zero-based index } else { for k := range st.Payload { idx := findAttribute(k, def.Attributes) diff --git a/twins/service_test.go b/twins/service_test.go index 21829f3276..c128e46aa3 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -6,6 +6,7 @@ package twins_test import ( "context" "fmt" + "math" "testing" "github.com/mainflux/mainflux/twins" @@ -21,6 +22,12 @@ const ( wrongToken = "wrong-token" email = "user@example.com" natsURL = "nats://localhost:4222" + + attrName1 = "temperature" + attrSubtopic1 = "engine" + attrName2 = "humidity" + attrSubtopic2 = "chassis" + numRecs = 100 ) func TestAddTwin(t *testing.T) { @@ -235,3 +242,55 @@ func TestRemoveTwin(t *testing.T) { assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) } } + +func TestSaveStates(t *testing.T) { + svc := mocks.NewService(map[string]string{token: email}) + + twin := twins.Twin{Owner: email} + def := mocks.CreateDefinition([]string{attrName1, attrName2}, []string{attrSubtopic1, attrSubtopic2}) + attr := def.Attributes[0] + tw, err := svc.AddTwin(context.Background(), token, twin, def) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + recs := mocks.CreateSenML(numRecs, attrName1) + var ttlAdded uint64 + + cases := []struct { + desc string + offset uint64 + limit uint64 + err error + }{ + { + desc: "add 10 states", + offset: 0, + limit: numRecs, + err: nil, + }, + { + desc: "add 20 states", + offset: 10, + limit: 10, + err: nil, + }, + } + + for _, tc := range cases { + + from := clamp(tc.offset, 0, numRecs) + to := clamp(tc.offset+tc.limit, 0, numRecs) + message, err := mocks.CreateMessage(attr, recs[from:to]) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + err = svc.SaveStates(message) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + ttlAdded += to - from + + page, err := svc.ListStates(context.TODO(), token, tc.offset, tc.limit, tw.ID) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + assert.Equal(t, ttlAdded, page.Total, fmt.Sprintf("%s: expected %s total got %s total\n", tc.desc, tc.err, err)) + } +} + +func clamp(n, min, max uint64) uint64 { + return uint64(math.Min(math.Max(float64(n), float64(min)), float64(max))) +} From bde84facb2634848ec442051978636fe723764fc Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 18 May 2020 16:35:00 +0200 Subject: [PATCH 16/17] Add service save state tests Signed-off-by: Darko Draskovic --- twins/mocks/states.go | 4 ++- twins/mocks/twins.go | 5 +++- twins/service_test.go | 59 +++++++++++++++++++++++++------------------ 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/twins/mocks/states.go b/twins/mocks/states.go index 708174f8b2..da747c2a8f 100644 --- a/twins/mocks/states.go +++ b/twins/mocks/states.go @@ -98,7 +98,9 @@ func (srm *stateRepositoryMock) RetrieveLast(ctx context.Context, id string) (tw items := make([]twins.State, 0) for _, v := range srm.states { - items = append(items, v) + if v.TwinID == id { + items = append(items, v) + } } sort.SliceStable(items, func(i, j int) bool { return items[i].ID < items[j].ID diff --git a/twins/mocks/twins.go b/twins/mocks/twins.go index 045a4e144b..585b721100 100644 --- a/twins/mocks/twins.go +++ b/twins/mocks/twins.go @@ -81,7 +81,10 @@ func (trm *twinRepositoryMock) RetrieveByAttribute(ctx context.Context, channel, } } - return ids, nil + if len(ids) > 0 { + return ids, nil + } + return ids, twins.ErrNotFound } func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offset uint64, limit uint64, name string, metadata twins.Metadata) (twins.Page, error) { diff --git a/twins/service_test.go b/twins/service_test.go index c128e46aa3..970214c92d 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -6,11 +6,11 @@ package twins_test import ( "context" "fmt" - "math" "testing" "github.com/mainflux/mainflux/twins" "github.com/mainflux/mainflux/twins/mocks" + "github.com/mainflux/senml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,6 +27,8 @@ const ( attrSubtopic1 = "engine" attrName2 = "humidity" attrSubtopic2 = "chassis" + attrName3 = "speed" + attrSubtopic3 = "wheel_2" numRecs = 100 ) @@ -249,6 +251,7 @@ func TestSaveStates(t *testing.T) { twin := twins.Twin{Owner: email} def := mocks.CreateDefinition([]string{attrName1, attrName2}, []string{attrSubtopic1, attrSubtopic2}) attr := def.Attributes[0] + attrSansTwin := mocks.CreateDefinition([]string{attrName3}, []string{attrSubtopic3}).Attributes[0] tw, err := svc.AddTwin(context.Background(), token, twin, def) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -256,41 +259,47 @@ func TestSaveStates(t *testing.T) { var ttlAdded uint64 cases := []struct { - desc string - offset uint64 - limit uint64 - err error + desc string + recs []senml.Record + attr twins.Attribute + err error }{ { - desc: "add 10 states", - offset: 0, - limit: numRecs, - err: nil, + desc: "add 100 states", + recs: recs, + attr: attr, + err: nil, }, { - desc: "add 20 states", - offset: 10, - limit: 10, - err: nil, + desc: "add 20 states", + recs: recs[10:30], + attr: attr, + err: nil, + }, + { + desc: "add 20 states for atttribute without twin", + recs: recs[30:50], + attr: attrSansTwin, + err: twins.ErrNotFound, + }, + { + desc: "use empty senml record", + recs: []senml.Record{}, + attr: attr, + err: nil, }, } for _, tc := range cases { - - from := clamp(tc.offset, 0, numRecs) - to := clamp(tc.offset+tc.limit, 0, numRecs) - message, err := mocks.CreateMessage(attr, recs[from:to]) + message, err := mocks.CreateMessage(tc.attr, tc.recs) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) err = svc.SaveStates(message) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - ttlAdded += to - from - - page, err := svc.ListStates(context.TODO(), token, tc.offset, tc.limit, tw.ID) + if err == nil { + ttlAdded += uint64(len(tc.recs)) + } + page, err := svc.ListStates(context.TODO(), token, 0, 10, tw.ID) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - assert.Equal(t, ttlAdded, page.Total, fmt.Sprintf("%s: expected %s total got %s total\n", tc.desc, tc.err, err)) + assert.Equal(t, ttlAdded, page.Total, fmt.Sprintf("%s: expected %d total got %d total\n", tc.desc, ttlAdded, page.Total)) } } - -func clamp(n, min, max uint64) uint64 { - return uint64(math.Min(math.Max(float64(n), float64(min)), float64(max))) -} From 431b537f374391a4df1c7cbc08bc972f28f6fae6 Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Mon, 18 May 2020 17:56:51 +0200 Subject: [PATCH 17/17] Add service list states test Signed-off-by: Darko Draskovic --- twins/api/http/endpoint_states_test.go | 2 +- twins/service_test.go | 112 ++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/twins/api/http/endpoint_states_test.go b/twins/api/http/endpoint_states_test.go index ac788e7dcf..33a9561cf6 100644 --- a/twins/api/http/endpoint_states_test.go +++ b/twins/api/http/endpoint_states_test.go @@ -51,8 +51,8 @@ func TestListStates(t *testing.T) { } def := mocks.CreateDefinition([]string{attrName1, attrName2}, []string{attrSubtopic1, attrSubtopic2}) tw, err := svc.AddTwin(context.Background(), token, twin, def) - attr := def.Attributes[0] require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + attr := def.Attributes[0] recs := mocks.CreateSenML(100, attrName1) message, err := mocks.CreateMessage(attr, recs) diff --git a/twins/service_test.go b/twins/service_test.go index 970214c92d..ad262ac090 100644 --- a/twins/service_test.go +++ b/twins/service_test.go @@ -262,23 +262,27 @@ func TestSaveStates(t *testing.T) { desc string recs []senml.Record attr twins.Attribute + size uint64 err error }{ { desc: "add 100 states", recs: recs, attr: attr, + size: numRecs, err: nil, }, { desc: "add 20 states", recs: recs[10:30], attr: attr, + size: 20, err: nil, }, { desc: "add 20 states for atttribute without twin", recs: recs[30:50], + size: 0, attr: attrSansTwin, err: twins.ErrNotFound, }, @@ -286,6 +290,7 @@ func TestSaveStates(t *testing.T) { desc: "use empty senml record", recs: []senml.Record{}, attr: attr, + size: 0, err: nil, }, } @@ -293,13 +298,114 @@ func TestSaveStates(t *testing.T) { for _, tc := range cases { message, err := mocks.CreateMessage(tc.attr, tc.recs) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + err = svc.SaveStates(message) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) - if err == nil { - ttlAdded += uint64(len(tc.recs)) - } + + ttlAdded += tc.size page, err := svc.ListStates(context.TODO(), token, 0, 10, tw.ID) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) assert.Equal(t, ttlAdded, page.Total, fmt.Sprintf("%s: expected %d total got %d total\n", tc.desc, ttlAdded, page.Total)) } } + +func TestListStates(t *testing.T) { + svc := mocks.NewService(map[string]string{token: email}) + + twin := twins.Twin{Owner: email} + def := mocks.CreateDefinition([]string{attrName1, attrName2}, []string{attrSubtopic1, attrSubtopic2}) + attr := def.Attributes[0] + tw, err := svc.AddTwin(context.Background(), token, twin, def) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + tw2, err := svc.AddTwin(context.Background(), token, + twins.Twin{Owner: email}, + mocks.CreateDefinition([]string{attrName3}, []string{attrSubtopic3})) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + recs := mocks.CreateSenML(numRecs, attrName1) + message, err := mocks.CreateMessage(attr, recs) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + err = svc.SaveStates(message) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + id string + token string + offset uint64 + limit uint64 + size int + err error + }{ + { + desc: "get a list of first 10 states", + id: tw.ID, + token: token, + offset: 0, + limit: 10, + size: 10, + err: nil, + }, + { + desc: "get a list of last 10 states", + id: tw.ID, + token: token, + offset: numRecs - 10, + limit: numRecs, + size: 10, + err: nil, + }, + { + desc: "get a list of last 10 states with limit > numRecs", + id: tw.ID, + token: token, + offset: numRecs - 10, + limit: numRecs + 10, + size: 10, + err: nil, + }, + { + desc: "get a list of first 10 states with offset == numRecs", + id: tw.ID, + token: token, + offset: numRecs, + limit: numRecs + 10, + size: 0, + err: nil, + }, + { + desc: "get a list with wrong user token", + id: tw.ID, + token: wrongToken, + offset: 0, + limit: 10, + size: 0, + err: twins.ErrUnauthorizedAccess, + }, + { + desc: "get a list with id of non-existent twin", + id: "1234567890", + token: token, + offset: 0, + limit: 10, + size: 0, + err: nil, + }, + { + desc: "get a list with id of existing twin without states ", + id: tw2.ID, + token: token, + offset: 0, + limit: 10, + size: 0, + err: nil, + }, + } + + for _, tc := range cases { + page, err := svc.ListStates(context.TODO(), tc.token, tc.offset, tc.limit, tc.id) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + assert.Equal(t, tc.size, len(page.States), fmt.Sprintf("%s: expected %d total got %d total\n", tc.desc, tc.size, len(page.States))) + } +}