MG-2173 - Generate mocks with mockery for Twins service (#2174)

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2024-04-23 14:18:42 +03:00
committed by GitHub
parent 07635ad828
commit 8915b73953
49 changed files with 1603 additions and 904 deletions
+12 -1
View File
@@ -72,6 +72,9 @@ jobs:
- "certs/certs.go"
- "certs/pki/vault.go"
- "certs/service.go"
- "twins/twins.go"
- "twins/states.go"
- "twins/service.go"
- name: Set up protoc
if: steps.changes.outputs.proto == 'true'
@@ -115,7 +118,7 @@ jobs:
- name: Check Mocks are up to Date
if: steps.changes.outputs.mocks == 'true'
run: |
MOCKERY_VERSION=v2.42.1
MOCKERY_VERSION=v2.42.3
go install github.com/vektra/mockery/v2@$MOCKERY_VERSION
mv ./pkg/sdk/mocks/sdk.go ./pkg/sdk/mocks/sdk.go.tmp
@@ -150,6 +153,10 @@ jobs:
mv ./certs/mocks/certs.go ./certs/mocks/certs.go.tmp
mv ./certs/mocks/pki.go ./certs/mocks/pki.go.tmp
mv ./certs/mocks/service.go ./certs/mocks/service.go.tmp
mv ./twins/mocks/service.go ./twins/mocks/service.go.tmp
mv ./twins/mocks/states.go ./twins/mocks/states.go.tmp
mv ./twins/mocks/repository.go ./twins/mocks/repository.go.tmp
mv ./twins/mocks/cache.go ./twins/mocks/cache.go.tmp
make mocks
@@ -197,3 +204,7 @@ jobs:
check_mock_changes ./certs/mocks/certs.go "Certs Repository ./certs/mocks/certs.go"
check_mock_changes ./certs/mocks/pki.go "PKI ./certs/mocks/pki.go"
check_mock_changes ./certs/mocks/service.go "Certs Service ./certs/mocks/service.go"
check_mock_changes ./twins/mocks/service.go "Twins Service ./twins/mocks/service.go"
check_mock_changes ./twins/mocks/states.go "Twins States ./twins/mocks/states.go"
check_mock_changes ./twins/mocks/repository.go "Twins Repository ./twins/mocks/repository.go"
check_mock_changes ./twins/mocks/cache.go "Twins Cache ./twins/mocks/cache.go"
+1 -1
View File
@@ -23,7 +23,7 @@ DOCKER_PROJECT ?= $(shell echo $(subst $(space),,$(USER_REPO)) | tr -c -s '[:aln
DOCKER_COMPOSE_COMMANDS_SUPPORTED := up down config
DEFAULT_DOCKER_COMPOSE_COMMAND := up
GRPC_MTLS_CERT_FILES_EXISTS = 0
MOCKERY_VERSION=v2.42.1
MOCKERY_VERSION=v2.42.3
ifneq ($(MG_MESSAGE_BROKER_TYPE),)
MG_MESSAGE_BROKER_TYPE := $(MG_MESSAGE_BROKER_TYPE)
else
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
-2
View File
@@ -137,8 +137,6 @@ func TestPublish(t *testing.T) {
repoCall2 := connsRM.On("Get", context.Background(), mock.Anything).Return("", tc.connectionsErr)
repoCall3 := pub.On("Publish", context.Background(), tc.msg.ApplicationID, mock.Anything).Return(tc.publishErr)
err := svc.Publish(context.Background(), &tc.msg)
fmt.Println(err)
fmt.Println(tc.err)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
repoCall.Unset()
repoCall1.Unset()
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+160 -71
View File
@@ -4,24 +4,29 @@
package http_test
import (
"context"
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/absmach/magistrala"
authmocks "github.com/absmach/magistrala/auth/mocks"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/twins"
"github.com/absmach/magistrala/twins/mocks"
"github.com/absmach/senml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const numRecs = 100
const (
numRecs = 100
publisher = "twins"
)
var (
subtopics = []string{"engine", "chassis", "wheel_2"}
@@ -40,42 +45,50 @@ type statesPageRes struct {
States []stateRes `json:"states"`
}
func NewService() (twins.Service, *authmocks.AuthClient, *mocks.TwinRepository, *mocks.TwinCache, *mocks.StateRepository) {
auth := new(authmocks.AuthClient)
twinsRepo := new(mocks.TwinRepository)
twinCache := new(mocks.TwinCache)
statesRepo := new(mocks.StateRepository)
idProvider := uuid.NewMock()
subs := map[string]string{"chanID": "chanID"}
broker := mocks.NewBroker(subs)
return twins.New(broker, auth, twinsRepo, twinCache, statesRepo, idProvider, "chanID", mglog.NewMock()), auth, twinsRepo, twinCache, statesRepo
}
func TestListStates(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, _, _, stateRepo := NewService()
ts := newServer(svc)
defer ts.Close()
twin := twins.Twin{
Owner: email,
}
def := mocks.CreateDefinition(channels[0:2], subtopics[0:2])
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
tw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
attr := def.Attributes[0]
twin := twins.Twin{
Owner: email,
Definitions: []twins.Definition{def},
ID: testsutil.GenerateUUID(t),
Created: time.Now(),
}
recs := make([]senml.Record, numRecs)
mocks.CreateSenML(recs)
message, err := mocks.CreateMessage(attr, recs)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
err = svc.SaveStates(context.Background(), message)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
var data []stateRes
for i := 0; i < len(recs); i++ {
res := createStateResponse(i, tw, recs[i])
res := createStateResponse(i, twin, recs[i])
data = append(data, res)
}
baseURL := fmt.Sprintf("%s/states/%s", ts.URL, tw.ID)
baseURL := fmt.Sprintf("%s/states/%s", ts.URL, twin.ID)
queryFmt := "%s?offset=%d&limit=%d"
cases := []struct {
desc string
auth string
status int
url string
res []stateRes
desc string
auth string
status int
url string
res []stateRes
err error
page twins.StatesPage
identifyErr error
userID string
}{
{
desc: "get a list of states",
@@ -83,6 +96,12 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: baseURL,
res: data[0:10],
err: nil,
page: twins.StatesPage{
States: convState(data[0:10]),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of states with valid offset and limit",
@@ -90,20 +109,30 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf(queryFmt, baseURL, 20, 15),
res: data[20:35],
page: twins.StatesPage{
States: convState(data[20:35]),
},
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of states with invalid token",
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 5),
res: nil,
desc: "get a list of states with invalid token",
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 5),
res: nil,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "get a list of states with empty token",
auth: "",
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 5),
res: nil,
desc: "get a list of states with empty token",
auth: "",
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 5),
res: nil,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "get a list of states with + limit > total",
@@ -111,48 +140,72 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf(queryFmt, baseURL, 91, 20),
res: data[91:],
page: twins.StatesPage{
States: convState(data[91:]),
},
err: nil,
identifyErr: nil,
userID: validID,
},
{
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 offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, -1, 5),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
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 negative limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, 0, -5),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
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 zero limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, 0, 0),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
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 limit greater than max",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, 0, 110),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
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 offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s?offset=invalid&limit=%d", baseURL, 15),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
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 with invalid limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s?offset=%d&limit=invalid", baseURL, 0),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of states without offset",
@@ -160,6 +213,12 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?limit=%d", baseURL, 15),
res: data[0:15],
page: twins.StatesPage{
States: convState(data[0:15]),
},
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of states without limit",
@@ -167,13 +226,22 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?offset=%d", baseURL, 14),
res: data[14:24],
page: twins.StatesPage{
States: convState(data[14:24]),
},
err: nil,
identifyErr: nil,
userID: validID,
},
{
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 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,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of states with redundant query parameters",
@@ -181,11 +249,18 @@ func TestListStates(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5),
res: data[0:5],
page: twins.StatesPage{
States: convState(data[0:5]),
},
err: nil,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, mock.Anything).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := stateRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
@@ -203,6 +278,7 @@ func TestListStates(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))
assert.ElementsMatch(t, tc.res, resData.States, fmt.Sprintf("%s: got incorrect body from response", tc.desc))
authCall.Unset()
repoCall.Unset()
}
}
@@ -215,3 +291,16 @@ func createStateResponse(id int, tw twins.Twin, rec senml.Record) stateRes {
Payload: map[string]interface{}{rec.BaseName: nil},
}
}
func convState(data []stateRes) []twins.State {
states := make([]twins.State, len(data))
for i, d := range data {
states[i] = twins.State{
TwinID: d.TwinID,
ID: d.ID,
Definition: d.Definition,
Payload: d.Payload,
}
}
return states
}
+376 -160
View File
@@ -4,7 +4,6 @@
package http_test
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -19,12 +18,11 @@ import (
"github.com/absmach/magistrala/internal/apiutil"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/twins"
httpapi "github.com/absmach/magistrala/twins/api/http"
"github.com/absmach/magistrala/twins/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const (
@@ -35,6 +33,8 @@ const (
wrongID = 0
maxNameSize = 1024
instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002"
retained = "saved"
validID = "123e4567-e89b-12d3-a456-426614174000"
)
var invalidName = strings.Repeat("m", maxNameSize+1)
@@ -101,7 +101,7 @@ func toJSON(data interface{}) (string, error) {
}
func TestAddTwin(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, twinCache, _ := NewService()
ts := newServer(svc)
defer ts.Close()
@@ -120,6 +120,10 @@ func TestAddTwin(t *testing.T) {
auth string
status int
location string
err error
saveErr error
identifyErr error
userID string
}{
{
desc: "add valid twin",
@@ -128,6 +132,10 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusCreated,
location: "/twins/123e4567-e89b-12d3-a456-000000000001",
err: nil,
saveErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin with empty JSON request",
@@ -136,6 +144,10 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusCreated,
location: "/twins/123e4567-e89b-12d3-a456-000000000002",
err: nil,
saveErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin with invalid auth token",
@@ -144,6 +156,9 @@ func TestAddTwin(t *testing.T) {
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
location: "",
err: svcerr.ErrAuthentication,
saveErr: svcerr.ErrCreateEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "add twin with empty auth token",
@@ -152,6 +167,9 @@ func TestAddTwin(t *testing.T) {
auth: "",
status: http.StatusUnauthorized,
location: "",
err: svcerr.ErrAuthentication,
saveErr: svcerr.ErrCreateEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "add twin with invalid request format",
@@ -160,6 +178,10 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrMalformedEntity,
saveErr: svcerr.ErrCreateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin with empty request",
@@ -168,6 +190,10 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrMalformedEntity,
saveErr: svcerr.ErrCreateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin without content type",
@@ -176,6 +202,10 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusUnsupportedMediaType,
location: "",
err: apiutil.ErrUnsupportedContentType,
saveErr: svcerr.ErrCreateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin with invalid name",
@@ -184,11 +214,17 @@ func TestAddTwin(t *testing.T) {
auth: token,
status: http.StatusBadRequest,
location: "",
err: svcerr.ErrMalformedEntity,
saveErr: svcerr.ErrCreateEntity,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("Save", mock.Anything, mock.Anything).Return(retained, tc.saveErr)
cacheCall := twinCache.On("Save", mock.Anything, mock.Anything).Return(tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
@@ -203,22 +239,21 @@ func TestAddTwin(t *testing.T) {
location := res.Header.Get("Location")
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
assert.Equal(t, tc.location, location, fmt.Sprintf("%s: expected location %s got %s", tc.desc, tc.location, location))
authCall.Unset()
repoCall.Unset()
cacheCall.Unset()
}
}
func TestUpdateTwin(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, twinCache, _ := NewService()
ts := newServer(svc)
defer ts.Close()
twin := twins.Twin{}
def := twins.Definition{}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
stw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
}
twin.Name = twinName
data, err := toJSON(twin)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
@@ -235,22 +270,35 @@ func TestUpdateTwin(t *testing.T) {
contentType string
auth string
status int
err error
retrieveErr error
updateErr error
identifyErr error
userID string
}{
{
desc: "update existing twin",
req: data,
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: token,
status: http.StatusOK,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "update twin with empty JSON request",
req: "{}",
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
err: svcerr.ErrMalformedEntity,
retrieveErr: nil,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "update non-existent twin",
@@ -259,46 +307,74 @@ func TestUpdateTwin(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusNotFound,
err: svcerr.ErrNotFound,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "update twin with invalid token",
req: data,
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "update twin with empty token",
req: data,
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: "",
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "update twin with invalid data format",
req: "{",
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
retrieveErr: nil,
updateErr: svcerr.ErrUpdateEntity,
userID: validID,
},
{
desc: "update twin with empty request",
req: "",
id: stw.ID,
id: twin.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
err: svcerr.ErrMalformedEntity,
retrieveErr: nil,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "update twin without content type",
req: data,
id: stw.ID,
id: twin.ID,
contentType: "",
auth: token,
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
retrieveErr: nil,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "update twin with invalid name",
@@ -306,11 +382,19 @@ func TestUpdateTwin(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusMethodNotAllowed,
err: svcerr.ErrMalformedEntity,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("RetrieveByID", mock.Anything, tc.id).Return(twins.Twin{}, tc.retrieveErr)
repoCall1 := twinRepo.On("Update", mock.Anything, mock.Anything).Return(tc.updateErr)
cacheCall := twinCache.On("Update", mock.Anything, mock.Anything).Return(tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodPut,
@@ -322,69 +406,88 @@ func TestUpdateTwin(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))
authCall.Unset()
repoCall.Unset()
repoCall1.Unset()
cacheCall.Unset()
}
}
func TestViewTwin(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, _, _ := NewService()
ts := newServer(svc)
defer ts.Close()
def := twins.Definition{}
twin := twins.Twin{}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
stw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
Revision: 50,
}
twres := twinRes{
Owner: stw.Owner,
Name: stw.Name,
ID: stw.ID,
Revision: stw.Revision,
Metadata: stw.Metadata,
Owner: twin.Owner,
Name: twin.Name,
ID: twin.ID,
Revision: twin.Revision,
Metadata: twin.Metadata,
}
cases := []struct {
desc string
id string
auth string
status int
res twinRes
desc string
id string
auth string
status int
res twinRes
err error
twin twins.Twin
identifyErr error
userID string
}{
{
desc: "view existing twin",
id: stw.ID,
auth: token,
status: http.StatusOK,
res: twres,
desc: "view existing twin",
id: twin.ID,
auth: token,
status: http.StatusOK,
res: twres,
err: nil,
twin: twin,
identifyErr: nil,
userID: validID,
},
{
desc: "view non-existent twin",
id: strconv.FormatUint(wrongID, 10),
auth: token,
status: http.StatusNotFound,
res: twinRes{},
desc: "view non-existent twin",
id: strconv.FormatUint(wrongID, 10),
auth: token,
status: http.StatusNotFound,
res: twinRes{},
err: svcerr.ErrNotFound,
identifyErr: nil,
userID: validID,
},
{
desc: "view twin by passing invalid token",
id: stw.ID,
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
res: twinRes{},
desc: "view twin by passing invalid token",
id: twin.ID,
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
res: twinRes{},
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "view twin by passing empty token",
id: stw.ID,
auth: "",
status: http.StatusUnauthorized,
res: twinRes{},
desc: "view twin by passing empty token",
id: twin.ID,
auth: "",
status: http.StatusUnauthorized,
res: twinRes{},
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("RetrieveByID", mock.Anything, tc.id).Return(tc.twin, tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
@@ -399,33 +502,32 @@ func TestViewTwin(t *testing.T) {
err = json.NewDecoder(res.Body).Decode(&resData)
assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error while decoding response body: %s\n", tc.desc, err))
assert.Equal(t, tc.res, resData, fmt.Sprintf("%s: expected body %v got %v", tc.desc, tc.res, resData))
authCall.Unset()
repoCall.Unset()
}
}
func TestListTwins(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, _, _ := NewService()
ts := newServer(svc)
defer ts.Close()
var data []twinRes
userID := testsutil.GenerateUUID(t)
for i := 0; i < 100; i++ {
name := fmt.Sprintf("%s-%d", twinName, i)
twin := twins.Twin{
Owner: email,
Name: name,
Owner: email,
Name: name,
ID: testsutil.GenerateUUID(t),
Revision: 150,
}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: userID}, nil)
tw, err := svc.AddTwin(context.Background(), token, twin, twins.Definition{})
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twres := twinRes{
Owner: tw.Owner,
ID: tw.ID,
Name: tw.Name,
Revision: tw.Revision,
Metadata: tw.Metadata,
Owner: twin.Owner,
ID: twin.ID,
Name: twin.Name,
Revision: twin.Revision,
Metadata: twin.Metadata,
}
data = append(data, twres)
}
@@ -433,11 +535,15 @@ func TestListTwins(t *testing.T) {
baseURL := 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 string
auth string
status int
url string
res []twinRes
err error
page twins.Page
identifyErr error
userID string
}{
{
desc: "get a list of twins",
@@ -445,20 +551,30 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: baseURL,
res: data[0:10],
err: nil,
page: twins.Page{
Twins: convTwin(data[0:10]),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with invalid token",
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 1),
res: nil,
desc: "get a list of twins with invalid token",
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 1),
res: nil,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "get a list of twins with empty token",
auth: "",
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 1),
res: nil,
desc: "get a list of twins with empty token",
auth: "",
status: http.StatusUnauthorized,
url: fmt.Sprintf(queryFmt, baseURL, 0, 1),
res: nil,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "get a list of twins with valid offset and limit",
@@ -466,6 +582,12 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf(queryFmt, baseURL, 25, 40),
res: data[25:65],
err: nil,
page: twins.Page{
Twins: convTwin(data[25:65]),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with offset + limit > total",
@@ -473,48 +595,72 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf(queryFmt, baseURL, 91, 20),
res: data[91:],
err: nil,
page: twins.Page{
Twins: convTwin(data[91:]),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with negative offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, -1, 5),
res: nil,
desc: "get a list of twins with negative offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, -1, 5),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with negative limit",
auth: token,
status: http.StatusBadRequest,
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, baseURL, 1, -5),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with zero limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, 1, 0),
res: nil,
desc: "get a list of twins with zero limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf(queryFmt, baseURL, 1, 0),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with limit greater than max",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s?offset=%d&limit=%d", baseURL, 0, 110),
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", baseURL, 0, 110),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with invalid offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s%s", baseURL, "?offset=e&limit=5"),
res: nil,
desc: "get a list of twins with invalid offset",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s%s", baseURL, "?offset=e&limit=5"),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with invalid limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s%s", baseURL, "?offset=5&limit=e"),
res: nil,
desc: "get a list of twins with invalid limit",
auth: token,
status: http.StatusBadRequest,
url: fmt.Sprintf("%s%s", baseURL, "?offset=5&limit=e"),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins without offset",
@@ -522,6 +668,12 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?limit=%d", baseURL, 5),
res: data[0:5],
err: nil,
page: twins.Page{
Twins: convTwin(data[0:5]),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins without limit",
@@ -529,13 +681,22 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?offset=%d", baseURL, 1),
res: data[1:11],
err: nil,
page: twins.Page{
Twins: convTwin(data[1:11]),
},
identifyErr: nil,
userID: validID,
},
{
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 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,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins with redundant query parameters",
@@ -543,13 +704,22 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?offset=%d&limit=%d&value=something", baseURL, 0, 5),
res: data[0:5],
err: nil,
page: twins.Page{
Twins: convTwin(data[0:5]),
},
identifyErr: nil,
userID: validID,
},
{
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", baseURL, 0, 5, invalidName),
res: nil,
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", baseURL, 0, 5, invalidName),
res: nil,
err: svcerr.ErrMalformedEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of twins filtering with valid name",
@@ -557,11 +727,18 @@ func TestListTwins(t *testing.T) {
status: http.StatusOK,
url: fmt.Sprintf("%s?offset=%d&limit=%d&name=%s", baseURL, 2, 1, twinName+"-2"),
res: data[2:3],
err: nil,
page: twins.Page{
Twins: convTwin(data[2:3]),
},
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: userID}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, nil)
repoCall := twinRepo.On("RetrieveAll", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
@@ -579,62 +756,87 @@ func TestListTwins(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))
assert.ElementsMatch(t, tc.res, resData.Twins, fmt.Sprintf("%s: got incorrect list of twins", tc.desc))
authCall.Unset()
repoCall.Unset()
}
}
func TestRemoveTwin(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, twinCache, _ := NewService()
ts := newServer(svc)
defer ts.Close()
def := twins.Definition{}
twin := twins.Twin{}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
stw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
Revision: 50,
}
cases := []struct {
desc string
id string
auth string
status int
desc string
id string
auth string
status int
err error
removeErr error
identifyErr error
userID string
}{
{
desc: "delete existing twin",
id: stw.ID,
auth: token,
status: http.StatusNoContent,
desc: "delete existing twin",
id: twin.ID,
auth: token,
status: http.StatusNoContent,
err: nil,
removeErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "delete non-existent twin",
id: strconv.FormatUint(wrongID, 10),
auth: token,
status: http.StatusNoContent,
desc: "delete non-existent twin",
id: strconv.FormatUint(wrongID, 10),
auth: token,
status: http.StatusNoContent,
err: nil,
removeErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "delete twin by passing empty id",
id: "",
auth: token,
status: http.StatusMethodNotAllowed,
desc: "delete twin by passing empty id",
id: "",
auth: token,
status: http.StatusMethodNotAllowed,
err: svcerr.ErrMalformedEntity,
removeErr: svcerr.ErrRemoveEntity,
identifyErr: nil,
userID: validID,
},
{
desc: "delete twin with invalid token",
id: stw.ID,
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
desc: "delete twin with invalid token",
id: twin.ID,
auth: authmocks.InvalidValue,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
removeErr: svcerr.ErrRemoveEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "delete twin with empty token",
id: stw.ID,
auth: "",
status: http.StatusUnauthorized,
desc: "delete twin with empty token",
id: twin.ID,
auth: "",
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
removeErr: svcerr.ErrRemoveEntity,
identifyErr: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("Remove", mock.Anything, tc.id).Return(tc.removeErr)
cacheCall2 := twinCache.On("Remove", mock.Anything, tc.id).Return(tc.err)
req := testRequest{
client: ts.Client(),
method: http.MethodDelete,
@@ -644,6 +846,20 @@ func TestRemoveTwin(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))
authCall.Unset()
repoCall.Unset()
cacheCall2.Unset()
}
}
func convTwin(data []twinRes) []twins.Twin {
twinSlice := make([]twins.Twin, len(data))
for i, d := range data {
twinSlice[i].ID = d.ID
twinSlice[i].Name = d.Name
twinSlice[i].Owner = d.Owner
twinSlice[i].Revision = d.Revision
twinSlice[i].Metadata = d.Metadata
}
return twinSlice
}
+133
View File
@@ -0,0 +1,133 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
package mocks
import (
context "context"
twins "github.com/absmach/magistrala/twins"
mock "github.com/stretchr/testify/mock"
)
// TwinCache is an autogenerated mock type for the TwinCache type
type TwinCache struct {
mock.Mock
}
// IDs provides a mock function with given fields: ctx, channel, subtopic
func (_m *TwinCache) IDs(ctx context.Context, channel string, subtopic string) ([]string, error) {
ret := _m.Called(ctx, channel, subtopic)
if len(ret) == 0 {
panic("no return value specified for IDs")
}
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok {
return rf(ctx, channel, subtopic)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok {
r0 = rf(ctx, channel, subtopic)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, channel, subtopic)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Remove provides a mock function with given fields: ctx, twinID
func (_m *TwinCache) Remove(ctx context.Context, twinID string) error {
ret := _m.Called(ctx, twinID)
if len(ret) == 0 {
panic("no return value specified for Remove")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, twinID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Save provides a mock function with given fields: ctx, twin
func (_m *TwinCache) Save(ctx context.Context, twin twins.Twin) error {
ret := _m.Called(ctx, twin)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) error); ok {
r0 = rf(ctx, twin)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveIDs provides a mock function with given fields: ctx, channel, subtopic, twinIDs
func (_m *TwinCache) SaveIDs(ctx context.Context, channel string, subtopic string, twinIDs []string) error {
ret := _m.Called(ctx, channel, subtopic, twinIDs)
if len(ret) == 0 {
panic("no return value specified for SaveIDs")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) error); ok {
r0 = rf(ctx, channel, subtopic, twinIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: ctx, twin
func (_m *TwinCache) Update(ctx context.Context, twin twins.Twin) error {
ret := _m.Called(ctx, twin)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) error); ok {
r0 = rf(ctx, twin)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewTwinCache creates a new instance of TwinCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTwinCache(t interface {
mock.TestingT
Cleanup(func())
}) *TwinCache {
mock := &TwinCache{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
-13
View File
@@ -1,13 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import "fmt"
// Since mocks will store data in map, and they need to resemble the real
// identifiers as much as possible, a key will be created as combination of
// owner and their id. This will allow searching by prefix or suffix.
func key(owner, id string) string {
return fmt.Sprintf("%s-%s", owner, id)
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"encoding/json"
"strconv"
"github.com/absmach/magistrala/pkg/messaging"
twins "github.com/absmach/magistrala/twins"
"github.com/absmach/senml"
)
var (
publisher = "twins"
id = 0
)
// CreateMessage creates Magistrala 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
}
// CreateDefinition creates twin definition.
func CreateDefinition(channels, subtopics []string) twins.Definition {
var def twins.Definition
for i := range channels {
attr := twins.Attribute{
Channel: channels[i],
Subtopic: subtopics[i],
PersistState: true,
}
def.Attributes = append(def.Attributes, attr)
}
return def
}
// CreateTwin creates twin.
func CreateTwin(channels, subtopics []string) twins.Twin {
id++
return twins.Twin{
ID: strconv.Itoa(id),
Definitions: []twins.Definition{CreateDefinition(channels, subtopics)},
}
}
+181
View File
@@ -0,0 +1,181 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
package mocks
import (
context "context"
twins "github.com/absmach/magistrala/twins"
mock "github.com/stretchr/testify/mock"
)
// TwinRepository is an autogenerated mock type for the TwinRepository type
type TwinRepository struct {
mock.Mock
}
// Remove provides a mock function with given fields: ctx, twinID
func (_m *TwinRepository) Remove(ctx context.Context, twinID string) error {
ret := _m.Called(ctx, twinID)
if len(ret) == 0 {
panic("no return value specified for Remove")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, twinID)
} else {
r0 = ret.Error(0)
}
return r0
}
// RetrieveAll provides a mock function with given fields: ctx, owner, offset, limit, name, metadata
func (_m *TwinRepository) RetrieveAll(ctx context.Context, owner string, offset uint64, limit uint64, name string, metadata twins.Metadata) (twins.Page, error) {
ret := _m.Called(ctx, owner, offset, limit, name, metadata)
if len(ret) == 0 {
panic("no return value specified for RetrieveAll")
}
var r0 twins.Page
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string, twins.Metadata) (twins.Page, error)); ok {
return rf(ctx, owner, offset, limit, name, metadata)
}
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string, twins.Metadata) twins.Page); ok {
r0 = rf(ctx, owner, offset, limit, name, metadata)
} else {
r0 = ret.Get(0).(twins.Page)
}
if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64, string, twins.Metadata) error); ok {
r1 = rf(ctx, owner, offset, limit, name, metadata)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveByAttribute provides a mock function with given fields: ctx, channel, subtopic
func (_m *TwinRepository) RetrieveByAttribute(ctx context.Context, channel string, subtopic string) ([]string, error) {
ret := _m.Called(ctx, channel, subtopic)
if len(ret) == 0 {
panic("no return value specified for RetrieveByAttribute")
}
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok {
return rf(ctx, channel, subtopic)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok {
r0 = rf(ctx, channel, subtopic)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, channel, subtopic)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveByID provides a mock function with given fields: ctx, twinID
func (_m *TwinRepository) RetrieveByID(ctx context.Context, twinID string) (twins.Twin, error) {
ret := _m.Called(ctx, twinID)
if len(ret) == 0 {
panic("no return value specified for RetrieveByID")
}
var r0 twins.Twin
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (twins.Twin, error)); ok {
return rf(ctx, twinID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) twins.Twin); ok {
r0 = rf(ctx, twinID)
} else {
r0 = ret.Get(0).(twins.Twin)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, twinID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Save provides a mock function with given fields: ctx, twin
func (_m *TwinRepository) Save(ctx context.Context, twin twins.Twin) (string, error) {
ret := _m.Called(ctx, twin)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) (string, error)); ok {
return rf(ctx, twin)
}
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) string); ok {
r0 = rf(ctx, twin)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, twins.Twin) error); ok {
r1 = rf(ctx, twin)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, twin
func (_m *TwinRepository) Update(ctx context.Context, twin twins.Twin) error {
ret := _m.Called(ctx, twin)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) error); ok {
r0 = rf(ctx, twin)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewTwinRepository creates a new instance of TwinRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewTwinRepository(t interface {
mock.TestingT
Cleanup(func())
}) *TwinRepository {
mock := &TwinRepository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+178 -59
View File
@@ -1,80 +1,199 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"encoding/json"
"strconv"
"time"
context "context"
authmocks "github.com/absmach/magistrala/auth/mocks"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/messaging"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/twins"
"github.com/absmach/senml"
messaging "github.com/absmach/magistrala/pkg/messaging"
mock "github.com/stretchr/testify/mock"
twins "github.com/absmach/magistrala/twins"
)
const publisher = "twins"
var id = 0
// NewService use mock dependencies to create real twins service.
func NewService() (twins.Service, *authmocks.AuthClient) {
auth := new(authmocks.AuthClient)
twinsRepo := NewTwinRepository()
twinCache := NewTwinCache()
statesRepo := NewStateRepository()
idProvider := uuid.NewMock()
subs := map[string]string{"chanID": "chanID"}
broker := NewBroker(subs)
return twins.New(broker, auth, twinsRepo, twinCache, statesRepo, idProvider, "chanID", mglog.NewMock()), auth
// Service is an autogenerated mock type for the Service type
type Service struct {
mock.Mock
}
// CreateDefinition creates twin definition.
func CreateDefinition(channels, subtopics []string) twins.Definition {
var def twins.Definition
for i := range channels {
attr := twins.Attribute{
Channel: channels[i],
Subtopic: subtopics[i],
PersistState: true,
}
def.Attributes = append(def.Attributes, attr)
// AddTwin provides a mock function with given fields: ctx, token, twin, def
func (_m *Service) AddTwin(ctx context.Context, token string, twin twins.Twin, def twins.Definition) (twins.Twin, error) {
ret := _m.Called(ctx, token, twin, def)
if len(ret) == 0 {
panic("no return value specified for AddTwin")
}
return def
var r0 twins.Twin
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, twins.Twin, twins.Definition) (twins.Twin, error)); ok {
return rf(ctx, token, twin, def)
}
if rf, ok := ret.Get(0).(func(context.Context, string, twins.Twin, twins.Definition) twins.Twin); ok {
r0 = rf(ctx, token, twin, def)
} else {
r0 = ret.Get(0).(twins.Twin)
}
if rf, ok := ret.Get(1).(func(context.Context, string, twins.Twin, twins.Definition) error); ok {
r1 = rf(ctx, token, twin, def)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateTwin creates twin.
func CreateTwin(channels, subtopics []string) twins.Twin {
id++
return twins.Twin{
ID: strconv.Itoa(id),
Definitions: []twins.Definition{CreateDefinition(channels, subtopics)},
// ListStates provides a mock function with given fields: ctx, token, offset, limit, twinID
func (_m *Service) ListStates(ctx context.Context, token string, offset uint64, limit uint64, twinID string) (twins.StatesPage, error) {
ret := _m.Called(ctx, token, offset, limit, twinID)
if len(ret) == 0 {
panic("no return value specified for ListStates")
}
var r0 twins.StatesPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string) (twins.StatesPage, error)); ok {
return rf(ctx, token, offset, limit, twinID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string) twins.StatesPage); ok {
r0 = rf(ctx, token, offset, limit, twinID)
} else {
r0 = ret.Get(0).(twins.StatesPage)
}
if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64, string) error); ok {
r1 = rf(ctx, token, offset, limit, twinID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateSenML creates SenML record array.
func CreateSenML(recs []senml.Record) {
for i, rec := range recs {
rec.BaseTime = float64(time.Now().Unix())
rec.Time = float64(i)
rec.Value = nil
// ListTwins provides a mock function with given fields: ctx, token, offset, limit, name, metadata
func (_m *Service) ListTwins(ctx context.Context, token string, offset uint64, limit uint64, name string, metadata twins.Metadata) (twins.Page, error) {
ret := _m.Called(ctx, token, offset, limit, name, metadata)
if len(ret) == 0 {
panic("no return value specified for ListTwins")
}
var r0 twins.Page
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string, twins.Metadata) (twins.Page, error)); ok {
return rf(ctx, token, offset, limit, name, metadata)
}
if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, string, twins.Metadata) twins.Page); ok {
r0 = rf(ctx, token, offset, limit, name, metadata)
} else {
r0 = ret.Get(0).(twins.Page)
}
if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64, string, twins.Metadata) error); ok {
r1 = rf(ctx, token, offset, limit, name, metadata)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateMessage creates Magistrala 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
// RemoveTwin provides a mock function with given fields: ctx, token, twinID
func (_m *Service) RemoveTwin(ctx context.Context, token string, twinID string) error {
ret := _m.Called(ctx, token, twinID)
if len(ret) == 0 {
panic("no return value specified for RemoveTwin")
}
return &messaging.Message{
Channel: attr.Channel,
Subtopic: attr.Subtopic,
Payload: mRecs,
Publisher: publisher,
}, nil
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(ctx, token, twinID)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveStates provides a mock function with given fields: ctx, msg
func (_m *Service) SaveStates(ctx context.Context, msg *messaging.Message) error {
ret := _m.Called(ctx, msg)
if len(ret) == 0 {
panic("no return value specified for SaveStates")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *messaging.Message) error); ok {
r0 = rf(ctx, msg)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateTwin provides a mock function with given fields: ctx, token, twin, def
func (_m *Service) UpdateTwin(ctx context.Context, token string, twin twins.Twin, def twins.Definition) error {
ret := _m.Called(ctx, token, twin, def)
if len(ret) == 0 {
panic("no return value specified for UpdateTwin")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, twins.Twin, twins.Definition) error); ok {
r0 = rf(ctx, token, twin, def)
} else {
r0 = ret.Error(0)
}
return r0
}
// ViewTwin provides a mock function with given fields: ctx, token, twinID
func (_m *Service) ViewTwin(ctx context.Context, token string, twinID string) (twins.Twin, error) {
ret := _m.Called(ctx, token, twinID)
if len(ret) == 0 {
panic("no return value specified for ViewTwin")
}
var r0 twins.Twin
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (twins.Twin, error)); ok {
return rf(ctx, token, twinID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) twins.Twin); ok {
r0 = rf(ctx, token, twinID)
} else {
r0 = ret.Get(0).(twins.Twin)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, token, twinID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
mock.TestingT
Cleanup(func())
}) *Service {
mock := &Service{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+124 -95
View File
@@ -1,122 +1,151 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"sort"
"strconv"
"strings"
"sync"
context "context"
"github.com/absmach/magistrala/twins"
twins "github.com/absmach/magistrala/twins"
mock "github.com/stretchr/testify/mock"
)
var _ twins.StateRepository = (*stateRepositoryMock)(nil)
type stateRepositoryMock struct {
mu sync.Mutex
states map[string]twins.State
// StateRepository is an autogenerated mock type for the StateRepository type
type StateRepository struct {
mock.Mock
}
// NewStateRepository creates in-memory twin repository.
func NewStateRepository() twins.StateRepository {
return &stateRepositoryMock{
states: make(map[string]twins.State),
}
}
// Count provides a mock function with given fields: ctx, twin
func (_m *StateRepository) Count(ctx context.Context, twin twins.Twin) (int64, error) {
ret := _m.Called(ctx, twin)
// SaveState persists the state.
func (srm *stateRepositoryMock) Save(ctx context.Context, st twins.State) error {
srm.mu.Lock()
defer srm.mu.Unlock()
srm.states[key(st.TwinID, strconv.FormatInt(st.ID, 10))] = st
return nil
}
// UpdateState updates the state.
func (srm *stateRepositoryMock) Update(ctx context.Context, st twins.State) error {
srm.mu.Lock()
defer srm.mu.Unlock()
srm.states[key(st.TwinID, strconv.FormatInt(st.ID, 10))] = st
return nil
}
// CountStates returns the number of states related to twin.
func (srm *stateRepositoryMock) Count(ctx context.Context, tw twins.Twin) (int64, error) {
return int64(len(srm.states)), nil
}
func (srm *stateRepositoryMock) RetrieveAll(ctx context.Context, offset, limit uint64, twinID string) (twins.StatesPage, error) {
srm.mu.Lock()
defer srm.mu.Unlock()
if limit <= 0 {
return twins.StatesPage{}, nil
if len(ret) == 0 {
panic("no return value specified for Count")
}
var items []twins.State
for k, v := range srm.states {
if (uint64)(len(items)) >= limit {
break
}
if !strings.HasPrefix(k, twinID) {
continue
}
id := uint64(v.ID)
if id >= offset && id < offset+limit {
items = append(items, v)
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) (int64, error)); ok {
return rf(ctx, twin)
}
if rf, ok := ret.Get(0).(func(context.Context, twins.Twin) int64); ok {
r0 = rf(ctx, twin)
} else {
r0 = ret.Get(0).(int64)
}
sort.SliceStable(items, func(i, j int) bool {
return items[i].ID < items[j].ID
})
page := twins.StatesPage{
States: items,
PageMetadata: twins.PageMetadata{
Total: srm.total(twinID),
Offset: offset,
Limit: limit,
},
if rf, ok := ret.Get(1).(func(context.Context, twins.Twin) error); ok {
r1 = rf(ctx, twin)
} else {
r1 = ret.Error(1)
}
return page, nil
return r0, r1
}
func (srm *stateRepositoryMock) total(twinID string) uint64 {
var total uint64
for k := range srm.states {
if strings.HasPrefix(k, twinID) {
total++
}
// RetrieveAll provides a mock function with given fields: ctx, offset, limit, twinID
func (_m *StateRepository) RetrieveAll(ctx context.Context, offset uint64, limit uint64, twinID string) (twins.StatesPage, error) {
ret := _m.Called(ctx, offset, limit, twinID)
if len(ret) == 0 {
panic("no return value specified for RetrieveAll")
}
return total
var r0 twins.StatesPage
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) (twins.StatesPage, error)); ok {
return rf(ctx, offset, limit, twinID)
}
if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, string) twins.StatesPage); ok {
r0 = rf(ctx, offset, limit, twinID)
} else {
r0 = ret.Get(0).(twins.StatesPage)
}
if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64, string) error); ok {
r1 = rf(ctx, offset, limit, twinID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RetrieveLast returns the last state related to twin spec by id.
func (srm *stateRepositoryMock) RetrieveLast(ctx context.Context, twinID string) (twins.State, error) {
srm.mu.Lock()
defer srm.mu.Unlock()
// RetrieveLast provides a mock function with given fields: ctx, twinID
func (_m *StateRepository) RetrieveLast(ctx context.Context, twinID string) (twins.State, error) {
ret := _m.Called(ctx, twinID)
items := make([]twins.State, 0)
for _, v := range srm.states {
if v.TwinID == twinID {
items = append(items, v)
}
if len(ret) == 0 {
panic("no return value specified for RetrieveLast")
}
sort.SliceStable(items, func(i, j int) bool {
return items[i].ID < items[j].ID
})
if len(items) > 0 {
return items[len(items)-1], nil
var r0 twins.State
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (twins.State, error)); ok {
return rf(ctx, twinID)
}
return twins.State{}, nil
if rf, ok := ret.Get(0).(func(context.Context, string) twins.State); ok {
r0 = rf(ctx, twinID)
} else {
r0 = ret.Get(0).(twins.State)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, twinID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Save provides a mock function with given fields: ctx, state
func (_m *StateRepository) Save(ctx context.Context, state twins.State) error {
ret := _m.Called(ctx, state)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, twins.State) error); ok {
r0 = rf(ctx, state)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: ctx, state
func (_m *StateRepository) Update(ctx context.Context, state twins.State) error {
ret := _m.Called(ctx, state)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, twins.State) error); ok {
r0 = rf(ctx, state)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewStateRepository creates a new instance of StateRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStateRepository(t interface {
mock.TestingT
Cleanup(func())
}) *StateRepository {
mock := &StateRepository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
-259
View File
@@ -1,259 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"sort"
"strconv"
"strings"
"sync"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/twins"
)
var _ twins.TwinRepository = (*twinRepositoryMock)(nil)
type twinRepositoryMock struct {
mu sync.Mutex
twins map[string]twins.Twin
}
// NewTwinRepository creates in-memory twin repository.
func NewTwinRepository() twins.TwinRepository {
return &twinRepositoryMock{
twins: make(map[string]twins.Twin),
}
}
func (trm *twinRepositoryMock) Save(ctx context.Context, twin twins.Twin) (string, error) {
trm.mu.Lock()
defer trm.mu.Unlock()
for _, tw := range trm.twins {
if tw.ID == twin.ID {
return "", repoerr.ErrConflict
}
}
trm.twins[key(twin.Owner, twin.ID)] = twin
return twin.ID, nil
}
func (trm *twinRepositoryMock) Update(ctx context.Context, twin twins.Twin) error {
trm.mu.Lock()
defer trm.mu.Unlock()
dbKey := key(twin.Owner, twin.ID)
if _, ok := trm.twins[dbKey]; !ok {
return repoerr.ErrNotFound
}
trm.twins[dbKey] = twin
return nil
}
func (trm *twinRepositoryMock) RetrieveByID(_ context.Context, twinID string) (twins.Twin, error) {
trm.mu.Lock()
defer trm.mu.Unlock()
for k, v := range trm.twins {
if twinID == v.ID {
return trm.twins[k], nil
}
}
return twins.Twin{}, repoerr.ErrNotFound
}
func (trm *twinRepositoryMock) RetrieveByAttribute(ctx context.Context, channel, subtopic string) ([]string, error) {
var ids []string
for _, twin := range trm.twins {
def := twin.Definitions[len(twin.Definitions)-1]
for _, attr := range def.Attributes {
if attr.Channel == channel && (attr.Subtopic == twins.SubtopicWildcard || attr.Subtopic == subtopic) {
ids = append(ids, twin.ID)
break
}
}
}
return ids, nil
}
func (trm *twinRepositoryMock) RetrieveAll(_ context.Context, owner string, offset, limit uint64, name string, metadata twins.Metadata) (twins.Page, error) {
trm.mu.Lock()
defer trm.mu.Unlock()
items := make([]twins.Twin, 0)
if limit <= 0 {
return twins.Page{}, nil
}
for k, v := range trm.twins {
if (uint64)(len(items)) >= limit {
break
}
if name != "" && v.Name != name {
continue
}
if !strings.HasPrefix(k, owner) {
continue
}
suffix := string(v.ID[len(uuid.Prefix):])
id, _ := strconv.ParseUint(suffix, 10, 64)
if id > offset && id <= offset+limit {
items = append(items, v)
}
}
sort.SliceStable(items, func(i, j int) bool {
return items[i].ID < items[j].ID
})
total := uint64(len(trm.twins))
page := twins.Page{
Twins: items,
PageMetadata: twins.PageMetadata{
Total: total,
Offset: offset,
Limit: limit,
},
}
return page, nil
}
func (trm *twinRepositoryMock) Remove(ctx context.Context, twinID string) error {
trm.mu.Lock()
defer trm.mu.Unlock()
for k, v := range trm.twins {
if twinID == v.ID {
delete(trm.twins, k)
return nil
}
}
return nil
}
type twinCacheMock struct {
mu sync.Mutex
attrIds map[string]map[string]bool
idAttrs map[string]map[string]bool
}
// NewTwinCache returns mock cache instance.
func NewTwinCache() twins.TwinCache {
return &twinCacheMock{
attrIds: make(map[string]map[string]bool),
idAttrs: make(map[string]map[string]bool),
}
}
func (tcm *twinCacheMock) Save(_ context.Context, twin twins.Twin) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()
if len(twin.Definitions) < 1 {
return nil
}
def := twin.Definitions[len(twin.Definitions)-1]
tcm.save(def, twin.ID)
return nil
}
func (tcm *twinCacheMock) SaveIDs(ctx context.Context, channel, subtopic string, ids []string) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()
for _, id := range ids {
attrKey := channel + subtopic
if _, ok := tcm.attrIds[attrKey]; !ok {
tcm.attrIds[attrKey] = make(map[string]bool)
}
tcm.attrIds[attrKey][id] = true
if _, ok := tcm.idAttrs[id]; !ok {
tcm.idAttrs[id] = make(map[string]bool)
}
tcm.idAttrs[id][attrKey] = true
}
return nil
}
func (tcm *twinCacheMock) Update(_ context.Context, twin twins.Twin) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()
if err := tcm.remove(twin.ID); err != nil {
return nil
}
if len(twin.Definitions) < 1 {
return nil
}
def := twin.Definitions[len(twin.Definitions)-1]
tcm.save(def, twin.ID)
return nil
}
func (tcm *twinCacheMock) IDs(_ context.Context, channel, subtopic string) ([]string, error) {
tcm.mu.Lock()
defer tcm.mu.Unlock()
var ids []string
for k := range tcm.attrIds[channel+subtopic] {
ids = append(ids, k)
}
for k := range tcm.attrIds[channel+twins.SubtopicWildcard] {
ids = append(ids, k)
}
return ids, nil
}
func (tcm *twinCacheMock) Remove(_ context.Context, twinID string) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()
return tcm.remove(twinID)
}
func (tcm *twinCacheMock) remove(twinID string) error {
attrKeys, ok := tcm.idAttrs[twinID]
if !ok {
return nil
}
delete(tcm.idAttrs, twinID)
for attrKey := range attrKeys {
delete(tcm.attrIds[attrKey], twinID)
}
return nil
}
func (tcm *twinCacheMock) save(def twins.Definition, twinID string) {
for _, attr := range def.Attributes {
attrKey := attr.Channel + attr.Subtopic
if _, ok := tcm.attrIds[attrKey]; !ok {
tcm.attrIds[attrKey] = make(map[string]bool)
}
tcm.attrIds[attrKey][twinID] = true
idKey := twinID
if _, ok := tcm.idAttrs[idKey]; !ok {
tcm.idAttrs[idKey] = make(map[string]bool)
}
tcm.idAttrs[idKey][attrKey] = true
}
}
+2
View File
@@ -22,6 +22,8 @@ const publisher = "twins"
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
//
//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines"
type Service interface {
// AddTwin adds new twin related to user identified by the provided key.
AddTwin(ctx context.Context, token string, twin Twin, def Definition) (tw Twin, err error)
+342 -210
View File
@@ -11,14 +11,15 @@ import (
"github.com/absmach/magistrala"
authmocks "github.com/absmach/magistrala/auth/mocks"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/errors"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/absmach/magistrala/twins"
"github.com/absmach/magistrala/twins/mocks"
"github.com/absmach/senml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const (
@@ -27,6 +28,8 @@ const (
token = "token"
email = "user@example.com"
numRecs = 100
retained = "saved"
validID = "123e4567-e89b-12d3-a456-426614174000"
)
var (
@@ -34,274 +37,349 @@ var (
channels = []string{"01ec3c3e-0e66-4e69-9751-a0545b44e08f", "48061e4f-7c23-4f5c-9012-0f9b7cd9d18d", "5b2180e4-e96b-4469-9dc1-b6745078d0b6"}
)
func NewService() (twins.Service, *authmocks.AuthClient, *mocks.TwinRepository, *mocks.TwinCache, *mocks.StateRepository) {
auth := new(authmocks.AuthClient)
twinsRepo := new(mocks.TwinRepository)
twinCache := new(mocks.TwinCache)
statesRepo := new(mocks.StateRepository)
idProvider := uuid.NewMock()
subs := map[string]string{"chanID": "chanID"}
broker := mocks.NewBroker(subs)
return twins.New(broker, auth, twinsRepo, twinCache, statesRepo, idProvider, "chanID", mglog.NewMock()), auth, twinsRepo, twinCache, statesRepo
}
func TestAddTwin(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, twinCache, _ := NewService()
twin := twins.Twin{}
def := twins.Definition{}
cases := []struct {
desc string
twin twins.Twin
token string
err error
desc string
twin twins.Twin
token string
err error
saveErr error
identifyErr error
userID string
}{
{
desc: "add new twin",
twin: twin,
token: token,
err: nil,
desc: "add new twin",
twin: twin,
token: token,
err: nil,
saveErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "add twin with wrong credentials",
twin: twin,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
desc: "add twin with wrong credentials",
twin: twin,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
saveErr: svcerr.ErrCreateEntity,
identifyErr: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("Save", context.Background(), mock.Anything).Return(retained, tc.saveErr)
cacheCall := twinCache.On("Save", context.Background(), mock.Anything).Return(tc.err)
_, err := svc.AddTwin(context.Background(), tc.token, tc.twin, def)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
authCall.Unset()
repoCall.Unset()
cacheCall.Unset()
}
}
func TestUpdateTwin(t *testing.T) {
svc, auth := mocks.NewService()
twin := twins.Twin{}
svc, auth, twinRepo, twinCache, _ := NewService()
other := twins.Twin{}
def := twins.Definition{}
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
}
other.ID = wrongID
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
saved, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
repoCall.Unset()
saved.Name = twinName
cases := []struct {
desc string
twin twins.Twin
token string
err error
desc string
twin twins.Twin
token string
err error
retrieveErr error
updateErr error
identifyErr error
userID string
}{
{
desc: "update existing twin",
twin: saved,
token: token,
err: nil,
desc: "update existing twin",
twin: twin,
token: token,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "update twin with wrong credentials",
twin: saved,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
desc: "update twin with wrong credentials",
twin: twin,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "update non-existing twin",
twin: other,
token: token,
err: svcerr.ErrNotFound,
desc: "update non-existing twin",
twin: other,
token: token,
err: svcerr.ErrNotFound,
retrieveErr: svcerr.ErrNotFound,
updateErr: svcerr.ErrUpdateEntity,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("RetrieveByID", context.Background(), tc.twin.ID).Return(tc.twin, tc.retrieveErr)
repoCall1 := twinRepo.On("Update", context.Background(), mock.Anything).Return(tc.updateErr)
cacheCall := twinCache.On("Update", context.Background(), mock.Anything).Return(tc.err)
err := svc.UpdateTwin(context.Background(), tc.token, tc.twin, def)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
authCall.Unset()
repoCall.Unset()
repoCall1.Unset()
cacheCall.Unset()
}
}
func TestViewTwin(t *testing.T) {
svc, auth := mocks.NewService()
twin := twins.Twin{}
def := twins.Definition{}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
saved, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
repoCall.Unset()
svc, auth, twinRepo, _, _ := NewService()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
}
cases := []struct {
desc string
id string
token string
err error
desc string
id string
token string
err error
identifyErr error
userID string
}{
{
desc: "view existing twin",
id: saved.ID,
token: token,
err: nil,
desc: "view existing twin",
id: twin.ID,
token: token,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "view twin with wrong credentials",
id: saved.ID,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
desc: "view twin with wrong credentials",
id: twin.ID,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "view non-existing twin",
id: wrongID,
token: token,
err: svcerr.ErrNotFound,
desc: "view non-existing twin",
id: wrongID,
token: token,
err: svcerr.ErrNotFound,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("RetrieveByID", context.Background(), tc.id).Return(twins.Twin{}, tc.err)
_, err := svc.ViewTwin(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
authCall.Unset()
repoCall.Unset()
}
}
func TestListTwins(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, _, _ := NewService()
twin := twins.Twin{Name: twinName, Owner: email}
def := twins.Definition{}
m := make(map[string]interface{})
m["serial"] = "123456"
twin.Metadata = m
n := uint64(10)
for i := uint64(0); i < n; i++ {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
_, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
repoCall.Unset()
}
cases := []struct {
desc string
token string
offset uint64
limit uint64
size uint64
metadata map[string]interface{}
err error
desc string
token string
offset uint64
limit uint64
size uint64
metadata map[string]interface{}
err error
repoerr error
identifyErr error
userID string
}{
{
desc: "list all twins",
token: token,
offset: 0,
limit: n,
size: n,
err: nil,
desc: "list all twins",
token: token,
offset: 0,
limit: n,
size: n,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "list with zero limit",
token: token,
limit: 0,
offset: 0,
size: 0,
err: nil,
desc: "list with zero limit",
token: token,
limit: 0,
offset: 0,
size: 0,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "list with offset and limit",
token: token,
offset: 8,
limit: 5,
size: 2,
err: nil,
desc: "list with offset and limit",
token: token,
offset: 8,
limit: 5,
size: 2,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "list with wrong credentials",
token: authmocks.InvalidValue,
limit: 0,
offset: n,
err: svcerr.ErrAuthentication,
desc: "list with wrong credentials",
token: authmocks.InvalidValue,
limit: 0,
offset: n,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("RetrieveAll", context.Background(), mock.Anything, tc.offset, tc.limit, twinName, mock.Anything).Return(twins.Page{}, tc.err)
_, err := svc.ListTwins(context.Background(), tc.token, tc.offset, tc.limit, twinName, tc.metadata)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
authCall.Unset()
repoCall.Unset()
}
}
func TestRemoveTwin(t *testing.T) {
svc, auth := mocks.NewService()
twin := twins.Twin{}
def := twins.Definition{}
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
saved, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
repoCall.Unset()
svc, auth, twinRepo, twinCache, _ := NewService()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
}
cases := []struct {
desc string
id string
token string
err error
desc string
id string
token string
err error
removeErr error
identifyErr error
userID string
}{
{
desc: "remove twin with wrong credentials",
id: saved.ID,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
desc: "remove twin with wrong credentials",
id: twin.ID,
token: authmocks.InvalidValue,
err: svcerr.ErrAuthentication,
removeErr: svcerr.ErrRemoveEntity,
identifyErr: svcerr.ErrAuthentication,
},
{
desc: "remove existing twin",
id: saved.ID,
token: token,
err: nil,
desc: "remove existing twin",
id: twin.ID,
token: token,
err: nil,
removeErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "remove removed twin",
id: saved.ID,
token: token,
err: nil,
desc: "remove removed twin",
id: twin.ID,
token: token,
err: nil,
removeErr: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "remove non-existing twin",
id: wrongID,
token: token,
err: nil,
desc: "remove non-existing twin",
id: wrongID,
token: token,
err: nil,
removeErr: nil,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
authCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall := twinRepo.On("Remove", context.Background(), tc.id).Return(tc.removeErr)
cacheCall := twinCache.On("Remove", context.Background(), tc.id).Return(tc.err)
err := svc.RemoveTwin(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
authCall.Unset()
repoCall.Unset()
cacheCall.Unset()
}
}
func TestSaveStates(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, twinRepo, twinCache, stateRepo := NewService()
twin := twins.Twin{Owner: email}
def := mocks.CreateDefinition(channels[0:2], subtopics[0:2])
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
Definitions: []twins.Definition{def},
}
attr := def.Attributes[0]
attrSansTwin := mocks.CreateDefinition(channels[2:3], subtopics[2:3]).Attributes[0]
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
tw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
defWildcard := mocks.CreateDefinition(channels[0:2], []string{twins.SubtopicWildcard, twins.SubtopicWildcard})
repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
twWildcard, err := svc.AddTwin(context.Background(), token, twin, defWildcard)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twWildcard := twins.Twin{
Definitions: []twins.Definition{defWildcard},
}
recs := make([]senml.Record, numRecs)
mocks.CreateSenML(recs)
var ttlAdded uint64
cases := []struct {
desc string
recs []senml.Record
attr twins.Attribute
size uint64
err error
desc string
recs []senml.Record
attr twins.Attribute
size uint64
err error
String []string
page twins.StatesPage
}{
{
desc: "add 100 states",
@@ -309,6 +387,11 @@ func TestSaveStates(t *testing.T) {
attr: attr,
size: numRecs,
err: nil,
page: twins.StatesPage{
PageMetadata: twins.PageMetadata{
Total: numRecs,
},
},
},
{
desc: "add 20 states",
@@ -316,6 +399,11 @@ func TestSaveStates(t *testing.T) {
attr: attr,
size: 20,
err: nil,
page: twins.StatesPage{
PageMetadata: twins.PageMetadata{
Total: numRecs + 20,
},
},
},
{
desc: "add 20 states for atttribute without twin",
@@ -323,6 +411,11 @@ func TestSaveStates(t *testing.T) {
size: 0,
attr: attrSansTwin,
err: svcerr.ErrNotFound,
page: twins.StatesPage{
PageMetadata: twins.PageMetadata{
Total: numRecs + 20,
},
},
},
{
desc: "use empty senml record",
@@ -330,135 +423,174 @@ func TestSaveStates(t *testing.T) {
attr: attr,
size: 0,
err: nil,
page: twins.StatesPage{
PageMetadata: twins.PageMetadata{
Total: numRecs + 20,
},
},
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
repoCall := auth.On("Identify", context.TODO(), &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
message, err := mocks.CreateMessage(tc.attr, tc.recs)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall1 := twinRepo.On("RetrieveByAttribute", context.Background(), mock.Anything, mock.Anything).Return(tc.String, nil)
repoCall2 := twinRepo.On("SaveIDs", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(tc.err)
repoCall3 := twinCache.On("IDs", context.Background(), mock.Anything, mock.Anything).Return(tc.String, nil)
err = svc.SaveStates(context.Background(), message)
assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
ttlAdded += tc.size
page, err := svc.ListStates(context.TODO(), token, 0, 10, tw.ID)
repoCall4 := stateRepo.On("RetrieveAll", context.TODO(), mock.Anything, mock.Anything, twin.ID).Return(tc.page, nil)
page, err := svc.ListStates(context.TODO(), token, 0, 10, twin.ID)
assert.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))
repoCall5 := stateRepo.On("RetrieveAll", context.TODO(), mock.Anything, mock.Anything, twWildcard.ID).Return(tc.page, nil)
page, err = svc.ListStates(context.TODO(), token, 0, 10, twWildcard.ID)
assert.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))
repoCall.Unset()
repoCall1.Unset()
repoCall2.Unset()
repoCall3.Unset()
repoCall4.Unset()
repoCall5.Unset()
}
}
func TestListStates(t *testing.T) {
svc, auth := mocks.NewService()
svc, auth, _, _, stateRepo := NewService()
twin := twins.Twin{Owner: email}
def := mocks.CreateDefinition(channels[0:2], subtopics[0:2])
attr := def.Attributes[0]
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
tw, err := svc.AddTwin(context.Background(), token, twin, def)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
twin := twins.Twin{
Owner: email,
ID: testsutil.GenerateUUID(t),
Name: twinName,
Definitions: []twins.Definition{def},
}
repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
tw2, err := svc.AddTwin(context.Background(), token,
twins.Twin{Owner: email},
mocks.CreateDefinition(channels[2:3], subtopics[2:3]))
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
recs := make([]senml.Record, numRecs)
mocks.CreateSenML(recs)
message, err := mocks.CreateMessage(attr, recs)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
err = svc.SaveStates(context.Background(), message)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
repoCall.Unset()
tw2 := twins.Twin{
Owner: email,
Definitions: []twins.Definition{mocks.CreateDefinition(channels[2:3], subtopics[2:3])},
}
cases := []struct {
desc string
id string
token string
offset uint64
limit uint64
size int
err error
desc string
id string
token string
offset uint64
limit uint64
size int
err error
page twins.StatesPage
identifyErr error
userID string
}{
{
desc: "get a list of first 10 states",
id: tw.ID,
id: twin.ID,
token: token,
offset: 0,
limit: 10,
size: 10,
err: nil,
page: twins.StatesPage{
States: genStates(10),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of last 10 states",
id: tw.ID,
id: twin.ID,
token: token,
offset: numRecs - 10,
limit: numRecs,
size: 10,
err: nil,
page: twins.StatesPage{
States: genStates(10),
},
identifyErr: nil,
userID: validID,
},
{
desc: "get a list of last 10 states with limit > numRecs",
id: tw.ID,
id: twin.ID,
token: token,
offset: numRecs - 10,
limit: numRecs + 10,
size: 10,
err: nil,
page: twins.StatesPage{
States: genStates(10),
},
identifyErr: nil,
userID: validID,
},
{
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 of first 10 states with offset == numRecs",
id: twin.ID,
token: token,
offset: numRecs,
limit: numRecs + 10,
size: 0,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list with wrong user token",
id: tw.ID,
token: authmocks.InvalidValue,
offset: 0,
limit: 10,
size: 0,
err: svcerr.ErrAuthentication,
desc: "get a list with wrong user token",
id: twin.ID,
token: authmocks.InvalidValue,
offset: 0,
limit: 10,
size: 0,
err: svcerr.ErrAuthentication,
identifyErr: svcerr.ErrAuthentication,
},
{
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 non-existent twin",
id: "1234567890",
token: token,
offset: 0,
limit: 10,
size: 0,
err: nil,
identifyErr: nil,
userID: validID,
},
{
desc: "get a list with id of existing twin without states ",
id: tw2.ID,
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,
identifyErr: nil,
userID: validID,
},
}
for _, tc := range cases {
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil)
repoCall := auth.On("Identify", context.TODO(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr)
repoCall1 := stateRepo.On("RetrieveAll", context.TODO(), mock.Anything, mock.Anything, tc.id).Return(tc.page, nil)
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)))
repoCall.Unset()
repoCall1.Unset()
}
}
func genStates(length int) []twins.State {
states := make([]twins.State, length)
for i := range states {
states[i] = twins.State{}
}
return states
}
+2
View File
@@ -25,6 +25,8 @@ type StatesPage struct {
}
// StateRepository specifies a state persistence API.
//
//go:generate mockery --name StateRepository --output=./mocks --filename states.go --quiet --note "Copyright (c) Abstract Machines"
type StateRepository interface {
// Save persists the state
Save(ctx context.Context, state State) error
+4
View File
@@ -55,6 +55,8 @@ type Page struct {
}
// TwinRepository specifies a twin persistence API.
//
//go:generate mockery --name TwinRepository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines"
type TwinRepository interface {
// Save persists the twin
Save(ctx context.Context, twin Twin) (string, error)
@@ -78,6 +80,8 @@ type TwinRepository interface {
}
// TwinCache contains twin caching interface.
//
//go:generate mockery --name TwinCache --output=./mocks --filename cache.go --quiet --note "Copyright (c) Abstract Machines"
type TwinCache interface {
// Save stores twin ID as element of channel-subtopic keyed set and vice versa.
Save(ctx context.Context, twin Twin) error
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines
+1 -1
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Abstract Machines