1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-27 13:48:49 +08:00
Mainflux.mainflux/things/service_test.go
qs-wang 7e9ab453d4
MF-1425 - Support external UUIDs for Things and Channels (#1518)
* MF-1425 - Rebase mainflux master to resolve conflicts

MF-1425 Enhancement to supply an external UUID for Things and Channels.
Resolve conflicts.




Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 - Test cases changes for SDK

MF-1425 Enhancement to supply an external UUID for Things and Channels.
These are the new testcases added for
- Things Service Testcases
- SDK Things and Channel Testcases

Signed-off-by: Anand Sivaram Palassery <aspnair@gmail.com>
Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 - Fixing Testcases

MF-1425 Enhancement to supply an external UUID for Things and Channels.
Because of the previous commits, the testcases were getting failed
because the way ID was modified.  This change is to make sure that
all testcases are revisited to get them fixed.

Signed-off-by: Anand Sivaram Palassery <aspnair@gmail.com>
Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 - Fixing review comments

Fixing the review comments provided.

Signed-off-by: Anand Sivaram Palassery <aspnair@gmail.com>
Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 - Fixing more review comments

Signed-off-by: Anand Sivaram Palassery <aspnair@gmail.com>
Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 - Fixing conflicts

MF-1425 Enhancement to supply an external UUID for Things and Channels.
Fixing the conflicts between aspnair master, and the mainflux master.

Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

* MF-1425 Fix the comment and code format per review comments

MF-1425 Enhancement to supply an external UUID for Things and Channels.
1. Remove un-valued comment for a private function
2. Format the code for better readibility

Signed-off-by: Q.S. Wang <wangqs_eclipse@yahoo.com>

* MF-1425 Enhancement to supply an external UUID for Things and Channels.

Fix the format of the API document

Signed-off-by: Q.S. Wang <wangqs_eclipse@yahoo.com>

* MF-1425 Enhancement to supply an external UUID for Things and Channels.

Rename the variable to make it readible.

Signed-off-by: Q.s <wangqs_eclipse@yahoo.com>

Co-authored-by: Anand Sivaram Palassery <aspnair@gmail.com>
Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2021-12-08 12:46:59 +01:00

1413 lines
34 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package things_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/mainflux/mainflux/things"
"github.com/mainflux/mainflux/things/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
wrongID = ""
wrongValue = "wrong-value"
adminEmail = "admin@example.com"
email = "user@example.com"
email2 = "user2@example.com"
token = "token"
token2 = "token2"
n = uint64(10)
prefix = "fe6b4e92-cc98-425e-b0aa-"
)
var (
thing = things.Thing{Name: "test"}
thingList = [n]things.Thing{}
channel = things.Channel{Name: "test"}
thsExtID = []things.Thing{{ID: prefix + "000000000001", Name: "a"}, {ID: prefix + "000000000002", Name: "b"}}
chsExtID = []things.Channel{{ID: prefix + "000000000001", Name: "a"}, {ID: prefix + "000000000002", Name: "b"}}
)
func newService(tokens map[string]string) things.Service {
userPolicy := mocks.MockSubjectSet{Object: "users", Relation: "member"}
adminPolicy := mocks.MockSubjectSet{Object: "authorities", Relation: "member"}
auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{
adminEmail: {userPolicy, adminPolicy}, email: {userPolicy}})
conns := make(chan mocks.Connection)
thingsRepo := mocks.NewThingRepository(conns)
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
chanCache := mocks.NewChannelCache()
thingCache := mocks.NewThingCache()
idProvider := uuid.NewMock()
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idProvider)
}
func TestInit(t *testing.T) {
for i := uint64(0); i < n; i++ {
thingList[i].Name = fmt.Sprintf("name-%d", i+1)
thingList[i].ID = fmt.Sprintf("%s%012d", prefix, i+1)
thingList[i].Key = fmt.Sprintf("%s1%011d", prefix, i+1)
}
}
func TestCreateThings(t *testing.T) {
svc := newService(map[string]string{token: email})
cases := []struct {
desc string
things []things.Thing
token string
err error
}{
{
desc: "create new things",
things: []things.Thing{{Name: "a"}, {Name: "b"}, {Name: "c"}, {Name: "d"}},
token: token,
err: nil,
},
{
desc: "create thing with wrong credentials",
things: []things.Thing{{Name: "e"}},
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "create new things with external UUID",
things: thsExtID,
token: token,
err: nil,
},
{
desc: "create new things with external wrong UUID",
things: []things.Thing{{ID: "b0aa-000000000001", Name: "a"}, {ID: "b0aa-000000000002", Name: "b"}},
token: token,
err: nil,
},
}
for _, tc := range cases {
_, err := svc.CreateThings(context.Background(), tc.token, tc.things...)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateThing(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
other := things.Thing{ID: wrongID, Key: "x"}
cases := []struct {
desc string
thing things.Thing
token string
err error
}{
{
desc: "update existing thing",
thing: th,
token: token,
err: nil,
},
{
desc: "update thing with wrong credentials",
thing: th,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update non-existing thing",
thing: other,
token: token,
err: things.ErrAuthorization,
},
}
for _, tc := range cases {
err := svc.UpdateThing(context.Background(), tc.token, tc.thing)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateKey(t *testing.T) {
key := "new-key"
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thing)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
cases := []struct {
desc string
token string
id string
key string
err error
}{
{
desc: "update key of an existing thing",
token: token,
id: th.ID,
key: key,
err: nil,
},
{
desc: "update key with invalid credentials",
token: wrongValue,
id: th.ID,
key: key,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update key of non-existing thing",
token: token,
id: wrongID,
key: wrongValue,
err: things.ErrAuthorization,
},
}
for _, tc := range cases {
err := svc.UpdateKey(context.Background(), tc.token, tc.id, tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestShareThing(t *testing.T) {
svc := newService(map[string]string{token: email, token2: email2})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
policies := []string{"read"}
cases := []struct {
desc string
token string
thingID string
policies []string
userIDs []string
err error
}{
{
desc: "share a thing with a valid user",
token: token,
thingID: th.ID,
policies: policies,
userIDs: []string{email2},
err: nil,
},
{
desc: "share a thing via unauthorized access",
token: token2,
thingID: th.ID,
policies: policies,
userIDs: []string{email2},
err: things.ErrAuthorization,
},
{
desc: "share a thing with invalid token",
token: wrongValue,
thingID: th.ID,
policies: policies,
userIDs: []string{email2},
err: things.ErrUnauthorizedAccess,
},
{
desc: "share a thing with partially invalid policies",
token: token,
thingID: th.ID,
policies: []string{"", "read"},
userIDs: []string{email2},
err: fmt.Errorf("cannot claim ownership on object '%s' by user '%s': %s", th.ID, email2, things.ErrMalformedEntity),
},
}
for _, tc := range cases {
err := svc.ShareThing(context.Background(), tc.token, tc.thingID, tc.policies, tc.userIDs)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestViewThing(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
cases := map[string]struct {
id string
token string
err error
}{
"view existing thing": {
id: th.ID,
token: token,
err: nil,
},
"view thing with wrong credentials": {
id: th.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
"view non-existing thing": {
id: wrongID,
token: token,
err: things.ErrAuthorization,
},
}
for desc, tc := range cases {
_, err := svc.ViewThing(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListThings(t *testing.T) {
svc := newService(map[string]string{token: email})
m := make(map[string]interface{})
m["serial"] = "123456"
thingList[0].Metadata = m
var ths []things.Thing
for i := uint64(0); i < n; i++ {
th := thingList[i]
ths = append(ths, th)
}
_, err := svc.CreateThings(context.Background(), token, ths...)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := map[string]struct {
token string
pageMetadata things.PageMetadata
size uint64
err error
}{
"list all things": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: n,
err: nil,
},
"list half": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n / 2,
Limit: n,
},
size: n / 2,
err: nil,
},
"list last thing": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n - 1,
Limit: n,
},
size: 1,
err: nil,
},
"list empty set": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n + 1,
Limit: n,
},
size: 0,
err: nil,
},
"list with zero limit": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 1,
Limit: 0,
},
size: 0,
err: nil,
},
"list with wrong credentials": {
token: wrongValue,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: 0,
},
size: 0,
err: things.ErrUnauthorizedAccess,
},
"list with metadata": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Metadata: m,
},
size: n,
err: nil,
},
"list all things sorted by name ascendent": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "asc",
},
size: n,
err: nil,
},
"list all things sorted by name descendent": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "desc",
},
size: n,
err: nil,
},
}
for desc, tc := range cases {
page, err := svc.ListThings(context.Background(), tc.token, tc.pageMetadata)
size := uint64(len(page.Things))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
// Check if Things list have been sorted properly
testSortThings(t, tc.pageMetadata, page.Things)
}
}
func TestListThingsByChannel(t *testing.T) {
svc := newService(map[string]string{token: email})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
ch := chs[0]
thsDisconNum := uint64(4)
var ths []things.Thing
for i := uint64(0); i < n; i++ {
th := thingList[i]
ths = append(ths, th)
}
thsc, err := svc.CreateThings(context.Background(), token, ths...)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
var thIDs []string
for _, thID := range thsc {
thIDs = append(thIDs, thID.ID)
}
chIDs := []string{chs[0].ID}
err = svc.Connect(context.Background(), token, chIDs, thIDs[0:n-thsDisconNum])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
// Wait for things and channels to connect
time.Sleep(time.Second)
cases := map[string]struct {
token string
chID string
pageMetadata things.PageMetadata
size uint64
err error
}{
"list all things by existing channel": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: n - thsDisconNum,
err: nil,
},
"list half of things by existing channel": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: n / 2,
Limit: n,
},
size: (n / 2) - thsDisconNum,
err: nil,
},
"list last thing by existing channel": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: n - 1 - thsDisconNum,
Limit: n,
},
size: 1,
err: nil,
},
"list empty set of things by existing channel": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: n + 1,
Limit: n,
},
size: 0,
err: nil,
},
"list things by existing channel with zero limit": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 1,
Limit: 0,
},
size: 0,
err: nil,
},
"list things by existing channel with wrong credentials": {
token: wrongValue,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: 0,
},
size: 0,
err: things.ErrUnauthorizedAccess,
},
"list things by non-existent channel with wrong credentials": {
token: token,
chID: "non-existent",
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: 0,
err: nil,
},
"list all non connected things by existing channel": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
},
size: thsDisconNum,
err: nil,
},
"list all things by channel sorted by name ascendent": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "asc",
},
size: n - thsDisconNum,
err: nil,
},
"list all non-connected things by channel sorted by name ascendent": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
Order: "name",
Dir: "asc",
},
size: thsDisconNum,
err: nil,
},
"list all things by channel sorted by name descendent": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "desc",
},
size: n - thsDisconNum,
err: nil,
},
"list all non-connected things by channel sorted by name descendent": {
token: token,
chID: ch.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
Order: "name",
Dir: "desc",
},
size: thsDisconNum,
err: nil,
},
}
for desc, tc := range cases {
page, err := svc.ListThingsByChannel(context.Background(), tc.token, tc.chID, tc.pageMetadata)
size := uint64(len(page.Things))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
// Check if Things by Channel list have been sorted properly
testSortThings(t, tc.pageMetadata, page.Things)
}
}
func TestRemoveThing(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
sth := ths[0]
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "remove thing with wrong credentials",
id: sth.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "remove existing thing",
id: sth.ID,
token: token,
err: nil,
},
{
desc: "remove removed thing",
id: sth.ID,
token: token,
err: nil,
},
{
desc: "remove non-existing thing",
id: wrongID,
token: token,
err: things.ErrAuthorization,
},
}
for _, tc := range cases {
err := svc.RemoveThing(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 TestCreateChannels(t *testing.T) {
svc := newService(map[string]string{token: email})
cases := []struct {
desc string
channels []things.Channel
token string
err error
}{
{
desc: "create new channels",
channels: []things.Channel{{Name: "a"}, {Name: "b"}, {Name: "c"}, {Name: "d"}},
token: token,
err: nil,
},
{
desc: "create channel with wrong credentials",
channels: []things.Channel{{Name: "e"}},
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "create new channels with external UUID",
channels: chsExtID,
token: token,
err: nil,
},
{
desc: "create new channels with invalid external UUID",
channels: []things.Channel{{ID: "b0aa-000000000001", Name: "a"}, {ID: "b0aa-000000000002", Name: "b"}},
token: token,
err: nil,
},
}
for _, cc := range cases {
_, err := svc.CreateChannels(context.Background(), cc.token, cc.channels...)
assert.True(t, errors.Contains(err, cc.err), fmt.Sprintf("%s: expected %s got %s\n", cc.desc, cc.err, err))
}
}
func TestUpdateChannel(t *testing.T) {
svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
other := things.Channel{ID: wrongID}
cases := []struct {
desc string
channel things.Channel
token string
err error
}{
{
desc: "update existing channel",
channel: ch,
token: token,
err: nil,
},
{
desc: "update channel with wrong credentials",
channel: ch,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update non-existing channel",
channel: other,
token: token,
err: things.ErrNotFound,
},
}
for _, tc := range cases {
err := svc.UpdateChannel(context.Background(), tc.token, tc.channel)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestViewChannel(t *testing.T) {
svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
cases := map[string]struct {
id string
token string
err error
metadata things.Metadata
}{
"view existing channel": {
id: ch.ID,
token: token,
err: nil,
},
"view channel with wrong credentials": {
id: ch.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
"view non-existing channel": {
id: wrongID,
token: token,
err: things.ErrNotFound,
},
"view channel with metadata": {
id: wrongID,
token: token,
err: things.ErrNotFound,
},
}
for desc, tc := range cases {
_, err := svc.ViewChannel(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListChannels(t *testing.T) {
svc := newService(map[string]string{token: email})
meta := things.Metadata{}
meta["name"] = "test-channel"
channel.Metadata = meta
var chs []things.Channel
for i := uint64(0); i < n; i++ {
ch := channel
ch.Name = fmt.Sprintf("name-%d", i)
chs = append(chs, ch)
}
_, err := svc.CreateChannels(context.Background(), token, chs...)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := map[string]struct {
token string
pageMetadata things.PageMetadata
size uint64
err error
}{
"list all channels": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: n,
err: nil,
},
"list half": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n / 2,
Limit: n,
},
size: n / 2,
err: nil,
},
"list last channel": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n - 1,
Limit: n,
},
size: 1,
err: nil,
},
"list empty set": {
token: token,
pageMetadata: things.PageMetadata{
Offset: n + 1,
Limit: n,
},
size: 0,
err: nil,
},
"list with zero limit and offset 1": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 1,
Limit: 0,
},
size: n - 1,
err: nil,
},
"list with wrong credentials": {
token: wrongValue,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: 0,
},
size: 0,
err: things.ErrUnauthorizedAccess,
},
"list with existing name": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Name: "chanel_name",
},
size: n,
err: nil,
},
"list with non-existent name": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Name: "wrong",
},
size: n,
err: nil,
},
"list all channels with metadata": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Metadata: meta,
},
size: n,
err: nil,
},
"list all channels sorted by name ascendent": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "asc",
},
size: n,
err: nil,
},
"list all channels sorted by name descendent": {
token: token,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "desc",
},
size: n,
err: nil,
},
}
for desc, tc := range cases {
page, err := svc.ListChannels(context.Background(), tc.token, tc.pageMetadata)
size := uint64(len(page.Channels))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
// Check if channels list have been sorted properly
testSortChannels(t, tc.pageMetadata, page.Channels)
}
}
func TestListChannelsByThing(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th := ths[0]
chsDisconNum := uint64(4)
var chs []things.Channel
for i := uint64(0); i < n; i++ {
ch := channel
ch.Name = fmt.Sprintf("name-%d", i)
chs = append(chs, ch)
}
chsc, err := svc.CreateChannels(context.Background(), token, chs...)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
var chIDs []string
for _, chID := range chsc {
chIDs = append(chIDs, chID.ID)
}
thIDs := []string{ths[0].ID}
err = svc.Connect(context.Background(), token, chIDs[0:n-chsDisconNum], thIDs)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
// Wait for things and channels to connect.
time.Sleep(time.Second)
cases := map[string]struct {
token string
thID string
pageMetadata things.PageMetadata
size uint64
err error
}{
"list all channels by existing thing": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: n - chsDisconNum,
err: nil,
},
"list half of channels by existing thing": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: (n - chsDisconNum) / 2,
Limit: n,
},
size: (n - chsDisconNum) / 2,
err: nil,
},
"list last channel by existing thing": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: n - 1 - chsDisconNum,
Limit: n,
},
size: 1,
err: nil,
},
"list empty set of channels by existing thing": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: n + 1,
Limit: n,
},
size: 0,
err: nil,
},
"list channels by existing thing with zero limit": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 1,
Limit: 0,
},
size: 0,
err: nil,
},
"list channels by existing thing with wrong credentials": {
token: wrongValue,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: 0,
},
size: 0,
err: things.ErrUnauthorizedAccess,
},
"list channels by non-existent thing": {
token: token,
thID: "non-existent",
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
},
size: 0,
err: nil,
},
"list all non-connected channels by existing thing": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
},
size: chsDisconNum,
err: nil,
},
"list all channels by thing sorted by name ascendent": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "asc",
},
size: n - chsDisconNum,
err: nil,
},
"list all non-connected channels by thing sorted by name ascendent": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
Order: "name",
Dir: "asc",
},
size: chsDisconNum,
err: nil,
},
"list all channels by thing sorted by name descendent": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Order: "name",
Dir: "desc",
},
size: n - chsDisconNum,
err: nil,
},
"list all non-connected channels by thing sorted by name descendent": {
token: token,
thID: th.ID,
pageMetadata: things.PageMetadata{
Offset: 0,
Limit: n,
Disconnected: true,
Order: "name",
Dir: "desc",
},
size: chsDisconNum,
err: nil,
},
}
for desc, tc := range cases {
page, err := svc.ListChannelsByThing(context.Background(), tc.token, tc.thID, tc.pageMetadata)
size := uint64(len(page.Channels))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
// Check if Channels by Thing list have been sorted properly
testSortChannels(t, tc.pageMetadata, page.Channels)
}
}
func TestRemoveChannel(t *testing.T) {
svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "remove channel with wrong credentials",
id: ch.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "remove existing channel",
id: ch.ID,
token: token,
err: nil,
},
{
desc: "remove removed channel",
id: ch.ID,
token: token,
err: nil,
},
{
desc: "remove non-existing channel",
id: ch.ID,
token: token,
err: nil,
},
}
for _, tc := range cases {
err := svc.RemoveChannel(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 TestConnect(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
cases := []struct {
desc string
token string
chanID string
thingID string
err error
}{
{
desc: "connect thing",
token: token,
chanID: ch.ID,
thingID: th.ID,
err: nil,
},
{
desc: "connect thing with wrong credentials",
token: wrongValue,
chanID: ch.ID,
thingID: th.ID,
err: things.ErrUnauthorizedAccess,
},
{
desc: "connect thing to non-existing channel",
token: token,
chanID: wrongID,
thingID: th.ID,
err: things.ErrNotFound,
},
{
desc: "connect non-existing thing to channel",
token: token,
chanID: ch.ID,
thingID: wrongID,
err: things.ErrNotFound,
},
}
for _, tc := range cases {
err := svc.Connect(context.Background(), tc.token, []string{tc.chanID}, []string{tc.thingID})
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestDisconnect(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID})
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
cases := []struct {
desc string
token string
chanID string
thingID string
err error
}{
{
desc: "disconnect connected thing",
token: token,
chanID: ch.ID,
thingID: th.ID,
err: nil,
},
{
desc: "disconnect disconnected thing",
token: token,
chanID: ch.ID,
thingID: th.ID,
err: things.ErrNotFound,
},
{
desc: "disconnect with wrong credentials",
token: wrongValue,
chanID: ch.ID,
thingID: th.ID,
err: things.ErrUnauthorizedAccess,
},
{
desc: "disconnect from non-existing channel",
token: token,
chanID: wrongID,
thingID: th.ID,
err: things.ErrNotFound,
},
{
desc: "disconnect non-existing thing",
token: token,
chanID: ch.ID,
thingID: wrongID,
err: things.ErrNotFound,
},
}
for _, tc := range cases {
err := svc.Disconnect(context.Background(), tc.token, []string{tc.chanID}, []string{tc.thingID})
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestCanAccessByKey(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
chs, err := svc.CreateChannels(context.Background(), token, channel, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
err = svc.Connect(context.Background(), token, []string{chs[0].ID}, []string{ths[0].ID})
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
cases := map[string]struct {
token string
channel string
err error
}{
"allowed access": {
token: ths[0].Key,
channel: chs[0].ID,
err: nil,
},
"non-existing thing": {
token: wrongValue,
channel: chs[0].ID,
err: things.ErrNotFound,
},
"non-existing chan": {
token: ths[0].Key,
channel: wrongValue,
err: things.ErrEntityConnected,
},
"non-connected channel": {
token: ths[0].Key,
channel: chs[1].ID,
err: things.ErrEntityConnected,
},
}
for desc, tc := range cases {
_, err := svc.CanAccessByKey(context.Background(), tc.channel, tc.token)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected '%s' got '%s'\n", desc, tc.err, err))
}
}
func TestCanAccessByID(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0], thingList[1])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID})
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
cases := map[string]struct {
thingID string
channel string
err error
}{
"allowed access": {
thingID: th.ID,
channel: ch.ID,
err: nil,
},
"access to non-existing thing": {
thingID: wrongValue,
channel: ch.ID,
err: things.ErrEntityConnected,
},
"access to non-existing channel": {
thingID: th.ID,
channel: wrongID,
err: things.ErrEntityConnected,
},
"access to not-connected thing": {
thingID: ths[1].ID,
channel: ch.ID,
err: things.ErrEntityConnected,
},
}
for desc, tc := range cases {
err := svc.CanAccessByID(context.Background(), tc.channel, tc.thingID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestIsChannelOwner(t *testing.T) {
svc := newService(map[string]string{token: email, token2: "john.doe@email.net"})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ownedCh := chs[0]
chs, err = svc.CreateChannels(context.Background(), token2, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
nonOwnedCh := chs[0]
cases := map[string]struct {
channel string
err error
}{
"user owns channel": {
channel: ownedCh.ID,
err: nil,
},
"user does not own channel": {
channel: nonOwnedCh.ID,
err: things.ErrNotFound,
},
"access to non-existing channel": {
channel: wrongID,
err: things.ErrNotFound,
},
}
for desc, tc := range cases {
err := svc.IsChannelOwner(context.Background(), email, tc.channel)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
svc := newService(map[string]string{token: email})
ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
cases := map[string]struct {
token string
id string
err error
}{
"identify existing thing": {
token: th.Key,
id: th.ID,
err: nil,
},
"identify non-existing thing": {
token: wrongValue,
id: wrongID,
err: things.ErrNotFound,
},
}
for desc, tc := range cases {
id, err := svc.Identify(context.Background(), tc.token)
assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.id, id))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func testSortThings(t *testing.T, pm things.PageMetadata, ths []things.Thing) {
switch pm.Order {
case "name":
current := ths[0]
for _, res := range ths {
if pm.Dir == "asc" {
assert.GreaterOrEqual(t, res.Name, current.Name)
}
if pm.Dir == "desc" {
assert.GreaterOrEqual(t, current.Name, res.Name)
}
current = res
}
default:
break
}
}
func testSortChannels(t *testing.T, pm things.PageMetadata, chs []things.Channel) {
switch pm.Order {
case "name":
current := chs[0]
for _, res := range chs {
if pm.Dir == "asc" {
assert.GreaterOrEqual(t, res.Name, current.Name)
}
if pm.Dir == "desc" {
assert.GreaterOrEqual(t, current.Name, res.Name)
}
current = res
}
default:
break
}
}