From dc26162160590681f972fe797374c028d0c5ab80 Mon Sep 17 00:00:00 2001 From: b1ackd0t Date: Mon, 12 Jun 2023 16:18:50 +0300 Subject: [PATCH] NOISSUE - Add SDK Tests (#1812) * Add Things Tests Signed-off-by: rodneyosodo * Add Channel Tests Signed-off-by: rodneyosodo * Add Certs Tests Signed-off-by: rodneyosodo * Add Consumer Tests Signed-off-by: rodneyosodo * Enrich Group Tests Signed-off-by: rodneyosodo * Add Tests For Health Signed-off-by: rodneyosodo * Add Tests For Tokens Signed-off-by: rodneyosodo * Rename SDK for Tests Signed-off-by: rodneyosodo * Add Policies Tests Signed-off-by: rodneyosodo * Fix Linter Signed-off-by: rodneyosodo * Fix Tests Signed-off-by: rodneyosodo * Make Variable Defination Inline Signed-off-by: rodneyosodo --------- Signed-off-by: rodneyosodo --- bootstrap/mocks/users.go | 2 +- cli/channels.go | 4 +- cli/things.go | 4 +- consumers/notifiers/api/endpoint_test.go | 2 +- consumers/notifiers/mocks/subscriptions.go | 3 + consumers/notifiers/service_test.go | 2 +- pkg/sdk/go/certs_test.go | 363 +++++ pkg/sdk/go/channels_test.go | 906 +++++++++++++ pkg/sdk/go/consumers.go | 6 +- pkg/sdk/go/consumers_test.go | 118 +- pkg/sdk/go/groups_test.go | 315 ++++- pkg/sdk/go/health_test.go | 66 + pkg/sdk/go/message_test.go | 8 +- pkg/sdk/go/policies_test.go | 787 ++++++++++- pkg/sdk/go/responses.go | 19 - pkg/sdk/go/setup_test.go | 141 +- pkg/sdk/go/things_test.go | 1426 ++++++++++++++++++++ pkg/sdk/go/tokens_test.go | 155 +++ pkg/sdk/go/users_test.go | 129 +- things/clients/service_test.go | 2 +- things/groups/service_test.go | 2 +- things/policies/mocks/channels.go | 14 +- things/policies/service_test.go | 2 +- users/clients/mocks/authn.go | 2 +- users/clients/service.go | 4 +- 25 files changed, 4340 insertions(+), 142 deletions(-) create mode 100644 pkg/sdk/go/certs_test.go create mode 100644 pkg/sdk/go/channels_test.go create mode 100644 pkg/sdk/go/health_test.go create mode 100644 pkg/sdk/go/things_test.go create mode 100644 pkg/sdk/go/tokens_test.go diff --git a/bootstrap/mocks/users.go b/bootstrap/mocks/users.go index 3b45ecaec6..588f21db7b 100644 --- a/bootstrap/mocks/users.go +++ b/bootstrap/mocks/users.go @@ -23,7 +23,7 @@ func NewAuthClient(users map[string]string) policies.AuthServiceClient { } func (svc serviceMock) Identify(ctx context.Context, in *policies.Token, opts ...grpc.CallOption) (*policies.UserIdentity, error) { - if id, ok := svc.users[in.Value]; ok { + if id, ok := svc.users[in.GetValue()]; ok { return &policies.UserIdentity{Id: id}, nil } return nil, errors.ErrAuthentication diff --git a/cli/channels.go b/cli/channels.go index 6c21c639ac..9be70680bf 100644 --- a/cli/channels.go +++ b/cli/channels.go @@ -116,8 +116,8 @@ var cmdChannels = []cobra.Command{ return } pm := mfxsdk.PageMetadata{ - Offset: uint64(Offset), - Limit: uint64(Limit), + Offset: uint64(Offset), + Limit: uint64(Limit), } cl, err := sdk.ThingsByChannel(args[0], pm, args[1]) if err != nil { diff --git a/cli/things.go b/cli/things.go index be4986c94d..9e2a4ffff4 100644 --- a/cli/things.go +++ b/cli/things.go @@ -300,8 +300,8 @@ var cmdThings = []cobra.Command{ return } pm := mfxsdk.PageMetadata{ - Offset: uint64(Offset), - Limit: uint64(Limit), + Offset: uint64(Offset), + Limit: uint64(Limit), } cl, err := sdk.ChannelsByThing(args[0], pm, args[1]) if err != nil { diff --git a/consumers/notifiers/api/endpoint_test.go b/consumers/notifiers/api/endpoint_test.go index 2773f65fa6..51240d2c5a 100644 --- a/consumers/notifiers/api/endpoint_test.go +++ b/consumers/notifiers/api/endpoint_test.go @@ -412,7 +412,7 @@ func TestRemove(t *testing.T) { desc: "remove not existing", id: "not existing", auth: token, - status: http.StatusNoContent, + status: http.StatusNotFound, }, { desc: "remove empty id", diff --git a/consumers/notifiers/mocks/subscriptions.go b/consumers/notifiers/mocks/subscriptions.go index cdd2adced2..ac0764edc9 100644 --- a/consumers/notifiers/mocks/subscriptions.go +++ b/consumers/notifiers/mocks/subscriptions.go @@ -123,6 +123,9 @@ func appendSubs(subs []notifiers.Subscription, sub notifiers.Subscription, max i func (srm *subRepoMock) Remove(_ context.Context, id string) error { srm.mu.Lock() defer srm.mu.Unlock() + if _, ok := srm.subs[id]; !ok { + return errors.ErrNotFound + } delete(srm.subs, id) return nil } diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index fff44d648d..726ae951da 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -257,7 +257,7 @@ func TestRemoveSubscription(t *testing.T) { desc: "test not existing", token: exampleUser1, id: "not_exist", - err: nil, + err: errors.ErrNotFound, }, { desc: "test with empty token", diff --git a/pkg/sdk/go/certs_test.go b/pkg/sdk/go/certs_test.go new file mode 100644 index 0000000000..ba0553ab3b --- /dev/null +++ b/pkg/sdk/go/certs_test.go @@ -0,0 +1,363 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package sdk_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + bsmocks "github.com/mainflux/mainflux/bootstrap/mocks" + "github.com/mainflux/mainflux/certs" + httpapi "github.com/mainflux/mainflux/certs/api" + "github.com/mainflux/mainflux/certs/mocks" + "github.com/mainflux/mainflux/internal/apiutil" + "github.com/mainflux/mainflux/logger" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + sdk "github.com/mainflux/mainflux/pkg/sdk/go" + "github.com/mainflux/mainflux/things/clients" + "github.com/mainflux/mainflux/things/policies" + pmocks "github.com/mainflux/mainflux/things/policies/mocks" + upolicies "github.com/mainflux/mainflux/users/policies" +) + +var ( + thingsNum = 1 + thingKey = "thingKey" + thingID = "1" + caPath = "../../../docker/ssl/certs/ca.crt" + caKeyPath = "../../../docker/ssl/certs/ca.key" + cfgAuthTimeout = "1s" + cfgSignHoursValid = "24h" +) + +func newCertsThingsService(auth upolicies.AuthServiceClient) clients.Service { + ths := make(map[string]mfclients.Client, thingsNum) + for i := 0; i < thingsNum; i++ { + id := strconv.Itoa(i + 1) + ths[id] = mfclients.Client{ + ID: id, + Credentials: mfclients.Credentials{ + Secret: thingKey, + }, + Owner: adminID, + Status: mfclients.EnabledStatus, + } + } + + return bsmocks.NewThingsService(ths, auth) +} + +func newCertService() (certs.Service, error) { + uauth := bsmocks.NewAuthClient(map[string]string{adminToken: adminID}) + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + server := newThingsServer(newCertsThingsService(uauth), psvc) + + config := sdk.Config{ + ThingsURL: server.URL, + } + + mfsdk := sdk.NewSDK(config) + repo := mocks.NewCertsRepository() + + tlsCert, caCert, err := certs.LoadCertificates(caPath, caKeyPath) + if err != nil { + return nil, err + } + + authTimeout, err := time.ParseDuration(cfgAuthTimeout) + if err != nil { + return nil, err + } + + pki := mocks.NewPkiAgent(tlsCert, caCert, cfgSignHoursValid, authTimeout) + + return certs.New(uauth, repo, mfsdk, pki), nil +} + +func newCertServer(svc certs.Service) *httptest.Server { + logger := logger.NewMock() + mux := httpapi.MakeHandler(svc, logger) + return httptest.NewServer(mux) +} + +func TestIssueCert(t *testing.T) { + svc, err := newCertService() + require.Nil(t, err, fmt.Sprintf("unexpected error during creating service: %s", err)) + ts := newCertServer(svc) + defer ts.Close() + + sdkConf := sdk.Config{ + CertsURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + + cases := []struct { + desc string + thingID string + duration string + token string + err errors.SDKError + }{ + { + desc: "create new cert with thing id and duration", + thingID: thingID, + duration: "10h", + token: adminToken, + err: nil, + }, + { + desc: "create new cert with empty thing id and duration", + thingID: "", + duration: "10h", + token: adminToken, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + }, + { + desc: "create new cert with invalid thing id and duration", + thingID: "ah", + duration: "10h", + token: adminToken, + err: errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusInternalServerError), + }, + { + desc: "create new cert with thing id and empty duration", + thingID: thingID, + duration: "", + token: exampleUser1, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingCertData, http.StatusBadRequest), + }, + { + desc: "create new cert with thing id and malformed duration", + thingID: thingID, + duration: "10g", + token: exampleUser1, + err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidCertData, http.StatusBadRequest), + }, + { + desc: "create new cert with empty token", + thingID: thingID, + duration: "10h", + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + }, + { + desc: "create new cert with invalid token", + thingID: thingID, + duration: "10h", + token: wrongValue, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), + }, + { + desc: "create new empty cert", + thingID: "", + duration: "", + token: adminToken, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + }, + } + + for _, tc := range cases { + cert, err := mfsdk.IssueCert(tc.thingID, tc.duration, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.NotEmpty(t, cert, fmt.Sprintf("%s: got empty cert", tc.desc)) + } + } +} + +func TestViewCert(t *testing.T) { + svc, err := newCertService() + require.Nil(t, err, fmt.Sprintf("unexpected error during creating service: %s", err)) + ts := newCertServer(svc) + defer ts.Close() + + sdkConf := sdk.Config{ + CertsURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + + cert, err := mfsdk.IssueCert(thingID, "10h", token) + require.Nil(t, err, fmt.Sprintf("unexpected error during creating cert: %s", err)) + + cases := []struct { + desc string + certID string + token string + err errors.SDKError + response sdk.Subscription + }{ + { + desc: "get existing cert", + certID: cert.CertSerial, + token: token, + err: nil, + response: sub1, + }, + { + desc: "get non-existent cert", + certID: "43", + token: token, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusInternalServerError), + response: sdk.Subscription{}, + }, + { + desc: "get cert with invalid token", + certID: cert.CertSerial, + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + response: sdk.Subscription{}, + }, + } + + for _, tc := range cases { + cert, err := mfsdk.ViewCert(tc.certID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.NotEmpty(t, cert, fmt.Sprintf("%s: got empty cert", tc.desc)) + } + } +} + +func TestViewCertByThing(t *testing.T) { + svc, err := newCertService() + require.Nil(t, err, fmt.Sprintf("unexpected error during creating service: %s", err)) + ts := newCertServer(svc) + defer ts.Close() + + sdkConf := sdk.Config{ + CertsURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + + _, err = mfsdk.IssueCert(thingID, "10h", token) + require.Nil(t, err, fmt.Sprintf("unexpected error during creating cert: %s", err)) + + cases := []struct { + desc string + thingID string + token string + err errors.SDKError + response sdk.Subscription + }{ + { + desc: "get existing cert", + thingID: thingID, + token: token, + err: nil, + response: sub1, + }, + { + desc: "get non-existent cert", + thingID: "43", + token: token, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusInternalServerError), + response: sdk.Subscription{}, + }, + { + desc: "get cert with invalid token", + thingID: thingID, + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + response: sdk.Subscription{}, + }, + } + + for _, tc := range cases { + cert, err := mfsdk.ViewCertByThing(tc.thingID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.NotEmpty(t, cert, fmt.Sprintf("%s: got empty cert", tc.desc)) + } + } +} + +func TestRevokeCert(t *testing.T) { + svc, err := newCertService() + require.Nil(t, err, fmt.Sprintf("unexpected error during creating service: %s", err)) + ts := newCertServer(svc) + defer ts.Close() + + sdkConf := sdk.Config{ + CertsURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + + _, err = mfsdk.IssueCert(thingID, "10h", adminToken) + require.Nil(t, err, fmt.Sprintf("unexpected error during creating cert: %s", err)) + + cases := []struct { + desc string + thingID string + token string + err errors.SDKError + }{ + { + desc: "revoke cert with invalid token", + thingID: thingID, + token: wrongValue, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), + }, + { + desc: "revoke non-existing cert", + thingID: "2", + token: token, + err: errors.NewSDKErrorWithStatus(certs.ErrFailedCertRevocation, http.StatusInternalServerError), + }, + { + desc: "revoke cert with invalid id", + thingID: "", + token: token, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + }, + { + desc: "revoke cert with empty token", + thingID: thingID, + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + }, + { + desc: "revoke existing cert", + thingID: thingID, + token: token, + err: nil, + }, + { + desc: "revoke deleted cert", + thingID: thingID, + token: token, + err: errors.NewSDKErrorWithStatus(certs.ErrFailedToRemoveCertFromDB, http.StatusInternalServerError), + }, + } + + for _, tc := range cases { + response, err := mfsdk.RevokeCert(tc.thingID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if err == nil { + assert.NotEmpty(t, response, fmt.Sprintf("%s: got empty revocation time", tc.desc)) + } + } +} diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go new file mode 100644 index 0000000000..e4198c645d --- /dev/null +++ b/pkg/sdk/go/channels_test.go @@ -0,0 +1,906 @@ +package sdk_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-zoo/bone" + "github.com/mainflux/mainflux/internal/apiutil" + "github.com/mainflux/mainflux/internal/testsutil" + "github.com/mainflux/mainflux/logger" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + mfgroups "github.com/mainflux/mainflux/pkg/groups" + sdk "github.com/mainflux/mainflux/pkg/sdk/go" + "github.com/mainflux/mainflux/things/clients" + "github.com/mainflux/mainflux/things/clients/mocks" + "github.com/mainflux/mainflux/things/groups" + "github.com/mainflux/mainflux/things/groups/api" + gmocks "github.com/mainflux/mainflux/things/groups/mocks" + "github.com/mainflux/mainflux/things/policies" + papi "github.com/mainflux/mainflux/things/policies/api/http" + pmocks "github.com/mainflux/mainflux/things/policies/mocks" + umocks "github.com/mainflux/mainflux/users/clients/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func newChannelsServer(csvc clients.Service, svc groups.Service, psvc policies.Service) *httptest.Server { + logger := logger.NewMock() + mux := bone.New() + api.MakeHandler(svc, mux, logger) + papi.MakePolicyHandler(csvc, psvc, mux, logger) + return httptest.NewServer(mux) +} + +func TestCreateChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + channel := sdk.Channel{ + Name: "channelName", + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + } + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + cases := []struct { + desc string + channel sdk.Channel + token string + err errors.SDKError + }{ + { + desc: "create channel successfully", + channel: channel, + token: token, + err: nil, + }, + { + desc: "create channel with existing name", + channel: channel, + err: nil, + }, + { + desc: "update channel that can't be marshalled", + channel: sdk.Channel{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + { + desc: "create channel with parent", + channel: sdk.Channel{ + Name: gName, + ParentID: testsutil.GenerateUUID(t, idProvider), + Status: mfclients.EnabledStatus.String(), + }, + err: nil, + }, + { + desc: "create channel with invalid parent", + channel: sdk.Channel{ + Name: gName, + ParentID: gmocks.WrongID, + Status: mfclients.EnabledStatus.String(), + }, + err: errors.NewSDKErrorWithStatus(errors.ErrCreateEntity, http.StatusInternalServerError), + }, + { + desc: "create channel with invalid owner", + channel: sdk.Channel{ + Name: gName, + OwnerID: gmocks.WrongID, + Status: mfclients.EnabledStatus.String(), + }, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "create channel with missing name", + channel: sdk.Channel{ + Status: mfclients.EnabledStatus.String(), + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrNameSize, http.StatusBadRequest), + }, + { + desc: "create a channel with every field defined", + channel: sdk.Channel{ + ID: generateUUID(t), + OwnerID: "owner", + ParentID: "parent", + Name: "name", + Description: description, + Metadata: validMetadata, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Status: mfclients.EnabledStatus.String(), + }, + token: token, + err: nil, + }, + } + for _, tc := range cases { + repoCall := gRepo.On("Save", mock.Anything, mock.Anything).Return(convertChannel(sdk.Channel{}), tc.err) + rChannel, err := mfsdk.CreateChannel(tc.channel, adminToken) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if err == nil { + assert.NotEmpty(t, rChannel, fmt.Sprintf("%s: expected not nil on client ID", tc.desc)) + ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestCreateChannels(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + channels := []sdk.Channel{ + { + Name: "channelName", + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + }, + { + Name: "channelName2", + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + }, + } + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + cases := []struct { + desc string + channels []sdk.Channel + response []sdk.Channel + token string + err errors.SDKError + }{ + { + desc: "create channels successfully", + channels: channels, + response: channels, + token: token, + err: nil, + }, + { + desc: "register empty channels", + channels: []sdk.Channel{}, + response: []sdk.Channel{}, + token: token, + err: errors.NewSDKErrorWithStatus(apiutil.ErrEmptyList, http.StatusBadRequest), + }, + { + desc: "register channels that can't be marshalled", + channels: []sdk.Channel{ + { + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + }, + response: []sdk.Channel{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + for _, tc := range cases { + repoCall := gRepo.On("Save", mock.Anything, mock.Anything).Return(convertChannels(tc.response), tc.err) + rChannel, err := mfsdk.CreateChannels(tc.channels, adminToken) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) + if err == nil { + assert.NotEmpty(t, rChannel, fmt.Sprintf("%s: expected not nil on client ID", tc.desc)) + ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestListChannels(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + var chs []sdk.Channel + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + for i := 10; i < 100; i++ { + gr := sdk.Channel{ + ID: generateUUID(t), + Name: fmt.Sprintf("channel_%d", i), + Metadata: sdk.Metadata{"name": fmt.Sprintf("thing_%d", i)}, + Status: mfclients.EnabledStatus.String(), + } + chs = append(chs, gr) + } + + cases := []struct { + desc string + token string + status mfclients.Status + total uint64 + offset uint64 + limit uint64 + level int + name string + ownerID string + metadata sdk.Metadata + err errors.SDKError + response []sdk.Channel + }{ + { + desc: "get a list of channels", + token: token, + limit: limit, + offset: offset, + total: total, + err: nil, + response: chs[offset:limit], + }, + { + desc: "get a list of channels with invalid token", + token: invalidToken, + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of channels with empty token", + token: "", + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of channels with zero limit", + token: token, + offset: offset, + limit: 0, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of channels with limit greater than max", + token: token, + offset: offset, + limit: 110, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: []sdk.Channel(nil), + }, + { + desc: "get a list of channels with given name", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Channel{chs[89]}, + }, + { + desc: "get a list of channels with level", + token: token, + offset: 0, + limit: 1, + level: 1, + err: nil, + response: []sdk.Channel{chs[0]}, + }, + { + desc: "get a list of channels with metadata", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Channel{chs[89]}, + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfgroups.GroupsPage{Groups: convertChannels(tc.response)}, tc.err) + pm := sdk.PageMetadata{} + page, err := mfsdk.Channels(pm, adminToken) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, len(tc.response), len(page.Channels), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestViewChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + channel := sdk.Channel{ + Name: "channelName", + Description: description, + Metadata: validMetadata, + Children: []*sdk.Channel{}, + Status: mfclients.EnabledStatus.String(), + } + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + channel.ID = generateUUID(t) + + cases := []struct { + desc string + token string + channelID string + response sdk.Channel + err errors.SDKError + }{ + { + + desc: "view channel", + token: adminToken, + channelID: channel.ID, + response: channel, + err: nil, + }, + { + desc: "view channel with invalid token", + token: "wrongtoken", + channelID: channel.ID, + response: sdk.Channel{Children: []*sdk.Channel{}}, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "view channel for wrong id", + token: adminToken, + channelID: gmocks.WrongID, + response: sdk.Channel{Children: []*sdk.Channel{}}, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("RetrieveByID", mock.Anything, tc.channelID).Return(convertChannel(tc.response), tc.err) + grp, err := mfsdk.Channel(tc.channelID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if len(tc.response.Children) == 0 { + tc.response.Children = nil + } + if len(grp.Children) == 0 { + grp.Children = nil + } + assert.Equal(t, tc.response, grp, fmt.Sprintf("%s: expected metadata %v got %v\n", tc.desc, tc.response, grp)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateGroupAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, tc.channelID) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestUpdateChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + channel := sdk.Channel{ + ID: generateUUID(t), + Name: "channelsName", + Description: description, + Metadata: validMetadata, + } + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + channel.ID = generateUUID(t) + + cases := []struct { + desc string + token string + channel sdk.Channel + response sdk.Channel + err errors.SDKError + }{ + { + desc: "update channel name", + channel: sdk.Channel{ + ID: channel.ID, + Name: "NewName", + }, + response: sdk.Channel{ + ID: channel.ID, + Name: "NewName", + }, + token: adminToken, + err: nil, + }, + { + desc: "update channel description", + channel: sdk.Channel{ + ID: channel.ID, + Description: "NewDescription", + }, + response: sdk.Channel{ + ID: channel.ID, + Description: "NewDescription", + }, + token: adminToken, + err: nil, + }, + { + desc: "update channel metadata", + channel: sdk.Channel{ + ID: channel.ID, + Metadata: sdk.Metadata{ + "field": "value2", + }, + }, + response: sdk.Channel{ + ID: channel.ID, + Metadata: sdk.Metadata{ + "field": "value2", + }, + }, + token: adminToken, + err: nil, + }, + { + desc: "update channel name with invalid channel id", + channel: sdk.Channel{ + ID: gmocks.WrongID, + Name: "NewName", + }, + response: sdk.Channel{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + { + desc: "update channel description with invalid channel id", + channel: sdk.Channel{ + ID: gmocks.WrongID, + Description: "NewDescription", + }, + response: sdk.Channel{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + { + desc: "update channel metadata with invalid channel id", + channel: sdk.Channel{ + ID: gmocks.WrongID, + Metadata: sdk.Metadata{ + "field": "value2", + }, + }, + response: sdk.Channel{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + { + desc: "update channel name with invalid token", + channel: sdk.Channel{ + ID: channel.ID, + Name: "NewName", + }, + response: sdk.Channel{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update channel description with invalid token", + channel: sdk.Channel{ + ID: channel.ID, + Description: "NewDescription", + }, + response: sdk.Channel{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update channel metadata with invalid token", + channel: sdk.Channel{ + ID: channel.ID, + Metadata: sdk.Metadata{ + "field": "value2", + }, + }, + response: sdk.Channel{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update channel that can't be marshalled", + channel: sdk.Channel{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Channel{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("Update", mock.Anything, mock.Anything).Return(convertChannel(tc.response), tc.err) + _, err := mfsdk.UpdateChannel(tc.channel, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateGroupAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestListChannelsByThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + var nChannels = uint64(100) + var aChannels = []sdk.Channel{} + + for i := uint64(1); i < nChannels; i++ { + channel := sdk.Channel{ + Name: fmt.Sprintf("membership_%d@example.com", i), + Metadata: sdk.Metadata{"role": "channel"}, + Status: mfclients.EnabledStatus.String(), + } + aChannels = append(aChannels, channel) + } + + cases := []struct { + desc string + token string + clientID string + page sdk.PageMetadata + response []sdk.Channel + err errors.SDKError + }{ + { + desc: "list channel with authorized token", + token: adminToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{}, + response: aChannels, + err: nil, + }, + { + desc: "list channel with offset and limit", + token: adminToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Offset: 6, + Total: nChannels, + Limit: nChannels, + Status: mfclients.AllStatus.String(), + }, + response: aChannels[6 : nChannels-1], + err: nil, + }, + { + desc: "list channel with given name", + token: adminToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Name: gName, + Offset: 6, + Total: nChannels, + Limit: nChannels, + Status: mfclients.AllStatus.String(), + }, + response: aChannels[6 : nChannels-1], + err: nil, + }, + { + desc: "list channel with given level", + token: adminToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Level: 1, + Offset: 6, + Total: nChannels, + Limit: nChannels, + Status: mfclients.AllStatus.String(), + }, + response: aChannels[6 : nChannels-1], + err: nil, + }, + { + desc: "list channel with metadata", + token: adminToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Metadata: validMetadata, + Offset: 6, + Total: nChannels, + Limit: nChannels, + Status: mfclients.AllStatus.String(), + }, + response: aChannels[6 : nChannels-1], + err: nil, + }, + { + desc: "list channel with an invalid token", + token: invalidToken, + clientID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{}, + response: []sdk.Channel(nil), + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "list channel with an invalid id", + token: adminToken, + clientID: gmocks.WrongID, + page: sdk.PageMetadata{}, + response: []sdk.Channel(nil), + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("Memberships", mock.Anything, tc.clientID, mock.Anything).Return(convertChannelsMembershipPage(sdk.ChannelsPage{Channels: tc.response}), tc.err) + page, err := mfsdk.ChannelsByThing(tc.clientID, tc.page, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, page.Channels, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page.Channels)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateGroupAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "Memberships", mock.Anything, tc.clientID, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Memberships was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestEnableChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + creationTime := time.Now().UTC() + channel := sdk.Channel{ + ID: generateUUID(t), + Name: gName, + OwnerID: generateUUID(t), + CreatedAt: creationTime, + UpdatedAt: creationTime, + Status: mfclients.Disabled, + } + + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + repoCall2 := gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(nil) + _, err := mfsdk.EnableChannel("wrongID", adminToken) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(mfgroups.ErrEnableGroup, http.StatusNotFound), fmt.Sprintf("Enable channel with wrong id: expected %v got %v", errors.ErrNotFound, err)) + ok := repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, "EvaluateGroupAccess was not called on enabling channel") + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") + assert.True(t, ok, "RetrieveByID was not called on enabling channel") + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + + ch := mfgroups.Group{ + ID: channel.ID, + Name: channel.Name, + Owner: channel.OwnerID, + CreatedAt: creationTime, + UpdatedAt: creationTime, + Status: mfclients.DisabledStatus, + } + + repoCall = pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 = gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(ch, nil) + repoCall2 = gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(ch, nil) + res, err := mfsdk.EnableChannel(channel.ID, adminToken) + assert.Nil(t, err, fmt.Sprintf("Enable channel with correct id: expected %v got %v", nil, err)) + assert.Equal(t, channel, res, fmt.Sprintf("Enable channel with correct id: expected %v got %v", channel, res)) + ok = repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, "EvaluateGroupAccess was not called on enabling channel") + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, channel.ID) + assert.True(t, ok, "RetrieveByID was not called on enabling channel") + ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", mock.Anything, mock.Anything) + assert.True(t, ok, "ChangeStatus was not called on enabling channel") + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() +} + +func TestDisableChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + csvc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + svc := groups.NewService(uauth, psvc, gRepo, idProvider) + + ts := newChannelsServer(csvc, svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + creationTime := time.Now().UTC() + channel := sdk.Channel{ + ID: generateUUID(t), + Name: gName, + OwnerID: generateUUID(t), + CreatedAt: creationTime, + UpdatedAt: creationTime, + Status: mfclients.Enabled, + } + + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) + repoCall2 := gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) + _, err := mfsdk.DisableChannel("wrongID", adminToken) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(mfgroups.ErrDisableGroup, http.StatusNotFound), fmt.Sprintf("Disable channel with wrong id: expected %v got %v", errors.ErrNotFound, err)) + ok := repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, "EvaluateGroupAccess was not called on disabling group with wrong id") + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, "wrongID") + assert.True(t, ok, "Memberships was not called on disabling channel with wrong id") + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + + ch := mfgroups.Group{ + ID: channel.ID, + Name: channel.Name, + Owner: channel.OwnerID, + CreatedAt: creationTime, + UpdatedAt: creationTime, + Status: mfclients.EnabledStatus, + } + + repoCall = pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 = gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(ch, nil) + repoCall2 = gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(ch, nil) + res, err := mfsdk.DisableChannel(channel.ID, adminToken) + assert.Nil(t, err, fmt.Sprintf("Disable channel with correct id: expected %v got %v", nil, err)) + assert.Equal(t, channel, res, fmt.Sprintf("Disable channel with correct id: expected %v got %v", channel, res)) + ok = repoCall.Parent.AssertCalled(t, "EvaluateGroupAccess", mock.Anything, mock.Anything) + assert.True(t, ok, "EvaluateGroupAccess was not called on disabling channel with correct id") + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, channel.ID) + assert.True(t, ok, "RetrieveByID was not called on disabling channel with correct id") + ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", mock.Anything, mock.Anything) + assert.True(t, ok, "ChangeStatus was not called on disabling channel with correct id") + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() +} diff --git a/pkg/sdk/go/consumers.go b/pkg/sdk/go/consumers.go index 23c0fbe313..2b6fd8067b 100644 --- a/pkg/sdk/go/consumers.go +++ b/pkg/sdk/go/consumers.go @@ -37,12 +37,12 @@ func (sdk mfSDK) CreateSubscription(topic, contact, token string) (string, error } id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", subscriptionEndpoint)) - + return id, nil } func (sdk mfSDK) ListSubscriptions(pm PageMetadata, token string) (SubscriptionPage, errors.SDKError) { - url, err := sdk.withQueryParams(sdk.certsURL, subscriptionEndpoint, pm) + url, err := sdk.withQueryParams(sdk.usersURL, subscriptionEndpoint, pm) if err != nil { return SubscriptionPage{}, errors.NewSDKError(err) } @@ -78,6 +78,6 @@ func (sdk mfSDK) DeleteSubscription(id, token string) errors.SDKError { url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, subscriptionEndpoint, id) _, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent) - + return err } diff --git a/pkg/sdk/go/consumers_test.go b/pkg/sdk/go/consumers_test.go index ac5de29186..c0a549bda9 100644 --- a/pkg/sdk/go/consumers_test.go +++ b/pkg/sdk/go/consumers_test.go @@ -63,7 +63,7 @@ func TestCreateSubscription(t *testing.T) { TLSVerification: false, } - mainfluxSDK := sdk.NewSDK(sdkConf) + mfsdk := sdk.NewSDK(sdkConf) cases := []struct { desc string @@ -103,7 +103,7 @@ func TestCreateSubscription(t *testing.T) { } for _, tc := range cases { - loc, err := mainfluxSDK.CreateSubscription(tc.subscription.Topic, tc.subscription.Contact, tc.token) + loc, err := mfsdk.CreateSubscription(tc.subscription.Topic, tc.subscription.Contact, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.empty, loc == "", fmt.Sprintf("%s: expected empty result location, got: %s", tc.desc, loc)) } @@ -119,8 +119,8 @@ func TestViewSubscription(t *testing.T) { TLSVerification: false, } - mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateSubscription("topic", "contact", exampleUser1) + mfsdk := sdk.NewSDK(sdkConf) + id, err := mfsdk.CreateSubscription("topic", "contact", exampleUser1) require.Nil(t, err, fmt.Sprintf("unexpected error during creating subscription: %s", err)) cases := []struct { @@ -154,7 +154,7 @@ func TestViewSubscription(t *testing.T) { } for _, tc := range cases { - respSub, err := mainfluxSDK.ViewSubscription(tc.subID, tc.token) + respSub, err := mfsdk.ViewSubscription(tc.subID, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) tc.response.ID = respSub.ID @@ -162,3 +162,111 @@ func TestViewSubscription(t *testing.T) { assert.Equal(t, tc.response, respSub, fmt.Sprintf("%s: expected response %s, got %s", tc.desc, tc.response, respSub)) } } + +func TestListSubscription(t *testing.T) { + svc := newSubscriptionService() + ts := newSubscriptionServer(svc) + defer ts.Close() + sdkConf := sdk.Config{ + UsersURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + var nSubs = 10 + subs := make([]sdk.Subscription, nSubs) + for i := 0; i < nSubs; i++ { + id, err := mfsdk.CreateSubscription(fmt.Sprintf("topic_%d", i), fmt.Sprintf("contact_%d", i), exampleUser1) + require.Nil(t, err, fmt.Sprintf("unexpected error during creating subscription: %s", err)) + sub, err := mfsdk.ViewSubscription(id, exampleUser1) + require.Nil(t, err, fmt.Sprintf("unexpected error during getting subscription: %s", err)) + subs[i] = sub + } + + cases := []struct { + desc string + page sdk.PageMetadata + token string + err errors.SDKError + response []sdk.Subscription + }{ + { + desc: "list all subscription", + token: exampleUser1, + page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs)}, + err: nil, + response: subs, + }, + { + desc: "list subscription with specific topic", + token: exampleUser1, + page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs), Topic: "topic_1"}, + err: nil, + response: []sdk.Subscription{subs[1]}, + }, + { + desc: "list subscription with specific contact", + token: exampleUser1, + page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs), Contact: "contact_1"}, + err: nil, + response: []sdk.Subscription{subs[1]}, + }, + } + + for _, tc := range cases { + subs, err := mfsdk.ListSubscriptions(tc.page, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, subs.Subscriptions, fmt.Sprintf("%s: expected response %v, got %v", tc.desc, tc.response, subs.Subscriptions)) + } +} + +func TestDeleteSubscription(t *testing.T) { + svc := newSubscriptionService() + ts := newSubscriptionServer(svc) + defer ts.Close() + sdkConf := sdk.Config{ + UsersURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + id, err := mfsdk.CreateSubscription("topic", "contact", exampleUser1) + require.Nil(t, err, fmt.Sprintf("unexpected error during creating subscription: %s", err)) + + cases := []struct { + desc string + subID string + token string + err errors.SDKError + response sdk.Subscription + }{ + { + desc: "delete existing subscription", + subID: id, + token: exampleUser1, + err: nil, + response: sub1, + }, + { + desc: "delete non-existent subscription", + subID: "43", + token: exampleUser1, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + response: sdk.Subscription{}, + }, + { + desc: "delete subscription with invalid token", + subID: id, + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), + response: sdk.Subscription{}, + }, + } + + for _, tc := range cases { + err := mfsdk.DeleteSubscription(tc.subID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + } +} diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index fc0e5e0c38..fe0a85d483 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -53,7 +53,7 @@ func TestCreateGroup(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) cases := []struct { desc string group sdk.Group @@ -123,10 +123,21 @@ func TestCreateGroup(t *testing.T) { token: token, err: nil, }, + { + desc: "create a group that can't be marshalled", + group: sdk.Group{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, } for _, tc := range cases { repoCall := gRepo.On("Save", mock.Anything, mock.Anything).Return(convertGroup(sdk.Group{}), tc.err) - rGroup, err := groupSDK.CreateGroup(tc.group, generateValidToken(t, csvc, cRepo)) + rGroup, err := mfsdk.CreateGroup(tc.group, generateValidToken(t, csvc, cRepo)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) if err == nil { assert.NotEmpty(t, rGroup, fmt.Sprintf("%s: expected not nil on client ID", tc.desc)) @@ -152,15 +163,145 @@ func TestListGroups(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) + + for i := 10; i < 100; i++ { + gr := sdk.Group{ + ID: generateUUID(t), + Name: fmt.Sprintf("group_%d", i), + Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, + Status: mfclients.EnabledStatus.String(), + } + grps = append(grps, gr) + } + + cases := []struct { + desc string + token string + status mfclients.Status + total uint64 + offset uint64 + limit uint64 + level int + name string + ownerID string + metadata sdk.Metadata + err errors.SDKError + response []sdk.Group + }{ + { + desc: "get a list of groups", + token: token, + limit: limit, + offset: offset, + total: total, + err: nil, + response: grps[offset:limit], + }, + { + desc: "get a list of groups with invalid token", + token: invalidToken, + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with empty token", + token: "", + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with zero limit", + token: token, + offset: offset, + limit: 0, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with limit greater than max", + token: token, + offset: offset, + limit: 110, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: []sdk.Group(nil), + }, + { + desc: "get a list of groups with given name", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Group{grps[89]}, + }, + { + desc: "get a list of groups with level", + token: token, + offset: 0, + limit: 1, + level: 1, + err: nil, + response: []sdk.Group{grps[0]}, + }, + { + desc: "get a list of groups with metadata", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Group{grps[89]}, + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := gRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfgroups.GroupsPage{Groups: convertGroups(tc.response)}, tc.err) + pm := sdk.PageMetadata{} + page, err := mfsdk.Groups(pm, generateValidToken(t, csvc, cRepo)) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestListParentGroups(t *testing.T) { + cRepo := new(cmocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) + ts := newGroupsServer(svc) + defer ts.Close() + + var grps []sdk.Group + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + parentID := "" for i := 10; i < 100; i++ { gr := sdk.Group{ ID: generateUUID(t), Name: fmt.Sprintf("group_%d", i), Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, Status: mfclients.EnabledStatus.String(), + ParentID: parentID, } + parentID = gr.ID grps = append(grps, gr) } @@ -252,7 +393,7 @@ func TestListGroups(t *testing.T) { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfgroups.GroupsPage{Groups: convertGroups(tc.response)}, tc.err) pm := sdk.PageMetadata{} - page, err := groupSDK.Groups(pm, generateValidToken(t, csvc, cRepo)) + page, err := mfsdk.Parents(parentID, pm, generateValidToken(t, csvc, cRepo)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) if tc.err == nil { @@ -264,6 +405,136 @@ func TestListGroups(t *testing.T) { } } +func TestListChildrenGroups(t *testing.T) { + cRepo := new(cmocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + pRepo := new(pmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := groups.NewService(gRepo, pRepo, tokenizer, idProvider) + ts := newGroupsServer(svc) + defer ts.Close() + + var grps []sdk.Group + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + parentID := "" + for i := 10; i < 100; i++ { + gr := sdk.Group{ + ID: generateUUID(t), + Name: fmt.Sprintf("group_%d", i), + Metadata: sdk.Metadata{"name": fmt.Sprintf("user_%d", i)}, + Status: mfclients.EnabledStatus.String(), + ParentID: parentID, + } + parentID = gr.ID + grps = append(grps, gr) + } + childID := grps[0].ID + + cases := []struct { + desc string + token string + status mfclients.Status + total uint64 + offset uint64 + limit uint64 + level int + name string + ownerID string + metadata sdk.Metadata + err errors.SDKError + response []sdk.Group + }{ + { + desc: "get a list of groups", + token: token, + limit: limit, + offset: offset, + total: total, + err: nil, + response: grps[offset:limit], + }, + { + desc: "get a list of groups with invalid token", + token: invalidToken, + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with empty token", + token: "", + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with zero limit", + token: token, + offset: offset, + limit: 0, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of groups with limit greater than max", + token: token, + offset: offset, + limit: 110, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: []sdk.Group(nil), + }, + { + desc: "get a list of groups with given name", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Group{grps[89]}, + }, + { + desc: "get a list of groups with level", + token: token, + offset: 0, + limit: 1, + level: 1, + err: nil, + response: []sdk.Group{grps[0]}, + }, + { + desc: "get a list of groups with metadata", + token: token, + offset: 0, + limit: 1, + err: nil, + metadata: sdk.Metadata{}, + response: []sdk.Group{grps[89]}, + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := gRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfgroups.GroupsPage{Groups: convertGroups(tc.response)}, tc.err) + pm := sdk.PageMetadata{} + page, err := mfsdk.Children(childID, pm, generateValidToken(t, csvc, cRepo)) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, len(tc.response), len(page.Groups), fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveAll was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} func TestViewGroup(t *testing.T) { cRepo := new(cmocks.ClientRepository) gRepo := new(gmocks.GroupRepository) @@ -286,7 +557,7 @@ func TestViewGroup(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) group.ID = generateUUID(t) cases := []struct { @@ -323,7 +594,7 @@ func TestViewGroup(t *testing.T) { for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("RetrieveByID", mock.Anything, tc.groupID).Return(convertGroup(tc.response), tc.err) - grp, err := groupSDK.Group(tc.groupID, tc.token) + grp, err := mfsdk.Group(tc.groupID, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) if len(tc.response.Children) == 0 { tc.response.Children = nil @@ -364,7 +635,7 @@ func TestUpdateGroup(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) group.ID = generateUUID(t) @@ -482,12 +753,24 @@ func TestUpdateGroup(t *testing.T) { token: invalidToken, err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), }, + { + desc: "update a group that can't be marshalled", + group: sdk.Group{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Group{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, } for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("Update", mock.Anything, mock.Anything).Return(convertGroup(tc.response), tc.err) - _, err := groupSDK.UpdateGroup(tc.group, tc.token) + _, err := mfsdk.UpdateGroup(tc.group, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) if tc.err == nil { ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) @@ -514,7 +797,7 @@ func TestListMemberships(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) var nGroups = uint64(100) var aGroups = []sdk.Group{} @@ -620,7 +903,7 @@ func TestListMemberships(t *testing.T) { for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("Memberships", mock.Anything, tc.clientID, mock.Anything).Return(convertMembershipsPage(sdk.MembershipsPage{Memberships: tc.response}), tc.err) - page, err := groupSDK.Memberships(tc.clientID, tc.page, tc.token) + page, err := mfsdk.Memberships(tc.clientID, tc.page, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page.Memberships, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page.Memberships)) if tc.err == nil { @@ -648,7 +931,7 @@ func TestEnableGroup(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) creationTime := time.Now().UTC() group := sdk.Group{ @@ -663,7 +946,7 @@ func TestEnableGroup(t *testing.T) { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) repoCall2 := gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) - _, err := groupSDK.EnableGroup("wrongID", generateValidToken(t, csvc, cRepo)) + _, err := mfsdk.EnableGroup("wrongID", generateValidToken(t, csvc, cRepo)) assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Enable group with wrong id: expected %v got %v", errors.ErrNotFound, err)) ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) assert.True(t, ok, "CheckAdmin was not called on enabling group") @@ -685,7 +968,7 @@ func TestEnableGroup(t *testing.T) { repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 = gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(g, nil) repoCall2 = gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(g, nil) - res, err := groupSDK.EnableGroup(group.ID, generateValidToken(t, csvc, cRepo)) + res, err := mfsdk.EnableGroup(group.ID, generateValidToken(t, csvc, cRepo)) assert.Nil(t, err, fmt.Sprintf("Enable group with correct id: expected %v got %v", nil, err)) assert.Equal(t, group, res, fmt.Sprintf("Enable group with correct id: expected %v got %v", group, res)) ok = repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) @@ -713,7 +996,7 @@ func TestDisableGroup(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - groupSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) creationTime := time.Now().UTC() group := sdk.Group{ @@ -728,7 +1011,7 @@ func TestDisableGroup(t *testing.T) { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) repoCall2 := gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(nil) - _, err := groupSDK.DisableGroup("wrongID", generateValidToken(t, csvc, cRepo)) + _, err := mfsdk.DisableGroup("wrongID", generateValidToken(t, csvc, cRepo)) assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), fmt.Sprintf("Disable group with wrong id: expected %v got %v", errors.ErrNotFound, err)) ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) assert.True(t, ok, "CheckAdmin was not called on disabling group with wrong id") @@ -750,7 +1033,7 @@ func TestDisableGroup(t *testing.T) { repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 = gRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(g, nil) repoCall2 = gRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(g, nil) - res, err := groupSDK.DisableGroup(group.ID, generateValidToken(t, csvc, cRepo)) + res, err := mfsdk.DisableGroup(group.ID, generateValidToken(t, csvc, cRepo)) assert.Nil(t, err, fmt.Sprintf("Disable group with correct id: expected %v got %v", nil, err)) assert.Equal(t, group, res, fmt.Sprintf("Disable group with correct id: expected %v got %v", group, res)) ok = repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) diff --git a/pkg/sdk/go/health_test.go b/pkg/sdk/go/health_test.go new file mode 100644 index 0000000000..1cc28900a8 --- /dev/null +++ b/pkg/sdk/go/health_test.go @@ -0,0 +1,66 @@ +// Copyright (c) Mainflux +// SPDX-License-Identifier: Apache-2.0 + +package sdk_test + +import ( + "fmt" + "testing" + + "github.com/mainflux/mainflux" + "github.com/mainflux/mainflux/pkg/errors" + sdk "github.com/mainflux/mainflux/pkg/sdk/go" + "github.com/mainflux/mainflux/things/clients" + "github.com/mainflux/mainflux/things/clients/mocks" + gmocks "github.com/mainflux/mainflux/things/groups/mocks" + "github.com/mainflux/mainflux/things/policies" + pmocks "github.com/mainflux/mainflux/things/policies/mocks" + cmocks "github.com/mainflux/mainflux/users/clients/mocks" + "github.com/stretchr/testify/assert" +) + +const ( + thingsDescription = "things service" + thingsStatus = "pass" +) + +func TestHealth(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + sdkConf := sdk.Config{ + ThingsURL: ts.URL, + MsgContentType: contentType, + TLSVerification: false, + } + + mfsdk := sdk.NewSDK(sdkConf) + cases := map[string]struct { + empty bool + err errors.SDKError + }{ + "get things service health check": { + empty: false, + err: nil, + }, + } + for desc, tc := range cases { + h, err := mfsdk.Health() + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", desc, tc.err, err)) + assert.Equal(t, thingsStatus, h.Status, fmt.Sprintf("%s: expected %s status, got %s", desc, thingsStatus, h.Status)) + assert.Equal(t, tc.empty, h.Version == "", fmt.Sprintf("%s: expected non-empty version", desc)) + assert.Equal(t, mainflux.Commit, h.Commit, fmt.Sprintf("%s: expected non-empty commit", desc)) + assert.Equal(t, thingsDescription, h.Description, fmt.Sprintf("%s: expected proper description, got %s", desc, h.Description)) + assert.Equal(t, mainflux.BuildTime, h.BuildTime, fmt.Sprintf("%s: expected default epoch date, got %s", desc, h.BuildTime)) + } +} diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go index 9b4f414406..10dffbbd11 100644 --- a/pkg/sdk/go/message_test.go +++ b/pkg/sdk/go/message_test.go @@ -48,7 +48,7 @@ func TestSendMessage(t *testing.T) { TLSVerification: false, } - mainfluxSDK := sdk.NewSDK(sdkConf) + mfsdk := sdk.NewSDK(sdkConf) cases := map[string]struct { chanID string @@ -94,7 +94,7 @@ func TestSendMessage(t *testing.T) { }, } for desc, tc := range cases { - err := mainfluxSDK.SendMessage(tc.chanID, tc.msg, tc.auth) + err := mfsdk.SendMessage(tc.chanID, tc.msg, tc.auth) if tc.err == nil { assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s", desc, err)) } else { @@ -117,7 +117,7 @@ func TestSetContentType(t *testing.T) { MsgContentType: contentType, TLSVerification: false, } - mainfluxSDK := sdk.NewSDK(sdkConf) + mfsdk := sdk.NewSDK(sdkConf) cases := []struct { desc string @@ -136,7 +136,7 @@ func TestSetContentType(t *testing.T) { }, } for _, tc := range cases { - err := mainfluxSDK.SetContentType(tc.cType) + err := mfsdk.SetContentType(tc.cType) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) } } diff --git a/pkg/sdk/go/policies_test.go b/pkg/sdk/go/policies_test.go index 391ce00a8b..74b3fda58c 100644 --- a/pkg/sdk/go/policies_test.go +++ b/pkg/sdk/go/policies_test.go @@ -12,37 +12,47 @@ import ( "github.com/mainflux/mainflux/logger" "github.com/mainflux/mainflux/pkg/errors" sdk "github.com/mainflux/mainflux/pkg/sdk/go" - "github.com/mainflux/mainflux/users/clients" - cmocks "github.com/mainflux/mainflux/users/clients/mocks" + tclients "github.com/mainflux/mainflux/things/clients" + tmocks "github.com/mainflux/mainflux/things/clients/mocks" + tgmocks "github.com/mainflux/mainflux/things/groups/mocks" + "github.com/mainflux/mainflux/things/policies" + tpolicies "github.com/mainflux/mainflux/things/policies" + tpmocks "github.com/mainflux/mainflux/things/policies/mocks" + uclients "github.com/mainflux/mainflux/users/clients" + umocks "github.com/mainflux/mainflux/users/clients/mocks" "github.com/mainflux/mainflux/users/jwt" - "github.com/mainflux/mainflux/users/policies" - api "github.com/mainflux/mainflux/users/policies/api/http" - pmocks "github.com/mainflux/mainflux/users/policies/mocks" + upolicies "github.com/mainflux/mainflux/users/policies" + uapi "github.com/mainflux/mainflux/users/policies/api/http" + upmocks "github.com/mainflux/mainflux/users/policies/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) -func newPolicyServer(svc policies.Service) *httptest.Server { +const addExistingPolicyDesc = "add existing policy" + +var utadminPolicy = umocks.SubjectSet{Subject: "things", Relation: []string{"g_add"}} + +func newPolicyServer(svc upolicies.Service) *httptest.Server { logger := logger.NewMock() mux := bone.New() - api.MakePolicyHandler(svc, mux, logger) + uapi.MakePolicyHandler(svc, mux, logger) return httptest.NewServer(mux) } func TestCreatePolicy(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) - csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) - svc := policies.NewService(pRepo, tokenizer, idProvider) + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) ts := newPolicyServer(svc) defer ts.Close() conf := sdk.Config{ UsersURL: ts.URL, } - policySDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} @@ -65,7 +75,7 @@ func TestCreatePolicy(t *testing.T) { err: nil, }, { - desc: "add existing policy", + desc: addExistingPolicyDesc, policy: sdk.Policy{ Subject: subject, Object: object, @@ -143,17 +153,17 @@ func TestCreatePolicy(t *testing.T) { for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) - repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertPolicyPage(tc.page), nil) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(tc.page), nil) repoCall2 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tc.err) repoCall3 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) - err := policySDK.CreatePolicy(tc.policy, tc.token) + err := mfsdk.CreatePolicy(tc.policy, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) if tc.err == nil { ok := repoCall.Parent.AssertCalled(t, "Retrieve", mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("Retrieve was not called on %s", tc.desc)) ok = repoCall2.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) - if tc.desc == "add existing policy" { + if tc.desc == addExistingPolicyDesc { ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) } @@ -165,20 +175,259 @@ func TestCreatePolicy(t *testing.T) { } } +func TestAuthorize(t *testing.T) { + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) + ts := newPolicyServer(svc) + defer ts.Close() + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + cases := []struct { + desc string + policy sdk.AccessRequest + page sdk.PolicyPage + token string + err errors.SDKError + }{ + { + desc: "authorize a valid policy with client entity", + policy: sdk.AccessRequest{ + Subject: subject, + Object: object, + Action: "c_list", + EntityType: "client", + }, + page: sdk.PolicyPage{}, + token: generateValidToken(t, csvc, cRepo), + err: nil, + }, + { + desc: "authorize a valid policy with group entity", + policy: sdk.AccessRequest{ + Subject: subject, + Object: object, + Action: "g_add", + EntityType: "group", + }, + page: sdk.PolicyPage{}, + token: generateValidToken(t, csvc, cRepo), + err: nil, + }, + { + desc: "authorize a policy with wrong action", + page: sdk.PolicyPage{}, + policy: sdk.AccessRequest{ + Object: "obj3", + Action: "wrong", + Subject: "sub3", + EntityType: "client", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "authorize a policy with empty object", + page: sdk.PolicyPage{}, + policy: sdk.AccessRequest{ + Action: "c_delete", + Subject: "sub4", + EntityType: "client", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingPolicyObj, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "authorize a policy with empty subject", + page: sdk.PolicyPage{}, + policy: sdk.AccessRequest{ + Action: "c_delete", + Object: "obj4", + EntityType: "client", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingPolicySub, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "authorize a policy with empty action", + page: sdk.PolicyPage{}, + policy: sdk.AccessRequest{ + Subject: "sub5", + Object: "obj5", + EntityType: "client", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + ok, err := mfsdk.Authorize(tc.policy, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + assert.True(t, ok, fmt.Sprintf("%s: expected true, got false", tc.desc)) + ok := repoCall.Parent.AssertCalled(t, "CheckAdmin", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("CheckAdmin was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestAssign(t *testing.T) { + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) + ts := newPolicyServer(svc) + defer ts.Close() + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} + + cases := []struct { + desc string + policy sdk.Policy + page sdk.PolicyPage + token string + err errors.SDKError + }{ + { + desc: "add new policy", + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{}, + token: generateValidToken(t, csvc, cRepo), + err: nil, + }, + { + desc: addExistingPolicyDesc, + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, + token: generateValidToken(t, csvc, cRepo), + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "add a new policy with owner", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + OwnerID: generateUUID(t), + Object: "objwithowner", + Actions: []string{"m_read"}, + Subject: "subwithowner", + }, + err: nil, + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "add a new policy with more actions", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj2", + Actions: []string{"c_delete", "c_update", "c_list"}, + Subject: "sub2", + }, + err: nil, + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "add a new policy with wrong action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj3", + Actions: []string{"wrong"}, + Subject: "sub3", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "add a new policy with empty object", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Subject: "sub4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingPolicyObj, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "add a new policy with empty subject", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Object: "obj4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingPolicySub, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + { + desc: "add a new policy with empty action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Subject: "sub5", + Object: "obj5", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: generateValidToken(t, csvc, cRepo), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(tc.page), nil) + repoCall2 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tc.err) + repoCall3 := pRepo.On("Save", mock.Anything, mock.Anything).Return(tc.err) + err := mfsdk.Assign(tc.policy.Actions, tc.policy.Subject, tc.policy.Object, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Retrieve", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Retrieve was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + if tc.desc == addExistingPolicyDesc { + ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + } +} func TestUpdatePolicy(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) - csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) - svc := policies.NewService(pRepo, tokenizer, idProvider) + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) ts := newPolicyServer(svc) defer ts.Close() conf := sdk.Config{ UsersURL: ts.URL, } - policySDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) policy := sdk.Policy{ Subject: subject, @@ -216,9 +465,9 @@ func TestUpdatePolicy(t *testing.T) { policy.Actions = tc.action policy.CreatedAt = time.Now() repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) - repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(policies.PolicyPage{}, nil) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(upolicies.PolicyPage{}, nil) repoCall2 := pRepo.On("Update", mock.Anything, mock.Anything).Return(tc.err) - err := policySDK.UpdatePolicy(policy, tc.token) + err := mfsdk.UpdatePolicy(policy, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) ok := repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) @@ -228,20 +477,85 @@ func TestUpdatePolicy(t *testing.T) { } } +func TestUpdateThingsPolicy(t *testing.T) { + cRepo := new(tmocks.ClientRepository) + gRepo := new(tgmocks.GroupRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) + thingCache := tmocks.NewCache() + policiesCache := tpmocks.NewCache() + + pRepo := new(tpmocks.PolicyRepository) + psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + policy := sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + } + + cases := []struct { + desc string + action []string + token string + err errors.SDKError + }{ + { + desc: "update policy actions with valid token", + action: []string{"m_write", "m_read"}, + token: adminToken, + err: nil, + }, + { + desc: "update policy action with invalid token", + action: []string{"m_write"}, + token: "non-existent", + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update policy action with wrong policy action", + action: []string{"wrong"}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + }, + } + + for _, tc := range cases { + policy.Actions = tc.action + policy.CreatedAt = time.Now() + repoCall := pRepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tpolicies.PolicyPage{}, nil) + repoCall1 := pRepo.On("Update", mock.Anything, mock.Anything).Return(policies.Policy{}, tc.err) + err := mfsdk.UpdateThingsPolicy(policy, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + ok := repoCall.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + repoCall.Unset() + repoCall1.Unset() + } +} + func TestListPolicies(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) - csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) - svc := policies.NewService(pRepo, tokenizer, idProvider) + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) ts := newPolicyServer(svc) defer ts.Close() conf := sdk.Config{ UsersURL: ts.URL, } - policySDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) id := generateUUID(t) var nPolicy = uint64(10) @@ -351,8 +665,8 @@ func TestListPolicies(t *testing.T) { for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) - repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertPolicyPage(sdk.PolicyPage{Policies: tc.response}), tc.err) - pp, err := policySDK.ListPolicies(tc.page, tc.token) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: tc.response}), tc.err) + pp, err := mfsdk.ListPolicies(tc.page, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, pp.Policies, fmt.Sprintf("%s: expected %v, got %v", tc.desc, tc.response, pp)) ok := repoCall.Parent.AssertCalled(t, "Retrieve", mock.Anything, mock.Anything) @@ -363,28 +677,28 @@ func TestListPolicies(t *testing.T) { } func TestDeletePolicy(t *testing.T) { - cRepo := new(cmocks.ClientRepository) - pRepo := new(pmocks.PolicyRepository) + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) - csvc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) - svc := policies.NewService(pRepo, tokenizer, idProvider) + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) ts := newPolicyServer(svc) defer ts.Close() conf := sdk.Config{ UsersURL: ts.URL, } - policySDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) sub := generateUUID(t) pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) - repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) - err := policySDK.DeletePolicy(pr, generateValidToken(t, csvc, cRepo)) + err := mfsdk.DeletePolicy(pr, generateValidToken(t, csvc, cRepo)) assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) assert.True(t, ok, "Delete was not called on valid policy") @@ -393,9 +707,9 @@ func TestDeletePolicy(t *testing.T) { repoCall.Unset() repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) - repoCall1 = pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) + repoCall1 = pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) - err = policySDK.DeletePolicy(pr, invalidToken) + err = mfsdk.DeletePolicy(pr, invalidToken) assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), fmt.Sprintf("expected %s got %s", pr, err)) ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) assert.True(t, ok, "Delete was not called on invalid policy") @@ -403,3 +717,398 @@ func TestDeletePolicy(t *testing.T) { repoCall1.Unset() repoCall.Unset() } + +func TestUnassign(t *testing.T) { + cRepo := new(umocks.ClientRepository) + pRepo := new(upmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + csvc := uclients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + svc := upolicies.NewService(pRepo, tokenizer, idProvider) + ts := newPolicyServer(svc) + defer ts.Close() + + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + sub := generateUUID(t) + pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} + cpr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} + + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) + repoCall2 := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) + err := mfsdk.Unassign(pr.Subject, pr.Object, generateValidToken(t, csvc, cRepo)) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + ok := repoCall1.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on valid policy") + repoCall2.Unset() + repoCall1.Unset() + repoCall.Unset() + + repoCall = pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 = pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertUserPolicyPage(sdk.PolicyPage{Policies: []sdk.Policy{cpr}}), nil) + repoCall2 = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) + err = mfsdk.Unassign(pr.Subject, pr.Object, invalidToken) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), fmt.Sprintf("expected %s got %s", pr, err)) + ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on invalid policy") + repoCall2.Unset() + repoCall1.Unset() + repoCall.Unset() +} + +func TestConnect(t *testing.T) { + cRepo := new(tmocks.ClientRepository) + gRepo := new(tgmocks.GroupRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) + thingCache := tmocks.NewCache() + policiesCache := tpmocks.NewCache() + + pRepo := new(tpmocks.PolicyRepository) + psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} + + cases := []struct { + desc string + policy sdk.Policy + page sdk.PolicyPage + token string + err errors.SDKError + }{ + { + desc: "add new policy", + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{}, + token: adminToken, + err: nil, + }, + { + desc: addExistingPolicyDesc, + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "add a new policy with owner", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + OwnerID: generateUUID(t), + Object: "objwithowner", + Actions: []string{"m_read"}, + Subject: "subwithowner", + }, + err: nil, + token: adminToken, + }, + { + desc: "add a new policy with more actions", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj2", + Actions: []string{"c_delete", "c_update", "c_list"}, + Subject: "sub2", + }, + err: nil, + token: adminToken, + }, + { + desc: "add a new policy with wrong action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj3", + Actions: []string{"wrong"}, + Subject: "sub3", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: adminToken, + }, + { + desc: "add a new policy with empty object", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Subject: "sub4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + token: adminToken, + }, + { + desc: "add a new policy with empty subject", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Object: "obj4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + token: adminToken, + }, + { + desc: "add a new policy with empty action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Subject: "sub5", + Object: "obj5", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: adminToken, + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertThingPolicyPage(tc.page), nil) + repoCall1 := pRepo.On("Update", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) + repoCall2 := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) + conn := sdk.ConnectionIDs{ChannelIDs: []string{tc.policy.Object}, ThingIDs: []string{tc.policy.Subject}, Actions: tc.policy.Actions} + err := mfsdk.Connect(conn, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Retrieve", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Retrieve was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + if tc.desc == addExistingPolicyDesc { + ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestConnectThing(t *testing.T) { + cRepo := new(tmocks.ClientRepository) + gRepo := new(tgmocks.GroupRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) + thingCache := tmocks.NewCache() + policiesCache := tpmocks.NewCache() + + pRepo := new(tpmocks.PolicyRepository) + psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + clientPolicy := sdk.Policy{Object: object, Actions: []string{"m_write", "g_add"}, Subject: subject} + + cases := []struct { + desc string + policy sdk.Policy + page sdk.PolicyPage + token string + err errors.SDKError + }{ + { + desc: "add new policy", + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{}, + token: adminToken, + err: nil, + }, + { + desc: addExistingPolicyDesc, + policy: sdk.Policy{ + Subject: subject, + Object: object, + Actions: []string{"m_write", "g_add"}, + }, + page: sdk.PolicyPage{Policies: []sdk.Policy{clientPolicy}}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "add a new policy with owner", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + OwnerID: generateUUID(t), + Object: "objwithowner", + Actions: []string{"m_read"}, + Subject: "subwithowner", + }, + err: nil, + token: adminToken, + }, + { + desc: "add a new policy with more actions", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj2", + Actions: []string{"c_delete", "c_update", "c_list"}, + Subject: "sub2", + }, + err: nil, + token: adminToken, + }, + { + desc: "add a new policy with wrong action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Object: "obj3", + Actions: []string{"wrong"}, + Subject: "sub3", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: adminToken, + }, + { + desc: "add a new policy with empty object", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Subject: "sub4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + token: adminToken, + }, + { + desc: "add a new policy with empty subject", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Actions: []string{"c_delete"}, + Object: "obj4", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), + token: adminToken, + }, + { + desc: "add a new policy with empty action", + page: sdk.PolicyPage{}, + policy: sdk.Policy{ + Subject: "sub5", + Object: "obj5", + }, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedPolicyAct, http.StatusInternalServerError), + token: adminToken, + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(convertThingPolicyPage(tc.page), nil) + repoCall1 := pRepo.On("Update", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) + repoCall2 := pRepo.On("Save", mock.Anything, mock.Anything).Return(convertThingPolicy(tc.policy), tc.err) + err := mfsdk.ConnectThing(tc.policy.Subject, tc.policy.Object, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Retrieve", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Retrieve was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + if tc.desc == addExistingPolicyDesc { + ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestDisconnectThing(t *testing.T) { + cRepo := new(tmocks.ClientRepository) + gRepo := new(tgmocks.GroupRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) + thingCache := tmocks.NewCache() + policiesCache := tpmocks.NewCache() + + pRepo := new(tpmocks.PolicyRepository) + psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + sub := generateUUID(t) + pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} + + repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) + err := mfsdk.DisconnectThing(pr.Subject, pr.Object, adminToken) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on valid policy") + repoCall.Unset() + + repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) + err = mfsdk.DisconnectThing(pr.Subject, pr.Object, invalidToken) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), fmt.Sprintf("expected %s got %s", pr, err)) + ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on invalid policy") + repoCall.Unset() +} + +func TestDisconnect(t *testing.T) { + cRepo := new(tmocks.ClientRepository) + gRepo := new(tgmocks.GroupRepository) + uauth := umocks.NewAuthService(users, map[string][]umocks.SubjectSet{adminID: {utadminPolicy}}) + thingCache := tmocks.NewCache() + policiesCache := tpmocks.NewCache() + + pRepo := new(tpmocks.PolicyRepository) + psvc := tpolicies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := tclients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + sub := generateUUID(t) + pr := sdk.Policy{Object: authoritiesObj, Actions: []string{"m_read", "g_add", "c_delete"}, Subject: sub} + + repoCall := pRepo.On("Delete", mock.Anything, mock.Anything).Return(nil) + conn := sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} + err := mfsdk.Disconnect(conn, adminToken) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + ok := repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on valid policy") + repoCall.Unset() + + repoCall = pRepo.On("Delete", mock.Anything, mock.Anything).Return(sdk.ErrFailedRemoval) + conn = sdk.ConnectionIDs{ChannelIDs: []string{pr.Object}, ThingIDs: []string{pr.Subject}} + err = mfsdk.Disconnect(conn, invalidToken) + assert.Equal(t, err, errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), fmt.Sprintf("expected %s got %s", pr, err)) + ok = repoCall.Parent.AssertCalled(t, "Delete", mock.Anything, mock.Anything) + assert.True(t, ok, "Delete was not called on invalid policy") + repoCall.Unset() +} diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go index 8b6f3685e6..1c3fb410a8 100644 --- a/pkg/sdk/go/responses.go +++ b/pkg/sdk/go/responses.go @@ -4,7 +4,6 @@ package sdk import ( - "net/http" "time" "github.com/mainflux/mainflux/pkg/transformers/senml" @@ -70,24 +69,6 @@ type PolicyPage struct { PageMetadata Policies []Policy } -type KeyRes struct { - ID string `json:"id,omitempty"` - Value string `json:"value,omitempty"` - IssuedAt time.Time `json:"issued_at,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` -} - -func (res KeyRes) Code() int { - return http.StatusCreated -} - -func (res KeyRes) Headers() map[string]string { - return map[string]string{} -} - -func (res KeyRes) Empty() bool { - return res.Value == "" -} type revokeCertsRes struct { RevocationTime time.Time `json:"revocation_time"` diff --git a/pkg/sdk/go/setup_test.go b/pkg/sdk/go/setup_test.go index 2ee51ae0b7..7c71de155b 100644 --- a/pkg/sdk/go/setup_test.go +++ b/pkg/sdk/go/setup_test.go @@ -13,10 +13,11 @@ import ( mfgroups "github.com/mainflux/mainflux/pkg/groups" sdk "github.com/mainflux/mainflux/pkg/sdk/go" "github.com/mainflux/mainflux/pkg/uuid" + tpolicies "github.com/mainflux/mainflux/things/policies" "github.com/mainflux/mainflux/users/clients" umocks "github.com/mainflux/mainflux/users/clients/mocks" "github.com/mainflux/mainflux/users/hasher" - "github.com/mainflux/mainflux/users/policies" + upolicies "github.com/mainflux/mainflux/users/policies" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -41,6 +42,13 @@ var ( Metadata: validMetadata, Status: mfclients.EnabledStatus.String(), } + thing = sdk.Thing{ + Name: "thingname", + Tags: []string{"tag1", "tag2"}, + Credentials: sdk.Credentials{Identity: "clientidentity", Secret: generateUUID(&testing.T{})}, + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + } description = "shortdescription" gName = "groupname" @@ -92,6 +100,12 @@ func convertClientsPage(cp sdk.UsersPage) mfclients.ClientsPage { } } +func convertThingsPage(cp sdk.ThingsPage) mfclients.ClientsPage { + return mfclients.ClientsPage{ + Clients: convertThings(cp.Things), + } +} + func convertClients(cs []sdk.User) []mfclients.Client { ccs := []mfclients.Client{} @@ -102,6 +116,16 @@ func convertClients(cs []sdk.User) []mfclients.Client { return ccs } +func convertThings(cs []sdk.Thing) []mfclients.Client { + ccs := []mfclients.Client{} + + for _, c := range cs { + ccs = append(ccs, convertThing(c)) + } + + return ccs +} + func convertGroups(cs []sdk.Group) []mfgroups.Group { cgs := []mfgroups.Group{} @@ -112,18 +136,49 @@ func convertGroups(cs []sdk.Group) []mfgroups.Group { return cgs } -func convertPolicies(cs []sdk.Policy) []policies.Policy { - ccs := []policies.Policy{} +func convertChannels(cs []sdk.Channel) []mfgroups.Group { + cgs := []mfgroups.Group{} + + for _, c := range cs { + cgs = append(cgs, convertChannel(c)) + } + + return cgs +} + +func convertUserPolicies(cs []sdk.Policy) []upolicies.Policy { + ccs := []upolicies.Policy{} for _, c := range cs { - ccs = append(ccs, convertPolicy(c)) + ccs = append(ccs, convertUserPolicy(c)) } return ccs } -func convertPolicy(sp sdk.Policy) policies.Policy { - return policies.Policy{ +func convertThingPolicies(cs []sdk.Policy) []tpolicies.Policy { + ccs := []tpolicies.Policy{} + + for _, c := range cs { + ccs = append(ccs, convertThingPolicy(c)) + } + + return ccs +} + +func convertUserPolicy(sp sdk.Policy) upolicies.Policy { + return upolicies.Policy{ + OwnerID: sp.OwnerID, + Subject: sp.Subject, + Object: sp.Object, + Actions: sp.Actions, + CreatedAt: sp.CreatedAt, + UpdatedAt: sp.UpdatedAt, + } +} + +func convertThingPolicy(sp sdk.Policy) tpolicies.Policy { + return tpolicies.Policy{ OwnerID: sp.OwnerID, Subject: sp.Subject, Object: sp.Object, @@ -144,6 +199,17 @@ func convertMembershipsPage(m sdk.MembershipsPage) mfgroups.MembershipsPage { } } +func convertChannelsMembershipPage(m sdk.ChannelsPage) mfgroups.MembershipsPage { + return mfgroups.MembershipsPage{ + Page: mfgroups.Page{ + Limit: m.Limit, + Total: m.Total, + Offset: m.Offset, + }, + Memberships: convertChannels(m.Channels), + } +} + func convertClientPage(p sdk.PageMetadata) mfclients.Page { if p.Status == "" { p.Status = mfclients.EnabledStatus.String() @@ -236,14 +302,69 @@ func convertClient(c sdk.User) mfclients.Client { } } -func convertPolicyPage(pp sdk.PolicyPage) policies.PolicyPage { - return policies.PolicyPage{ - Page: policies.Page{ +func convertThing(c sdk.Thing) mfclients.Client { + if c.Status == "" { + c.Status = mfclients.EnabledStatus.String() + } + status, err := mfclients.ToStatus(c.Status) + if err != nil { + return mfclients.Client{} + } + return mfclients.Client{ + ID: c.ID, + Name: c.Name, + Tags: c.Tags, + Owner: c.Owner, + Credentials: mfclients.Credentials(c.Credentials), + Metadata: mfclients.Metadata(c.Metadata), + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + Status: status, + } +} + +func convertChannel(g sdk.Channel) mfgroups.Group { + if g.Status == "" { + g.Status = mfclients.EnabledStatus.String() + } + status, err := mfclients.ToStatus(g.Status) + if err != nil { + return mfgroups.Group{} + } + return mfgroups.Group{ + ID: g.ID, + Owner: g.OwnerID, + Parent: g.ParentID, + Name: g.Name, + Description: g.Description, + Metadata: mfclients.Metadata(g.Metadata), + Level: g.Level, + Path: g.Path, + CreatedAt: g.CreatedAt, + UpdatedAt: g.UpdatedAt, + Status: status, + } +} + +func convertUserPolicyPage(pp sdk.PolicyPage) upolicies.PolicyPage { + return upolicies.PolicyPage{ + Page: upolicies.Page{ + Limit: pp.Limit, + Total: pp.Total, + Offset: pp.Offset, + }, + Policies: convertUserPolicies(pp.Policies), + } +} + +func convertThingPolicyPage(pp sdk.PolicyPage) tpolicies.PolicyPage { + return tpolicies.PolicyPage{ + Page: tpolicies.Page{ Limit: pp.Limit, Total: pp.Total, Offset: pp.Offset, }, - Policies: convertPolicies(pp.Policies), + Policies: convertThingPolicies(pp.Policies), } } diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go new file mode 100644 index 0000000000..e810e2a90f --- /dev/null +++ b/pkg/sdk/go/things_test.go @@ -0,0 +1,1426 @@ +package sdk_test + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-zoo/bone" + "github.com/mainflux/mainflux/internal/apiutil" + "github.com/mainflux/mainflux/internal/testsutil" + mflog "github.com/mainflux/mainflux/logger" + mfclients "github.com/mainflux/mainflux/pkg/clients" + "github.com/mainflux/mainflux/pkg/errors" + sdk "github.com/mainflux/mainflux/pkg/sdk/go" + "github.com/mainflux/mainflux/things/clients" + "github.com/mainflux/mainflux/things/clients/api" + "github.com/mainflux/mainflux/things/clients/mocks" + gmocks "github.com/mainflux/mainflux/things/groups/mocks" + "github.com/mainflux/mainflux/things/policies" + papi "github.com/mainflux/mainflux/things/policies/api/http" + pmocks "github.com/mainflux/mainflux/things/policies/mocks" + cmocks "github.com/mainflux/mainflux/users/clients/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + adminToken = "token" + adminID = generateUUID(&testing.T{}) + users = map[string]string{adminToken: adminID} + ID = testsutil.GenerateUUID(&testing.T{}, idProvider) + uadminPolicy = cmocks.SubjectSet{Subject: ID, Relation: clients.AdminRelationKey} +) + +func newThingsServer(svc clients.Service, psvc policies.Service) *httptest.Server { + logger := mflog.NewMock() + mux := bone.New() + api.MakeHandler(svc, mux, logger) + papi.MakePolicyHandler(svc, psvc, mux, logger) + return httptest.NewServer(mux) +} + +func TestCreateThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + thing := sdk.Thing{ + Name: "test", + Status: mfclients.EnabledStatus.String(), + } + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + cases := []struct { + desc string + client sdk.Thing + response sdk.Thing + token string + err errors.SDKError + }{ + { + desc: "register new thing", + client: thing, + response: thing, + token: token, + err: nil, + }, + { + desc: "register existing thing", + client: thing, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "register empty thing", + client: sdk.Thing{}, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest), + }, + { + desc: "register a thing that can't be marshalled", + client: sdk.Thing{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + { + desc: "register thing with empty secret", + client: sdk.Thing{ + Name: "emptysecret", + Credentials: sdk.Credentials{ + Secret: "", + }, + }, + response: sdk.Thing{ + Name: "emptysecret", + Credentials: sdk.Credentials{ + Secret: "", + }, + }, + token: token, + err: nil, + }, + { + desc: "register thing with empty identity", + client: sdk.Thing{ + Credentials: sdk.Credentials{ + Identity: "", + Secret: secret, + }, + }, + response: sdk.Thing{ + Credentials: sdk.Credentials{ + Identity: "", + Secret: secret, + }, + }, + token: token, + err: nil, + }, + { + desc: "register empty thing", + client: sdk.Thing{}, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMalformedEntity, http.StatusBadRequest), + }, + { + desc: "register thing with every field defined", + client: sdk.Thing{ + ID: id, + Name: "name", + Tags: []string{"tag1", "tag2"}, + Owner: id, + Credentials: user.Credentials, + Metadata: validMetadata, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Status: mfclients.EnabledStatus.String(), + }, + response: sdk.Thing{ + ID: id, + Name: "name", + Tags: []string{"tag1", "tag2"}, + Owner: id, + Credentials: user.Credentials, + Metadata: validMetadata, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Status: mfclients.EnabledStatus.String(), + }, + token: token, + err: nil, + }, + } + for _, tc := range cases { + repoCall := cRepo.On("Save", mock.Anything, mock.Anything).Return(tc.response, tc.err) + rThing, err := mfsdk.CreateThing(tc.client, tc.token) + tc.response.ID = rThing.ID + tc.response.Owner = rThing.Owner + tc.response.CreatedAt = rThing.CreatedAt + tc.response.UpdatedAt = rThing.UpdatedAt + rThing.Credentials.Secret = tc.response.Credentials.Secret + rThing.Status = tc.response.Status + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, rThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rThing)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestCreateThings(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + things := []sdk.Thing{ + { + Name: "test", + Status: mfclients.EnabledStatus.String(), + }, + { + Name: "test2", + Status: mfclients.EnabledStatus.String(), + }, + } + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + cases := []struct { + desc string + things []sdk.Thing + response []sdk.Thing + token string + err errors.SDKError + }{ + { + desc: "register new things", + things: things, + response: things, + token: token, + err: nil, + }, + { + desc: "register existing things", + things: things, + response: []sdk.Thing{}, + token: token, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + }, + { + desc: "register empty things", + things: []sdk.Thing{}, + response: []sdk.Thing{}, + token: token, + err: errors.NewSDKErrorWithStatus(apiutil.ErrEmptyList, http.StatusBadRequest), + }, + { + desc: "register things that can't be marshalled", + things: []sdk.Thing{ + { + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + }, + response: []sdk.Thing{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + for _, tc := range cases { + repoCall := cRepo.On("Save", mock.Anything, mock.Anything).Return(tc.response, tc.err) + rThing, err := mfsdk.CreateThings(tc.things, tc.token) + for i, t := range rThing { + tc.response[i].ID = t.ID + tc.response[i].Owner = t.Owner + tc.response[i].CreatedAt = t.CreatedAt + tc.response[i].UpdatedAt = t.UpdatedAt + tc.response[i].Credentials.Secret = t.Credentials.Secret + t.Status = tc.response[i].Status + } + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, rThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rThing)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestListThings(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + var ths []sdk.Thing + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + owner := generateUUID(t) + for i := 10; i < 100; i++ { + th := sdk.Thing{ + ID: generateUUID(t), + Name: fmt.Sprintf("thing_%d", i), + Credentials: sdk.Credentials{ + Identity: fmt.Sprintf("identity_%d", i), + Secret: generateUUID(t), + }, + Metadata: sdk.Metadata{"name": fmt.Sprintf("thing_%d", i)}, + Status: mfclients.EnabledStatus.String(), + } + if i == 50 { + th.Owner = owner + th.Status = mfclients.DisabledStatus.String() + th.Tags = []string{"tag1", "tag2"} + } + ths = append(ths, th) + } + + cases := []struct { + desc string + token string + status string + total uint64 + offset uint64 + limit uint64 + name string + identifier string + ownerID string + tag string + metadata sdk.Metadata + err errors.SDKError + response []sdk.Thing + }{ + { + desc: "get a list of things", + token: token, + limit: limit, + offset: offset, + total: total, + err: nil, + response: ths[offset:limit], + }, + { + desc: "get a list of things with invalid token", + token: invalidToken, + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of things with empty token", + token: "", + offset: offset, + limit: limit, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedList, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of things with zero limit", + token: token, + offset: offset, + limit: 0, + err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusInternalServerError), + response: nil, + }, + { + desc: "get a list of things with limit greater than max", + token: token, + offset: offset, + limit: 110, + err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusInternalServerError), + response: []sdk.Thing(nil), + }, + { + desc: "get a list of things with same identity", + token: token, + offset: 0, + limit: 1, + err: nil, + identifier: Identity, + metadata: sdk.Metadata{}, + response: []sdk.Thing{ths[89]}, + }, + { + desc: "get a list of things with same identity and metadata", + token: token, + offset: 0, + limit: 1, + err: nil, + identifier: Identity, + metadata: sdk.Metadata{ + "name": "client99", + }, + response: []sdk.Thing{ths[89]}, + }, + { + desc: "list things with given metadata", + token: adminToken, + offset: 0, + limit: 1, + metadata: sdk.Metadata{ + "name": "client99", + }, + response: []sdk.Thing{ths[89]}, + err: nil, + }, + { + desc: "list things with given name", + token: adminToken, + offset: 0, + limit: 1, + name: "client10", + response: []sdk.Thing{ths[0]}, + err: nil, + }, + { + desc: "list things with given owner", + token: adminToken, + offset: 0, + limit: 1, + ownerID: owner, + response: []sdk.Thing{ths[50]}, + err: nil, + }, + { + desc: "list things with given status", + token: adminToken, + offset: 0, + limit: 1, + status: mfclients.DisabledStatus.String(), + response: []sdk.Thing{ths[50]}, + err: nil, + }, + { + desc: "list things with given tag", + token: adminToken, + offset: 0, + limit: 1, + tag: "tag1", + response: []sdk.Thing{ths[50]}, + err: nil, + }, + } + + for _, tc := range cases { + pm := sdk.PageMetadata{ + Status: tc.status, + Total: total, + Offset: tc.offset, + Limit: tc.limit, + Name: tc.name, + OwnerID: tc.ownerID, + Metadata: tc.metadata, + Tag: tc.tag, + } + + repoCall := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfclients.ClientsPage{Page: convertClientPage(pm), Clients: convertThings(tc.response)}, tc.err) + page, err := mfsdk.Things(pm, adminToken) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, page.Things, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) + repoCall.Unset() + } +} + +func TestListThingsByChannel(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + var nThing = uint64(10) + var aThings = []sdk.Thing{} + + for i := uint64(1); i < nThing; i++ { + thing := sdk.Thing{ + Name: fmt.Sprintf("member_%d@example.com", i), + Credentials: sdk.Credentials{ + Secret: generateUUID(t), + }, + Tags: []string{"tag1", "tag2"}, + Metadata: sdk.Metadata{"role": "client"}, + Status: mfclients.EnabledStatus.String(), + } + aThings = append(aThings, thing) + } + + cases := []struct { + desc string + token string + channelID string + page sdk.PageMetadata + response []sdk.Thing + err errors.SDKError + }{ + { + desc: "list things with authorized token", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{}, + response: aThings, + err: nil, + }, + { + desc: "list things with offset and limit", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Offset: 4, + Limit: nThing, + }, + response: aThings[4:], + err: nil, + }, + { + desc: "list things with given name", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Name: Identity, + Offset: 6, + Limit: nThing, + }, + response: aThings[6:], + err: nil, + }, + + { + desc: "list things with given ownerID", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + OwnerID: user.Owner, + Offset: 6, + Limit: nThing, + }, + response: aThings[6:], + err: nil, + }, + { + desc: "list things with given subject", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Subject: subject, + Offset: 6, + Limit: nThing, + }, + response: aThings[6:], + err: nil, + }, + { + desc: "list things with given object", + token: adminToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{ + Object: object, + Offset: 6, + Limit: nThing, + }, + response: aThings[6:], + err: nil, + }, + { + desc: "list things with an invalid token", + token: invalidToken, + channelID: testsutil.GenerateUUID(t, idProvider), + page: sdk.PageMetadata{}, + response: []sdk.Thing(nil), + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "list things with an invalid id", + token: adminToken, + channelID: mocks.WrongID, + page: sdk.PageMetadata{}, + response: []sdk.Thing(nil), + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + } + + for _, tc := range cases { + repoCall := cRepo.On("Members", mock.Anything, tc.channelID, mock.Anything).Return(mfclients.MembersPage{Members: convertThings(tc.response)}, tc.err) + membersPage, err := mfsdk.ThingsByChannel(tc.channelID, tc.page, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, membersPage.Things, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, membersPage.Things)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "Members", mock.Anything, tc.channelID, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Members was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + thing := sdk.Thing{ + Name: "thingname", + Tags: []string{"tag1", "tag2"}, + Credentials: sdk.Credentials{Identity: "clientidentity", Secret: generateUUID(t)}, + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + } + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + cases := []struct { + desc string + token string + thingID string + response sdk.Thing + err errors.SDKError + }{ + { + desc: "view thing successfully", + response: thing, + token: adminToken, + thingID: generateUUID(t), + err: nil, + }, + { + desc: "view thing with an invalid token", + response: sdk.Thing{}, + token: invalidToken, + thingID: generateUUID(t), + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "view thing with valid token and invalid thing id", + response: sdk.Thing{}, + token: adminToken, + thingID: mocks.WrongID, + err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound), + }, + { + desc: "view thing with an invalid token and invalid thing id", + response: sdk.Thing{}, + token: invalidToken, + thingID: mocks.WrongID, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.thingID).Return(convertThing(tc.response), tc.err) + rClient, err := mfsdk.Thing(tc.thingID, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, tc.thingID) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + repoCall1.Unset() + repoCall.Unset() + } +} + +func TestUpdateThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + thing := sdk.Thing{ + ID: generateUUID(t), + Name: "clientname", + Credentials: sdk.Credentials{Secret: generateUUID(t)}, + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + } + + thing1 := thing + thing1.Name = "Updated client" + + thing2 := thing + thing2.Metadata = sdk.Metadata{"role": "test"} + thing2.ID = invalidIdentity + + cases := []struct { + desc string + thing sdk.Thing + response sdk.Thing + token string + err errors.SDKError + }{ + { + desc: "update thing name with valid token", + thing: thing1, + response: thing1, + token: adminToken, + err: nil, + }, + { + desc: "update thing name with invalid token", + thing: thing1, + response: sdk.Thing{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update thing name with invalid id", + thing: thing2, + response: sdk.Thing{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), + }, + { + desc: "update thing that can't be marshalled", + thing: sdk.Thing{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("Update", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + uClient, err := mfsdk.UpdateThing(tc.thing, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateThingAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateThingAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "Update", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Update was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestUpdateThingTags(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + thing := sdk.Thing{ + ID: generateUUID(t), + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: sdk.Credentials{Secret: generateUUID(t)}, + Status: mfclients.EnabledStatus.String(), + } + + thing1 := thing + thing1.Tags = []string{"updatedTag1", "updatedTag2"} + + thing2 := thing + thing2.ID = invalidIdentity + + cases := []struct { + desc string + thing sdk.Thing + response sdk.Thing + token string + err error + }{ + { + desc: "update thing name with valid token", + thing: thing, + response: thing1, + token: adminToken, + err: nil, + }, + { + desc: "update thing name with invalid token", + thing: thing1, + response: sdk.Thing{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update thing name with invalid id", + thing: thing2, + response: sdk.Thing{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), + }, + { + desc: "update thing that can't be marshalled", + thing: sdk.Thing{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("UpdateTags", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + uClient, err := mfsdk.UpdateThingTags(tc.thing, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateThingAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateThingAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "UpdateTags", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("UpdateTags was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestUpdateThingSecret(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + user.ID = generateUUID(t) + rthing := thing + rthing.Credentials.Secret, _ = phasher.Hash(user.Credentials.Secret) + + cases := []struct { + desc string + oldSecret string + newSecret string + token string + response sdk.Thing + err error + }{ + { + desc: "update thing secret with valid token", + oldSecret: thing.Credentials.Secret, + newSecret: "newSecret", + token: adminToken, + response: rthing, + err: nil, + }, + { + desc: "update thing secret with invalid token", + oldSecret: thing.Credentials.Secret, + newSecret: "newPassword", + token: "non-existent", + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update thing secret with wrong old secret", + oldSecret: "oldSecret", + newSecret: "newSecret", + token: adminToken, + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidSecret, http.StatusBadRequest), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("UpdateSecret", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + uClient, err := mfsdk.UpdateThingSecret(tc.oldSecret, tc.newSecret, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) + if tc.err == nil { + ok := repoCall1.Parent.AssertCalled(t, "UpdateSecret", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("UpdateSecret was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestUpdateThingOwner(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + thing = sdk.Thing{ + ID: generateUUID(t), + Name: "clientname", + Tags: []string{"tag1", "tag2"}, + Credentials: sdk.Credentials{Identity: "clientidentity", Secret: generateUUID(t)}, + Metadata: validMetadata, + Status: mfclients.EnabledStatus.String(), + Owner: "owner", + } + + thing2 := thing + thing2.ID = invalidIdentity + + cases := []struct { + desc string + thing sdk.Thing + response sdk.Thing + token string + err errors.SDKError + }{ + { + desc: "update thing name with valid token", + thing: thing, + response: thing, + token: adminToken, + err: nil, + }, + { + desc: "update thing name with invalid token", + thing: thing2, + response: sdk.Thing{}, + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + { + desc: "update thing name with invalid id", + thing: thing2, + response: sdk.Thing{}, + token: adminToken, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), + }, + { + desc: "update thing that can't be marshalled", + thing: sdk.Thing{ + Name: "test", + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.Thing{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("UpdateOwner", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + uClient, err := mfsdk.UpdateThingOwner(tc.thing, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateThingAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateThingAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "UpdateOwner", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("UpdateOwner was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestEnableThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + enabledThing1 := sdk.Thing{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client1@example.com", Secret: generateUUID(t)}, Status: mfclients.EnabledStatus.String()} + disabledThing1 := sdk.Thing{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client3@example.com", Secret: generateUUID(t)}, Status: mfclients.DisabledStatus.String()} + endisabledThing1 := disabledThing1 + endisabledThing1.Status = mfclients.EnabledStatus.String() + endisabledThing1.ID = testsutil.GenerateUUID(t, idProvider) + + cases := []struct { + desc string + id string + token string + thing sdk.Thing + response sdk.Thing + err errors.SDKError + }{ + { + desc: "enable disabled thing", + id: disabledThing1.ID, + token: adminToken, + thing: disabledThing1, + response: endisabledThing1, + err: nil, + }, + { + desc: "enable enabled thing", + id: enabledThing1.ID, + token: adminToken, + thing: enabledThing1, + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedEnable, http.StatusInternalServerError), + }, + { + desc: "enable non-existing thing", + id: mocks.WrongID, + token: adminToken, + thing: sdk.Thing{}, + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedEnable, http.StatusNotFound), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertThing(tc.thing), tc.err) + repoCall2 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + eClient, err := mfsdk.EnableThing(tc.id, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, eClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, eClient)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateThingAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateThingAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, tc.id) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } + + cases2 := []struct { + desc string + token string + status string + metadata sdk.Metadata + response sdk.ThingsPage + size uint64 + }{ + { + desc: "list enabled clients", + status: mfclients.EnabledStatus.String(), + size: 2, + response: sdk.ThingsPage{ + Things: []sdk.Thing{enabledThing1, endisabledThing1}, + }, + }, + { + desc: "list disabled clients", + status: mfclients.DisabledStatus.String(), + size: 1, + response: sdk.ThingsPage{ + Things: []sdk.Thing{disabledThing1}, + }, + }, + { + desc: "list enabled and disabled clients", + status: mfclients.AllStatus.String(), + size: 3, + response: sdk.ThingsPage{ + Things: []sdk.Thing{enabledThing1, disabledThing1, endisabledThing1}, + }, + }, + } + + for _, tc := range cases2 { + pm := sdk.PageMetadata{ + Total: 100, + Offset: 0, + Limit: 100, + Status: tc.status, + } + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertThingsPage(tc.response), nil) + clientsPage, err := mfsdk.Things(pm, adminToken) + assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + size := uint64(len(clientsPage.Things)) + assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestDisableThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + enabledThing1 := sdk.Thing{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client1@example.com", Secret: generateUUID(t)}, Status: mfclients.EnabledStatus.String()} + disabledThing1 := sdk.Thing{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client3@example.com", Secret: generateUUID(t)}, Status: mfclients.DisabledStatus.String()} + disenabledThing1 := enabledThing1 + disenabledThing1.Status = mfclients.DisabledStatus.String() + disenabledThing1.ID = testsutil.GenerateUUID(t, idProvider) + + cases := []struct { + desc string + id string + token string + thing sdk.Thing + response sdk.Thing + err errors.SDKError + }{ + { + desc: "disable enabled thing", + id: enabledThing1.ID, + token: adminToken, + thing: enabledThing1, + response: disenabledThing1, + err: nil, + }, + { + desc: "disable disabled thing", + id: disabledThing1.ID, + token: adminToken, + thing: disabledThing1, + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedDisable, http.StatusInternalServerError), + }, + { + desc: "disable non-existing thing", + id: mocks.WrongID, + thing: sdk.Thing{}, + token: adminToken, + response: sdk.Thing{}, + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedDisable, http.StatusNotFound), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertThing(tc.thing), tc.err) + repoCall2 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertThing(tc.response), tc.err) + dThing, err := mfsdk.DisableThing(tc.id, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, dThing, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, dThing)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "EvaluateThingAccess", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("EvaluateThingAccess was not called on %s", tc.desc)) + ok = repoCall1.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, tc.id) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + ok = repoCall2.Parent.AssertCalled(t, "ChangeStatus", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("ChangeStatus was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } + + cases2 := []struct { + desc string + token string + status string + metadata sdk.Metadata + response sdk.ThingsPage + size uint64 + }{ + { + desc: "list enabled things", + status: mfclients.EnabledStatus.String(), + size: 2, + response: sdk.ThingsPage{ + Things: []sdk.Thing{enabledThing1, disenabledThing1}, + }, + }, + { + desc: "list disabled things", + status: mfclients.DisabledStatus.String(), + size: 1, + response: sdk.ThingsPage{ + Things: []sdk.Thing{disabledThing1}, + }, + }, + { + desc: "list enabled and disabled things", + status: mfclients.AllStatus.String(), + size: 3, + response: sdk.ThingsPage{ + Things: []sdk.Thing{enabledThing1, disabledThing1, disenabledThing1}, + }, + }, + } + + for _, tc := range cases2 { + pm := sdk.PageMetadata{ + Total: 100, + Offset: 0, + Limit: 100, + Status: tc.status, + } + repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) + repoCall1 := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertThingsPage(tc.response), nil) + page, err := mfsdk.Things(pm, adminToken) + assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + size := uint64(len(page.Things)) + assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) + repoCall.Unset() + repoCall1.Unset() + } +} + +func TestIdentify(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + thing = sdk.Thing{ + ID: generateUUID(t), + Name: "clientname", + Credentials: sdk.Credentials{Identity: "clientidentity", Secret: generateUUID(t)}, + Status: mfclients.EnabledStatus.String(), + } + + cases := []struct { + desc string + secret string + response string + err errors.SDKError + }{ + { + desc: "identify thing successfully", + response: thing.ID, + secret: thing.Credentials.Secret, + err: nil, + }, + { + desc: "identify thing with an invalid token", + response: "", + secret: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), + }, + } + + for _, tc := range cases { + repoCall := cRepo.On("RetrieveBySecret", mock.Anything, mock.Anything).Return(convertThing(thing), tc.err) + id, err := mfsdk.IdentifyThing(tc.secret) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + assert.Equal(t, tc.response, id, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, id)) + if tc.err == nil { + ok := repoCall.Parent.AssertCalled(t, "RetrieveBySecret", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveBySecret was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestShareThing(t *testing.T) { + cRepo := new(mocks.ClientRepository) + gRepo := new(gmocks.GroupRepository) + uauth := cmocks.NewAuthService(users, map[string][]cmocks.SubjectSet{adminID: {uadminPolicy}}) + thingCache := mocks.NewCache() + policiesCache := pmocks.NewCache() + + pRepo := new(pmocks.PolicyRepository) + psvc := policies.NewService(uauth, pRepo, policiesCache, idProvider) + + svc := clients.NewService(uauth, psvc, cRepo, gRepo, thingCache, idProvider) + ts := newThingsServer(svc, psvc) + defer ts.Close() + + conf := sdk.Config{ + ThingsURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + cases := []struct { + desc string + thingID string + groupID string + userID string + token string + err errors.SDKError + }{ + { + desc: "share thing with valid token", + thingID: generateUUID(t), + groupID: generateUUID(t), + userID: generateUUID(t), + token: adminToken, + err: nil, + }, + { + desc: "share thing with invalid token", + thingID: generateUUID(t), + groupID: generateUUID(t), + userID: generateUUID(t), + token: invalidToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusUnauthorized), + }, + } + + for _, tc := range cases { + repoCall := pRepo.On("EvaluateGroupAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall1 := pRepo.On("EvaluateThingAccess", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + repoCall3 := pRepo.On("Retrieve", mock.Anything, mock.Anything).Return(policies.PolicyPage{}, nil) + repoCall4 := pRepo.On("Save", mock.Anything, mock.Anything).Return(policies.Policy{}, nil) + err := mfsdk.ShareThing(tc.thingID, tc.groupID, tc.userID, []string{"c_list", "c_delete"}, tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + ok := repoCall4.Parent.AssertCalled(t, "Save", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("Save was not called on %s", tc.desc)) + } + repoCall.Unset() + repoCall1.Unset() + repoCall3.Unset() + repoCall4.Unset() + } +} diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go new file mode 100644 index 0000000000..51434182db --- /dev/null +++ b/pkg/sdk/go/tokens_test.go @@ -0,0 +1,155 @@ +package sdk_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/mainflux/mainflux/internal/apiutil" + "github.com/mainflux/mainflux/pkg/errors" + sdk "github.com/mainflux/mainflux/pkg/sdk/go" + "github.com/mainflux/mainflux/users/clients" + "github.com/mainflux/mainflux/users/clients/mocks" + "github.com/mainflux/mainflux/users/jwt" + pmocks "github.com/mainflux/mainflux/users/policies/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestIssueToken(t *testing.T) { + cRepo := new(mocks.ClientRepository) + pRepo := new(pmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + svc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + ts := newClientServer(svc) + defer ts.Close() + + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + client := sdk.User{ + ID: generateUUID(t), + Credentials: sdk.Credentials{ + Identity: "valid@example.com", + Secret: "secret", + }, + Status: sdk.EnabledStatus, + } + rClient := client + rClient.Credentials.Secret, _ = phasher.Hash(client.Credentials.Secret) + + wrongClient := client + wrongClient.Credentials.Secret, _ = phasher.Hash("wrong") + + cases := []struct { + desc string + client sdk.User + dbClient sdk.User + err errors.SDKError + }{ + { + desc: "issue token for a new user", + client: client, + dbClient: rClient, + err: nil, + }, + { + desc: "issue token for an empty user", + client: sdk.User{}, + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingIdentity, http.StatusInternalServerError), + }, + { + desc: "issue token for invalid secret", + client: sdk.User{ + Credentials: sdk.Credentials{ + Identity: "invalid", + Secret: "secret", + }, + }, + dbClient: wrongClient, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), + }, + } + for _, tc := range cases { + repoCall := cRepo.On("RetrieveByIdentity", mock.Anything, mock.Anything).Return(convertClient(tc.dbClient), tc.err) + token, err := mfsdk.CreateToken(tc.client) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + assert.NotEmpty(t, token, fmt.Sprintf("%s: expected token, got empty", tc.desc)) + ok := repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveByIdentity was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} + +func TestRefreshToken(t *testing.T) { + cRepo := new(mocks.ClientRepository) + pRepo := new(pmocks.PolicyRepository) + tokenizer := jwt.NewTokenRepo([]byte(secret), accessDuration, refreshDuration) + + svc := clients.NewService(cRepo, pRepo, tokenizer, emailer, phasher, idProvider, passRegex) + ts := newClientServer(svc) + defer ts.Close() + + conf := sdk.Config{ + UsersURL: ts.URL, + } + mfsdk := sdk.NewSDK(conf) + + user := sdk.User{ + ID: generateUUID(t), + Name: "validtoken", + Credentials: sdk.Credentials{ + Identity: "validtoken", + Secret: "secret", + }, + Status: sdk.EnabledStatus, + } + rUser := user + rUser.Credentials.Secret, _ = phasher.Hash(user.Credentials.Secret) + + repoCall := cRepo.On("RetrieveByIdentity", context.Background(), user.Credentials.Identity).Return(convertClient(rUser), nil) + token, err := svc.IssueToken(context.Background(), user.Credentials.Identity, user.Credentials.Secret) + assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Create token expected nil got %s\n", err)) + ok := repoCall.Parent.AssertCalled(t, "RetrieveByIdentity", context.Background(), user.Credentials.Identity) + assert.True(t, ok, "RetrieveByIdentity was not called on creating token") + repoCall.Unset() + + cases := []struct { + desc string + token string + err errors.SDKError + }{ + { + desc: "refresh token for a valid refresh token", + token: token.RefreshToken, + err: nil, + }, + { + desc: "refresh token for a valid access token", + token: token.AccessToken, + err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized), + }, + { + desc: "refresh token for an empty token", + token: "", + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusInternalServerError), + }, + } + for _, tc := range cases { + repoCall := cRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(convertClient(user), tc.err) + _, err := mfsdk.RefreshToken(tc.token) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) + if tc.err == nil { + assert.NotEmpty(t, token, fmt.Sprintf("%s: expected token, got empty", tc.desc)) + ok := repoCall.Parent.AssertCalled(t, "RetrieveByID", mock.Anything, mock.Anything) + assert.True(t, ok, fmt.Sprintf("RetrieveByID was not called on %s", tc.desc)) + } + repoCall.Unset() + } +} diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 3ca25ff765..83b96d571d 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -50,7 +50,7 @@ func TestCreateClient(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) token := testsutil.GenerateValidToken(t, testsutil.GenerateUUID(t, idProvider), svc, cRepo, phasher) cases := []struct { @@ -81,6 +81,21 @@ func TestCreateClient(t *testing.T) { token: token, err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest), }, + { + desc: "register a user that can't be marshalled", + client: sdk.User{ + Credentials: sdk.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.User{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, { desc: "register user with invalid identity", client: sdk.User{ @@ -154,7 +169,7 @@ func TestCreateClient(t *testing.T) { } for _, tc := range cases { repoCall := cRepo.On("Save", mock.Anything, mock.Anything).Return(tc.response, tc.err) - rClient, err := clientSDK.CreateUser(tc.client, tc.token) + rClient, err := mfsdk.CreateUser(tc.client, tc.token) tc.response.ID = rClient.ID tc.response.Owner = rClient.Owner tc.response.CreatedAt = rClient.CreatedAt @@ -183,7 +198,7 @@ func TestListClients(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) for i := 10; i < 100; i++ { cl := sdk.User{ @@ -346,7 +361,7 @@ func TestListClients(t *testing.T) { repoCall := pRepo.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(errors.ErrAuthorization) repoCall1 := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(errors.ErrAuthorization) repoCall2 := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(mfclients.ClientsPage{Page: convertClientPage(pm), Clients: convertClients(tc.response)}, tc.err) - page, err := clientSDK.Users(pm, generateValidToken(t, svc, cRepo)) + page, err := mfsdk.Users(pm, generateValidToken(t, svc, cRepo)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, page.Users, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, page)) repoCall.Unset() @@ -367,7 +382,7 @@ func TestListMembers(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) var nClients = uint64(10) var aClients = []sdk.User{} @@ -483,7 +498,7 @@ func TestListMembers(t *testing.T) { for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("Members", mock.Anything, tc.groupID, mock.Anything).Return(mfclients.MembersPage{Members: convertClients(tc.response)}, tc.err) - membersPage, err := clientSDK.Members(tc.groupID, tc.page, tc.token) + membersPage, err := mfsdk.Members(tc.groupID, tc.page, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, membersPage.Members, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, membersPage.Members)) if tc.err == nil { @@ -516,7 +531,7 @@ func TestClient(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) cases := []struct { desc string @@ -559,7 +574,7 @@ func TestClient(t *testing.T) { repoCall := pRepo.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(nil) repoCall1 := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall2 := cRepo.On("RetrieveByID", mock.Anything, tc.clientID).Return(convertClient(tc.response), tc.err) - rClient, err := clientSDK.User(tc.clientID, tc.token) + rClient, err := mfsdk.User(tc.clientID, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient)) if tc.err == nil { @@ -593,7 +608,7 @@ func TestProfile(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) cases := []struct { desc string @@ -617,7 +632,7 @@ func TestProfile(t *testing.T) { for _, tc := range cases { repoCall := cRepo.On("RetrieveByID", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - rClient, err := clientSDK.UserProfile(tc.token) + rClient, err := mfsdk.UserProfile(tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, rClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, rClient)) if tc.err == nil { @@ -640,7 +655,7 @@ func TestUpdateClient(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) user = sdk.User{ ID: generateUUID(t), @@ -686,12 +701,27 @@ func TestUpdateClient(t *testing.T) { token: generateValidToken(t, svc, cRepo), err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), }, + { + desc: "update a user that can't be marshalled", + client: sdk.User{ + Credentials: sdk.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.User{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, } for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("Update", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - uClient, err := clientSDK.UpdateUser(tc.client, tc.token) + uClient, err := mfsdk.UpdateUser(tc.client, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) if tc.err == nil { @@ -717,7 +747,7 @@ func TestUpdateClientTags(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) user = sdk.User{ ID: generateUUID(t), @@ -762,12 +792,28 @@ func TestUpdateClientTags(t *testing.T) { token: generateValidToken(t, svc, cRepo), err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), }, + { + desc: "update a user that can't be marshalled", + client: sdk.User{ + ID: generateUUID(t), + Credentials: sdk.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.User{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, } for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("UpdateTags", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - uClient, err := clientSDK.UpdateUserTags(tc.client, tc.token) + uClient, err := mfsdk.UpdateUserTags(tc.client, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) if tc.err == nil { @@ -793,7 +839,7 @@ func TestUpdateClientIdentity(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) user = sdk.User{ ID: generateUUID(t), @@ -836,12 +882,28 @@ func TestUpdateClientIdentity(t *testing.T) { token: generateValidToken(t, svc, cRepo), err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), }, + { + desc: "update a user that can't be marshalled", + client: sdk.User{ + ID: generateUUID(t), + Credentials: sdk.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.User{}, + token: generateValidToken(t, svc, cRepo), + err: errors.NewSDKErrorWithStatus(fmt.Errorf("json: unsupported type: chan int"), http.StatusInternalServerError), + }, } for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("UpdateIdentity", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - uClient, err := clientSDK.UpdateUserIdentity(tc.client, tc.token) + uClient, err := mfsdk.UpdateUserIdentity(tc.client, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) if tc.err == nil { @@ -867,7 +929,7 @@ func TestUpdateClientSecret(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) user.ID = generateUUID(t) rclient := user @@ -916,7 +978,7 @@ func TestUpdateClientSecret(t *testing.T) { repoCall := cRepo.On("RetrieveByID", mock.Anything, user.ID).Return(convertClient(tc.response), tc.err) repoCall1 := cRepo.On("RetrieveByIdentity", mock.Anything, user.Credentials.Identity).Return(convertClient(tc.response), tc.err) repoCall2 := cRepo.On("UpdateSecret", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - uClient, err := clientSDK.UpdatePassword(tc.oldSecret, tc.newSecret, tc.token) + uClient, err := mfsdk.UpdatePassword(tc.oldSecret, tc.newSecret, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) if tc.err == nil { @@ -945,7 +1007,7 @@ func TestUpdateClientOwner(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) user = sdk.User{ ID: generateUUID(t), @@ -988,12 +1050,27 @@ func TestUpdateClientOwner(t *testing.T) { token: generateValidToken(t, svc, cRepo), err: errors.NewSDKErrorWithStatus(sdk.ErrFailedUpdate, http.StatusInternalServerError), }, + { + desc: "update a user that can't be marshalled", + client: sdk.User{ + Credentials: sdk.Credentials{ + Identity: "user@example.com", + Secret: "12345678", + }, + Metadata: map[string]interface{}{ + "test": make(chan int), + }, + }, + response: sdk.User{}, + token: token, + err: errors.NewSDKError(fmt.Errorf("json: unsupported type: chan int")), + }, } for _, tc := range cases { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("UpdateOwner", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - uClient, err := clientSDK.UpdateUserOwner(tc.client, tc.token) + uClient, err := mfsdk.UpdateUserOwner(tc.client, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, uClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, uClient)) if tc.err == nil { @@ -1019,7 +1096,7 @@ func TestEnableClient(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) enabledClient1 := sdk.User{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: mfclients.EnabledStatus.String()} disabledClient1 := sdk.User{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: mfclients.DisabledStatus.String()} @@ -1065,7 +1142,7 @@ func TestEnableClient(t *testing.T) { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertClient(tc.client), tc.err) repoCall2 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - eClient, err := clientSDK.EnableUser(tc.id, tc.token) + eClient, err := mfsdk.EnableUser(tc.id, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, eClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, eClient)) if tc.err == nil { @@ -1124,7 +1201,7 @@ func TestEnableClient(t *testing.T) { } repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertClientsPage(tc.response), nil) - clientsPage, err := clientSDK.Users(pm, generateValidToken(t, svc, cRepo)) + clientsPage, err := mfsdk.Users(pm, generateValidToken(t, svc, cRepo)) assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(clientsPage.Users)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) @@ -1145,7 +1222,7 @@ func TestDisableClient(t *testing.T) { conf := sdk.Config{ UsersURL: ts.URL, } - clientSDK := sdk.NewSDK(conf) + mfsdk := sdk.NewSDK(conf) enabledClient1 := sdk.User{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client1@example.com", Secret: "password"}, Status: mfclients.EnabledStatus.String()} disabledClient1 := sdk.User{ID: testsutil.GenerateUUID(t, idProvider), Credentials: sdk.Credentials{Identity: "client3@example.com", Secret: "password"}, Status: mfclients.DisabledStatus.String()} @@ -1191,7 +1268,7 @@ func TestDisableClient(t *testing.T) { repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("RetrieveByID", mock.Anything, tc.id).Return(convertClient(tc.client), tc.err) repoCall2 := cRepo.On("ChangeStatus", mock.Anything, mock.Anything).Return(convertClient(tc.response), tc.err) - dClient, err := clientSDK.DisableUser(tc.id, tc.token) + dClient, err := mfsdk.DisableUser(tc.id, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) assert.Equal(t, tc.response, dClient, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.response, dClient)) if tc.err == nil { @@ -1250,7 +1327,7 @@ func TestDisableClient(t *testing.T) { } repoCall := pRepo.On("CheckAdmin", mock.Anything, mock.Anything).Return(nil) repoCall1 := cRepo.On("RetrieveAll", mock.Anything, mock.Anything).Return(convertClientsPage(tc.response), nil) - page, err := clientSDK.Users(pm, generateValidToken(t, svc, cRepo)) + page, err := mfsdk.Users(pm, generateValidToken(t, svc, cRepo)) assert.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) size := uint64(len(page.Users)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", tc.desc, tc.size, size)) diff --git a/things/clients/service_test.go b/things/clients/service_test.go index 3146172532..a1123eb084 100644 --- a/things/clients/service_test.go +++ b/things/clients/service_test.go @@ -44,7 +44,7 @@ func newService(tokens map[string]string) (clients.Service, *mocks.ClientReposit adminPolicy := mocks.MockSubjectSet{Object: ID, Relation: clients.AdminRelationKey} auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{adminEmail: {adminPolicy}}) thingCache := mocks.NewCache() - policiesCache := pmocks.NewChannelCache() + policiesCache := pmocks.NewCache() idProvider := uuid.NewMock() cRepo := new(mocks.ClientRepository) gRepo := new(gmocks.GroupRepository) diff --git a/things/groups/service_test.go b/things/groups/service_test.go index 6cdd0202c1..59cdd84e7b 100644 --- a/things/groups/service_test.go +++ b/things/groups/service_test.go @@ -47,7 +47,7 @@ func newService(tokens map[string]string) (groups.Service, *gmocks.GroupReposito gRepo := new(gmocks.GroupRepository) pRepo := new(pmocks.PolicyRepository) - policiesCache := pmocks.NewChannelCache() + policiesCache := pmocks.NewCache() psvc := policies.NewService(auth, pRepo, policiesCache, idProvider) diff --git a/things/policies/mocks/channels.go b/things/policies/mocks/channels.go index 758b3d1247..52dbef455f 100644 --- a/things/policies/mocks/channels.go +++ b/things/policies/mocks/channels.go @@ -10,19 +10,19 @@ import ( "github.com/mainflux/mainflux/things/policies" ) -type channelCacheMock struct { +type cacheMock struct { mu sync.Mutex policies map[string]string } -// NewChannelCache returns mock cache instance. -func NewChannelCache() policies.Cache { - return &channelCacheMock{ +// NewCache returns mock cache instance. +func NewCache() policies.Cache { + return &cacheMock{ policies: make(map[string]string), } } -func (ccm *channelCacheMock) Put(_ context.Context, policy policies.Policy) error { +func (ccm *cacheMock) Put(_ context.Context, policy policies.Policy) error { ccm.mu.Lock() defer ccm.mu.Unlock() @@ -30,7 +30,7 @@ func (ccm *channelCacheMock) Put(_ context.Context, policy policies.Policy) erro return nil } -func (ccm *channelCacheMock) Get(_ context.Context, policy policies.Policy) (policies.Policy, error) { +func (ccm *cacheMock) Get(_ context.Context, policy policies.Policy) (policies.Policy, error) { ccm.mu.Lock() defer ccm.mu.Unlock() actions := ccm.policies[fmt.Sprintf("%s:%s", policy.Subject, policy.Object)] @@ -46,7 +46,7 @@ func (ccm *channelCacheMock) Get(_ context.Context, policy policies.Policy) (pol return policies.Policy{}, errors.ErrNotFound } -func (ccm *channelCacheMock) Remove(_ context.Context, policy policies.Policy) error { +func (ccm *cacheMock) Remove(_ context.Context, policy policies.Policy) error { ccm.mu.Lock() defer ccm.mu.Unlock() diff --git a/things/policies/service_test.go b/things/policies/service_test.go index 151ddf8741..c5dfb703b7 100644 --- a/things/policies/service_test.go +++ b/things/policies/service_test.go @@ -31,7 +31,7 @@ func newService(tokens map[string]string) (policies.Service, *pmocks.PolicyRepos adminPolicy := mocks.MockSubjectSet{Object: "things", Relation: clients.AdminRelationKey} auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{adminEmail: {adminPolicy}}) idProvider := uuid.NewMock() - policiesCache := pmocks.NewChannelCache() + policiesCache := pmocks.NewCache() pRepo := new(pmocks.PolicyRepository) uRepo := new(umocks.PolicyRepository) diff --git a/users/clients/mocks/authn.go b/users/clients/mocks/authn.go index 8619796993..81304dc3c1 100644 --- a/users/clients/mocks/authn.go +++ b/users/clients/mocks/authn.go @@ -56,7 +56,7 @@ func (svc authServiceMock) Issue(ctx context.Context, in *policies.IssueReq, opt func (svc authServiceMock) Authorize(ctx context.Context, req *policies.AuthorizeReq, _ ...grpc.CallOption) (r *policies.AuthorizeRes, err error) { for _, policy := range svc.authz[req.GetSub()] { for _, r := range policy.Relation { - if r == req.GetAct() && policy.Subject == req.GetSub() { + if r == req.GetAct() && policy.Subject == req.GetObj() { return &policies.AuthorizeRes{Authorized: true}, nil } } diff --git a/users/clients/service.go b/users/clients/service.go index 8bf2de739b..35fbc1a798 100644 --- a/users/clients/service.go +++ b/users/clients/service.go @@ -119,8 +119,8 @@ func (svc service) IssueToken(ctx context.Context, identity, secret string) (jwt return svc.tokens.Issue(ctx, claims) } -func (svc service) RefreshToken(ctx context.Context, accessToken string) (jwt.Token, error) { - claims, err := svc.tokens.Parse(ctx, accessToken) +func (svc service) RefreshToken(ctx context.Context, refreshToken string) (jwt.Token, error) { + claims, err := svc.tokens.Parse(ctx, refreshToken) if err != nil { return jwt.Token{}, errors.Wrap(errors.ErrAuthentication, err) }