From 539be0f0112dccc75cbf3a1d7af6724f1c9917db Mon Sep 17 00:00:00 2001 From: Steve Munene <61874077+nyagamunene@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:01:24 +0300 Subject: [PATCH] MG-2145 - Generate mocks with mockery for Lora service (#2146) Signed-off-by: nyagamunene --- .github/workflows/check-generated-files.yml | 3 + lora/adapter_test.go | 413 ++++++++++++++++++-- lora/mocks/publisher.go | 25 -- lora/mocks/routes.go | 109 ++++-- lora/routemap.go | 2 + 5 files changed, 463 insertions(+), 89 deletions(-) delete mode 100644 lora/mocks/publisher.go diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index e53e851f73..3ff3ee001b 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -65,6 +65,7 @@ jobs: - "users/hasher.go" - "mqtt/events/streams.go" - "readers/messages.go" + - "lora/routemap.go" - name: Set up protoc if: steps.changes.outputs.proto == 'true' @@ -136,6 +137,7 @@ jobs: mv ./users/mocks/hasher.go ./users/mocks/hasher.go.tmp 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 make mocks @@ -176,3 +178,4 @@ jobs: check_mock_changes ./users/mocks/hasher.go "Users Hasher ./users/mocks/hasher.go" 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" diff --git a/lora/adapter_test.go b/lora/adapter_test.go index 2046a11b58..80747c176c 100644 --- a/lora/adapter_test.go +++ b/lora/adapter_test.go @@ -12,8 +12,9 @@ import ( "github.com/absmach/magistrala/lora" "github.com/absmach/magistrala/lora/mocks" "github.com/absmach/magistrala/pkg/errors" + pubmocks "github.com/absmach/magistrala/pkg/messaging/mocks" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/mock" ) const ( @@ -26,13 +27,19 @@ const ( devEUI2 = "devEUI-2" appID2 = "appID-2" msg = `[{"bn":"msg-base-name","n":"temperature","v": 17},{"n":"humidity","v": 56}]` + invalid = "wrong" +) + +var ( + pub *pubmocks.PubSub + thingsRM, channelsRM, connsRM *mocks.RouteMapRepository ) func newService() lora.Service { - pub := mocks.NewPublisher() - thingsRM := mocks.NewRouteMap() - channelsRM := mocks.NewRouteMap() - connsRM := mocks.NewRouteMap() + pub = new(pubmocks.PubSub) + thingsRM = new(mocks.RouteMapRepository) + channelsRM = new(mocks.RouteMapRepository) + connsRM = new(mocks.RouteMapRepository) return lora.New(pub, thingsRM, channelsRM, connsRM) } @@ -40,27 +47,16 @@ func newService() lora.Service { func TestPublish(t *testing.T) { svc := newService() - err := svc.CreateChannel(context.Background(), chanID, appID) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - - err = svc.CreateThing(context.Background(), thingID, devEUI) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - - err = svc.ConnectThing(context.Background(), chanID, thingID) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - - err = svc.CreateChannel(context.Background(), chanID2, appID2) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - - err = svc.CreateThing(context.Background(), thingID2, devEUI2) - require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) - msgBase64 := base64.StdEncoding.EncodeToString([]byte(msg)) cases := []struct { - desc string - err error - msg lora.Message + desc string + err error + msg lora.Message + getThingErr error + getChannelErr error + connectionsErr error + publishErr error }{ { desc: "publish message with existing route-map and valid Data", @@ -70,6 +66,10 @@ func TestPublish(t *testing.T) { DevEUI: devEUI, Data: msgBase64, }, + getThingErr: nil, + getChannelErr: nil, + connectionsErr: nil, + publishErr: nil, }, { desc: "publish message with existing route-map and invalid Data", @@ -79,6 +79,10 @@ func TestPublish(t *testing.T) { DevEUI: devEUI, Data: "wrong", }, + getThingErr: nil, + getChannelErr: nil, + connectionsErr: nil, + publishErr: errors.New("Failed publishing"), }, { desc: "publish message with non existing appID route-map", @@ -87,6 +91,7 @@ func TestPublish(t *testing.T) { ApplicationID: "wrong", DevEUI: devEUI, }, + getChannelErr: lora.ErrNotFoundApp, }, { desc: "publish message with non existing devEUI route-map", @@ -95,6 +100,7 @@ func TestPublish(t *testing.T) { ApplicationID: appID, DevEUI: "wrong", }, + getThingErr: lora.ErrNotFoundDev, }, { desc: "publish message with non existing connection route-map", @@ -103,11 +109,372 @@ func TestPublish(t *testing.T) { ApplicationID: appID2, DevEUI: devEUI2, }, + connectionsErr: lora.ErrNotConnected, + }, + { + desc: "publish message with wrong Object", + err: errors.New("json: unsupported type: chan int"), + msg: lora.Message{ + ApplicationID: appID2, + DevEUI: devEUI2, + Object: make(chan int), + }, + }, + { + desc: "publish message with valid Object", + err: nil, + msg: lora.Message{ + ApplicationID: appID2, + DevEUI: devEUI2, + Object: map[string]interface{}{"key": "value"}, + }, }, } for _, tc := range cases { + repoCall := thingsRM.On("Get", context.Background(), tc.msg.DevEUI).Return(tc.msg.DevEUI, tc.getThingErr) + repoCall1 := channelsRM.On("Get", context.Background(), tc.msg.ApplicationID).Return(tc.msg.ApplicationID, tc.getChannelErr) + repoCall2 := connsRM.On("Get", context.Background(), mock.Anything).Return("", tc.connectionsErr) + repoCall3 := pub.On("Publish", context.Background(), tc.msg.ApplicationID, mock.Anything).Return(tc.publishErr) err := svc.Publish(context.Background(), &tc.msg) + fmt.Println(err) + fmt.Println(tc.err) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + repoCall3.Unset() + } +} + +func TestCreateChannel(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ChanID string + AppID string + }{ + { + desc: "create channel with valid data", + err: nil, + ChanID: chanID, + AppID: appID, + }, + { + desc: "create channel with empty chanID", + err: lora.ErrNotFoundApp, + ChanID: "", + AppID: appID, + }, + { + desc: "create channel with empty appID", + err: lora.ErrNotFoundApp, + ChanID: chanID, + AppID: "", + }, + } + + for _, tc := range cases { + repoCall := channelsRM.On("Save", context.Background(), tc.ChanID, tc.AppID).Return(tc.err) + err := svc.CreateChannel(context.Background(), tc.ChanID, tc.AppID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestCreateThing(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ThingID string + DevEUI string + }{ + { + desc: "create thing with valid data", + err: nil, + ThingID: thingID, + DevEUI: devEUI, + }, + { + desc: "create thing with empty thingID", + err: lora.ErrNotFoundDev, + ThingID: "", + DevEUI: devEUI, + }, + { + desc: "create thing with empty devEUI", + err: lora.ErrNotFoundDev, + ThingID: thingID, + DevEUI: "", + }, + } + + for _, tc := range cases { + repoCall := thingsRM.On("Save", context.Background(), tc.ThingID, tc.DevEUI).Return(tc.err) + err := svc.CreateThing(context.Background(), tc.ThingID, tc.DevEUI) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestConnectThing(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + channelID string + thingID string + getThingErr error + getChannelErr error + }{ + { + desc: "connect thing with valid data", + err: nil, + channelID: chanID, + thingID: thingID, + getThingErr: nil, + getChannelErr: nil, + }, + { + desc: "connect thing with non existing thing", + err: lora.ErrNotFoundDev, + channelID: chanID, + thingID: invalid, + getThingErr: lora.ErrNotFoundDev, + }, + { + desc: "connect thing with non existing channel", + err: lora.ErrNotFoundApp, + channelID: invalid, + thingID: thingID, + getChannelErr: lora.ErrNotFoundApp, + }, + } + + for _, tc := range cases { + repoCall := thingsRM.On("Get", context.Background(), tc.thingID).Return(devEUI, tc.getThingErr) + repoCall1 := channelsRM.On("Get", context.Background(), tc.channelID).Return(appID, tc.getChannelErr) + repoCall2 := connsRM.On("Save", context.Background(), mock.Anything, mock.Anything).Return(tc.err) + err := svc.ConnectThing(context.Background(), tc.channelID, tc.thingID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestDisconnectThing(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + channelID string + thingID string + getThingErr error + getChannelErr error + }{ + { + desc: "disconnect thing with valid data", + err: nil, + channelID: chanID, + thingID: thingID, + getThingErr: nil, + getChannelErr: nil, + }, + { + desc: "disconnect thing with non existing thing ID", + err: lora.ErrNotFoundDev, + channelID: chanID, + thingID: invalid, + getThingErr: lora.ErrNotFoundDev, + }, + { + desc: "disconnect thing with non existing channel", + err: lora.ErrNotFoundApp, + channelID: invalid, + thingID: thingID, + getChannelErr: lora.ErrNotFoundApp, + }, + } + + for _, tc := range cases { + repoCall := thingsRM.On("Get", context.Background(), tc.thingID).Return(devEUI, tc.getThingErr) + repoCall1 := channelsRM.On("Get", context.Background(), tc.channelID).Return(appID, tc.getChannelErr) + repoCall2 := connsRM.On("Remove", context.Background(), mock.Anything).Return(tc.err) + err := svc.DisconnectThing(context.Background(), tc.channelID, tc.thingID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + repoCall1.Unset() + repoCall2.Unset() + } +} + +func TestRemoveChannel(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ChanID string + }{ + { + desc: "remove channel with valid data", + err: nil, + ChanID: chanID, + }, + { + desc: "remove channel with non existing channel", + err: lora.ErrNotFoundApp, + ChanID: invalid, + }, + { + desc: "remove channel with empty channelID", + err: lora.ErrNotFoundApp, + ChanID: "", + }, + } + + for _, tc := range cases { + repoCall := channelsRM.On("Remove", context.Background(), tc.ChanID).Return(tc.err) + err := svc.RemoveChannel(context.Background(), tc.ChanID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestRemoveThing(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ThingID string + }{ + { + desc: "remove thing with valid data", + err: nil, + ThingID: thingID, + }, + { + desc: "remove thing with non existing thing", + err: lora.ErrNotFoundDev, + ThingID: invalid, + }, + { + desc: "remove thing with empty thingID", + err: lora.ErrNotFoundDev, + ThingID: "", + }, + } + + for _, tc := range cases { + repoCall := thingsRM.On("Remove", context.Background(), tc.ThingID).Return(tc.err) + err := svc.RemoveThing(context.Background(), tc.ThingID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestUpdateChannel(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ChanID string + AppID string + }{ + { + desc: "update channel with valid data", + err: nil, + ChanID: chanID, + AppID: appID, + }, + { + desc: "update channel with non existing channel", + err: lora.ErrNotFoundApp, + ChanID: invalid, + AppID: appID, + }, + { + desc: "update channel with empty channelID", + err: lora.ErrNotFoundApp, + ChanID: "", + AppID: appID, + }, + { + desc: "update channel with empty appID", + err: lora.ErrNotFoundApp, + ChanID: chanID, + AppID: "", + }, + { + desc: "update channel with non existing appID", + err: lora.ErrNotFoundApp, + ChanID: chanID, + AppID: invalid, + }, + } + + for _, tc := range cases { + repoCall := channelsRM.On("Save", context.Background(), tc.ChanID, tc.AppID).Return(tc.err) + err := svc.UpdateChannel(context.Background(), tc.ChanID, tc.AppID) + assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) + repoCall.Unset() + } +} + +func TestUpdateThing(t *testing.T) { + svc := newService() + + cases := []struct { + desc string + err error + ThingID string + DevEUI string + }{ + { + desc: "update thing with valid data", + err: nil, + ThingID: thingID, + DevEUI: devEUI, + }, + { + desc: "update thing with non existing thing", + err: lora.ErrNotFoundDev, + ThingID: invalid, + DevEUI: devEUI, + }, + { + desc: "update thing with empty thingID", + err: lora.ErrNotFoundDev, + ThingID: "", + DevEUI: devEUI, + }, + { + desc: "update thing with empty devEUI", + err: lora.ErrNotFoundDev, + ThingID: thingID, + DevEUI: "", + }, + { + desc: "update thing with non existing devEUI", + err: lora.ErrNotFoundDev, + ThingID: thingID, + DevEUI: invalid, + }, + } + + for _, tc := range cases { + repoCall := thingsRM.On("Save", context.Background(), tc.ThingID, tc.DevEUI).Return(tc.err) + err := svc.UpdateThing(context.Background(), tc.ThingID, tc.DevEUI) 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/lora/mocks/publisher.go b/lora/mocks/publisher.go deleted file mode 100644 index 90ad6d757e..0000000000 --- a/lora/mocks/publisher.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package mocks - -import ( - "context" - - "github.com/absmach/magistrala/pkg/messaging" -) - -type mockPublisher struct{} - -// NewPublisher returns mock message publisher. -func NewPublisher() messaging.Publisher { - return mockPublisher{} -} - -func (pub mockPublisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { - return nil -} - -func (pub mockPublisher) Close() error { - return nil -} diff --git a/lora/mocks/routes.go b/lora/mocks/routes.go index 9f059b6837..0219da3afb 100644 --- a/lora/mocks/routes.go +++ b/lora/mocks/routes.go @@ -1,67 +1,94 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + // Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 package mocks import ( - "context" - "errors" - "sync" + context "context" - "github.com/absmach/magistrala/lora" + mock "github.com/stretchr/testify/mock" ) -type routeMapMock struct { - mu sync.Mutex - routes map[string]string +// RouteMapRepository is an autogenerated mock type for the RouteMapRepository type +type RouteMapRepository struct { + mock.Mock } -// NewRouteMap returns mock route-map instance. -func NewRouteMap() lora.RouteMapRepository { - return &routeMapMock{ - routes: make(map[string]string), +// Get provides a mock function with given fields: _a0, _a1 +func (_m *RouteMapRepository) Get(_a0 context.Context, _a1 string) (string, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Get") } -} -func (trm *routeMapMock) Save(_ context.Context, mgxID, extID string) error { - trm.mu.Lock() - defer trm.mu.Unlock() + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } - trm.routes[extID] = mgxID - trm.routes[mgxID] = extID - return nil + return r0, r1 } -func (trm *routeMapMock) Get(_ context.Context, extID string) (string, error) { - trm.mu.Lock() - defer trm.mu.Unlock() +// Remove provides a mock function with given fields: _a0, _a1 +func (_m *RouteMapRepository) Remove(_a0 context.Context, _a1 string) error { + ret := _m.Called(_a0, _a1) - id, ok := trm.routes[extID] - if !ok { - return "", errors.New("route-map not found") + if len(ret) == 0 { + panic("no return value specified for Remove") } - return id, nil + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 } -func (trm *routeMapMock) Remove(_ context.Context, extID string) error { - trm.mu.Lock() - defer trm.mu.Unlock() +// Save provides a mock function with given fields: _a0, _a1, _a2 +func (_m *RouteMapRepository) Save(_a0 context.Context, _a1 string, _a2 string) error { + ret := _m.Called(_a0, _a1, _a2) - var mgxID string - for i, val := range trm.routes { - if val == extID { - mgxID = val - delete(trm.routes, i) - } + if len(ret) == 0 { + panic("no return value specified for Save") } - for i, val := range trm.routes { - if val == mgxID { - delete(trm.routes, i) - return nil - } + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) } - return nil + return r0 +} + +// NewRouteMapRepository creates a new instance of RouteMapRepository. 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 NewRouteMapRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *RouteMapRepository { + mock := &RouteMapRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/lora/routemap.go b/lora/routemap.go index 781b50db5a..c20e23433f 100644 --- a/lora/routemap.go +++ b/lora/routemap.go @@ -6,6 +6,8 @@ package lora import "context" // RouteMapRepository store route map between Lora App Server and Magistrala. +// +//go:generate mockery --name RouteMapRepository --output=./mocks --filename routes.go --quiet --note "Copyright (c) Abstract Machines" type RouteMapRepository interface { // Save stores/routes pair lora application topic & magistrala channel. Save(context.Context, string, string) error