1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00
Mainflux.mainflux/bootstrap/service_test.go
b1ackd0t 5c270abe29
NOISSUE - Uncomment Code (#1926)
* Uncomment all code

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* feat(linters): add godox and dupword linters

This commit adds two new linters, godox and dupword, to the linter configuration file (.golangci.yml). The godox linter checks for occurrences of TODO and FIXME comments in the codebase, helping to ensure that these comments are not forgotten or left unresolved. The dupword linter detects duplicate words in comments and strings, which can be a sign of typos or errors. These new linters will enhance the code quality and maintainability of the project.

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

* uncomment tests in /pkg/sdk/go/tokens_test.go

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>

---------

Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
2023-10-18 16:45:08 +02:00

798 lines
21 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package bootstrap_test
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http/httptest"
"sort"
"testing"
"github.com/go-chi/chi/v5"
"github.com/mainflux/mainflux"
authmocks "github.com/mainflux/mainflux/auth/mocks"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
"github.com/mainflux/mainflux/internal/groups"
chmocks "github.com/mainflux/mainflux/internal/groups/mocks"
mflog "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
mfgroups "github.com/mainflux/mainflux/pkg/groups"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/mainflux/mainflux/things"
thapi "github.com/mainflux/mainflux/things/api/http"
thmocks "github.com/mainflux/mainflux/things/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
validToken = "validToken"
invalidToken = "invalidToken"
email = "test@example.com"
unknown = "unknown"
channelsNum = 3
instanceID = "5de9b29a-feb9-11ed-be56-0242ac120002"
)
var (
encKey = []byte("1234567891011121")
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
ExternalID: "external_id",
ExternalKey: "external_key",
Channels: []bootstrap.Channel{channel},
Content: "config",
}
)
func newService(url string, auth mainflux.AuthServiceClient) bootstrap.Service {
things := mocks.NewConfigsRepository()
config := mfsdk.Config{
ThingsURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(auth, things, sdk, encKey)
}
func newThingsService() (things.Service, mfgroups.Service, mainflux.AuthServiceClient) {
auth := new(authmocks.Service)
thingCache := thmocks.NewCache()
idProvider := uuid.NewMock()
cRepo := new(thmocks.Repository)
gRepo := new(chmocks.Repository)
return things.NewService(auth, cRepo, gRepo, thingCache, idProvider), groups.NewService(gRepo, idProvider, auth), auth
}
func newThingsServer(tsvc things.Service, gsvc mfgroups.Service) *httptest.Server {
logger := mflog.NewMock()
mux := chi.NewRouter()
thapi.MakeHandler(tsvc, gsvc, mux, logger, instanceID)
return httptest.NewServer(mux)
}
func enc(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
func TestAdd(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
neID := config
neID.ThingID = "non-existent"
wrongChannels := config
ch := channel
ch.ID = "invalid"
wrongChannels.Channels = append(wrongChannels.Channels, ch)
cases := []struct {
desc string
config bootstrap.Config
token string
err error
}{
{
desc: "add a new config",
config: config,
token: validToken,
err: nil,
},
{
desc: "add a config with an invalid ID",
config: neID,
token: validToken,
err: errors.ErrNotFound,
},
{
desc: "add a config with wrong credentials",
config: config,
token: invalidToken,
err: errors.ErrAuthentication,
},
{
desc: "add a config with invalid list of channels",
config: wrongChannels,
token: validToken,
err: errors.ErrMalformedEntity,
},
}
for _, tc := range cases {
_, err := svc.Add(context.Background(), tc.token, tc.config)
switch err {
case nil:
assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error : %s", tc.desc, err))
default:
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
}
func TestView(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "view an existing config",
id: saved.ThingID,
token: validToken,
err: nil,
},
{
desc: "view a non-existing config",
id: unknown,
token: validToken,
err: errors.ErrNotFound,
},
{
desc: "view a config with wrong credentials",
id: config.ThingID,
token: invalidToken,
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
_, err := svc.View(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdate(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
c := config
ch := channel
ch.ID = "2"
c.Channels = append(c.Channels, ch)
saved, err := svc.Add(context.Background(), validToken, c)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
modifiedCreated := saved
modifiedCreated.Content = "new-config"
modifiedCreated.Name = "new name"
nonExisting := config
nonExisting.ThingID = unknown
cases := []struct {
desc string
config bootstrap.Config
token string
err error
}{
{
desc: "update a config with state Created",
config: modifiedCreated,
token: validToken,
err: nil,
},
{
desc: "update a non-existing config",
config: nonExisting,
token: validToken,
err: errors.ErrNotFound,
},
{
desc: "update a config with wrong credentials",
config: saved,
token: invalidToken,
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
err := svc.Update(context.Background(), tc.token, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCert(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
c := config
ch := channel
ch.ID = "2"
c.Channels = append(c.Channels, ch)
saved, err := svc.Add(context.Background(), validToken, c)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
token string
thingKey string
clientCert string
clientKey string
caCert string
expectedConfig bootstrap.Config
err error
}{
{
desc: "update certs for the valid config",
thingKey: saved.ThingKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: validToken,
expectedConfig: bootstrap.Config{
Name: saved.Name,
ThingKey: saved.ThingKey,
Channels: saved.Channels,
ExternalID: saved.ExternalID,
ExternalKey: saved.ExternalKey,
Content: saved.Content,
State: saved.State,
Owner: saved.Owner,
ThingID: saved.ThingID,
ClientCert: "newCert",
CACert: "newCert",
ClientKey: "newKey",
},
err: nil,
},
{
desc: "update cert for a non-existing config",
thingKey: "empty",
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: validToken,
expectedConfig: bootstrap.Config{},
err: errors.ErrNotFound,
},
{
desc: "update config cert with wrong credentials",
thingKey: saved.ThingKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: invalidToken,
expectedConfig: bootstrap.Config{},
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
cfg, err := svc.UpdateCert(context.Background(), tc.token, tc.thingKey, tc.clientCert, tc.clientKey, tc.caCert)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
sort.Slice(cfg.Channels, func(i, j int) bool {
return cfg.Channels[i].ID < cfg.Channels[j].ID
})
sort.Slice(tc.expectedConfig.Channels, func(i, j int) bool {
return tc.expectedConfig.Channels[i].ID < tc.expectedConfig.Channels[j].ID
})
assert.Equal(t, tc.expectedConfig, cfg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.expectedConfig, cfg))
}
}
func TestUpdateConnections(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
c := config
ch := channel
ch.ID = "2"
c.Channels = append(c.Channels, ch)
created, err := svc.Add(context.Background(), validToken, c)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
externalID, err := uuid.New().ID()
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = externalID
active, err := svc.Add(context.Background(), validToken, c)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = svc.ChangeState(context.Background(), validToken, active.ThingID, bootstrap.Active)
assert.Nil(t, err, fmt.Sprintf("Changing state expected to succeed: %s.\n", err))
nonExisting := config
nonExisting.ThingID = unknown
cases := []struct {
desc string
token string
id string
connections []string
err error
}{
{
desc: "update connections for config with state Inactive",
token: validToken,
id: created.ThingID,
connections: []string{"2"},
err: nil,
},
{
desc: "update connections for config with state Active",
token: validToken,
id: active.ThingID,
connections: []string{"3"},
err: nil,
},
{
desc: "update connections for non-existing config",
token: validToken,
id: "",
connections: []string{"3"},
err: errors.ErrNotFound,
},
{
desc: "update connections with invalid channels",
token: validToken,
id: created.ThingID,
connections: []string{"wrong"},
err: errors.ErrMalformedEntity,
},
{
desc: "update connections a config with wrong credentials",
token: invalidToken,
id: created.ThingKey,
connections: []string{"2", "3"},
err: errors.ErrAuthentication,
},
}
for _, tc := range cases {
err := svc.UpdateConnections(context.Background(), tc.token, tc.id, tc.connections)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestList(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
numThings := 101
var saved []bootstrap.Config
for i := 0; i < numThings; i++ {
c := config
id, err := uuid.New().ID()
assert.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = id
c.ExternalKey = id
c.Name = fmt.Sprintf("%s-%d", config.Name, i)
s, err := svc.Add(context.Background(), validToken, c)
saved = append(saved, s)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
}
// Set one Thing to the different state
err := svc.ChangeState(context.Background(), validToken, "42", bootstrap.Active)
assert.Nil(t, err, fmt.Sprintf("Changing config state expected to succeed: %s.\n", err))
saved[41].State = bootstrap.Active
cases := []struct {
desc string
config bootstrap.ConfigsPage
filter bootstrap.Filter
offset uint64
limit uint64
token string
err error
}{
{
desc: "list configs",
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 0,
Limit: 10,
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
token: validToken,
offset: 0,
limit: 10,
err: nil,
},
{
desc: "list configs with specified name",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 0,
Limit: 100,
Configs: saved[95:96],
},
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}},
token: validToken,
offset: 0,
limit: 100,
err: nil,
},
{
desc: "list configs with invalid token",
config: bootstrap.ConfigsPage{},
filter: bootstrap.Filter{},
token: invalidToken,
offset: 0,
limit: 10,
err: errors.ErrAuthentication,
},
{
desc: "list last page",
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 95,
Limit: 10,
Configs: saved[95:],
},
filter: bootstrap.Filter{},
token: validToken,
offset: 95,
limit: 10,
err: nil,
},
{
desc: "list configs with Active state",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 35,
Limit: 20,
Configs: []bootstrap.Config{saved[41]},
},
filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}},
token: validToken,
offset: 35,
limit: 20,
err: nil,
},
}
for _, tc := range cases {
result, err := svc.List(context.Background(), tc.token, tc.filter, tc.offset, tc.limit)
assert.ElementsMatch(t, tc.config.Configs, result.Configs, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Configs, result.Configs))
assert.Equal(t, tc.config.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Total, result.Total))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemove(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "view a config with wrong credentials",
id: saved.ThingID,
token: invalidToken,
err: errors.ErrAuthentication,
},
{
desc: "remove an existing config",
id: saved.ThingID,
token: validToken,
err: nil,
},
{
desc: "remove removed config",
id: saved.ThingID,
token: validToken,
err: nil,
},
{
desc: "remove non-existing config",
id: unknown,
token: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.Remove(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestBootstrap(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
e, err := enc([]byte(saved.ExternalKey))
assert.Nil(t, err, fmt.Sprintf("Encrypting external key expected to succeed: %s.\n", err))
cases := []struct {
desc string
config bootstrap.Config
externalKey string
externalID string
err error
encrypted bool
}{
{
desc: "bootstrap using invalid external id",
config: bootstrap.Config{},
externalID: "invalid",
externalKey: saved.ExternalKey,
err: errors.ErrNotFound,
encrypted: false,
},
{
desc: "bootstrap using invalid external key",
config: bootstrap.Config{},
externalID: saved.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrExternalKey,
encrypted: false,
},
{
desc: "bootstrap an existing config",
config: saved,
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
encrypted: false,
},
{
desc: "bootstrap encrypted",
config: saved,
externalID: saved.ExternalID,
externalKey: hex.EncodeToString(e),
err: nil,
encrypted: true,
},
}
for _, tc := range cases {
config, err := svc.Bootstrap(context.Background(), tc.externalKey, tc.externalID, tc.encrypted)
assert.Equal(t, tc.config, config, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.config, config))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestChangeState(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
state bootstrap.State
id string
token string
err error
}{
{
desc: "change state with wrong credentials",
state: bootstrap.Active,
id: saved.ThingID,
token: invalidToken,
err: errors.ErrAuthentication,
},
{
desc: "change state of non-existing config",
state: bootstrap.Active,
id: unknown,
token: validToken,
err: errors.ErrNotFound,
},
{
desc: "change state to Active",
state: bootstrap.Active,
id: saved.ThingID,
token: validToken,
err: nil,
},
{
desc: "change state to current state",
state: bootstrap.Active,
id: saved.ThingID,
token: validToken,
err: nil,
},
{
desc: "change state to Inactive",
state: bootstrap.Inactive,
id: saved.ThingID,
token: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.ChangeState(context.Background(), tc.token, tc.id, tc.state)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateChannelHandler(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
_, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
ch := bootstrap.Channel{
ID: channel.ID,
Name: "new name",
Metadata: map[string]interface{}{"meta": "new"},
}
cases := []struct {
desc string
channel bootstrap.Channel
err error
}{
{
desc: "update an existing channel",
channel: ch,
err: nil,
},
{
desc: "update a non-existing channel",
channel: bootstrap.Channel{ID: ""},
err: nil,
},
}
for _, tc := range cases {
err := svc.UpdateChannelHandler(context.Background(), tc.channel)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemoveChannelHandler(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
_, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
err error
}{
{
desc: "remove an existing channel",
id: channel.ID,
err: nil,
},
{
desc: "remove a non-existing channel",
id: "unknown",
err: nil,
},
}
for _, tc := range cases {
err := svc.RemoveChannelHandler(context.Background(), tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRemoveCoinfigHandler(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
assert.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
err error
}{
{
desc: "remove an existing config",
id: saved.ThingID,
err: nil,
},
{
desc: "remove a non-existing channel",
id: "unknown",
err: nil,
},
}
for _, tc := range cases {
err := svc.RemoveConfigHandler(context.Background(), tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestDisconnectThingsHandler(t *testing.T) {
tsvc, gsvc, auth := newThingsService()
ts := newThingsServer(tsvc, gsvc)
svc := newService(ts.URL, auth)
saved, err := svc.Add(context.Background(), validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
thingID string
channelID string
err error
}{
{
desc: "disconnect",
channelID: channel.ID,
thingID: saved.ThingID,
err: nil,
},
{
desc: "disconnect disconnected",
channelID: channel.ID,
thingID: saved.ThingID,
err: nil,
},
}
for _, tc := range cases {
err := svc.DisconnectThingHandler(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))
}
}