diff --git a/api/things.yml b/api/things.yml index 59109116..e36088a6 100644 --- a/api/things.yml +++ b/api/things.yml @@ -636,7 +636,10 @@ components: id: type: string format: uuid - description: Thing unique identifier + description: Thing unique identifier. This can be either + provided by the user or left blank. If the user provides a UUID, + it would be validated. If there is not one provided then + the service will generate one in UUID format. ThingReqSchema: type: object properties: diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index e628ca18..0ad32f60 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -15,7 +15,10 @@ import ( ) var ( - channel = sdk.Channel{ID: "001", Name: "test"} + ch1 = sdk.Channel{Name: "test1"} + ch2 = sdk.Channel{ID: "fe6b4e92-cc98-425e-b0aa-000000000001", Name: "test1"} + ch3 = sdk.Channel{ID: "fe6b4e92-cc98-425e-b0aa-000000000002", Name: "test2"} + chPrefix = "fe6b4e92-cc98-425e-b0aa-" emptyChannel = sdk.Channel{} ) @@ -24,6 +27,8 @@ func TestCreateChannel(t *testing.T) { ts := newThingsServer(svc) defer ts.Close() + chWrongExtID := sdk.Channel{ID: "b0aa-000000000001", Name: "1", Metadata:metadata} + sdkConf := sdk.Config{ ThingsURL: ts.URL, MsgContentType: contentType, @@ -41,21 +46,21 @@ func TestCreateChannel(t *testing.T) { }{ { desc: "create new channel", - channel: channel, + channel: ch1, token: token, err: nil, empty: false, }, { desc: "create new channel with empty token", - channel: channel, + channel: ch1, token: "", err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized), empty: true, }, { desc: "create new channel with invalid token", - channel: channel, + channel: ch1, token: wrongValue, err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized), empty: true, @@ -67,6 +72,20 @@ func TestCreateChannel(t *testing.T) { err: nil, empty: false, }, + { + desc: "create a new channel with external UUID", + channel: ch2, + token: token, + err: nil, + empty: false, + }, + { + desc: "create a new channel with wrong external UUID", + channel: chWrongExtID, + token: token, + err: createError(sdk.ErrFailedCreation, http.StatusBadRequest), + empty: true, + }, } for _, tc := range cases { @@ -90,8 +109,8 @@ func TestCreateChannels(t *testing.T) { mainfluxSDK := sdk.NewSDK(sdkConf) channels := []sdk.Channel{ - sdk.Channel{ID: "001", Name: "1"}, - sdk.Channel{ID: "002", Name: "2"}, + ch2, + ch3, } cases := []struct { @@ -151,7 +170,7 @@ func TestChannel(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateChannel(channel, token) + id, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { @@ -166,7 +185,7 @@ func TestChannel(t *testing.T) { chanID: id, token: token, err: nil, - response: channel, + response: ch2, }, { desc: "get non-existent channel", @@ -204,7 +223,9 @@ func TestChannels(t *testing.T) { var channels []sdk.Channel mainfluxSDK := sdk.NewSDK(sdkConf) for i := 1; i < 101; i++ { - ch := sdk.Channel{ID: fmt.Sprintf("%03d", i), Name: "test"} + id := fmt.Sprintf("%s%012d", chPrefix, i) + name := fmt.Sprintf("test-%d", i) + ch := sdk.Channel{ID: id, Name: name} _, err := mainfluxSDK.CreateChannel(ch, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) channels = append(channels, ch) @@ -294,10 +315,9 @@ func TestChannelsByThing(t *testing.T) { var chsDiscoNum = 1 var channels []sdk.Channel for i := 1; i < n+1; i++ { - ch := sdk.Channel{ - ID: fmt.Sprintf("%03d", i), - Name: "test", - } + id := fmt.Sprintf("%s%012d", chPrefix, i) + name := fmt.Sprintf("test-%d", i) + ch := sdk.Channel{ID: id, Name: name} cid, err := mainfluxSDK.CreateChannel(ch, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -419,7 +439,7 @@ func TestUpdateChannel(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateChannel(channel, token) + id, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error %s", err)) cases := []struct { @@ -477,7 +497,7 @@ func TestDeleteChannel(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateChannel(channel, token) + id, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index bf6c0129..2d58ad2e 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -29,14 +29,13 @@ const ( wrongValue = "wrong_value" badID = "999" emptyValue = "" - - keyPrefix = "123e4567-e89b-12d3-a456-" ) var ( metadata = map[string]interface{}{"meta": "data"} metadata2 = map[string]interface{}{"meta": "data2"} - thing = sdk.Thing{ID: "001", Name: "test_device", Metadata: metadata} + th1 = sdk.Thing{ID: "fe6b4e92-cc98-425e-b0aa-000000000001", Name: "test1", Metadata: metadata} + th2 = sdk.Thing{ID: "fe6b4e92-cc98-425e-b0aa-000000000002", Name: "test2", Metadata: metadata} emptyThing = sdk.Thing{} ) @@ -82,28 +81,28 @@ func TestCreateThing(t *testing.T) { }{ { desc: "create new thing", - thing: thing, + thing: th1, token: token, err: nil, - location: "001", + location: th1.ID, }, { desc: "create new empty thing", thing: emptyThing, token: token, err: nil, - location: "002", + location: fmt.Sprintf("%s%012d", uuid.Prefix, 2), }, { desc: "create new thing with empty token", - thing: thing, + thing: th1, token: "", err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized), location: "", }, { desc: "create new thing with invalid token", - thing: thing, + thing: th1, token: wrongValue, err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized), location: "", @@ -131,8 +130,16 @@ func TestCreateThings(t *testing.T) { mainfluxSDK := sdk.NewSDK(sdkConf) things := []sdk.Thing{ - sdk.Thing{ID: "001", Name: "1", Key: "1"}, - sdk.Thing{ID: "002", Name: "2", Key: "2"}, + th1, + th2, + } + thsExtID := []sdk.Thing{ + {ID: th1.ID, Name: "1", Key: "1", Metadata: metadata}, + {ID: th2.ID, Name: "2", Key: "2", Metadata: metadata}, + } + thsWrongExtID := []sdk.Thing{ + {ID: "b0aa-000000000001", Name: "1", Key: "1", Metadata: metadata}, + {ID: "b0aa-000000000002", Name: "2", Key: "2", Metadata: metadata2}, } cases := []struct { @@ -170,6 +177,20 @@ func TestCreateThings(t *testing.T) { err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized), res: []sdk.Thing{}, }, + { + desc: "create new things with external UUID", + things: thsExtID, + token: token, + err: nil, + res: things, + }, + { + desc: "create new things with wrong external UUID", + things: thsWrongExtID, + token: token, + err: createError(sdk.ErrFailedCreation, http.StatusBadRequest), + res: []sdk.Thing{}, + }, } for _, tc := range cases { res, err := mainfluxSDK.CreateThings(tc.things, tc.token) @@ -193,9 +214,9 @@ func TestThing(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateThing(thing, token) + id, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - thing.Key = fmt.Sprintf("%s%012d", keyPrefix, 2) + th1.Key = fmt.Sprintf("%s%012d", uuid.Prefix, 1) cases := []struct { desc string @@ -209,7 +230,7 @@ func TestThing(t *testing.T) { thID: id, token: token, err: nil, - response: thing, + response: th1, }, { desc: "get non-existent thing", @@ -248,10 +269,12 @@ func TestThings(t *testing.T) { mainfluxSDK := sdk.NewSDK(sdkConf) for i := 1; i < 101; i++ { - th := sdk.Thing{ID: fmt.Sprintf("%03d", i), Name: "test_device", Metadata: metadata} + id := fmt.Sprintf("%s%012d", chPrefix, i) + name := fmt.Sprintf("test-%d", i) + th := sdk.Thing{ID: id, Name: name, Metadata: metadata} _, err := mainfluxSDK.CreateThing(th, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - th.Key = fmt.Sprintf("%s%012d", keyPrefix, 2*i) + th.Key = fmt.Sprintf("%s%012d", uuid.Prefix, i) things = append(things, th) } @@ -340,11 +363,13 @@ func TestThingsByChannel(t *testing.T) { var thsDiscoNum = 1 var things []sdk.Thing for i := 1; i < n+1; i++ { + id := fmt.Sprintf("%s%012d", chPrefix, i) + name := fmt.Sprintf("test-%d", i) th := sdk.Thing{ - ID: fmt.Sprintf("%03d", i), - Name: "test_device", + ID: id, + Name: name, Metadata: metadata, - Key: fmt.Sprintf("%s%012d", keyPrefix, 2*i+1), + Key: fmt.Sprintf("%s%012d", uuid.Prefix, 2*i+1), } tid, err := mainfluxSDK.CreateThing(th, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -467,9 +492,9 @@ func TestUpdateThing(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateThing(thing, token) + id, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - thing.Name = "test2" + th1.Name = "test2" cases := []struct { desc string @@ -546,7 +571,7 @@ func TestDeleteThing(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - id, err := mainfluxSDK.CreateThing(thing, token) + id, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { @@ -614,13 +639,13 @@ func TestConnectThing(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - thingID, err := mainfluxSDK.CreateThing(thing, token) + thingID, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID1, err := mainfluxSDK.CreateChannel(channel, token) + chanID1, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken) + chanID2, err := mainfluxSDK.CreateChannel(ch3, otherToken) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { @@ -715,13 +740,13 @@ func TestConnect(t *testing.T) { } mainfluxSDK := sdk.NewSDK(sdkConf) - thingID, err := mainfluxSDK.CreateThing(thing, token) + thingID, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID1, err := mainfluxSDK.CreateChannel(channel, token) + chanID1, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken) + chanID2, err := mainfluxSDK.CreateChannel(ch3, otherToken) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { @@ -818,10 +843,10 @@ func TestDisconnectThing(t *testing.T) { mainfluxSDK := sdk.NewSDK(sdkConf) - thingID, err := mainfluxSDK.CreateThing(thing, token) + thingID, err := mainfluxSDK.CreateThing(th1, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID1, err := mainfluxSDK.CreateChannel(channel, token) + chanID1, err := mainfluxSDK.CreateChannel(ch2, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) conIDs := sdk.ConnectionIDs{ @@ -831,7 +856,7 @@ func TestDisconnectThing(t *testing.T) { err = mainfluxSDK.Connect(conIDs, token) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken) + chanID2, err := mainfluxSDK.CreateChannel(ch2, otherToken) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) cases := []struct { diff --git a/things/api/things/http/endpoint.go b/things/api/things/http/endpoint.go index 52641a8a..fc343f8e 100644 --- a/things/api/things/http/endpoint.go +++ b/things/api/things/http/endpoint.go @@ -22,6 +22,7 @@ func createThingEndpoint(svc things.Service) endpoint.Endpoint { th := things.Thing{ Key: req.Key, + ID: req.ID, Name: req.Name, Metadata: req.Metadata, } @@ -52,6 +53,7 @@ func createThingsEndpoint(svc things.Service) endpoint.Endpoint { th := things.Thing{ Name: tReq.Name, Key: tReq.Key, + ID: tReq.ID, Metadata: tReq.Metadata, } ths = append(ths, th) @@ -263,7 +265,11 @@ func createChannelEndpoint(svc things.Service) endpoint.Endpoint { return nil, err } - ch := things.Channel{Name: req.Name, Metadata: req.Metadata} + ch := things.Channel{ + Name: req.Name, + ID: req.ID, + Metadata: req.Metadata} + saved, err := svc.CreateChannels(ctx, req.token, ch) if err != nil { return nil, err @@ -290,6 +296,7 @@ func createChannelsEndpoint(svc things.Service) endpoint.Endpoint { ch := things.Channel{ Metadata: cReq.Metadata, Name: cReq.Name, + ID: cReq.ID, } chs = append(chs, ch) } diff --git a/things/api/things/http/endpoint_test.go b/things/api/things/http/endpoint_test.go index 378dd056..91b15e4a 100644 --- a/things/api/things/http/endpoint_test.go +++ b/things/api/things/http/endpoint_test.go @@ -36,6 +36,7 @@ const ( nameKey = "name" ascKey = "asc" descKey = "desc" + prefix = "fe6b4e92-cc98-425e-b0aa-" ) var ( @@ -131,7 +132,7 @@ func TestCreateThing(t *testing.T) { contentType: contentType, auth: token, status: http.StatusCreated, - location: "/things/001", + location: fmt.Sprintf("/things/%s%012d", uuid.Prefix, 1), }, { desc: "add thing with existing key", @@ -147,7 +148,7 @@ func TestCreateThing(t *testing.T) { contentType: contentType, auth: token, status: http.StatusCreated, - location: "/things/002", + location: fmt.Sprintf("/things/%s%012d", uuid.Prefix, 3), }, { desc: "add thing with invalid auth token", @@ -766,7 +767,10 @@ func TestListThings(t *testing.T) { data := []thingRes{} for i := 0; i < 100; i++ { - ths, err := svc.CreateThings(context.Background(), token, thing) + id := fmt.Sprintf("%s%012d", prefix, i + 1) + thing1 := thing + thing1.ID = id + ths, err := svc.CreateThings(context.Background(), token, thing1) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) th := ths[0] data = append(data, thingRes{ @@ -1003,7 +1007,8 @@ func TestSearchThings(t *testing.T) { data := []thingRes{} for i := 0; i < 100; i++ { name := "name_" + fmt.Sprintf("%03d", i+1) - ths, err := svc.CreateThings(context.Background(), token, things.Thing{Name: name, Metadata: map[string]interface{}{"test": name}}) + id := fmt.Sprintf("%s%012d", prefix, i + 1) + ths, err := svc.CreateThings(context.Background(), token, things.Thing{ID: id, Name: name, Metadata: map[string]interface{}{"test": name}}) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) th := ths[0] data = append(data, thingRes{ @@ -1170,7 +1175,10 @@ func TestListThingsByChannel(t *testing.T) { data := []thingRes{} for i := 0; i < 101; i++ { - ths, err := svc.CreateThings(context.Background(), token, thing) + id := fmt.Sprintf("%s%012d", prefix, i + 1) + thing1 := thing + thing1.ID = id + ths, err := svc.CreateThings(context.Background(), token, thing1) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) th := ths[0] err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID}) @@ -1418,7 +1426,7 @@ func TestCreateChannel(t *testing.T) { contentType: contentType, auth: token, status: http.StatusCreated, - location: "/channels/001", + location: fmt.Sprintf("/channels/%s%012d", uuid.Prefix, 1), }, { desc: "create new channel with invalid token", @@ -1450,7 +1458,7 @@ func TestCreateChannel(t *testing.T) { contentType: contentType, auth: token, status: http.StatusCreated, - location: "/channels/002", + location: fmt.Sprintf("/channels/%s%012d", uuid.Prefix, 2), }, { desc: "create new channel with empty request", @@ -2021,7 +2029,10 @@ func TestListChannelsByThing(t *testing.T) { channels := []channelRes{} for i := 0; i < 101; i++ { - chs, err := svc.CreateChannels(context.Background(), token, channel) + id := fmt.Sprintf("%s%012d", prefix, i + 1) + channel1 := channel + channel1.ID = id + chs, err := svc.CreateChannels(context.Background(), token, channel1) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) ch := chs[0] err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID}) diff --git a/things/api/things/http/requests.go b/things/api/things/http/requests.go index f481aa1d..7b55d876 100644 --- a/things/api/things/http/requests.go +++ b/things/api/things/http/requests.go @@ -4,6 +4,7 @@ package http import ( + "github.com/gofrs/uuid" "github.com/mainflux/mainflux/auth" "github.com/mainflux/mainflux/things" ) @@ -24,14 +25,28 @@ type createThingReq struct { token string Name string `json:"name,omitempty"` Key string `json:"key,omitempty"` + ID string `json:"id,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` } +func validateUUID(extID string) (err error) { + id, err := uuid.FromString(extID) + if id.String() != extID || err != nil { + return things.ErrMalformedEntity + } + + return nil +} + func (req createThingReq) validate() error { if req.token == "" { return things.ErrUnauthorizedAccess } + if req.ID != "" && validateUUID(req.ID) != nil { + return things.ErrMalformedEntity + } + if len(req.Name) > maxNameSize { return things.ErrMalformedEntity } @@ -54,6 +69,10 @@ func (req createThingsReq) validate() error { } for _, thing := range req.Things { + if thing.ID != "" && validateUUID(thing.ID) != nil { + return things.ErrMalformedEntity + } + if len(thing.Name) > maxNameSize { return things.ErrMalformedEntity } @@ -129,6 +148,7 @@ func (req updateKeyReq) validate() error { type createChannelReq struct { token string Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` } @@ -137,6 +157,10 @@ func (req createChannelReq) validate() error { return things.ErrUnauthorizedAccess } + if req.ID != "" && validateUUID(req.ID) != nil { + return things.ErrMalformedEntity + } + if len(req.Name) > maxNameSize { return things.ErrMalformedEntity } @@ -159,6 +183,10 @@ func (req createChannelsReq) validate() error { } for _, channel := range req.Channels { + if channel.ID != "" && validateUUID(channel.ID) != nil { + return things.ErrMalformedEntity + } + if len(channel.Name) > maxNameSize { return things.ErrMalformedEntity } diff --git a/things/mocks/auth.go b/things/mocks/auth.go index d6803094..9e0051f9 100644 --- a/things/mocks/auth.go +++ b/things/mocks/auth.go @@ -69,12 +69,7 @@ func (svc authServiceMock) AddPolicy(ctx context.Context, in *mainflux.AddPolicy return &mainflux.AddPolicyRes{}, things.ErrMalformedEntity } - // Mock thingsRepository saves the thing ID after padding the ID by 3. (see things/mocks/things.go) - // Since we are adding policies within the Service layer, we are storing them as a full ID which is - // eventually not compatible with the one inside of the mock things repository. Therefore, we are - // getting last three part of the ID as below. obj := in.GetObj() - obj = obj[len(obj)-3:] svc.policies[in.GetSub()] = append(svc.policies[in.GetSub()], MockSubjectSet{Object: obj, Relation: in.GetAct()}) return &mainflux.AddPolicyRes{Authorized: true}, nil } diff --git a/things/mocks/channels.go b/things/mocks/channels.go index f459eb5f..aedda933 100644 --- a/things/mocks/channels.go +++ b/things/mocks/channels.go @@ -6,7 +6,6 @@ package mocks import ( "context" "fmt" - "strconv" "strings" "sync" @@ -48,7 +47,9 @@ func (crm *channelRepositoryMock) Save(_ context.Context, channels ...things.Cha for i := range channels { crm.counter++ - channels[i].ID = fmt.Sprintf("%03d", crm.counter) + if channels[i].ID == "" { + channels[i].ID = fmt.Sprintf("%03d", crm.counter) + } crm.channels[key(channels[i].Owner, channels[i].ID)] = channels[i] } @@ -136,7 +137,7 @@ func (crm *channelRepositoryMock) RetrieveByThing(_ context.Context, owner, thID switch pm.Disconnected { case false: for _, co := range crm.cconns[thID] { - id, _ := strconv.ParseUint(co.ID, 10, 64) + id := parseID(co.ID) if id >= first && id < last { chs = append(chs, co) } @@ -144,7 +145,7 @@ func (crm *channelRepositoryMock) RetrieveByThing(_ context.Context, owner, thID default: for _, ch := range crm.channels { conn := false - id, _ := strconv.ParseUint(ch.ID, 10, 64) + id := parseID(ch.ID) if id >= first && id < last { for _, co := range crm.cconns[thID] { if ch.ID == co.ID { diff --git a/things/mocks/commons.go b/things/mocks/commons.go index 909c6ce6..5177e6bc 100644 --- a/things/mocks/commons.go +++ b/things/mocks/commons.go @@ -6,10 +6,13 @@ package mocks import ( "fmt" "sort" + "strconv" "github.com/mainflux/mainflux/things" ) +const uuidLen = 36 + // Since mocks will store data in map, and they need to resemble the real // identifiers as much as possible, a key will be created as combination of // owner and their own identifiers. This will allow searching either by @@ -83,3 +86,14 @@ func sortChannels(pm things.PageMetadata, chs []things.Channel) []things.Channel return chs } + +func parseID(ID string) (id uint64) { + var serialNum string + + if len(ID) == uuidLen { + serialNum = ID[len(ID)-6:] + } + id, _ = strconv.ParseUint(serialNum, 10, 64) + + return +} diff --git a/things/mocks/things.go b/things/mocks/things.go index 3df7932b..7215ce0d 100644 --- a/things/mocks/things.go +++ b/things/mocks/things.go @@ -6,7 +6,6 @@ package mocks import ( "context" "fmt" - "strconv" "strings" "sync" @@ -55,7 +54,9 @@ func (trm *thingRepositoryMock) Save(_ context.Context, ths ...things.Thing) ([] } trm.counter++ - ths[i].ID = fmt.Sprintf("%03d", trm.counter) + if ths[i].ID == "" { + ths[i].ID = fmt.Sprintf("%03d", trm.counter) + } trm.things[key(ths[i].Owner, ths[i].ID)] = ths[i] } @@ -128,7 +129,7 @@ func (trm *thingRepositoryMock) RetrieveAll(_ context.Context, owner string, pm // itself (see mocks/commons.go). prefix := fmt.Sprintf("%s-", owner) for k, v := range trm.things { - id, _ := strconv.ParseUint(v.ID, 10, 64) + id := parseID(v.ID) if strings.HasPrefix(k, prefix) && id >= first && id < last { ths = append(ths, v) } @@ -167,7 +168,7 @@ func (trm *thingRepositoryMock) RetrieveByIDs(_ context.Context, thingIDs []stri for _, id := range thingIDs { suffix := fmt.Sprintf("-%s", id) for k, v := range trm.things { - id, _ := strconv.ParseUint(v.ID, 10, 64) + id := parseID(v.ID) if strings.HasSuffix(k, suffix) && id >= first && id < last { items = append(items, v) } @@ -205,7 +206,7 @@ func (trm *thingRepositoryMock) RetrieveByChannel(_ context.Context, owner, chID switch pm.Disconnected { case false: for _, co := range trm.tconns[chID] { - id, _ := strconv.ParseUint(co.ID, 10, 64) + id := parseID(co.ID) if id >= first && id < last { ths = append(ths, co) } @@ -213,7 +214,7 @@ func (trm *thingRepositoryMock) RetrieveByChannel(_ context.Context, owner, chID default: for _, th := range trm.things { conn := false - id, _ := strconv.ParseUint(th.ID, 10, 64) + id := parseID(th.ID) if id >= first && id < last { for _, co := range trm.tconns[chID] { if th.ID == co.ID { diff --git a/things/redis/streams_test.go b/things/redis/streams_test.go index 4438b300..90c609a9 100644 --- a/things/redis/streams_test.go +++ b/things/redis/streams_test.go @@ -76,7 +76,7 @@ func TestCreateThings(t *testing.T) { key: token, err: nil, event: map[string]interface{}{ - "id": "001", + "id": "123e4567-e89b-12d3-a456-000000000001", "name": "a", "owner": email, "metadata": "{\"test\":\"test\"}", @@ -302,7 +302,7 @@ func TestCreateChannels(t *testing.T) { key: token, err: nil, event: map[string]interface{}{ - "id": "001", + "id": "123e4567-e89b-12d3-a456-000000000001", "name": "a", "metadata": "{\"test\":\"test\"}", "owner": email, diff --git a/things/service.go b/things/service.go index 26a634dc..03a1dadf 100644 --- a/things/service.go +++ b/things/service.go @@ -192,6 +192,7 @@ func (ts *thingsService) CreateThings(ctx context.Context, token string, things ths := []Thing{} for _, thing := range things { th, err := ts.createThing(ctx, &thing, res) + if err != nil { return []Thing{}, err } @@ -203,18 +204,24 @@ func (ts *thingsService) CreateThings(ctx context.Context, token string, things // createThing saves the Thing and adds identity as an owner(Read, Write, Delete policies) of the Thing. func (ts *thingsService) createThing(ctx context.Context, thing *Thing, identity *mainflux.UserIdentity) (Thing, error) { - thID, err := ts.idProvider.ID() - if err != nil { - return Thing{}, errors.Wrap(ErrCreateUUID, err) - } - thing.ID = thID + thing.Owner = identity.GetEmail() - if thing.Key == "" { - thing.Key, err = ts.idProvider.ID() + if thing.ID == "" { + id, err := ts.idProvider.ID() if err != nil { return Thing{}, errors.Wrap(ErrCreateUUID, err) } + thing.ID = id + } + + if thing.Key == "" { + key, err := ts.idProvider.ID() + + if err != nil { + return Thing{}, errors.Wrap(ErrCreateUUID, err) + } + thing.Key = key } ths, err := ts.things.Save(ctx, *thing) @@ -401,11 +408,13 @@ func (ts *thingsService) CreateChannels(ctx context.Context, token string, chann } func (ts *thingsService) createChannel(ctx context.Context, channel *Channel, identity *mainflux.UserIdentity) (Channel, error) { - chID, err := ts.idProvider.ID() - if err != nil { - return Channel{}, errors.Wrap(ErrCreateUUID, err) + if channel.ID == "" { + chID, err := ts.idProvider.ID() + if err != nil { + return Channel{}, errors.Wrap(ErrCreateUUID, err) + } + channel.ID = chID } - channel.ID = chID channel.Owner = identity.GetEmail() chs, err := ts.channels.Save(ctx, *channel) diff --git a/things/service_test.go b/things/service_test.go index a4bcf813..b87c92b5 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -26,11 +26,15 @@ const ( token = "token" token2 = "token2" n = uint64(10) + prefix = "fe6b4e92-cc98-425e-b0aa-" ) var ( - thing = things.Thing{Name: "test"} - channel = things.Channel{Name: "test"} + 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 { @@ -48,6 +52,14 @@ func newService(tokens map[string]string) things.Service { 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}) @@ -69,6 +81,18 @@ func TestCreateThings(t *testing.T) { 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 { @@ -79,7 +103,7 @@ func TestCreateThings(t *testing.T) { func TestUpdateThing(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + 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"} @@ -161,7 +185,7 @@ func TestUpdateKey(t *testing.T) { func TestShareThing(t *testing.T) { svc := newService(map[string]string{token: email, token2: email2}) - ths, err := svc.CreateThings(context.Background(), token, thing) + 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"} @@ -217,7 +241,7 @@ func TestShareThing(t *testing.T) { func TestViewThing(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + ths, err := svc.CreateThings(context.Background(), token, thingList[0]) require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) th := ths[0] @@ -254,12 +278,11 @@ func TestListThings(t *testing.T) { m := make(map[string]interface{}) m["serial"] = "123456" - thing.Metadata = m + thingList[0].Metadata = m var ths []things.Thing for i := uint64(0); i < n; i++ { - th := thing - th.Name = fmt.Sprintf("name-%d", i) + th := thingList[i] ths = append(ths, th) } @@ -382,8 +405,7 @@ func TestListThingsByChannel(t *testing.T) { var ths []things.Thing for i := uint64(0); i < n; i++ { - th := thing - th.Name = fmt.Sprintf("name-%d", i) + th := thingList[i] ths = append(ths, th) } @@ -555,7 +577,7 @@ func TestListThingsByChannel(t *testing.T) { func TestRemoveThing(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + ths, err := svc.CreateThings(context.Background(), token, thingList[0]) require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) sth := ths[0] @@ -618,6 +640,18 @@ func TestCreateChannels(t *testing.T) { 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 { @@ -849,7 +883,7 @@ func TestListChannels(t *testing.T) { func TestListChannelsByThing(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + ths, err := svc.CreateThings(context.Background(), token, thingList[0]) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) th := ths[0] @@ -1075,7 +1109,7 @@ func TestRemoveChannel(t *testing.T) { func TestConnect(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + 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) @@ -1128,7 +1162,7 @@ func TestConnect(t *testing.T) { func TestDisconnect(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + 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) @@ -1191,7 +1225,7 @@ func TestDisconnect(t *testing.T) { func TestCanAccessByKey(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + 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)) @@ -1234,7 +1268,7 @@ func TestCanAccessByKey(t *testing.T) { func TestCanAccessByID(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing, thing) + 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) @@ -1313,7 +1347,7 @@ func TestIsChannelOwner(t *testing.T) { func TestIdentify(t *testing.T) { svc := newService(map[string]string{token: email}) - ths, err := svc.CreateThings(context.Background(), token, thing) + ths, err := svc.CreateThings(context.Background(), token, thingList[0]) require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err)) th := ths[0]