diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index 3ff3ee001b..adafdfa2ba 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -66,6 +66,9 @@ jobs: - "mqtt/events/streams.go" - "readers/messages.go" - "lora/routemap.go" + - "consumers/notifiers/notifier.go" + - "consumers/notifiers/service.go" + - "consumers/notifiers/subscriptions.go" - name: Set up protoc if: steps.changes.outputs.proto == 'true' @@ -138,6 +141,9 @@ jobs: mv ./mqtt/mocks/events.go ./mqtt/mocks/events.go.tmp mv ./readers/mocks/messages.go ./readers/mocks/messages.go.tmp mv ./lora/mocks/routes.go ./lora/mocks/routes.go.tmp + mv ./consumers/notifiers/mocks/notifier.go ./consumers/notifiers/mocks/notifier.go.tmp + mv ./consumers/notifiers/mocks/service.go ./consumers/notifiers/mocks/service.go.tmp + mv ./consumers/notifiers/mocks/repository.go ./consumers/notifiers/mocks/repository.go.tmp make mocks @@ -179,3 +185,6 @@ jobs: check_mock_changes ./mqtt/mocks/events.go "MQTT Events Store ./mqtt/mocks/events.go" check_mock_changes ./readers/mocks/messages.go "Message Readers ./readers/mocks/messages.go" check_mock_changes ./lora/mocks/routes.go "LoRa Repository ./lora/mocks/routes.go" + check_mock_changes ./consumers/notifiers/mocks/notifier.go "Notifiers Notifier ./consumers/notifiers/mocks/notifier.go" + check_mock_changes ./consumers/notifiers/mocks/service.go "Notifiers Service ./consumers/notifiers/mocks/service.go" + check_mock_changes ./consumers/notifiers/mocks/repository.go "Notifiers Repository ./consumers/notifiers/mocks/repository.go" diff --git a/consumers/notifiers/api/endpoint_test.go b/consumers/notifiers/api/endpoint_test.go index aab51f8d83..6fc5d4dc62 100644 --- a/consumers/notifiers/api/endpoint_test.go +++ b/consumers/notifiers/api/endpoint_test.go @@ -4,21 +4,21 @@ package api_test import ( - "context" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" + "path" "strings" "testing" - "github.com/absmach/magistrala" authmocks "github.com/absmach/magistrala/auth/mocks" "github.com/absmach/magistrala/consumers/notifiers" httpapi "github.com/absmach/magistrala/consumers/notifiers/api" "github.com/absmach/magistrala/consumers/notifiers/mocks" "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/uuid" @@ -67,19 +67,11 @@ func (tr testRequest) make() (*http.Response, error) { return tr.client.Do(req) } -func newService() (notifiers.Service, *authmocks.AuthClient) { - auth := new(authmocks.AuthClient) - repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) - idp := uuid.NewMock() - notif := mocks.NewNotifier() - from := "exampleFrom" - return notifiers.New(auth, repo, idp, notif, from), auth -} - -func newServer(svc notifiers.Service) *httptest.Server { +func newServer() (*httptest.Server, *mocks.Service) { logger := mglog.NewMock() + svc := new(mocks.Service) mux := httpapi.MakeHandler(svc, logger, instanceID) - return httptest.NewServer(mux) + return httptest.NewServer(mux), svc } func toJSON(data interface{}) string { @@ -91,8 +83,7 @@ func toJSON(data interface{}) string { } func TestCreate(t *testing.T) { - svc, auth := newService() - ss := newServer(svc) + ss, svc := newServer() defer ss.Close() sub := notifiers.Subscription{ @@ -112,6 +103,7 @@ func TestCreate(t *testing.T) { auth string status int location string + err error }{ { desc: "add successfully", @@ -120,6 +112,7 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusCreated, location: fmt.Sprintf("/subscriptions/%s%012d", uuid.Prefix, 1), + err: nil, }, { desc: "add an existing subscription", @@ -128,6 +121,7 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusConflict, location: "", + err: svcerr.ErrConflict, }, { desc: "add with empty topic", @@ -136,6 +130,7 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusBadRequest, location: "", + err: svcerr.ErrMalformedEntity, }, { desc: "add with empty contact", @@ -144,6 +139,7 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusBadRequest, location: "", + err: svcerr.ErrMalformedEntity, }, { desc: "add with invalid auth token", @@ -152,6 +148,7 @@ func TestCreate(t *testing.T) { auth: authmocks.InvalidValue, status: http.StatusUnauthorized, location: "", + err: svcerr.ErrAuthentication, }, { desc: "add with empty auth token", @@ -160,6 +157,7 @@ func TestCreate(t *testing.T) { auth: "", status: http.StatusUnauthorized, location: "", + err: svcerr.ErrAuthentication, }, { desc: "add with invalid request format", @@ -168,6 +166,7 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusBadRequest, location: "", + err: svcerr.ErrMalformedEntity, }, { desc: "add without content type", @@ -176,11 +175,12 @@ func TestCreate(t *testing.T) { auth: token, status: http.StatusUnsupportedMediaType, location: "", + err: apiutil.ErrUnsupportedContentType, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + svcCall := svc.On("CreateSubscription", mock.Anything, tc.auth, sub).Return(path.Base(tc.location), tc.err) req := testRequest{ client: ss.Client(), @@ -197,26 +197,23 @@ func TestCreate(t *testing.T) { assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) assert.Equal(t, tc.location, location, fmt.Sprintf("%s: expected location %s got %s", tc.desc, tc.location, location)) - repoCall.Unset() + svcCall.Unset() } } func TestView(t *testing.T) { - svc, auth := newService() - ss := newServer(svc) + ss, svc := newServer() defer ss.Close() sub := notifiers.Subscription{ Topic: topic, Contact: contact1, + ID: testsutil.GenerateUUID(t), + OwnerID: validID, } - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), token, sub) - assert.Nil(t, err, fmt.Sprintf("got an error creating id: %s", err)) - repoCall.Unset() sr := subRes{ - ID: id, + ID: sub.ID, OwnerID: validID, Contact: sub.Contact, Topic: sub.Topic, @@ -229,13 +226,17 @@ func TestView(t *testing.T) { auth string status int res string + err error + Sub notifiers.Subscription }{ { desc: "view successfully", - id: id, + id: sub.ID, auth: token, status: http.StatusOK, res: data, + err: nil, + Sub: sub, }, { desc: "view not existing", @@ -243,25 +244,28 @@ func TestView(t *testing.T) { auth: token, status: http.StatusNotFound, res: notFoundRes, + err: svcerr.ErrNotFound, }, { desc: "view with invalid auth token", - id: id, + id: sub.ID, auth: authmocks.InvalidValue, status: http.StatusUnauthorized, res: unauthRes, + err: svcerr.ErrAuthentication, }, { desc: "view with empty auth token", - id: id, + id: sub.ID, auth: "", status: http.StatusUnauthorized, res: missingTokRes, + err: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + svcCall := svc.On("ViewSubscription", mock.Anything, tc.auth, tc.id).Return(tc.Sub, tc.err) req := testRequest{ client: ss.Client(), @@ -277,36 +281,33 @@ func TestView(t *testing.T) { assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected body %s got %s", tc.desc, tc.res, data)) - repoCall.Unset() + svcCall.Unset() } } func TestList(t *testing.T) { - svc, auth := newService() - ss := newServer(svc) + ss, svc := newServer() defer ss.Close() const numSubs = 100 var subs []subRes + var sub notifiers.Subscription for i := 0; i < numSubs; i++ { - sub := notifiers.Subscription{ + sub = notifiers.Subscription{ Topic: fmt.Sprintf("topic.subtopic.%d", i), Contact: contact1, + ID: testsutil.GenerateUUID(t), } if i%2 == 0 { sub.Contact = contact2 } - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), token, sub) sr := subRes{ - ID: id, + ID: sub.ID, OwnerID: validID, Contact: sub.Contact, Topic: sub.Topic, } - assert.Nil(t, err, fmt.Sprintf("got an error creating id: %s", err)) - repoCall.Unset() subs = append(subs, sr) } noLimit := toJSON(page{Offset: 5, Limit: 20, Total: numSubs, Subscriptions: subs[5:25]}) @@ -324,6 +325,8 @@ func TestList(t *testing.T) { auth string status int res string + err error + page notifiers.Page }{ { desc: "list default limit", @@ -333,6 +336,15 @@ func TestList(t *testing.T) { auth: token, status: http.StatusOK, res: noLimit, + err: nil, + page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 5, + Limit: 20, + }, + Total: numSubs, + Subscriptions: subscriptionsSlice(subs, 5, 25), + }, }, { desc: "list not existing", @@ -342,6 +354,7 @@ func TestList(t *testing.T) { auth: token, status: http.StatusNotFound, res: notFoundRes, + err: svcerr.ErrNotFound, }, { desc: "list one with topic", @@ -351,6 +364,15 @@ func TestList(t *testing.T) { auth: token, status: http.StatusOK, res: one, + err: nil, + page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 0, + Limit: 20, + }, + Total: 1, + Subscriptions: subscriptionsSlice(subs, 10, 11), + }, }, { desc: "list with contact", @@ -362,6 +384,15 @@ func TestList(t *testing.T) { auth: token, status: http.StatusOK, res: contactList, + err: nil, + page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 10, + Limit: 10, + }, + Total: 50, + Subscriptions: subscriptionsSlice(contact2Subs, 0, 10), + }, }, { desc: "list with invalid query", @@ -371,24 +402,26 @@ func TestList(t *testing.T) { auth: token, status: http.StatusBadRequest, res: invalidRes, + err: svcerr.ErrMalformedEntity, }, { desc: "list with invalid auth token", auth: authmocks.InvalidValue, status: http.StatusUnauthorized, res: unauthRes, + err: svcerr.ErrAuthentication, }, { desc: "list with empty auth token", auth: "", status: http.StatusUnauthorized, res: missingTokRes, + err: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) - + svcCall := svc.On("ListSubscriptions", mock.Anything, tc.auth, mock.Anything).Return(tc.page, tc.err) req := testRequest{ client: ss.Client(), method: http.MethodGet, @@ -403,23 +436,14 @@ func TestList(t *testing.T) { assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) assert.Equal(t, tc.res, data, fmt.Sprintf("%s: got unexpected body\n", tc.desc)) - repoCall.Unset() + svcCall.Unset() } } func TestRemove(t *testing.T) { - svc, auth := newService() - ss := newServer(svc) + ss, svc := newServer() defer ss.Close() - - sub := notifiers.Subscription{ - Topic: "topic", - Contact: contact1, - } - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), token, sub) - assert.Nil(t, err, fmt.Sprintf("got an error creating id: %s", err)) - repoCall.Unset() + id := testsutil.GenerateUUID(t) cases := []struct { desc string @@ -427,24 +451,28 @@ func TestRemove(t *testing.T) { auth string status int res string + err error }{ { desc: "remove successfully", id: id, auth: token, status: http.StatusNoContent, + err: nil, }, { desc: "remove not existing", id: "not existing", auth: token, status: http.StatusNotFound, + err: svcerr.ErrNotFound, }, { desc: "remove empty id", id: "", auth: token, status: http.StatusBadRequest, + err: svcerr.ErrMalformedEntity, }, { desc: "view with invalid auth token", @@ -452,6 +480,7 @@ func TestRemove(t *testing.T) { auth: authmocks.InvalidValue, status: http.StatusUnauthorized, res: unauthRes, + err: svcerr.ErrAuthentication, }, { desc: "view with empty auth token", @@ -459,11 +488,12 @@ func TestRemove(t *testing.T) { auth: "", status: http.StatusUnauthorized, res: missingTokRes, + err: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.auth}).Return(&magistrala.IdentityRes{Id: validID}, nil) + svcCall := svc.On("RemoveSubscription", mock.Anything, tc.auth, tc.id).Return(tc.err) req := testRequest{ client: ss.Client(), @@ -475,7 +505,7 @@ func TestRemove(t *testing.T) { assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err)) assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode)) - repoCall.Unset() + svcCall.Unset() } } @@ -502,3 +532,17 @@ type page struct { Total uint `json:"total,omitempty"` Subscriptions []subRes `json:"subscriptions,omitempty"` } + +func subscriptionsSlice(subs []subRes, start, end int) []notifiers.Subscription { + var res []notifiers.Subscription + for i := start; i < end; i++ { + sub := subs[i] + res = append(res, notifiers.Subscription{ + ID: sub.ID, + OwnerID: sub.OwnerID, + Contact: sub.Contact, + Topic: sub.Topic, + }) + } + return res +} diff --git a/consumers/notifiers/mocks/notifier.go b/consumers/notifiers/mocks/notifier.go index 84c199a21f..12f7fd73c3 100644 --- a/consumers/notifiers/mocks/notifier.go +++ b/consumers/notifiers/mocks/notifier.go @@ -1,29 +1,47 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + // Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 package mocks import ( - "github.com/absmach/magistrala/consumers/notifiers" - "github.com/absmach/magistrala/pkg/messaging" + messaging "github.com/absmach/magistrala/pkg/messaging" + mock "github.com/stretchr/testify/mock" ) -var _ notifiers.Notifier = (*notifier)(nil) +// Notifier is an autogenerated mock type for the Notifier type +type Notifier struct { + mock.Mock +} + +// Notify provides a mock function with given fields: from, to, msg +func (_m *Notifier) Notify(from string, to []string, msg *messaging.Message) error { + ret := _m.Called(from, to, msg) -const InvalidSender = "invalid@example.com" + if len(ret) == 0 { + panic("no return value specified for Notify") + } -type notifier struct{} + var r0 error + if rf, ok := ret.Get(0).(func(string, []string, *messaging.Message) error); ok { + r0 = rf(from, to, msg) + } else { + r0 = ret.Error(0) + } -// NewNotifier returns a new Notifier mock. -func NewNotifier() notifiers.Notifier { - return notifier{} + return r0 } -func (n notifier) Notify(from string, to []string, msg *messaging.Message) error { - for _, t := range to { - if t == InvalidSender { - return notifiers.ErrNotify - } - } - return nil +// NewNotifier creates a new instance of Notifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewNotifier(t interface { + mock.TestingT + Cleanup(func()) +}) *Notifier { + mock := &Notifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/consumers/notifiers/mocks/repository.go b/consumers/notifiers/mocks/repository.go new file mode 100644 index 0000000000..479998d743 --- /dev/null +++ b/consumers/notifiers/mocks/repository.go @@ -0,0 +1,133 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + notifiers "github.com/absmach/magistrala/consumers/notifiers" + mock "github.com/stretchr/testify/mock" +) + +// SubscriptionsRepository is an autogenerated mock type for the SubscriptionsRepository type +type SubscriptionsRepository struct { + mock.Mock +} + +// Remove provides a mock function with given fields: ctx, id +func (_m *SubscriptionsRepository) Remove(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Remove") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Retrieve provides a mock function with given fields: ctx, id +func (_m *SubscriptionsRepository) Retrieve(ctx context.Context, id string) (notifiers.Subscription, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Retrieve") + } + + var r0 notifiers.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (notifiers.Subscription, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) notifiers.Subscription); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(notifiers.Subscription) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RetrieveAll provides a mock function with given fields: ctx, pm +func (_m *SubscriptionsRepository) RetrieveAll(ctx context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) { + ret := _m.Called(ctx, pm) + + if len(ret) == 0 { + panic("no return value specified for RetrieveAll") + } + + var r0 notifiers.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, notifiers.PageMetadata) (notifiers.Page, error)); ok { + return rf(ctx, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, notifiers.PageMetadata) notifiers.Page); ok { + r0 = rf(ctx, pm) + } else { + r0 = ret.Get(0).(notifiers.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, notifiers.PageMetadata) error); ok { + r1 = rf(ctx, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Save provides a mock function with given fields: ctx, sub +func (_m *SubscriptionsRepository) Save(ctx context.Context, sub notifiers.Subscription) (string, error) { + ret := _m.Called(ctx, sub) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, notifiers.Subscription) (string, error)); ok { + return rf(ctx, sub) + } + if rf, ok := ret.Get(0).(func(context.Context, notifiers.Subscription) string); ok { + r0 = rf(ctx, sub) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, notifiers.Subscription) error); ok { + r1 = rf(ctx, sub) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSubscriptionsRepository creates a new instance of SubscriptionsRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSubscriptionsRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *SubscriptionsRepository { + mock := &SubscriptionsRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/consumers/notifiers/mocks/service.go b/consumers/notifiers/mocks/service.go new file mode 100644 index 0000000000..853902cd80 --- /dev/null +++ b/consumers/notifiers/mocks/service.go @@ -0,0 +1,151 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +// Copyright (c) Abstract Machines + +package mocks + +import ( + context "context" + + notifiers "github.com/absmach/magistrala/consumers/notifiers" + mock "github.com/stretchr/testify/mock" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// ConsumeBlocking provides a mock function with given fields: ctx, messages +func (_m *Service) ConsumeBlocking(ctx context.Context, messages interface{}) error { + ret := _m.Called(ctx, messages) + + if len(ret) == 0 { + panic("no return value specified for ConsumeBlocking") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}) error); ok { + r0 = rf(ctx, messages) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateSubscription provides a mock function with given fields: ctx, token, sub +func (_m *Service) CreateSubscription(ctx context.Context, token string, sub notifiers.Subscription) (string, error) { + ret := _m.Called(ctx, token, sub) + + if len(ret) == 0 { + panic("no return value specified for CreateSubscription") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.Subscription) (string, error)); ok { + return rf(ctx, token, sub) + } + if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.Subscription) string); ok { + r0 = rf(ctx, token, sub) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, notifiers.Subscription) error); ok { + r1 = rf(ctx, token, sub) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListSubscriptions provides a mock function with given fields: ctx, token, pm +func (_m *Service) ListSubscriptions(ctx context.Context, token string, pm notifiers.PageMetadata) (notifiers.Page, error) { + ret := _m.Called(ctx, token, pm) + + if len(ret) == 0 { + panic("no return value specified for ListSubscriptions") + } + + var r0 notifiers.Page + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.PageMetadata) (notifiers.Page, error)); ok { + return rf(ctx, token, pm) + } + if rf, ok := ret.Get(0).(func(context.Context, string, notifiers.PageMetadata) notifiers.Page); ok { + r0 = rf(ctx, token, pm) + } else { + r0 = ret.Get(0).(notifiers.Page) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, notifiers.PageMetadata) error); ok { + r1 = rf(ctx, token, pm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveSubscription provides a mock function with given fields: ctx, token, id +func (_m *Service) RemoveSubscription(ctx context.Context, token string, id string) error { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for RemoveSubscription") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ViewSubscription provides a mock function with given fields: ctx, token, id +func (_m *Service) ViewSubscription(ctx context.Context, token string, id string) (notifiers.Subscription, error) { + ret := _m.Called(ctx, token, id) + + if len(ret) == 0 { + panic("no return value specified for ViewSubscription") + } + + var r0 notifiers.Subscription + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (notifiers.Subscription, error)); ok { + return rf(ctx, token, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) notifiers.Subscription); ok { + r0 = rf(ctx, token, id) + } else { + r0 = ret.Get(0).(notifiers.Subscription) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, token, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/consumers/notifiers/mocks/subscriptions.go b/consumers/notifiers/mocks/subscriptions.go deleted file mode 100644 index 594c18f991..0000000000 --- a/consumers/notifiers/mocks/subscriptions.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mocks - -import ( - "context" - "sort" - "sync" - - "github.com/absmach/magistrala/consumers/notifiers" - repoerr "github.com/absmach/magistrala/pkg/errors/repository" -) - -var _ notifiers.SubscriptionsRepository = (*subRepoMock)(nil) - -type subRepoMock struct { - mu sync.Mutex - subs map[string]notifiers.Subscription -} - -// NewRepo returns a new Subscriptions repository mock. -func NewRepo(subs map[string]notifiers.Subscription) notifiers.SubscriptionsRepository { - return &subRepoMock{ - subs: subs, - } -} - -func (srm *subRepoMock) Save(_ context.Context, sub notifiers.Subscription) (string, error) { - srm.mu.Lock() - defer srm.mu.Unlock() - if _, ok := srm.subs[sub.ID]; ok { - return "", repoerr.ErrConflict - } - for _, s := range srm.subs { - if s.Contact == sub.Contact && s.Topic == sub.Topic { - return "", repoerr.ErrConflict - } - } - - srm.subs[sub.ID] = sub - return sub.ID, nil -} - -func (srm *subRepoMock) Retrieve(_ context.Context, id string) (notifiers.Subscription, error) { - srm.mu.Lock() - defer srm.mu.Unlock() - ret, ok := srm.subs[id] - if !ok { - return notifiers.Subscription{}, repoerr.ErrNotFound - } - return ret, nil -} - -func (srm *subRepoMock) RetrieveAll(_ context.Context, pm notifiers.PageMetadata) (notifiers.Page, error) { - srm.mu.Lock() - defer srm.mu.Unlock() - - // Sort keys - keys := make([]string, 0) - for k := range srm.subs { - keys = append(keys, k) - } - sort.Strings(keys) - - var subs []notifiers.Subscription - var total int - offset := int(pm.Offset) - for _, k := range keys { - v := srm.subs[k] - if pm.Topic == "" { - if pm.Contact == "" { - if total < offset { - total++ - continue - } - total++ - subs = appendSubs(subs, v, pm.Limit) - continue - } - if pm.Contact == v.Contact { - if total < offset { - total++ - continue - } - total++ - subs = appendSubs(subs, v, pm.Limit) - continue - } - } - if pm.Topic == v.Topic { - if pm.Contact == "" || pm.Contact == v.Contact { - if total < offset { - total++ - continue - } - total++ - subs = appendSubs(subs, v, pm.Limit) - } - } - } - - if len(subs) == 0 { - return notifiers.Page{}, repoerr.ErrNotFound - } - - ret := notifiers.Page{ - PageMetadata: pm, - Total: uint(total), - Subscriptions: subs, - } - - return ret, nil -} - -func appendSubs(subs []notifiers.Subscription, sub notifiers.Subscription, max int) []notifiers.Subscription { - if len(subs) < max || max == -1 { - subs = append(subs, sub) - } - return subs -} - -func (srm *subRepoMock) Remove(_ context.Context, id string) error { - srm.mu.Lock() - defer srm.mu.Unlock() - if _, ok := srm.subs[id]; !ok { - return repoerr.ErrNotFound - } - delete(srm.subs, id) - return nil -} diff --git a/consumers/notifiers/notifier.go b/consumers/notifiers/notifier.go index d7130b87fc..2c23bc9e58 100644 --- a/consumers/notifiers/notifier.go +++ b/consumers/notifiers/notifier.go @@ -13,6 +13,8 @@ import ( var ErrNotify = errors.New("error sending notification") // Notifier represents an API for sending notification. +// +//go:generate mockery --name Notifier --output=./mocks --filename notifier.go --quiet --note "Copyright (c) Abstract Machines" type Notifier interface { // Notify method is used to send notification for the // received message to the provided list of receivers. diff --git a/consumers/notifiers/service.go b/consumers/notifiers/service.go index e7333650d2..5a54dab943 100644 --- a/consumers/notifiers/service.go +++ b/consumers/notifiers/service.go @@ -19,6 +19,8 @@ var ErrMessage = errors.New("failed to convert to Magistrala message") var _ consumers.AsyncConsumer = (*notifierService)(nil) // Service reprents a notification service. +// +//go:generate mockery --name Service --output=./mocks --filename service.go --quiet --note "Copyright (c) Abstract Machines" type Service interface { // CreateSubscription persists a subscription. // Successful operation is indicated by non-nil error response. diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index c4fb5212e4..28facdcf0d 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -20,7 +20,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) const ( @@ -30,125 +29,136 @@ const ( validID = "d4ebb847-5d0e-4e46-bdd9-b6aceaaa3a22" ) -func newService() (notifiers.Service, *authmocks.AuthClient) { - repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) +func newService() (notifiers.Service, *authmocks.AuthClient, *mocks.SubscriptionsRepository) { + repo := new(mocks.SubscriptionsRepository) auth := new(authmocks.AuthClient) - notifier := mocks.NewNotifier() + notifier := new(mocks.Notifier) idp := uuid.NewMock() from := "exampleFrom" - return notifiers.New(auth, repo, idp, notifier, from), auth + return notifiers.New(auth, repo, idp, notifier, from), auth, repo } func TestCreateSubscription(t *testing.T) { - svc, auth := newService() + svc, auth, repo := newService() cases := []struct { - desc string - token string - sub notifiers.Subscription - id string - err error + desc string + token string + sub notifiers.Subscription + id string + err error + identifyErr error + userID string }{ { - desc: "test success", - token: exampleUser1, - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: uuid.Prefix + fmt.Sprintf("%012d", 1), - err: nil, + desc: "test success", + token: exampleUser1, + sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, + id: uuid.Prefix + fmt.Sprintf("%012d", 1), + err: nil, + identifyErr: nil, + userID: validID, }, { - desc: "test already existing", - token: exampleUser1, - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: "", - err: repoerr.ErrConflict, + desc: "test already existing", + token: exampleUser1, + sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, + id: "", + err: repoerr.ErrConflict, + identifyErr: nil, + userID: validID, }, { - desc: "test with empty token", - token: "", - sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, - id: "", - err: svcerr.ErrAuthentication, + desc: "test with empty token", + token: "", + sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, + id: "", + err: svcerr.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: testsutil.GenerateUUID(t)}, nil) + repoCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("Save", context.Background(), mock.Anything).Return(tc.id, tc.err) id, err := svc.CreateSubscription(context.Background(), tc.token, tc.sub) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.id, id)) repoCall.Unset() + repoCall1.Unset() } } func TestViewSubscription(t *testing.T) { - svc, auth := newService() - sub := notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"} - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), exampleUser1, sub) - require.Nil(t, err, "Saving a Subscription must succeed") - repoCall.Unset() - sub.ID = id - sub.OwnerID = validID + svc, auth, repo := newService() + sub := notifiers.Subscription{ + Contact: exampleUser1, + Topic: "valid.topic", + ID: testsutil.GenerateUUID(t), + OwnerID: validID, + } cases := []struct { - desc string - token string - id string - sub notifiers.Subscription - err error + desc string + token string + id string + sub notifiers.Subscription + err error + identifyErr error + userID string }{ { - desc: "test success", - token: exampleUser1, - id: id, - sub: sub, - err: nil, + desc: "test success", + token: exampleUser1, + id: validID, + sub: sub, + err: nil, + identifyErr: nil, + userID: validID, }, { - desc: "test not existing", - token: exampleUser1, - id: "not_exist", - sub: notifiers.Subscription{}, - err: svcerr.ErrNotFound, + desc: "test not existing", + token: exampleUser1, + id: "not_exist", + sub: notifiers.Subscription{}, + err: svcerr.ErrNotFound, + identifyErr: nil, + userID: validID, }, { - desc: "test with empty token", - token: "", - id: id, - sub: notifiers.Subscription{}, - err: svcerr.ErrAuthentication, + desc: "test with empty token", + token: "", + id: validID, + sub: notifiers.Subscription{}, + err: svcerr.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("Retrieve", context.Background(), tc.id).Return(tc.sub, tc.err) sub, err := svc.ViewSubscription(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.sub, sub, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.sub, sub)) repoCall.Unset() + repoCall1.Unset() } } func TestListSubscriptions(t *testing.T) { - svc, auth := newService() + svc, auth, repo := newService() sub := notifiers.Subscription{Contact: exampleUser1, OwnerID: exampleUser1} topic := "topic.subtopic" var subs []notifiers.Subscription for i := 0; i < total; i++ { tmp := sub - token := exampleUser1 if i%2 == 0 { tmp.Contact = exampleUser2 tmp.OwnerID = exampleUser2 - token = exampleUser2 } tmp.Topic = fmt.Sprintf("%s.%d", topic, i) - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: token}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), token, tmp) - require.Nil(t, err, "Saving a Subscription must succeed") - repoCall.Unset() - tmp.ID = id + tmp.ID = testsutil.GenerateUUID(t) tmp.OwnerID = validID subs = append(subs, tmp) } @@ -159,11 +169,13 @@ func TestListSubscriptions(t *testing.T) { } cases := []struct { - desc string - token string - pageMeta notifiers.PageMetadata - page notifiers.Page - err error + desc string + token string + pageMeta notifiers.PageMetadata + page notifiers.Page + err error + identifyErr error + userID string }{ { desc: "test success", @@ -181,6 +193,8 @@ func TestListSubscriptions(t *testing.T) { Subscriptions: subs[:3], Total: total, }, + identifyErr: nil, + userID: validID, }, { desc: "test not existing", @@ -189,8 +203,10 @@ func TestListSubscriptions(t *testing.T) { Limit: 10, Contact: "empty@example.com", }, - page: notifiers.Page{}, - err: svcerr.ErrNotFound, + page: notifiers.Page{}, + err: svcerr.ErrNotFound, + identifyErr: nil, + userID: validID, }, { desc: "test with empty token", @@ -200,8 +216,9 @@ func TestListSubscriptions(t *testing.T) { Limit: 12, Topic: "topic.subtopic.13", }, - page: notifiers.Page{}, - err: svcerr.ErrAuthentication, + page: notifiers.Page{}, + err: svcerr.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, }, { desc: "test with topic", @@ -218,7 +235,9 @@ func TestListSubscriptions(t *testing.T) { Subscriptions: subs[4:5], Total: 1, }, - err: nil, + err: nil, + identifyErr: nil, + userID: validID, }, { desc: "test with contact and offset", @@ -237,65 +256,77 @@ func TestListSubscriptions(t *testing.T) { Subscriptions: offsetSubs, Total: uint(total / 2), }, - err: nil, + err: nil, + identifyErr: nil, + userID: validID, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("RetrieveAll", context.Background(), tc.pageMeta).Return(tc.page, tc.err) page, err := svc.ListSubscriptions(context.Background(), tc.token, tc.pageMeta) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.page, page, fmt.Sprintf("%s: got unexpected page\n", tc.desc)) repoCall.Unset() + repoCall1.Unset() } } func TestRemoveSubscription(t *testing.T) { - svc, auth := newService() - sub := notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"} - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := svc.CreateSubscription(context.Background(), exampleUser1, sub) - require.Nil(t, err, "Saving a Subscription must succeed") - repoCall.Unset() - sub.ID = id - sub.OwnerID = validID + svc, auth, repo := newService() + sub := notifiers.Subscription{ + Contact: exampleUser1, + Topic: "valid.topic", + ID: testsutil.GenerateUUID(t), + OwnerID: validID, + } cases := []struct { - desc string - token string - id string - err error + desc string + token string + id string + err error + identifyErr error + userID string }{ { - desc: "test success", - token: exampleUser1, - id: id, - err: nil, + desc: "test success", + token: exampleUser1, + id: sub.ID, + err: nil, + identifyErr: nil, + userID: validID, }, { - desc: "test not existing", - token: exampleUser1, - id: "not_exist", - err: svcerr.ErrNotFound, + desc: "test not existing", + token: exampleUser1, + id: "not_exist", + err: svcerr.ErrNotFound, + identifyErr: nil, + userID: validID, }, { - desc: "test with empty token", - token: "", - id: id, - err: svcerr.ErrAuthentication, + desc: "test with empty token", + token: "", + id: sub.ID, + err: svcerr.ErrAuthentication, + identifyErr: svcerr.ErrAuthentication, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", context.Background(), &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("Remove", context.Background(), tc.id).Return(tc.err) err := svc.RemoveSubscription(context.Background(), tc.token, tc.id) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) repoCall.Unset() + repoCall1.Unset() } } func TestConsume(t *testing.T) { - svc, auth := newService() + svc, _, repo := newService() sub := notifiers.Subscription{ Contact: exampleUser1, OwnerID: validID, @@ -307,18 +338,9 @@ func TestConsume(t *testing.T) { if i%2 == 0 { tmp.Topic = fmt.Sprintf("%s-2", sub.Topic) } - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - _, err := svc.CreateSubscription(context.Background(), exampleUser1, tmp) - require.Nil(t, err, "Saving a Subscription must succeed") - repoCall.Unset() } - - sub.Contact = mocks.InvalidSender + sub.Contact = "invalid@example.com" sub.Topic = fmt.Sprintf("%s-2", sub.Topic) - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - _, err := svc.CreateSubscription(context.Background(), exampleUser1, sub) - require.Nil(t, err, "Saving a Subscription must succeed") - repoCall.Unset() msg := messaging.Message{ Channel: "topic", @@ -347,7 +369,9 @@ func TestConsume(t *testing.T) { } for _, tc := range cases { + repoCall := repo.On("RetrieveAll", context.TODO(), mock.Anything).Return(notifiers.Page{}, tc.err) err := svc.ConsumeBlocking(context.TODO(), tc.msg) assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() } } diff --git a/consumers/notifiers/subscriptions.go b/consumers/notifiers/subscriptions.go index 0d2166da56..dcaf4eb60f 100644 --- a/consumers/notifiers/subscriptions.go +++ b/consumers/notifiers/subscriptions.go @@ -30,6 +30,8 @@ type PageMetadata struct { } // SubscriptionsRepository specifies a Subscription persistence API. +// +//go:generate mockery --name SubscriptionsRepository --output=./mocks --filename repository.go --quiet --note "Copyright (c) Abstract Machines" type SubscriptionsRepository interface { // Save persists a subscription. Successful operation is indicated by non-nil // error response. diff --git a/pkg/sdk/go/consumers_test.go b/pkg/sdk/go/consumers_test.go index 70e1de4825..dbb9f2b503 100644 --- a/pkg/sdk/go/consumers_test.go +++ b/pkg/sdk/go/consumers_test.go @@ -15,6 +15,7 @@ import ( httpapi "github.com/absmach/magistrala/consumers/notifiers/api" "github.com/absmach/magistrala/consumers/notifiers/mocks" "github.com/absmach/magistrala/internal/apiutil" + "github.com/absmach/magistrala/internal/testsutil" mglog "github.com/absmach/magistrala/logger" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -22,7 +23,6 @@ import ( "github.com/absmach/magistrala/pkg/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) var ( @@ -32,12 +32,13 @@ var ( } emptySubscription = sdk.Subscription{} exampleUser1 = "email1@example.com" + ID = testsutil.GenerateUUID(&testing.T{}) ) -func setupSubscriptions() (*httptest.Server, *authmocks.AuthClient) { - repo := mocks.NewRepo(make(map[string]notifiers.Subscription)) +func setupSubscriptions() (*httptest.Server, *authmocks.AuthClient, *mocks.SubscriptionsRepository) { + repo := new(mocks.SubscriptionsRepository) auth := new(authmocks.AuthClient) - notifier := mocks.NewNotifier() + notifier := new(mocks.Notifier) idp := uuid.NewMock() from := "exampleFrom" @@ -45,11 +46,11 @@ func setupSubscriptions() (*httptest.Server, *authmocks.AuthClient) { logger := mglog.NewMock() mux := httpapi.MakeHandler(svc, logger, instanceID) - return httptest.NewServer(mux), auth + return httptest.NewServer(mux), auth, repo } func TestCreateSubscription(t *testing.T) { - ts, auth := setupSubscriptions() + ts, auth, repo := setupSubscriptions() defer ts.Close() sdkConf := sdk.Config{ @@ -66,6 +67,9 @@ func TestCreateSubscription(t *testing.T) { token string err errors.SDKError empty bool + id string + identifyErr error + userID string }{ { desc: "create new subscription", @@ -73,6 +77,9 @@ func TestCreateSubscription(t *testing.T) { token: exampleUser1, err: nil, empty: false, + id: ID, + identifyErr: nil, + userID: validID, }, { desc: "create new subscription with empty token", @@ -80,6 +87,8 @@ func TestCreateSubscription(t *testing.T) { token: "", err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), empty: true, + id: "", + identifyErr: svcerr.ErrAuthorization, }, { desc: "create new subscription with invalid token", @@ -87,6 +96,8 @@ func TestCreateSubscription(t *testing.T) { token: authmocks.InvalidValue, err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized), empty: true, + id: "", + identifyErr: svcerr.ErrAuthorization, }, { desc: "create new empty subscription", @@ -94,20 +105,25 @@ func TestCreateSubscription(t *testing.T) { token: token, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTopic), http.StatusBadRequest), empty: true, + id: "", + identifyErr: nil, + userID: validID, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("Save", mock.Anything, mock.Anything).Return(tc.id, tc.err) loc, err := mgsdk.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)) repoCall.Unset() + repoCall1.Unset() } } func TestViewSubscription(t *testing.T) { - ts, auth := setupSubscriptions() + ts, auth, repo := setupSubscriptions() defer ts.Close() sdkConf := sdk.Config{ UsersURL: ts.URL, @@ -116,54 +132,59 @@ func TestViewSubscription(t *testing.T) { } mgsdk := sdk.NewSDK(sdkConf) - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := mgsdk.CreateSubscription("topic", "contact", exampleUser1) - require.Nil(t, err, fmt.Sprintf("unexpected error during creating subscription: %s", err)) - repoCall.Unset() cases := []struct { - desc string - subID string - token string - err errors.SDKError - response sdk.Subscription + desc string + subID string + token string + err errors.SDKError + response sdk.Subscription + identifyErr error + userID string }{ { - desc: "get existing subscription", - subID: id, - token: exampleUser1, - err: nil, - response: sub1, + desc: "get existing subscription", + subID: ID, + token: exampleUser1, + err: nil, + response: sub1, + identifyErr: nil, + userID: validID, }, { - desc: "get non-existent subscription", - subID: "43", - token: exampleUser1, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - response: sdk.Subscription{}, + desc: "get non-existent subscription", + subID: "43", + token: exampleUser1, + err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), + response: sdk.Subscription{}, + identifyErr: nil, + userID: validID, }, { - desc: "get subscription with invalid token", - subID: id, - token: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - response: sdk.Subscription{}, + desc: "get subscription with invalid token", + subID: ID, + token: "", + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + response: sdk.Subscription{}, + identifyErr: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("Retrieve", mock.Anything, mock.Anything).Return(notifiers.Subscription{Contact: sub1.Contact, Topic: sub1.Topic}, tc.err) respSub, err := mgsdk.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 tc.response.OwnerID = respSub.OwnerID assert.Equal(t, tc.response, respSub, fmt.Sprintf("%s: expected response %s, got %s", tc.desc, tc.response, respSub)) repoCall.Unset() + repoCall1.Unset() } } func TestListSubscription(t *testing.T) { - ts, auth := setupSubscriptions() + ts, auth, repo := setupSubscriptions() defer ts.Close() sdkConf := sdk.Config{ UsersURL: ts.URL, @@ -174,24 +195,16 @@ func TestListSubscription(t *testing.T) { mgsdk := sdk.NewSDK(sdkConf) nSubs := 10 subs := make([]sdk.Subscription, nSubs) - for i := 0; i < nSubs; i++ { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := mgsdk.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)) - repoCall.Unset() - repoCall = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - sub, err := mgsdk.ViewSubscription(id, exampleUser1) - require.Nil(t, err, fmt.Sprintf("unexpected error during getting subscription: %s", err)) - repoCall.Unset() - subs[i] = sub - } cases := []struct { - desc string - page sdk.PageMetadata - token string - err errors.SDKError - response []sdk.Subscription + desc string + page sdk.PageMetadata + token string + err errors.SDKError + response []sdk.Subscription + Page notifiers.Page + identifyErr error + userID string }{ { desc: "list all subscription", @@ -199,6 +212,15 @@ func TestListSubscription(t *testing.T) { page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs)}, err: nil, response: subs, + Page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 0, + Limit: nSubs, + }, + Subscriptions: subSlice(subs, 0, 10), + }, + identifyErr: nil, + userID: validID, }, { desc: "list subscription with specific topic", @@ -206,6 +228,16 @@ func TestListSubscription(t *testing.T) { page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs), Topic: "topic_1"}, err: nil, response: []sdk.Subscription{subs[1]}, + Page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 0, + Limit: nSubs, + Topic: "topic_1", + }, + Subscriptions: subSlice(subs, 0, 1), + }, + identifyErr: nil, + userID: validID, }, { desc: "list subscription with specific contact", @@ -213,20 +245,32 @@ func TestListSubscription(t *testing.T) { page: sdk.PageMetadata{Offset: 0, Limit: uint64(nSubs), Contact: "contact_1"}, err: nil, response: []sdk.Subscription{subs[1]}, + Page: notifiers.Page{ + PageMetadata: notifiers.PageMetadata{ + Offset: 0, + Limit: nSubs, + Contact: "contact_1", + }, + Subscriptions: subSlice(subs, 0, 1), + }, + identifyErr: nil, + userID: validID, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.identifyErr) + repoCall1 := repo.On("RetrieveAll", mock.Anything, mock.Anything).Return(tc.Page, tc.err) subs, err := mgsdk.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)) repoCall.Unset() + repoCall1.Unset() } } func TestDeleteSubscription(t *testing.T) { - ts, auth := setupSubscriptions() + ts, auth, repo := setupSubscriptions() defer ts.Close() sdkConf := sdk.Config{ UsersURL: ts.URL, @@ -235,45 +279,64 @@ func TestDeleteSubscription(t *testing.T) { } mgsdk := sdk.NewSDK(sdkConf) - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: exampleUser1}).Return(&magistrala.IdentityRes{Id: validID}, nil) - id, err := mgsdk.CreateSubscription("topic", "contact", exampleUser1) - require.Nil(t, err, fmt.Sprintf("unexpected error during creating subscription: %s", err)) - repoCall.Unset() cases := []struct { - desc string - subID string - token string - err errors.SDKError - response sdk.Subscription + desc string + subID string + token string + err errors.SDKError + response sdk.Subscription + identifyErr error + userID string }{ { - desc: "delete existing subscription", - subID: id, - token: exampleUser1, - err: nil, - response: sub1, + desc: "delete existing subscription", + subID: ID, + token: exampleUser1, + err: nil, + response: sub1, + identifyErr: nil, + userID: validID, }, { - desc: "delete non-existent subscription", - subID: "43", - token: exampleUser1, - err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), - response: sdk.Subscription{}, + desc: "delete non-existent subscription", + subID: "43", + token: exampleUser1, + err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), + response: sdk.Subscription{}, + identifyErr: nil, + userID: validID, }, { - desc: "delete subscription with invalid token", - subID: id, - token: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), - response: sdk.Subscription{}, + desc: "delete subscription with invalid token", + subID: ID, + token: "", + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + response: sdk.Subscription{}, + identifyErr: svcerr.ErrAuthorization, }, } for _, tc := range cases { - repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID}, nil) + repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: tc.userID}, tc.err) + repoCall1 := repo.On("Remove", mock.Anything, mock.Anything).Return(tc.err) err := mgsdk.DeleteSubscription(tc.subID, tc.token) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err)) repoCall.Unset() + repoCall1.Unset() + } +} + +func subSlice(subs []sdk.Subscription, start, end int) []notifiers.Subscription { + var res []notifiers.Subscription + for i := start; i < end; i++ { + sub := subs[i] + res = append(res, notifiers.Subscription{ + ID: sub.ID, + OwnerID: sub.OwnerID, + Contact: sub.Contact, + Topic: sub.Topic, + }) } + return res }