diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 080620dad..583685b2f 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -26,7 +26,9 @@ import ( "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/groups" chmocks "github.com/absmach/magistrala/internal/groups/mocks" + "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" mggroups "github.com/absmach/magistrala/pkg/groups" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" @@ -36,37 +38,44 @@ import ( thmocks "github.com/absmach/magistrala/things/mocks" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/mock" ) const ( - validToken = "validToken" - invalidToken = "invalidToken" - email = "test@example.com" - unknown = "unknown" - channelsNum = 3 - contentType = "application/json" - wrongID = "wrong_id" - addExternalID = "external-id" - addExternalKey = "external-key" - addName = "name" - addContent = "config" - instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" + validToken = "validToken" + invalidToken = "invalid" + email = "test@example.com" + unknown = "unknown" + channelsNum = 3 + contentType = "application/json" + wrongID = "wrong_id" + + addName = "name" + addContent = "config" + instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) var ( - encKey = []byte("1234567891011121") - metadata = map[string]interface{}{"meta": "data"} - addReq = struct { + encKey = []byte("1234567891011121") + metadata = map[string]interface{}{"meta": "data"} + addExternalID = testsutil.GenerateUUID(&testing.T{}) + addExternalKey = testsutil.GenerateUUID(&testing.T{}) + addThingID = testsutil.GenerateUUID(&testing.T{}) + addThingKey = testsutil.GenerateUUID(&testing.T{}) + addReq = struct { ThingID string `json:"thing_id"` + ThingKey string `json:"thing_key"` ExternalID string `json:"external_id"` ExternalKey string `json:"external_key"` Channels []string `json:"channels"` Name string `json:"name"` Content string `json:"content"` }{ - ExternalID: "external-id", - ExternalKey: "external-key", + ThingID: addThingID, + ThingKey: addThingKey, + ExternalID: addExternalID, + ExternalKey: addExternalKey, Channels: []string{"1"}, Name: "name", Content: "config", @@ -80,7 +89,7 @@ var ( ClientKey string `json:"client_key,omitempty"` CACert string `json:"ca_cert,omitempty"` }{ - Channels: []string{"2", "3"}, + Channels: []string{"1"}, Content: "config update", State: 1, ClientCert: "newcert", @@ -92,7 +101,7 @@ var ( missingKeyRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerKey.Error(), Msg: apiutil.ErrValidation.Error()}) bsErrorRes = toJSON(apiutil.ErrorRes{Err: errors.ErrNotFound.Error(), Msg: bootstrap.ErrBootstrap.Error()}) extKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKey.Error()}) - extSecKeyRes = toJSON(apiutil.ErrorRes{Err: "encoding/hex: invalid byte: U+0078 'x'", Msg: bootstrap.ErrExternalKeySecure.Error()}) + extSecKeyRes = toJSON(apiutil.ErrorRes{Err: "encoding/hex: invalid byte: U+002D '-'", Msg: bootstrap.ErrExternalKeySecure.Error()}) ) type testRequest struct { @@ -105,16 +114,23 @@ type testRequest struct { body io.Reader } -func newConfig(channels []bootstrap.Channel) bootstrap.Config { +func newConfig() bootstrap.Config { return bootstrap.Config{ + ThingID: addThingID, + ThingKey: addThingKey, ExternalID: addExternalID, ExternalKey: addExternalKey, - Channels: channels, - Name: addName, - Content: addContent, - ClientCert: "newcert", - ClientKey: "newkey", - CACert: "newca", + Channels: []bootstrap.Channel{ + { + ID: "1", + Metadata: metadata, + }, + }, + Name: addName, + Content: addContent, + ClientCert: "newcert", + ClientKey: "newkey", + CACert: "newca", } } @@ -178,27 +194,14 @@ func newService(url string, auth magistrala.AuthServiceClient) bootstrap.Service return bootstrap.New(auth, things, sdk, encKey) } -func generateChannels() map[string]mggroups.Group { - channels := make(map[string]mggroups.Group, channelsNum) - for i := 0; i < channelsNum; i++ { - id := strconv.Itoa(i + 1) - channels[id] = mggroups.Group{ - ID: id, - Owner: email, - Metadata: metadata, - } - } - return channels -} - -func newThingsService() (things.Service, mggroups.Service, magistrala.AuthServiceClient) { +func newThingsService() (things.Service, mggroups.Service, *thmocks.Repository, *chmocks.Repository, *authmocks.Service) { auth := new(authmocks.Service) thingCache := thmocks.NewCache() idProvider := uuid.NewMock() - cRepo := new(thmocks.Repository) - gRepo := new(chmocks.Repository) + trepo := new(thmocks.Repository) + chrepo := new(chmocks.Repository) - return things.NewService(auth, cRepo, gRepo, thingCache, idProvider), groups.NewService(gRepo, idProvider, auth), auth + return things.NewService(auth, trepo, chrepo, thingCache, idProvider), groups.NewService(chrepo, idProvider, auth), trepo, chrepo, auth } func newThingsServer(tsvc things.Service, gsvc mggroups.Service) *httptest.Server { @@ -224,7 +227,7 @@ func toJSON(data interface{}) string { } func TestAdd(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) @@ -232,7 +235,7 @@ func TestAdd(t *testing.T) { data := toJSON(addReq) neID := addReq - neID.ThingID = "non-existent" + neID.ThingID = testsutil.GenerateUUID(t) neData := toJSON(neID) invalidChannels := addReq @@ -284,7 +287,7 @@ func TestAdd(t *testing.T) { req: neData, auth: validToken, contentType: contentType, - status: http.StatusNotFound, + status: http.StatusConflict, location: "", }, { @@ -292,7 +295,7 @@ func TestAdd(t *testing.T) { req: wrongData, auth: validToken, contentType: contentType, - status: http.StatusBadRequest, + status: http.StatusConflict, location: "", }, { @@ -329,6 +332,10 @@ func TestAdd(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: addThingID, Credentials: clients.Credentials{Secret: addThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPost, @@ -344,27 +351,30 @@ func TestAdd(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)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() } } func TestView(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{}) - - mgChs := generateChannels() - for id, ch := range mgChs { - c.Channels = append(c.Channels, bootstrap.Channel{ - ID: ch.ID, - Name: fmt.Sprintf("%s%s", "name ", id), - Metadata: map[string]interface{}{"type": fmt.Sprintf("some type %s", id)}, - }) - } + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() var channels []channel for _, ch := range saved.Channels { @@ -420,6 +430,7 @@ func TestView(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) req := testRequest{ client: bs.Client(), method: http.MethodGet, @@ -440,19 +451,28 @@ func TestView(t *testing.T) { tc.res.Channels = []channel{} view.Channels = []channel{} assert.Equal(t, tc.res, view, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, view)) + repoCall.Unset() } } func TestUpdate(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() data := toJSON(updateReq) @@ -523,6 +543,7 @@ func TestUpdate(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPut, @@ -534,19 +555,28 @@ func TestUpdate(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)) + repoCall.Unset() } } func TestUpdateCert(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() data := toJSON(updateReq) @@ -617,6 +647,7 @@ func TestUpdateCert(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPatch, @@ -628,19 +659,28 @@ func TestUpdateCert(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)) + repoCall.Unset() } } func TestUpdateConnections(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() data := toJSON(updateReq) @@ -703,7 +743,7 @@ func TestUpdateConnections(t *testing.T) { id: saved.ThingID, auth: validToken, contentType: contentType, - status: http.StatusBadRequest, + status: http.StatusNotFound, }, { desc: "update a config with invalid request format", @@ -724,6 +764,12 @@ func TestUpdateConnections(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) + repoCall4 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) + repoCall5 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPut, @@ -735,6 +781,12 @@ func TestUpdateConnections(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)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + repoCall5.Unset() } } @@ -744,13 +796,13 @@ func TestList(t *testing.T) { var active, inactive []config list := make([]config, configNum) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) path := fmt.Sprintf("%s/%s", bs.URL, "things/configs") - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() for i := 0; i < configNum; i++ { c.ExternalID = strconv.Itoa(i) @@ -758,8 +810,16 @@ func TestList(t *testing.T) { c.Name = fmt.Sprintf("%s-%d", addName, i) c.ExternalKey = fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i)) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() var channels []channel for _, ch := range saved.Channels { @@ -784,8 +844,17 @@ func TestList(t *testing.T) { if i%2 == 0 { state = bootstrap.Inactive } + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + err := svc.ChangeState(context.Background(), validToken, list[i].ThingID, state) assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err)) + + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + list[i].State = state if state == bootstrap.Inactive { inactive = append(inactive, list[i]) @@ -966,6 +1035,8 @@ func TestList(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + req := testRequest{ client: bs.Client(), method: http.MethodGet, @@ -985,19 +1056,29 @@ func TestList(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.ElementsMatch(t, tc.res.Configs, body.Configs, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res.Configs, body.Configs)) assert.Equal(t, tc.res.Total, body.Total, fmt.Sprintf("%s: expected response total '%d' got '%d'", tc.desc, tc.res.Total, body.Total)) + + repoCall.Unset() } } func TestRemove(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string @@ -1038,6 +1119,7 @@ func TestRemove(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) req := testRequest{ client: bs.Client(), method: http.MethodDelete, @@ -1047,19 +1129,28 @@ func TestRemove(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)) + repoCall.Unset() } } func TestBootstrap(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() encExternKey, err := enc([]byte(c.ExternalKey)) assert.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err)) @@ -1156,6 +1247,7 @@ func TestBootstrap(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.externalKey}).Return(&magistrala.IdentityRes{Id: validID}, nil) req := testRequest{ client: bs.Client(), method: http.MethodGet, @@ -1175,19 +1267,28 @@ func TestBootstrap(t *testing.T) { data := strings.Trim(string(body), "\n") assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, data)) + repoCall.Unset() } } func TestChangeState(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) bs := newBootstrapServer(svc) - c := newConfig([]bootstrap.Channel{{ID: "1"}}) + c := newConfig() + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() inactive := fmt.Sprintf("{\"state\": %d}", bootstrap.Inactive) active := fmt.Sprintf("{\"state\": %d}", bootstrap.Active) @@ -1267,6 +1368,10 @@ func TestChangeState(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 = auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall3 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(&magistrala.DeletePoliciesRes{Deleted: true}, nil) req := testRequest{ client: bs.Client(), method: http.MethodPut, @@ -1278,6 +1383,10 @@ func TestChangeState(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)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() } } @@ -1304,3 +1413,11 @@ type configPage struct { Limit uint64 `json:"limit"` Configs []config `json:"configs"` } + +func toGroup(ch bootstrap.Channel) mggroups.Group { + return mggroups.Group{ + ID: ch.ID, + Name: ch.Name, + Metadata: ch.Metadata, + } +} diff --git a/bootstrap/events/producer/events.go b/bootstrap/events/producer/events.go index 13c666fad..3cdbf5487 100644 --- a/bootstrap/events/producer/events.go +++ b/bootstrap/events/producer/events.go @@ -5,8 +5,6 @@ package producer import ( "encoding/json" - "fmt" - "strings" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/pkg/events" @@ -75,7 +73,11 @@ func (ce configEvent) Encode() (map[string]interface{}, error) { for i, ch := range ce.Channels { channels[i] = ch.ID } - val["channels"] = fmt.Sprintf("[%s]", strings.Join(channels, ", ")) + data, err := json.Marshal(channels) + if err != nil { + return map[string]interface{}{}, err + } + val["channels"] = string(data) } if ce.ClientCert != "" { val["client_cert"] = ce.ClientCert @@ -170,7 +172,11 @@ func (be bootstrapEvent) Encode() (map[string]interface{}, error) { for i, ch := range be.Channels { channels[i] = ch.ID } - val["channels"] = fmt.Sprintf("[%s]", strings.Join(channels, ", ")) + data, err := json.Marshal(channels) + if err != nil { + return map[string]interface{}{}, err + } + val["channels"] = string(data) } if be.ClientCert != "" { val["client_cert"] = be.ClientCert @@ -206,9 +212,14 @@ type updateConnectionsEvent struct { } func (uce updateConnectionsEvent) Encode() (map[string]interface{}, error) { + data, err := json.Marshal(uce.mgChannels) + if err != nil { + return map[string]interface{}{}, err + } + return map[string]interface{}{ "thing_id": uce.mgThing, - "channels": fmt.Sprintf("[%s]", strings.Join(uce.mgChannels, ", ")), + "channels": string(data), "operation": thingUpdateConnections, }, nil } diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index 23a6235ef..d714f81e8 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -20,7 +20,9 @@ import ( "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/groups" chmocks "github.com/absmach/magistrala/internal/groups/mocks" + "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" mggroups "github.com/absmach/magistrala/pkg/groups" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" @@ -31,13 +33,14 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-redis/redis/v8" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) const ( streamID = "magistrala.bootstrap" email = "user@example.com" - validToken = "validToken" + validToken = "token" channelsNum = 3 defaultTimout = 5 @@ -59,7 +62,7 @@ const ( channelUpdateHandler = channelPrefix + "update_handler" certUpdate = "cert.update" - + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" ) @@ -67,14 +70,16 @@ var ( encKey = []byte("1234567891011121") channel = bootstrap.Channel{ - ID: "1", + ID: testsutil.GenerateUUID(&testing.T{}), Name: "name", Metadata: map[string]interface{}{"name": "value"}, } config = bootstrap.Config{ - ExternalID: "external_id", - ExternalKey: "external_key", + ThingID: testsutil.GenerateUUID(&testing.T{}), + ThingKey: testsutil.GenerateUUID(&testing.T{}), + ExternalID: testsutil.GenerateUUID(&testing.T{}), + ExternalKey: testsutil.GenerateUUID(&testing.T{}), Channels: []bootstrap.Channel{channel}, Content: "config", } @@ -90,14 +95,14 @@ func newService(url string, auth magistrala.AuthServiceClient) bootstrap.Service return bootstrap.New(auth, configs, sdk, encKey) } -func newThingsService() (things.Service, mggroups.Service, magistrala.AuthServiceClient) { +func newThingsService() (things.Service, mggroups.Service, *thmocks.Repository, *chmocks.Repository, *authmocks.Service) { auth := new(authmocks.Service) thingCache := thmocks.NewCache() idProvider := uuid.NewMock() - cRepo := new(thmocks.Repository) - gRepo := new(chmocks.Repository) + trepo := new(thmocks.Repository) + chrepo := new(chmocks.Repository) - return things.NewService(auth, cRepo, gRepo, thingCache, idProvider), groups.NewService(gRepo, idProvider, auth), auth + return things.NewService(auth, trepo, chrepo, thingCache, idProvider), groups.NewService(chrepo, idProvider, auth), trepo, chrepo, auth } func newThingsServer(tsvc things.Service, gsvc mggroups.Service) *httptest.Server { @@ -112,11 +117,11 @@ func TestAdd(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) - assert.Nil(t, err, fmt.Sprintf("go unexpected error on creating event store middleware: %s", err)) + assert.Nil(t, err, fmt.Sprintf("got unexpected error on creating event store middleware: %s", err)) var channels []string for _, ch := range config.Channels { @@ -161,6 +166,10 @@ func TestAdd(t *testing.T) { lastID := "0" for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.err) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: tc.config.ThingID, Credentials: clients.Credentials{Secret: tc.config.ThingKey}}, tc.err) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(tc.config.Channels[0]), tc.err) _, err := svc.Add(context.Background(), tc.token, tc.config) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -177,22 +186,39 @@ func TestAdd(t *testing.T) { } test(t, tc.event, event, tc.desc) + + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() } } func TestView(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) svcConfig, svcErr := svc.View(context.Background(), validToken, saved.ThingID) + repoCall.Unset() svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) assert.Nil(t, err, fmt.Sprintf("go unexpected error on creating event store middleware: %s", err)) + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) esConfig, esErr := svc.View(context.Background(), validToken, saved.ThingID) + repoCall.Unset() assert.Equal(t, svcConfig, esConfig, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfig, esConfig)) assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr)) @@ -202,7 +228,7 @@ func TestUpdate(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -211,10 +237,18 @@ func TestUpdate(t *testing.T) { c := config ch := channel - ch.ID = "2" + ch.ID = testsutil.GenerateUUID(t) c.Channels = append(c.Channels, ch) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -226,6 +260,10 @@ func TestUpdate(t *testing.T) { nonExisting := config nonExisting.ThingID = "unknown" + channels := []string{modified.Channels[0].ID, modified.Channels[1].ID} + chs, err := json.Marshal(channels) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + cases := []struct { desc string config bootstrap.Config @@ -243,10 +281,10 @@ func TestUpdate(t *testing.T) { "content": modified.Content, "timestamp": time.Now().UnixNano(), "operation": configUpdate, - "channels": "[1, 2]", - "external_id": "external_id", - "thing_id": "1", - "owner": email, + "channels": string(chs), + "external_id": modified.ExternalID, + "thing_id": modified.ThingID, + "owner": validID, "state": "0", "occurred_at": time.Now().UnixNano(), }, @@ -262,6 +300,7 @@ func TestUpdate(t *testing.T) { lastID := "0" for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) err := svc.Update(context.Background(), tc.token, tc.config) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -280,6 +319,7 @@ func TestUpdate(t *testing.T) { } test(t, tc.event, event, tc.desc) + repoCall.Unset() } } @@ -287,14 +327,22 @@ func TestUpdateConnections(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) assert.Nil(t, err, fmt.Sprintf("go unexpected error on creating event store middleware: %s", err)) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, config) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -310,7 +358,7 @@ func TestUpdateConnections(t *testing.T) { desc: "update connections successfully", id: saved.ThingID, token: validToken, - connections: []string{"2"}, + connections: []string{config.Channels[0].ID}, err: nil, event: map[string]interface{}{ "thing_id": saved.ThingID, @@ -324,13 +372,19 @@ func TestUpdateConnections(t *testing.T) { id: saved.ThingID, token: validToken, connections: []string{"256"}, - err: errors.ErrMalformedEntity, + err: errors.ErrNotFound, event: nil, }, } lastID := "0" for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) + repoCall4 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) + repoCall5 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) err := svc.UpdateConnections(context.Background(), tc.token, tc.id, tc.connections) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -347,6 +401,12 @@ func TestUpdateConnections(t *testing.T) { } test(t, tc.event, event, tc.desc) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + repoCall5.Unset() } } @@ -354,14 +414,21 @@ func TestUpdateCert(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) assert.Nil(t, err, fmt.Sprintf("go unexpected error on creating event store middleware: %s", err)) - + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, config) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -394,7 +461,7 @@ func TestUpdateCert(t *testing.T) { { desc: "invalid token", id: saved.ThingID, - token: "invalidToken", + token: "invalid", clientCert: "clientCert", clientKey: "clientKey", caCert: "caCert", @@ -444,7 +511,7 @@ func TestUpdateCert(t *testing.T) { { desc: "update cert with invalid token", id: saved.ThingID, - token: "invalidToken", + token: "invalid", clientCert: "clientCert", clientKey: "clientKey", caCert: "caCert", @@ -482,6 +549,8 @@ func TestUpdateCert(t *testing.T) { lastID := "0" for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + _, err := svc.UpdateCert(context.Background(), tc.token, tc.id, tc.clientCert, tc.clientKey, tc.caCert) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -498,24 +567,36 @@ func TestUpdateCert(t *testing.T) { } test(t, tc.event, event, tc.desc) + + repoCall.Unset() } } func TestList(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) _, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() offset := uint64(0) limit := uint64(10) + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) svcConfigs, svcErr := svc.List(context.Background(), validToken, bootstrap.Filter{}, offset, limit) - + repoCall.Unset() svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) assert.Nil(t, err, fmt.Sprintf("go unexpected error on creating event store middleware: %s", err)) + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) esConfigs, esErr := svc.List(context.Background(), validToken, bootstrap.Filter{}, offset, limit) + repoCall.Unset() assert.Equal(t, svcConfigs, esConfigs) assert.Equal(t, svcErr, esErr) } @@ -524,7 +605,7 @@ func TestRemove(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -532,8 +613,16 @@ func TestRemove(t *testing.T) { c := config + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -566,6 +655,7 @@ func TestRemove(t *testing.T) { lastID := "0" for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) err := svc.Remove(context.Background(), tc.token, tc.id) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -582,6 +672,7 @@ func TestRemove(t *testing.T) { } test(t, tc.event, event, tc.desc) + repoCall.Unset() } } @@ -589,7 +680,7 @@ func TestBootstrap(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -597,8 +688,17 @@ func TestBootstrap(t *testing.T) { c := config + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, c) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -659,7 +759,7 @@ func TestChangeState(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -667,8 +767,17 @@ func TestChangeState(t *testing.T) { c := config + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, c) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + err = redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) @@ -696,7 +805,7 @@ func TestChangeState(t *testing.T) { { desc: "change state invalid credentials", id: saved.ThingID, - token: "", + token: "invalid", state: bootstrap.Inactive, err: errors.ErrAuthentication, event: nil, @@ -705,6 +814,10 @@ func TestChangeState(t *testing.T) { lastID := "0" for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 = auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall3 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) err := svc.ChangeState(context.Background(), tc.token, tc.id, tc.state) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) @@ -721,6 +834,10 @@ func TestChangeState(t *testing.T) { } test(t, tc.event, event, tc.desc) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() } } @@ -728,7 +845,7 @@ func TestUpdateChannelHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, _, _, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -815,7 +932,7 @@ func TestRemoveChannelHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, _, _, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -882,7 +999,7 @@ func TestRemoveConfigHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, _, _, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -899,7 +1016,7 @@ func TestRemoveConfigHandler(t *testing.T) { }{ { desc: "remove config handler successfully", - configID: "1", + configID: channel.ID, err: nil, event: map[string]interface{}{ "config_id": channel.ID, @@ -949,7 +1066,7 @@ func TestDisconnectThingHandler(t *testing.T) { err := redisClient.FlushAll(context.Background()).Err() assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, _, _, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) svc, err = producer.NewEventStoreMiddleware(context.Background(), svc, redisURL) @@ -1056,11 +1173,11 @@ func test(t *testing.T, expected, actual map[string]interface{}, description str ech := expected["channels"] ach := actual["channels"] - che := []int{} + che := []string{} err = json.Unmarshal([]byte(ech.(string)), &che) require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid channels, got %s", description, err)) - cha := []int{} + cha := []string{} err = json.Unmarshal([]byte(ach.(string)), &cha) require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid channels, got %s", description, err)) @@ -1073,3 +1190,11 @@ func test(t *testing.T, expected, actual map[string]interface{}, description str assert.Equal(t, expected, actual, fmt.Sprintf("%s: got incorrect event\n", description)) } } + +func toGroup(ch bootstrap.Channel) mggroups.Group { + return mggroups.Group{ + ID: ch.ID, + Name: ch.Name, + Metadata: ch.Metadata, + } +} diff --git a/bootstrap/service_test.go b/bootstrap/service_test.go index 0a1a37d5e..cfc51889a 100644 --- a/bootstrap/service_test.go +++ b/bootstrap/service_test.go @@ -21,7 +21,9 @@ import ( "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/groups" chmocks "github.com/absmach/magistrala/internal/groups/mocks" + "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" + "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" mggroups "github.com/absmach/magistrala/pkg/groups" mgsdk "github.com/absmach/magistrala/pkg/sdk/go" @@ -31,30 +33,33 @@ import ( thmocks "github.com/absmach/magistrala/things/mocks" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/mock" ) const ( validToken = "validToken" - invalidToken = "invalidToken" + invalidToken = "invalid" email = "test@example.com" unknown = "unknown" channelsNum = 3 instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002" + validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) var ( encKey = []byte("1234567891011121") channel = bootstrap.Channel{ - ID: "1", + ID: testsutil.GenerateUUID(&testing.T{}), Name: "name", Metadata: map[string]interface{}{"name": "value"}, } config = bootstrap.Config{ - ExternalID: "external_id", - ExternalKey: "external_key", + ThingID: testsutil.GenerateUUID(&testing.T{}), + ThingKey: testsutil.GenerateUUID(&testing.T{}), + ExternalID: testsutil.GenerateUUID(&testing.T{}), + ExternalKey: testsutil.GenerateUUID(&testing.T{}), Channels: []bootstrap.Channel{channel}, Content: "config", } @@ -70,14 +75,14 @@ func newService(url string, auth magistrala.AuthServiceClient) bootstrap.Service return bootstrap.New(auth, things, sdk, encKey) } -func newThingsService() (things.Service, mggroups.Service, magistrala.AuthServiceClient) { +func newThingsService() (things.Service, mggroups.Service, *thmocks.Repository, *chmocks.Repository, *authmocks.Service) { auth := new(authmocks.Service) thingCache := thmocks.NewCache() idProvider := uuid.NewMock() - cRepo := new(thmocks.Repository) - gRepo := new(chmocks.Repository) + trepo := new(thmocks.Repository) + chrepo := new(chmocks.Repository) - return things.NewService(auth, cRepo, gRepo, thingCache, idProvider), groups.NewService(gRepo, idProvider, auth), auth + return things.NewService(auth, trepo, chrepo, thingCache, idProvider), groups.NewService(chrepo, idProvider, auth), trepo, chrepo, auth } func newThingsServer(tsvc things.Service, gsvc mggroups.Service) *httptest.Server { @@ -104,7 +109,7 @@ func enc(in []byte) ([]byte, error) { } func TestAdd(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) neID := config @@ -148,6 +153,10 @@ func TestAdd(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, tc.err) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: tc.config.ThingID, Credentials: clients.Credentials{Secret: tc.config.ThingKey}}, tc.err) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, tc.err) _, err := svc.Add(context.Background(), tc.token, tc.config) switch err { case nil: @@ -155,15 +164,27 @@ func TestAdd(t *testing.T) { default: 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() + repoCall2.Unset() + repoCall3.Unset() } } func TestView(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string @@ -192,13 +213,15 @@ func TestView(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) _, err := svc.View(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)) + repoCall.Unset() } } func TestUpdate(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) c := config @@ -206,8 +229,17 @@ func TestUpdate(t *testing.T) { ch := channel ch.ID = "2" c.Channels = append(c.Channels, ch) + + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() modifiedCreated := saved modifiedCreated.Content = "new-config" @@ -243,13 +275,15 @@ func TestUpdate(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) err := svc.Update(context.Background(), tc.token, tc.config) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } func TestUpdateCert(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) c := config @@ -257,13 +291,21 @@ func TestUpdateCert(t *testing.T) { ch := channel ch.ID = "2" c.Channels = append(c.Channels, ch) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string token string - thingKey string + thingID string clientCert string clientKey string caCert string @@ -272,7 +314,7 @@ func TestUpdateCert(t *testing.T) { }{ { desc: "update certs for the valid config", - thingKey: saved.ThingKey, + thingID: saved.ThingID, clientCert: "newCert", clientKey: "newKey", caCert: "newCert", @@ -295,7 +337,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update cert for a non-existing config", - thingKey: "empty", + thingID: "empty", clientCert: "newCert", clientKey: "newKey", caCert: "newCert", @@ -305,7 +347,7 @@ func TestUpdateCert(t *testing.T) { }, { desc: "update config cert with wrong credentials", - thingKey: saved.ThingKey, + thingID: saved.ThingID, clientCert: "newCert", clientKey: "newKey", caCert: "newCert", @@ -316,7 +358,8 @@ func TestUpdateCert(t *testing.T) { } for _, tc := range cases { - cfg, err := svc.UpdateCert(context.Background(), tc.token, tc.thingKey, tc.clientCert, tc.clientKey, tc.caCert) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + cfg, err := svc.UpdateCert(context.Background(), tc.token, tc.thingID, tc.clientCert, tc.clientKey, tc.caCert) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) sort.Slice(cfg.Channels, func(i, j int) bool { return cfg.Channels[i].ID < cfg.Channels[j].ID @@ -325,28 +368,48 @@ func TestUpdateCert(t *testing.T) { return tc.expectedConfig.Channels[i].ID < tc.expectedConfig.Channels[j].ID }) assert.Equal(t, tc.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg)) + repoCall.Unset() } } func TestUpdateConnections(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) c := config ch := channel - ch.ID = "2" - c.Channels = append(c.Channels, ch) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) created, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - - externalID, err := uuid.New().ID() - assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ExternalID = externalID + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + + c.ExternalID = testsutil.GenerateUUID(t) + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 = trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 = chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) active, err := svc.Add(context.Background(), validToken, c) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 = auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) err = svc.ChangeState(context.Background(), validToken, active.ThingID, bootstrap.Active) assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() nonExisting := config nonExisting.ThingID = unknown @@ -362,14 +425,14 @@ func TestUpdateConnections(t *testing.T) { desc: "update connections for config with state Inactive", token: validToken, id: created.ThingID, - connections: []string{"2"}, + connections: []string{ch.ID}, err: nil, }, { desc: "update connections for config with state Active", token: validToken, id: active.ThingID, - connections: []string{"3"}, + connections: []string{ch.ID}, err: nil, }, { @@ -384,43 +447,68 @@ func TestUpdateConnections(t *testing.T) { token: validToken, id: created.ThingID, connections: []string{"wrong"}, - err: errors.ErrMalformedEntity, + err: errors.ErrNotFound, }, { desc: "update connections a config with wrong credentials", token: invalidToken, id: created.ThingKey, - connections: []string{"2", "3"}, + connections: []string{ch.ID, "3"}, err: errors.ErrAuthentication, }, } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) + repoCall4 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil) + repoCall5 := auth.On("AddPolicy", mock.Anything, mock.Anything).Return(&magistrala.AddPolicyRes{Authorized: true}, nil) err := svc.UpdateConnections(context.Background(), tc.token, tc.id, tc.connections) 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() + repoCall2.Unset() + repoCall3.Unset() + repoCall4.Unset() + repoCall5.Unset() } } func TestList(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) numThings := 101 var saved []bootstrap.Config for i := 0; i < numThings; i++ { c := config - id, err := uuid.New().ID() - assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err)) - c.ExternalID = id - c.ExternalKey = id + c.ExternalID = testsutil.GenerateUUID(t) + c.ExternalKey = testsutil.GenerateUUID(t) c.Name = fmt.Sprintf("%s-%d", config.Name, i) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: c.ThingID, Credentials: clients.Credentials{Secret: c.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(c.Channels[0]), nil) s, err := svc.Add(context.Background(), validToken, c) - saved = append(saved, s) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + saved = append(saved, s) } // Set one Thing to the different state - err := svc.ChangeState(context.Background(), validToken, "42", bootstrap.Active) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + err := svc.ChangeState(context.Background(), validToken, saved[41].ThingID, bootstrap.Active) assert.Nil(t, err, fmt.Sprintf("Changing config state expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + saved[41].State = bootstrap.Active cases := []struct { @@ -500,19 +588,29 @@ func TestList(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) result, err := svc.List(context.Background(), tc.token, tc.filter, tc.offset, tc.limit) assert.ElementsMatch(t, tc.config.Configs, result.Configs, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Configs, result.Configs)) assert.Equal(t, tc.config.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Total, result.Total)) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } func TestRemove(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string @@ -547,17 +645,27 @@ func TestRemove(t *testing.T) { } for _, tc := range cases { + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) err := svc.Remove(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)) + repoCall.Unset() } } func TestBootstrap(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() e, err := enc([]byte(saved.ExternalKey)) assert.Nil(t, err, fmt.Sprintf("Encrypting external key expected to succeed: %s.\n", err)) @@ -612,11 +720,19 @@ func TestBootstrap(t *testing.T) { } func TestChangeState(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(toGroup(config.Channels[0]), nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string @@ -663,17 +779,33 @@ func TestChangeState(t *testing.T) { } for _, tc := range cases { + repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil) + repoCall1 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 = auth.On("AddPolicies", mock.Anything, mock.Anything).Return(&magistrala.AddPoliciesRes{Authorized: true}, nil) + repoCall3 := auth.On("DeletePolicies", mock.Anything, mock.Anything).Return(&magistrala.DeletePoliciesRes{Deleted: true}, nil) err := svc.ChangeState(context.Background(), tc.token, tc.id, tc.state) 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() + repoCall2.Unset() + repoCall3.Unset() } } func TestUpdateChannelHandler(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) _, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() ch := bootstrap.Channel{ ID: channel.ID, Name: "new name", @@ -704,12 +836,19 @@ func TestUpdateChannelHandler(t *testing.T) { } func TestRemoveChannelHandler(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) _, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string id string @@ -734,11 +873,19 @@ func TestRemoveChannelHandler(t *testing.T) { } func TestRemoveCoinfigHandler(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, config) assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string @@ -764,12 +911,19 @@ func TestRemoveCoinfigHandler(t *testing.T) { } func TestDisconnectThingsHandler(t *testing.T) { - tsvc, gsvc, auth := newThingsService() + tsvc, gsvc, trepo, chrepo, auth := newThingsService() ts := newThingsServer(tsvc, gsvc) svc := newService(ts.URL, auth) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil) + repoCall2 := trepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(clients.Client{ID: config.ThingID, Credentials: clients.Credentials{Secret: config.ThingKey}}, nil) + repoCall3 := chrepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(mggroups.Group{}, nil) saved, err := svc.Add(context.Background(), validToken, config) - require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) - + assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() cases := []struct { desc string thingID string @@ -795,3 +949,11 @@ func TestDisconnectThingsHandler(t *testing.T) { assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) } } + +func toGroup(ch bootstrap.Channel) mggroups.Group { + return mggroups.Group{ + ID: ch.ID, + Name: ch.Name, + Metadata: ch.Metadata, + } +}