1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-27 13:48:49 +08:00

MF-707 - Allow custom Thing key (#726)

* Add support for setting up thing key manually

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix existing tests and add new ones

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SQL schema for things entity

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add update thing key endpoint to swagger docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix response code when handling conflicting key

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
This commit is contained in:
Aleksandar Novaković 2019-04-25 14:37:51 +02:00 committed by Manuel Imperiale
parent aba7d909ed
commit dc9333237f
17 changed files with 920 additions and 413 deletions

View File

@ -162,6 +162,10 @@ func (svc *mainfluxThings) UpdateThing(string, things.Thing) error {
panic("not implemented") panic("not implemented")
} }
func (svc *mainfluxThings) UpdateKey(string, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListThings(string, uint64, uint64) (things.ThingsPage, error) { func (svc *mainfluxThings) ListThings(string, uint64, uint64) (things.ThingsPage, error) {
panic("not implemented") panic("not implemented")
} }

View File

@ -23,10 +23,11 @@ func addThingEndpoint(svc things.Service) endpoint.Endpoint {
} }
thing := things.Thing{ thing := things.Thing{
Key: req.Key,
Name: req.Name, Name: req.Name,
Metadata: req.Metadata, Metadata: req.Metadata,
} }
saved, err := svc.AddThing(req.key, thing) saved, err := svc.AddThing(req.token, thing)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -53,7 +54,24 @@ func updateThingEndpoint(svc things.Service) endpoint.Endpoint {
Metadata: req.Metadata, Metadata: req.Metadata,
} }
if err := svc.UpdateThing(req.key, thing); err != nil { if err := svc.UpdateThing(req.token, thing); err != nil {
return nil, err
}
res := thingRes{id: req.id, created: false}
return res, nil
}
}
func updateKeyEndpoint(svc things.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateKeyReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.UpdateKey(req.token, req.id, req.Key); err != nil {
return nil, err return nil, err
} }
@ -70,7 +88,7 @@ func viewThingEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
thing, err := svc.ViewThing(req.key, req.id) thing, err := svc.ViewThing(req.token, req.id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,7 +112,7 @@ func listThingsEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
page, err := svc.ListThings(req.key, req.offset, req.limit) page, err := svc.ListThings(req.token, req.offset, req.limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -130,7 +148,7 @@ func listThingsByChannelEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
page, err := svc.ListThingsByChannel(req.key, req.id, req.offset, req.limit) page, err := svc.ListThingsByChannel(req.token, req.id, req.offset, req.limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -171,7 +189,7 @@ func removeThingEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
if err := svc.RemoveThing(req.key, req.id); err != nil { if err := svc.RemoveThing(req.token, req.id); err != nil {
return nil, err return nil, err
} }
@ -188,7 +206,7 @@ func createChannelEndpoint(svc things.Service) endpoint.Endpoint {
} }
channel := things.Channel{Name: req.Name, Metadata: req.Metadata} channel := things.Channel{Name: req.Name, Metadata: req.Metadata}
saved, err := svc.CreateChannel(req.key, channel) saved, err := svc.CreateChannel(req.token, channel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,7 +232,7 @@ func updateChannelEndpoint(svc things.Service) endpoint.Endpoint {
Name: req.Name, Name: req.Name,
Metadata: req.Metadata, Metadata: req.Metadata,
} }
if err := svc.UpdateChannel(req.key, channel); err != nil { if err := svc.UpdateChannel(req.token, channel); err != nil {
return nil, err return nil, err
} }
@ -234,7 +252,7 @@ func viewChannelEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
channel, err := svc.ViewChannel(req.key, req.id) channel, err := svc.ViewChannel(req.token, req.id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -258,7 +276,7 @@ func listChannelsEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
page, err := svc.ListChannels(req.key, req.offset, req.limit) page, err := svc.ListChannels(req.token, req.offset, req.limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -295,7 +313,7 @@ func listChannelsByThingEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
page, err := svc.ListChannelsByThing(req.key, req.id, req.offset, req.limit) page, err := svc.ListChannelsByThing(req.token, req.id, req.offset, req.limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -333,7 +351,7 @@ func removeChannelEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
if err := svc.RemoveChannel(req.key, req.id); err != nil { if err := svc.RemoveChannel(req.token, req.id); err != nil {
return nil, err return nil, err
} }
@ -349,7 +367,7 @@ func connectEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
if err := svc.Connect(cr.key, cr.chanID, cr.thingID); err != nil { if err := svc.Connect(cr.token, cr.chanID, cr.thingID); err != nil {
return nil, err return nil, err
} }
@ -365,7 +383,7 @@ func disconnectEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err return nil, err
} }
if err := svc.Disconnect(cr.key, cr.chanID, cr.thingID); err != nil { if err := svc.Disconnect(cr.token, cr.chanID, cr.thingID); err != nil {
return nil, err return nil, err
} }

View File

@ -89,7 +89,9 @@ func TestAddThing(t *testing.T) {
ts := newServer(svc) ts := newServer(svc)
defer ts.Close() defer ts.Close()
data := toJSON(thing) th := thing
th.Key = "key"
data := toJSON(th)
cases := []struct { cases := []struct {
desc string desc string
@ -107,6 +109,14 @@ func TestAddThing(t *testing.T) {
status: http.StatusCreated, status: http.StatusCreated,
location: "/things/1", location: "/things/1",
}, },
{
desc: "add thing with existing key",
req: data,
contentType: contentType,
auth: token,
status: http.StatusUnprocessableEntity,
location: "",
},
{ {
desc: "add thing with empty JSON request", desc: "add thing with empty JSON request",
req: "{}", req: "{}",
@ -280,6 +290,126 @@ func TestUpdateThing(t *testing.T) {
} }
} }
func TestUpdateKey(t *testing.T) {
svc := newService(map[string]string{token: email})
ts := newServer(svc)
defer ts.Close()
th := thing
th.Key = "key"
sth, _ := svc.AddThing(token, th)
sth.Key = "new-key"
data := toJSON(sth)
sth.Key = "key"
dummyData := toJSON(sth)
cases := []struct {
desc string
req string
id string
contentType string
auth string
status int
}{
{
desc: "update key for an existing thing",
req: data,
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusOK,
},
{
desc: "update thing with conflicting key",
req: data,
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusUnprocessableEntity,
},
{
desc: "update key with empty JSON request",
req: "{}",
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update key of non-existent thing",
req: dummyData,
id: strconv.FormatUint(wrongID, 10),
contentType: contentType,
auth: token,
status: http.StatusNotFound,
},
{
desc: "update thing with invalid id",
req: dummyData,
id: "invalid",
contentType: contentType,
auth: token,
status: http.StatusNotFound,
},
{
desc: "update thing with invalid user token",
req: data,
id: sth.ID,
contentType: contentType,
auth: wrongValue,
status: http.StatusForbidden,
},
{
desc: "update thing with empty user token",
req: data,
id: sth.ID,
contentType: contentType,
auth: "",
status: http.StatusForbidden,
},
{
desc: "update thing with invalid data format",
req: "{",
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update thing with empty request",
req: "",
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update thing without content type",
req: data,
id: sth.ID,
contentType: "",
auth: token,
status: http.StatusUnsupportedMediaType,
},
}
for _, tc := range cases {
req := testRequest{
client: ts.Client(),
method: http.MethodPatch,
url: fmt.Sprintf("%s/things/%s/key", ts.URL, tc.id),
contentType: tc.contentType,
token: tc.auth,
body: strings.NewReader(tc.req),
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestViewThing(t *testing.T) { func TestViewThing(t *testing.T) {
svc := newService(map[string]string{token: email}) svc := newService(map[string]string{token: email})
ts := newServer(svc) ts := newServer(svc)

View File

@ -15,26 +15,15 @@ type apiReq interface {
validate() error validate() error
} }
type identityReq struct {
key string
}
func (req identityReq) validate() error {
if req.key == "" {
return things.ErrUnauthorizedAccess
}
return nil
}
type addThingReq struct { type addThingReq struct {
key string token string
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
} }
func (req addThingReq) validate() error { func (req addThingReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -42,14 +31,14 @@ func (req addThingReq) validate() error {
} }
type updateThingReq struct { type updateThingReq struct {
key string token string
id string id string
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
} }
func (req updateThingReq) validate() error { func (req updateThingReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -60,14 +49,32 @@ func (req updateThingReq) validate() error {
return nil return nil
} }
type updateKeyReq struct {
token string
id string
Key string `json:"key"`
}
func (req updateKeyReq) validate() error {
if req.token == "" {
return things.ErrUnauthorizedAccess
}
if req.id == "" || req.Key == "" {
return things.ErrMalformedEntity
}
return nil
}
type createChannelReq struct { type createChannelReq struct {
key string token string
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
} }
func (req createChannelReq) validate() error { func (req createChannelReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -75,14 +82,14 @@ func (req createChannelReq) validate() error {
} }
type updateChannelReq struct { type updateChannelReq struct {
key string token string
id string id string
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"`
} }
func (req updateChannelReq) validate() error { func (req updateChannelReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -94,12 +101,12 @@ func (req updateChannelReq) validate() error {
} }
type viewResourceReq struct { type viewResourceReq struct {
key string token string
id string id string
} }
func (req viewResourceReq) validate() error { func (req viewResourceReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -111,13 +118,13 @@ func (req viewResourceReq) validate() error {
} }
type listResourcesReq struct { type listResourcesReq struct {
key string token string
offset uint64 offset uint64
limit uint64 limit uint64
} }
func (req *listResourcesReq) validate() error { func (req *listResourcesReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -129,14 +136,14 @@ func (req *listResourcesReq) validate() error {
} }
type listByConnectionReq struct { type listByConnectionReq struct {
key string token string
id string id string
offset uint64 offset uint64
limit uint64 limit uint64
} }
func (req listByConnectionReq) validate() error { func (req listByConnectionReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }
@ -152,13 +159,13 @@ func (req listByConnectionReq) validate() error {
} }
type connectionReq struct { type connectionReq struct {
key string token string
chanID string chanID string
thingID string thingID string
} }
func (req connectionReq) validate() error { func (req connectionReq) validate() error {
if req.key == "" { if req.token == "" {
return things.ErrUnauthorizedAccess return things.ErrUnauthorizedAccess
} }

View File

@ -17,51 +17,30 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIdentityReqValidation(t *testing.T) {
cases := map[string]struct {
key string
err error
}{
"non-empty token": {
key: uuid.NewV4().String(),
err: nil,
},
"empty token": {
key: "",
err: things.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
req := identityReq{key: tc.key}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestAddThingReqValidation(t *testing.T) { func TestAddThingReqValidation(t *testing.T) {
key := uuid.NewV4().String() token := uuid.NewV4().String()
valid := things.Thing{} valid := things.Thing{}
cases := map[string]struct { cases := map[string]struct {
thing things.Thing thing things.Thing
key string token string
err error err error
}{ }{
"valid thing addition request": { "valid thing addition request": {
thing: valid, thing: valid,
key: key, token: token,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
thing: valid, thing: valid,
key: "", err: things.ErrUnauthorizedAccess, token: "",
err: things.ErrUnauthorizedAccess,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := addThingReq{ req := addThingReq{
key: tc.key, token: tc.token,
Name: tc.thing.Name, Name: tc.thing.Name,
Metadata: tc.thing.Metadata, Metadata: tc.thing.Metadata,
} }
@ -72,38 +51,38 @@ func TestAddThingReqValidation(t *testing.T) {
} }
func TestUpdateThingReqValidation(t *testing.T) { func TestUpdateThingReqValidation(t *testing.T) {
key := uuid.NewV4().String() token := uuid.NewV4().String()
valid := things.Thing{ID: "1"} valid := things.Thing{ID: "1"}
cases := map[string]struct { cases := map[string]struct {
thing things.Thing thing things.Thing
id string id string
key string token string
err error err error
}{ }{
"valid thing update request": { "valid thing update request": {
thing: valid, thing: valid,
id: valid.ID, id: valid.ID,
key: key, token: token,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
thing: valid, thing: valid,
id: valid.ID, id: valid.ID,
key: "", token: "",
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"empty thing id": { "empty thing id": {
thing: valid, thing: valid,
id: "", id: "",
key: key, token: token,
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := updateThingReq{ req := updateThingReq{
key: tc.key, token: tc.token,
id: tc.id, id: tc.id,
Name: tc.thing.Name, Name: tc.thing.Name,
Metadata: tc.thing.Metadata, Metadata: tc.thing.Metadata,
@ -114,31 +93,79 @@ func TestUpdateThingReqValidation(t *testing.T) {
} }
} }
func TestUpdateKeyReqValidation(t *testing.T) {
token := uuid.NewV4().String()
thing := things.Thing{ID: "1", Key: "key"}
cases := map[string]struct {
token string
id string
key string
err error
}{
"valid key update request": {
token: token,
id: thing.ID,
key: thing.Key,
err: nil,
},
"missing token": {
token: "",
id: thing.ID,
key: thing.Key,
err: things.ErrUnauthorizedAccess,
},
"empty thing id": {
token: token,
id: "",
key: thing.Key,
err: things.ErrMalformedEntity,
},
"empty key": {
token: token,
id: thing.ID,
key: "",
err: things.ErrMalformedEntity,
},
}
for desc, tc := range cases {
req := updateKeyReq{
token: tc.token,
id: tc.id,
Key: tc.key,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestCreateChannelReqValidation(t *testing.T) { func TestCreateChannelReqValidation(t *testing.T) {
channel := things.Channel{} channel := things.Channel{}
key := uuid.NewV4().String() token := uuid.NewV4().String()
cases := map[string]struct { cases := map[string]struct {
channel things.Channel channel things.Channel
key string token string
err error err error
}{ }{
"valid channel creation request": { "valid channel creation request": {
channel: channel, channel: channel,
key: key, token: token,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
channel: channel, channel: channel,
key: "", token: "",
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := createChannelReq{ req := createChannelReq{
key: tc.key, token: tc.token,
Name: tc.channel.Name, Name: tc.channel.Name,
} }
err := req.validate() err := req.validate()
@ -147,40 +174,40 @@ func TestCreateChannelReqValidation(t *testing.T) {
} }
func TestUpdateChannelReqValidation(t *testing.T) { func TestUpdateChannelReqValidation(t *testing.T) {
key := uuid.NewV4().String() token := uuid.NewV4().String()
channel := things.Channel{ID: "1"} channel := things.Channel{ID: "1"}
cases := map[string]struct { cases := map[string]struct {
channel things.Channel channel things.Channel
id string id string
key string token string
err error err error
}{ }{
"valid channel update request": { "valid channel update request": {
channel: channel, channel: channel,
id: channel.ID, id: channel.ID,
key: key, token: token,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
channel: channel, channel: channel,
id: channel.ID, id: channel.ID,
key: "", token: "",
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"empty channel id": { "empty channel id": {
channel: channel, channel: channel,
id: "", id: "",
key: key, token: token,
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := updateChannelReq{ req := updateChannelReq{
key: tc.key, token: tc.token,
id: tc.id, id: tc.id,
Name: tc.channel.Name, Name: tc.channel.Name,
} }
err := req.validate() err := req.validate()
@ -189,68 +216,68 @@ func TestUpdateChannelReqValidation(t *testing.T) {
} }
func TestViewResourceReqValidation(t *testing.T) { func TestViewResourceReqValidation(t *testing.T) {
key := uuid.NewV4().String() token := uuid.NewV4().String()
id := uint64(1) id := uint64(1)
cases := map[string]struct { cases := map[string]struct {
id string id string
key string token string
err error err error
}{ }{
"valid resource viewing request": { "valid resource viewing request": {
id: strconv.FormatUint(id, 10), id: strconv.FormatUint(id, 10),
key: key, token: token,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
id: strconv.FormatUint(id, 10), id: strconv.FormatUint(id, 10),
key: "", token: "",
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"empty resource id": { "empty resource id": {
id: "", id: "",
key: key, token: token,
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
req := viewResourceReq{tc.key, tc.id} req := viewResourceReq{tc.token, tc.id}
err := req.validate() err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
} }
} }
func TestListResourcesReqValidation(t *testing.T) { func TestListResourcesReqValidation(t *testing.T) {
key := uuid.NewV4().String() token := uuid.NewV4().String()
value := uint64(10) value := uint64(10)
cases := map[string]struct { cases := map[string]struct {
key string token string
offset uint64 offset uint64
limit uint64 limit uint64
err error err error
}{ }{
"valid listing request": { "valid listing request": {
key: key, token: token,
offset: value, offset: value,
limit: value, limit: value,
err: nil, err: nil,
}, },
"missing token": { "missing token": {
key: "", token: "",
offset: value, offset: value,
limit: value, limit: value,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"zero limit": { "zero limit": {
key: key, token: token,
offset: value, offset: value,
limit: 0, limit: 0,
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
"too big limit": { "too big limit": {
key: key, token: token,
offset: value, offset: value,
limit: 20 * value, limit: 20 * value,
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
@ -259,7 +286,7 @@ func TestListResourcesReqValidation(t *testing.T) {
for desc, tc := range cases { for desc, tc := range cases {
req := listResourcesReq{ req := listResourcesReq{
key: tc.key, token: tc.token,
offset: tc.offset, offset: tc.offset,
limit: tc.limit, limit: tc.limit,
} }
@ -271,31 +298,31 @@ func TestListResourcesReqValidation(t *testing.T) {
func TestConnectionReqValidation(t *testing.T) { func TestConnectionReqValidation(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
key string token string
chanID string chanID string
thingID string thingID string
err error err error
}{ }{
"valid key": { "valid token": {
key: "valid-key", token: "valid-token",
chanID: "1", chanID: "1",
thingID: "1", thingID: "1",
err: nil, err: nil,
}, },
"empty key": { "empty token": {
key: "", token: "",
chanID: "1", chanID: "1",
thingID: "1", thingID: "1",
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"empty channel id": { "empty channel id": {
key: "valid-key", token: "valid-token",
chanID: "", chanID: "",
thingID: "1", thingID: "1",
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
"empty thing id": { "empty thing id": {
key: "valid-key", token: "valid-token",
chanID: "1", chanID: "1",
thingID: "", thingID: "",
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
@ -304,7 +331,7 @@ func TestConnectionReqValidation(t *testing.T) {
for desc, tc := range cases { for desc, tc := range cases {
req := connectionReq{ req := connectionReq{
key: tc.key, token: tc.token,
chanID: tc.chanID, chanID: tc.chanID,
thingID: tc.thingID, thingID: tc.thingID,
} }

View File

@ -52,6 +52,13 @@ func MakeHandler(svc things.Service) http.Handler {
opts..., opts...,
)) ))
r.Patch("/things/:id/key", kithttp.NewServer(
updateKeyEndpoint(svc),
decodeKeyUpdate,
encodeResponse,
opts...,
))
r.Put("/things/:id", kithttp.NewServer( r.Put("/things/:id", kithttp.NewServer(
updateThingEndpoint(svc), updateThingEndpoint(svc),
decodeThingUpdate, decodeThingUpdate,
@ -154,7 +161,7 @@ func decodeThingCreation(_ context.Context, r *http.Request) (interface{}, error
return nil, errUnsupportedContentType return nil, errUnsupportedContentType
} }
req := addThingReq{key: r.Header.Get("Authorization")} req := addThingReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }
@ -168,8 +175,24 @@ func decodeThingUpdate(_ context.Context, r *http.Request) (interface{}, error)
} }
req := updateThingReq{ req := updateThingReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"), id: bone.GetValue(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeKeyUpdate(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := updateKeyReq{
token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"),
} }
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
@ -183,7 +206,7 @@ func decodeChannelCreation(_ context.Context, r *http.Request) (interface{}, err
return nil, errUnsupportedContentType return nil, errUnsupportedContentType
} }
req := createChannelReq{key: r.Header.Get("Authorization")} req := createChannelReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
} }
@ -197,8 +220,8 @@ func decodeChannelUpdate(_ context.Context, r *http.Request) (interface{}, error
} }
req := updateChannelReq{ req := updateChannelReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"), id: bone.GetValue(r, "id"),
} }
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err return nil, err
@ -209,8 +232,8 @@ func decodeChannelUpdate(_ context.Context, r *http.Request) (interface{}, error
func decodeView(_ context.Context, r *http.Request) (interface{}, error) { func decodeView(_ context.Context, r *http.Request) (interface{}, error) {
req := viewResourceReq{ req := viewResourceReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"), id: bone.GetValue(r, "id"),
} }
return req, nil return req, nil
@ -228,7 +251,7 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) {
} }
req := listResourcesReq{ req := listResourcesReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
offset: o, offset: o,
limit: l, limit: l,
} }
@ -248,7 +271,7 @@ func decodeListByConnection(_ context.Context, r *http.Request) (interface{}, er
} }
req := listByConnectionReq{ req := listByConnectionReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"), id: bone.GetValue(r, "id"),
offset: o, offset: o,
limit: l, limit: l,
@ -259,7 +282,7 @@ func decodeListByConnection(_ context.Context, r *http.Request) (interface{}, er
func decodeConnection(_ context.Context, r *http.Request) (interface{}, error) { func decodeConnection(_ context.Context, r *http.Request) (interface{}, error) {
req := connectionReq{ req := connectionReq{
key: r.Header.Get("Authorization"), token: r.Header.Get("Authorization"),
chanID: bone.GetValue(r, "chanId"), chanID: bone.GetValue(r, "chanId"),
thingID: bone.GetValue(r, "thingId"), thingID: bone.GetValue(r, "thingId"),
} }
@ -295,6 +318,8 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
case things.ErrNotFound: case things.ErrNotFound:
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
case things.ErrConflict:
w.WriteHeader(http.StatusUnprocessableEntity)
case errUnsupportedContentType: case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType) w.WriteHeader(http.StatusUnsupportedMediaType)
case errInvalidQueryParams: case errInvalidQueryParams:

View File

@ -29,9 +29,9 @@ func LoggingMiddleware(svc things.Service, logger log.Logger) things.Service {
return &loggingMiddleware{logger, svc} return &loggingMiddleware{logger, svc}
} }
func (lm *loggingMiddleware) AddThing(key string, thing things.Thing) (saved things.Thing, err error) { func (lm *loggingMiddleware) AddThing(token string, thing things.Thing) (saved things.Thing, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method add_thing for key %s and thing %s took %s to complete", key, saved.ID, time.Since(begin)) message := fmt.Sprintf("Method add_thing for token %s and thing %s took %s to complete", token, saved.ID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -39,12 +39,12 @@ func (lm *loggingMiddleware) AddThing(key string, thing things.Thing) (saved thi
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.AddThing(key, thing) return lm.svc.AddThing(token, thing)
} }
func (lm *loggingMiddleware) UpdateThing(key string, thing things.Thing) (err error) { func (lm *loggingMiddleware) UpdateThing(token string, thing things.Thing) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method update_thing for key %s and thing %s took %s to complete", key, thing.ID, time.Since(begin)) message := fmt.Sprintf("Method update_thing for token %s and thing %s took %s to complete", token, thing.ID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -52,12 +52,12 @@ func (lm *loggingMiddleware) UpdateThing(key string, thing things.Thing) (err er
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.UpdateThing(key, thing) return lm.svc.UpdateThing(token, thing)
} }
func (lm *loggingMiddleware) ViewThing(key, id string) (thing things.Thing, err error) { func (lm *loggingMiddleware) UpdateKey(token, id, key string) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method view_thing for key %s and thing %s took %s to complete", key, id, time.Since(begin)) message := fmt.Sprintf("Method update_key for thing %s and key %s took %s to complete", id, key, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -65,12 +65,12 @@ func (lm *loggingMiddleware) ViewThing(key, id string) (thing things.Thing, err
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ViewThing(key, id) return lm.svc.UpdateKey(token, id, key)
} }
func (lm *loggingMiddleware) ListThings(key string, offset, limit uint64) (_ things.ThingsPage, err error) { func (lm *loggingMiddleware) ViewThing(token, id string) (thing things.Thing, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method list_things for key %s took %s to complete", key, time.Since(begin)) message := fmt.Sprintf("Method view_thing for token %s and thing %s took %s to complete", token, id, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -78,10 +78,23 @@ func (lm *loggingMiddleware) ListThings(key string, offset, limit uint64) (_ thi
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ListThings(key, offset, limit) return lm.svc.ViewThing(token, id)
} }
func (lm *loggingMiddleware) ListThingsByChannel(key, id string, offset, limit uint64) (_ things.ThingsPage, err error) { func (lm *loggingMiddleware) ListThings(token string, offset, limit uint64) (_ things.ThingsPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_things for token %s took %s to complete", token, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListThings(token, offset, limit)
}
func (lm *loggingMiddleware) ListThingsByChannel(token, id string, offset, limit uint64) (_ things.ThingsPage, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method list_things_by_channel for channel %s took %s to complete", id, time.Since(begin)) message := fmt.Sprintf("Method list_things_by_channel for channel %s took %s to complete", id, time.Since(begin))
if err != nil { if err != nil {
@ -90,12 +103,12 @@ func (lm *loggingMiddleware) ListThingsByChannel(key, id string, offset, limit u
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ListThingsByChannel(key, id, offset, limit) return lm.svc.ListThingsByChannel(token, id, offset, limit)
} }
func (lm *loggingMiddleware) RemoveThing(key, id string) (err error) { func (lm *loggingMiddleware) RemoveThing(token, id string) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_thing for key %s and thing %s took %s to complete", key, id, time.Since(begin)) message := fmt.Sprintf("Method remove_thing for token %s and thing %s took %s to complete", token, id, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -103,12 +116,12 @@ func (lm *loggingMiddleware) RemoveThing(key, id string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.RemoveThing(key, id) return lm.svc.RemoveThing(token, id)
} }
func (lm *loggingMiddleware) CreateChannel(key string, channel things.Channel) (saved things.Channel, err error) { func (lm *loggingMiddleware) CreateChannel(token string, channel things.Channel) (saved things.Channel, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method create_channel for key %s and channel %s took %s to complete", key, channel.ID, time.Since(begin)) message := fmt.Sprintf("Method create_channel for token %s and channel %s took %s to complete", token, channel.ID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -116,12 +129,12 @@ func (lm *loggingMiddleware) CreateChannel(key string, channel things.Channel) (
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.CreateChannel(key, channel) return lm.svc.CreateChannel(token, channel)
} }
func (lm *loggingMiddleware) UpdateChannel(key string, channel things.Channel) (err error) { func (lm *loggingMiddleware) UpdateChannel(token string, channel things.Channel) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method update_channel for key %s and channel %s took %s to complete", key, channel.ID, time.Since(begin)) message := fmt.Sprintf("Method update_channel for token %s and channel %s took %s to complete", token, channel.ID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -129,12 +142,12 @@ func (lm *loggingMiddleware) UpdateChannel(key string, channel things.Channel) (
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.UpdateChannel(key, channel) return lm.svc.UpdateChannel(token, channel)
} }
func (lm *loggingMiddleware) ViewChannel(key, id string) (channel things.Channel, err error) { func (lm *loggingMiddleware) ViewChannel(token, id string) (channel things.Channel, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method view_channel for key %s and channel %s took %s to complete", key, id, time.Since(begin)) message := fmt.Sprintf("Method view_channel for token %s and channel %s took %s to complete", token, id, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -142,12 +155,12 @@ func (lm *loggingMiddleware) ViewChannel(key, id string) (channel things.Channel
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ViewChannel(key, id) return lm.svc.ViewChannel(token, id)
} }
func (lm *loggingMiddleware) ListChannels(key string, offset, limit uint64) (_ things.ChannelsPage, err error) { func (lm *loggingMiddleware) ListChannels(token string, offset, limit uint64) (_ things.ChannelsPage, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method list_channels for key %s took %s to complete", key, time.Since(begin)) message := fmt.Sprintf("Method list_channels for token %s took %s to complete", token, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -155,10 +168,10 @@ func (lm *loggingMiddleware) ListChannels(key string, offset, limit uint64) (_ t
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ListChannels(key, offset, limit) return lm.svc.ListChannels(token, offset, limit)
} }
func (lm *loggingMiddleware) ListChannelsByThing(key, id string, offset, limit uint64) (_ things.ChannelsPage, err error) { func (lm *loggingMiddleware) ListChannelsByThing(token, id string, offset, limit uint64) (_ things.ChannelsPage, err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method list_channels_by_thing for thing %s took %s to complete", id, time.Since(begin)) message := fmt.Sprintf("Method list_channels_by_thing for thing %s took %s to complete", id, time.Since(begin))
if err != nil { if err != nil {
@ -167,12 +180,12 @@ func (lm *loggingMiddleware) ListChannelsByThing(key, id string, offset, limit u
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.ListChannelsByThing(key, id, offset, limit) return lm.svc.ListChannelsByThing(token, id, offset, limit)
} }
func (lm *loggingMiddleware) RemoveChannel(key, id string) (err error) { func (lm *loggingMiddleware) RemoveChannel(token, id string) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_channel for key %s and channel %s took %s to complete", key, id, time.Since(begin)) message := fmt.Sprintf("Method remove_channel for token %s and channel %s took %s to complete", token, id, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -180,12 +193,12 @@ func (lm *loggingMiddleware) RemoveChannel(key, id string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.RemoveChannel(key, id) return lm.svc.RemoveChannel(token, id)
} }
func (lm *loggingMiddleware) Connect(key, chanID, thingID string) (err error) { func (lm *loggingMiddleware) Connect(token, chanID, thingID string) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method connect for key %s, channel %s and thing %s took %s to complete", key, chanID, thingID, time.Since(begin)) message := fmt.Sprintf("Method connect for token %s, channel %s and thing %s took %s to complete", token, chanID, thingID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -193,12 +206,12 @@ func (lm *loggingMiddleware) Connect(key, chanID, thingID string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.Connect(key, chanID, thingID) return lm.svc.Connect(token, chanID, thingID)
} }
func (lm *loggingMiddleware) Disconnect(key, chanID, thingID string) (err error) { func (lm *loggingMiddleware) Disconnect(token, chanID, thingID string) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
message := fmt.Sprintf("Method disconnect for key %s, channel %s and thing %s took %s to complete", key, chanID, thingID, time.Since(begin)) message := fmt.Sprintf("Method disconnect for token %s, channel %s and thing %s took %s to complete", token, chanID, thingID, time.Since(begin))
if err != nil { if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err)) lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return return
@ -206,7 +219,7 @@ func (lm *loggingMiddleware) Disconnect(key, chanID, thingID string) (err error)
lm.logger.Info(fmt.Sprintf("%s without errors.", message)) lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now()) }(time.Now())
return lm.svc.Disconnect(key, chanID, thingID) return lm.svc.Disconnect(token, chanID, thingID)
} }
func (lm *loggingMiddleware) CanAccess(id, key string) (thing string, err error) { func (lm *loggingMiddleware) CanAccess(id, key string) (thing string, err error) {

View File

@ -34,130 +34,139 @@ func MetricsMiddleware(svc things.Service, counter metrics.Counter, latency metr
} }
} }
func (ms *metricsMiddleware) AddThing(key string, thing things.Thing) (things.Thing, error) { func (ms *metricsMiddleware) AddThing(token string, thing things.Thing) (things.Thing, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "add_thing").Add(1) ms.counter.With("method", "add_thing").Add(1)
ms.latency.With("method", "add_thing").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "add_thing").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.AddThing(key, thing) return ms.svc.AddThing(token, thing)
} }
func (ms *metricsMiddleware) UpdateThing(key string, thing things.Thing) error { func (ms *metricsMiddleware) UpdateThing(token string, thing things.Thing) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "update_thing").Add(1) ms.counter.With("method", "update_thing").Add(1)
ms.latency.With("method", "update_thing").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "update_thing").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.UpdateThing(key, thing) return ms.svc.UpdateThing(token, thing)
} }
func (ms *metricsMiddleware) ViewThing(key, id string) (things.Thing, error) { func (ms *metricsMiddleware) UpdateKey(token, id, key string) error {
defer func(begin time.Time) {
ms.counter.With("method", "update_key").Add(1)
ms.latency.With("method", "update_key").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateKey(token, id, key)
}
func (ms *metricsMiddleware) ViewThing(token, id string) (things.Thing, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "view_thing").Add(1) ms.counter.With("method", "view_thing").Add(1)
ms.latency.With("method", "view_thing").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "view_thing").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ViewThing(key, id) return ms.svc.ViewThing(token, id)
} }
func (ms *metricsMiddleware) ListThings(key string, offset, limit uint64) (things.ThingsPage, error) { func (ms *metricsMiddleware) ListThings(token string, offset, limit uint64) (things.ThingsPage, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "list_things").Add(1) ms.counter.With("method", "list_things").Add(1)
ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ListThings(key, offset, limit) return ms.svc.ListThings(token, offset, limit)
} }
func (ms *metricsMiddleware) ListThingsByChannel(key, id string, offset, limit uint64) (things.ThingsPage, error) { func (ms *metricsMiddleware) ListThingsByChannel(token, id string, offset, limit uint64) (things.ThingsPage, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "list_things_by_channel").Add(1) ms.counter.With("method", "list_things_by_channel").Add(1)
ms.latency.With("method", "list_things_by_channel").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "list_things_by_channel").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ListThingsByChannel(key, id, offset, limit) return ms.svc.ListThingsByChannel(token, id, offset, limit)
} }
func (ms *metricsMiddleware) RemoveThing(key, id string) error { func (ms *metricsMiddleware) RemoveThing(token, id string) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "remove_thing").Add(1) ms.counter.With("method", "remove_thing").Add(1)
ms.latency.With("method", "remove_thing").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "remove_thing").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.RemoveThing(key, id) return ms.svc.RemoveThing(token, id)
} }
func (ms *metricsMiddleware) CreateChannel(key string, channel things.Channel) (things.Channel, error) { func (ms *metricsMiddleware) CreateChannel(token string, channel things.Channel) (things.Channel, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "create_channel").Add(1) ms.counter.With("method", "create_channel").Add(1)
ms.latency.With("method", "create_channel").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "create_channel").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.CreateChannel(key, channel) return ms.svc.CreateChannel(token, channel)
} }
func (ms *metricsMiddleware) UpdateChannel(key string, channel things.Channel) error { func (ms *metricsMiddleware) UpdateChannel(token string, channel things.Channel) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "update_channel").Add(1) ms.counter.With("method", "update_channel").Add(1)
ms.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.UpdateChannel(key, channel) return ms.svc.UpdateChannel(token, channel)
} }
func (ms *metricsMiddleware) ViewChannel(key, id string) (things.Channel, error) { func (ms *metricsMiddleware) ViewChannel(token, id string) (things.Channel, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "view_channel").Add(1) ms.counter.With("method", "view_channel").Add(1)
ms.latency.With("method", "view_channel").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "view_channel").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ViewChannel(key, id) return ms.svc.ViewChannel(token, id)
} }
func (ms *metricsMiddleware) ListChannels(key string, offset, limit uint64) (things.ChannelsPage, error) { func (ms *metricsMiddleware) ListChannels(token string, offset, limit uint64) (things.ChannelsPage, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "list_channels").Add(1) ms.counter.With("method", "list_channels").Add(1)
ms.latency.With("method", "list_channels").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "list_channels").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ListChannels(key, offset, limit) return ms.svc.ListChannels(token, offset, limit)
} }
func (ms *metricsMiddleware) ListChannelsByThing(key, id string, offset, limit uint64) (things.ChannelsPage, error) { func (ms *metricsMiddleware) ListChannelsByThing(token, id string, offset, limit uint64) (things.ChannelsPage, error) {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "list_channels_by_thing").Add(1) ms.counter.With("method", "list_channels_by_thing").Add(1)
ms.latency.With("method", "list_channels_by_thing").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "list_channels_by_thing").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.ListChannelsByThing(key, id, offset, limit) return ms.svc.ListChannelsByThing(token, id, offset, limit)
} }
func (ms *metricsMiddleware) RemoveChannel(key, id string) error { func (ms *metricsMiddleware) RemoveChannel(token, id string) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "remove_channel").Add(1) ms.counter.With("method", "remove_channel").Add(1)
ms.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.RemoveChannel(key, id) return ms.svc.RemoveChannel(token, id)
} }
func (ms *metricsMiddleware) Connect(key, chanID, thingID string) error { func (ms *metricsMiddleware) Connect(token, chanID, thingID string) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "connect").Add(1) ms.counter.With("method", "connect").Add(1)
ms.latency.With("method", "connect").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "connect").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.Connect(key, chanID, thingID) return ms.svc.Connect(token, chanID, thingID)
} }
func (ms *metricsMiddleware) Disconnect(key, chanID, thingID string) error { func (ms *metricsMiddleware) Disconnect(token, chanID, thingID string) error {
defer func(begin time.Time) { defer func(begin time.Time) {
ms.counter.With("method", "disconnect").Add(1) ms.counter.With("method", "disconnect").Add(1)
ms.latency.With("method", "disconnect").Observe(time.Since(begin).Seconds()) ms.latency.With("method", "disconnect").Observe(time.Since(begin).Seconds())
}(time.Now()) }(time.Now())
return ms.svc.Disconnect(key, chanID, thingID) return ms.svc.Disconnect(token, chanID, thingID)
} }
func (ms *metricsMiddleware) CanAccess(id, key string) (string, error) { func (ms *metricsMiddleware) CanAccess(id, key string) (string, error) {

View File

@ -51,6 +51,12 @@ func (trm *thingRepositoryMock) Save(thing things.Thing) (string, error) {
trm.mu.Lock() trm.mu.Lock()
defer trm.mu.Unlock() defer trm.mu.Unlock()
for _, th := range trm.things {
if th.Key == thing.Key {
return "", things.ErrConflict
}
}
trm.counter++ trm.counter++
thing.ID = strconv.FormatUint(trm.counter, 10) thing.ID = strconv.FormatUint(trm.counter, 10)
trm.things[key(thing.Owner, thing.ID)] = thing trm.things[key(thing.Owner, thing.ID)] = thing
@ -73,6 +79,29 @@ func (trm *thingRepositoryMock) Update(thing things.Thing) error {
return nil return nil
} }
func (trm *thingRepositoryMock) UpdateKey(owner, id, val string) error {
trm.mu.Lock()
defer trm.mu.Unlock()
for _, th := range trm.things {
if th.Key == val {
return things.ErrConflict
}
}
dbKey := key(owner, id)
th, ok := trm.things[dbKey]
if !ok {
return things.ErrNotFound
}
th.Key = val
trm.things[dbKey] = th
return nil
}
func (trm *thingRepositoryMock) RetrieveByID(owner, id string) (things.Thing, error) { func (trm *thingRepositoryMock) RetrieveByID(owner, id string) (things.Thing, error) {
trm.mu.Lock() trm.mu.Lock()
defer trm.mu.Unlock() defer trm.mu.Unlock()

View File

@ -55,7 +55,7 @@ func migrateDB(db *sqlx.DB) error {
`CREATE TABLE IF NOT EXISTS things ( `CREATE TABLE IF NOT EXISTS things (
id UUID, id UUID,
owner VARCHAR(254), owner VARCHAR(254),
key CHAR(36) UNIQUE NOT NULL, key VARCHAR(4096) UNIQUE NOT NULL,
name TEXT, name TEXT,
metadata JSON, metadata JSON,
PRIMARY KEY (id, owner) PRIMARY KEY (id, owner)

View File

@ -43,8 +43,13 @@ func (tr thingRepository) Save(thing things.Thing) (string, error) {
_, err = tr.db.NamedExec(q, dbth) _, err = tr.db.NamedExec(q, dbth)
if err != nil { if err != nil {
pqErr, ok := err.(*pq.Error) pqErr, ok := err.(*pq.Error)
if ok && errInvalid == pqErr.Code.Name() { if ok {
return "", things.ErrMalformedEntity switch pqErr.Code.Name() {
case errInvalid:
return "", things.ErrMalformedEntity
case errDuplicate:
return "", things.ErrConflict
}
} }
return "", err return "", err
@ -83,6 +88,41 @@ func (tr thingRepository) Update(thing things.Thing) error {
return nil return nil
} }
func (tr thingRepository) UpdateKey(owner, id, key string) error {
q := `UPDATE things SET key = :key WHERE owner = :owner AND id = :id;`
dbth := dbThing{
ID: id,
Owner: owner,
Key: key,
}
res, err := tr.db.NamedExec(q, dbth)
if err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid:
return things.ErrMalformedEntity
case errDuplicate:
return things.ErrConflict
}
}
return err
}
cnt, err := res.RowsAffected()
if err != nil {
return err
}
if cnt == 0 {
return things.ErrNotFound
}
return nil
}
func (tr thingRepository) RetrieveByID(owner, id string) (things.Thing, error) { func (tr thingRepository) RetrieveByID(owner, id string) (things.Thing, error) {
q := `SELECT name, key, metadata FROM things WHERE id = $1 AND owner = $2;` q := `SELECT name, key, metadata FROM things WHERE id = $1 AND owner = $2;`

View File

@ -48,6 +48,11 @@ func TestThingSave(t *testing.T) {
}, },
err: things.ErrMalformedEntity, err: things.ErrMalformedEntity,
}, },
{
desc: "create thing with conflicting key",
thing: thing,
err: things.ErrConflict,
},
} }
for _, tc := range cases { for _, tc := range cases {
@ -111,6 +116,78 @@ func TestThingUpdate(t *testing.T) {
} }
} }
func TestUpdateKey(t *testing.T) {
email := "thing-update=key@example.com"
newKey := "new-key"
thingRepo := postgres.NewThingRepository(db, testLog)
existingThing := things.Thing{
ID: uuid.New().ID(),
Owner: email,
Key: uuid.New().ID(),
}
existingID, _ := thingRepo.Save(existingThing)
existingThing.ID = existingID
thing := things.Thing{
ID: uuid.New().ID(),
Owner: email,
Key: uuid.New().ID(),
}
id, _ := thingRepo.Save(thing)
thing.ID = id
cases := []struct {
desc string
owner string
id string
key string
err error
}{
{
desc: "update key of an existing thing",
owner: thing.Owner,
id: thing.ID,
key: newKey,
err: nil,
},
{
desc: "update key of a non-existing thing with existing user",
owner: thing.Owner,
id: uuid.New().ID(),
key: newKey,
err: things.ErrNotFound,
},
{
desc: "update key of an existing thing with non-existing user",
owner: wrongValue,
id: thing.ID,
key: newKey,
err: things.ErrNotFound,
},
{
desc: "update key of a non-existing thing with non-existing user",
owner: wrongValue,
id: uuid.New().ID(),
key: newKey,
err: things.ErrNotFound,
},
{
desc: "update key with existing key value",
owner: thing.Owner,
id: thing.ID,
key: existingThing.Key,
err: things.ErrConflict,
},
}
for _, tc := range cases {
err := thingRepo.UpdateKey(tc.owner, tc.id, tc.key)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestSingleThingRetrieval(t *testing.T) { func TestSingleThingRetrieval(t *testing.T) {
email := "thing-single-retrieval@example.com" email := "thing-single-retrieval@example.com"
thingRepo := postgres.NewThingRepository(db, testLog) thingRepo := postgres.NewThingRepository(db, testLog)

View File

@ -33,8 +33,8 @@ func NewEventStoreMiddleware(svc things.Service, client *redis.Client) things.Se
} }
} }
func (es eventStore) AddThing(key string, thing things.Thing) (things.Thing, error) { func (es eventStore) AddThing(token string, thing things.Thing) (things.Thing, error) {
sth, err := es.svc.AddThing(key, thing) sth, err := es.svc.AddThing(token, thing)
if err != nil { if err != nil {
return sth, err return sth, err
} }
@ -55,8 +55,8 @@ func (es eventStore) AddThing(key string, thing things.Thing) (things.Thing, err
return sth, err return sth, err
} }
func (es eventStore) UpdateThing(key string, thing things.Thing) error { func (es eventStore) UpdateThing(token string, thing things.Thing) error {
if err := es.svc.UpdateThing(key, thing); err != nil { if err := es.svc.UpdateThing(token, thing); err != nil {
return err return err
} }
@ -75,20 +75,27 @@ func (es eventStore) UpdateThing(key string, thing things.Thing) error {
return nil return nil
} }
func (es eventStore) ViewThing(key, id string) (things.Thing, error) { // UpdateKey doesn't send event because key shouldn't be sent over stream.
return es.svc.ViewThing(key, id) // Maybe we can start publishing this event at some point, without key value
// in order to notify adapters to disconnect connected things after key update.
func (es eventStore) UpdateKey(token, id, key string) error {
return es.svc.UpdateKey(token, id, key)
} }
func (es eventStore) ListThings(key string, offset, limit uint64) (things.ThingsPage, error) { func (es eventStore) ViewThing(token, id string) (things.Thing, error) {
return es.svc.ListThings(key, offset, limit) return es.svc.ViewThing(token, id)
} }
func (es eventStore) ListThingsByChannel(key, id string, offset, limit uint64) (things.ThingsPage, error) { func (es eventStore) ListThings(token string, offset, limit uint64) (things.ThingsPage, error) {
return es.svc.ListThingsByChannel(key, id, offset, limit) return es.svc.ListThings(token, offset, limit)
} }
func (es eventStore) RemoveThing(key, id string) error { func (es eventStore) ListThingsByChannel(token, id string, offset, limit uint64) (things.ThingsPage, error) {
if err := es.svc.RemoveThing(key, id); err != nil { return es.svc.ListThingsByChannel(token, id, offset, limit)
}
func (es eventStore) RemoveThing(token, id string) error {
if err := es.svc.RemoveThing(token, id); err != nil {
return err return err
} }
@ -105,8 +112,8 @@ func (es eventStore) RemoveThing(key, id string) error {
return nil return nil
} }
func (es eventStore) CreateChannel(key string, channel things.Channel) (things.Channel, error) { func (es eventStore) CreateChannel(token string, channel things.Channel) (things.Channel, error) {
sch, err := es.svc.CreateChannel(key, channel) sch, err := es.svc.CreateChannel(token, channel)
if err != nil { if err != nil {
return sch, err return sch, err
} }
@ -127,8 +134,8 @@ func (es eventStore) CreateChannel(key string, channel things.Channel) (things.C
return sch, err return sch, err
} }
func (es eventStore) UpdateChannel(key string, channel things.Channel) error { func (es eventStore) UpdateChannel(token string, channel things.Channel) error {
if err := es.svc.UpdateChannel(key, channel); err != nil { if err := es.svc.UpdateChannel(token, channel); err != nil {
return err return err
} }
@ -147,20 +154,20 @@ func (es eventStore) UpdateChannel(key string, channel things.Channel) error {
return nil return nil
} }
func (es eventStore) ViewChannel(key, id string) (things.Channel, error) { func (es eventStore) ViewChannel(token, id string) (things.Channel, error) {
return es.svc.ViewChannel(key, id) return es.svc.ViewChannel(token, id)
} }
func (es eventStore) ListChannels(key string, offset, limit uint64) (things.ChannelsPage, error) { func (es eventStore) ListChannels(token string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannels(key, offset, limit) return es.svc.ListChannels(token, offset, limit)
} }
func (es eventStore) ListChannelsByThing(key, id string, offset, limit uint64) (things.ChannelsPage, error) { func (es eventStore) ListChannelsByThing(token, id string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannelsByThing(key, id, offset, limit) return es.svc.ListChannelsByThing(token, id, offset, limit)
} }
func (es eventStore) RemoveChannel(key, id string) error { func (es eventStore) RemoveChannel(token, id string) error {
if err := es.svc.RemoveChannel(key, id); err != nil { if err := es.svc.RemoveChannel(token, id); err != nil {
return err return err
} }
@ -177,8 +184,8 @@ func (es eventStore) RemoveChannel(key, id string) error {
return nil return nil
} }
func (es eventStore) Connect(key, chanID, thingID string) error { func (es eventStore) Connect(token, chanID, thingID string) error {
if err := es.svc.Connect(key, chanID, thingID); err != nil { if err := es.svc.Connect(token, chanID, thingID); err != nil {
return err return err
} }
@ -196,8 +203,8 @@ func (es eventStore) Connect(key, chanID, thingID string) error {
return nil return nil
} }
func (es eventStore) Disconnect(key, chanID, thingID string) error { func (es eventStore) Disconnect(token, chanID, thingID string) error {
if err := es.svc.Disconnect(key, chanID, thingID); err != nil { if err := es.svc.Disconnect(token, chanID, thingID); err != nil {
return err return err
} }

View File

@ -26,6 +26,9 @@ var (
// ErrNotFound indicates a non-existent entity request. // ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity") ErrNotFound = errors.New("non-existent entity")
// ErrConflict indicates that entity already exists.
ErrConflict = errors.New("entity already exists")
) )
// Service specifies an API that must be fullfiled by the domain service // Service specifies an API that must be fullfiled by the domain service
@ -38,6 +41,10 @@ type Service interface {
// belongs to the user identified by the provided key. // belongs to the user identified by the provided key.
UpdateThing(string, Thing) error UpdateThing(string, Thing) error
// UpdateKey updates key value of the existing thing. A non-nil error is
// returned to indicate operation failure.
UpdateKey(string, string, string) error
// ViewThing retrieves data about the thing identified with the provided // ViewThing retrieves data about the thing identified with the provided
// ID, that belongs to the user identified by the provided key. // ID, that belongs to the user identified by the provided key.
ViewThing(string, string) (Thing, error) ViewThing(string, string) (Thing, error)
@ -124,7 +131,7 @@ func New(users mainflux.UsersServiceClient, things ThingRepository, channels Cha
} }
} }
func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) { func (ts *thingsService) AddThing(token string, thing Thing) (Thing, error) {
if err := thing.Validate(); err != nil { if err := thing.Validate(); err != nil {
return Thing{}, ErrMalformedEntity return Thing{}, ErrMalformedEntity
} }
@ -132,14 +139,17 @@ func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return Thing{}, ErrUnauthorizedAccess return Thing{}, ErrUnauthorizedAccess
} }
thing.ID = ts.idp.ID() thing.ID = ts.idp.ID()
thing.Owner = res.GetValue() thing.Owner = res.GetValue()
thing.Key = ts.idp.ID()
if thing.Key == "" {
thing.Key = ts.idp.ID()
}
id, err := ts.things.Save(thing) id, err := ts.things.Save(thing)
if err != nil { if err != nil {
@ -150,7 +160,7 @@ func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) {
return thing, nil return thing, nil
} }
func (ts *thingsService) UpdateThing(key string, thing Thing) error { func (ts *thingsService) UpdateThing(token string, thing Thing) error {
if err := thing.Validate(); err != nil { if err := thing.Validate(); err != nil {
return ErrMalformedEntity return ErrMalformedEntity
} }
@ -158,7 +168,7 @@ func (ts *thingsService) UpdateThing(key string, thing Thing) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }
@ -168,11 +178,26 @@ func (ts *thingsService) UpdateThing(key string, thing Thing) error {
return ts.things.Update(thing) return ts.things.Update(thing)
} }
func (ts *thingsService) ViewThing(key, id string) (Thing, error) { func (ts *thingsService) UpdateKey(token, id, key string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return ErrUnauthorizedAccess
}
owner := res.GetValue()
return ts.things.UpdateKey(owner, id, key)
}
func (ts *thingsService) ViewThing(token, id string) (Thing, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return Thing{}, ErrUnauthorizedAccess return Thing{}, ErrUnauthorizedAccess
} }
@ -180,11 +205,11 @@ func (ts *thingsService) ViewThing(key, id string) (Thing, error) {
return ts.things.RetrieveByID(res.GetValue(), id) return ts.things.RetrieveByID(res.GetValue(), id)
} }
func (ts *thingsService) ListThings(key string, offset, limit uint64) (ThingsPage, error) { func (ts *thingsService) ListThings(token string, offset, limit uint64) (ThingsPage, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ThingsPage{}, ErrUnauthorizedAccess return ThingsPage{}, ErrUnauthorizedAccess
} }
@ -192,11 +217,11 @@ func (ts *thingsService) ListThings(key string, offset, limit uint64) (ThingsPag
return ts.things.RetrieveAll(res.GetValue(), offset, limit), nil return ts.things.RetrieveAll(res.GetValue(), offset, limit), nil
} }
func (ts *thingsService) ListThingsByChannel(key, channel string, offset, limit uint64) (ThingsPage, error) { func (ts *thingsService) ListThingsByChannel(token, channel string, offset, limit uint64) (ThingsPage, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ThingsPage{}, ErrUnauthorizedAccess return ThingsPage{}, ErrUnauthorizedAccess
} }
@ -204,11 +229,11 @@ func (ts *thingsService) ListThingsByChannel(key, channel string, offset, limit
return ts.things.RetrieveByChannel(res.GetValue(), channel, offset, limit), nil return ts.things.RetrieveByChannel(res.GetValue(), channel, offset, limit), nil
} }
func (ts *thingsService) RemoveThing(key string, id string) error { func (ts *thingsService) RemoveThing(token, id string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }
@ -217,11 +242,11 @@ func (ts *thingsService) RemoveThing(key string, id string) error {
return ts.things.Remove(res.GetValue(), id) return ts.things.Remove(res.GetValue(), id)
} }
func (ts *thingsService) CreateChannel(key string, channel Channel) (Channel, error) { func (ts *thingsService) CreateChannel(token string, channel Channel) (Channel, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return Channel{}, ErrUnauthorizedAccess return Channel{}, ErrUnauthorizedAccess
} }
@ -238,11 +263,11 @@ func (ts *thingsService) CreateChannel(key string, channel Channel) (Channel, er
return channel, nil return channel, nil
} }
func (ts *thingsService) UpdateChannel(key string, channel Channel) error { func (ts *thingsService) UpdateChannel(token string, channel Channel) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }
@ -251,11 +276,11 @@ func (ts *thingsService) UpdateChannel(key string, channel Channel) error {
return ts.channels.Update(channel) return ts.channels.Update(channel)
} }
func (ts *thingsService) ViewChannel(key, id string) (Channel, error) { func (ts *thingsService) ViewChannel(token, id string) (Channel, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return Channel{}, ErrUnauthorizedAccess return Channel{}, ErrUnauthorizedAccess
} }
@ -263,11 +288,11 @@ func (ts *thingsService) ViewChannel(key, id string) (Channel, error) {
return ts.channels.RetrieveByID(res.GetValue(), id) return ts.channels.RetrieveByID(res.GetValue(), id)
} }
func (ts *thingsService) ListChannels(key string, offset, limit uint64) (ChannelsPage, error) { func (ts *thingsService) ListChannels(token string, offset, limit uint64) (ChannelsPage, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ChannelsPage{}, ErrUnauthorizedAccess return ChannelsPage{}, ErrUnauthorizedAccess
} }
@ -275,11 +300,11 @@ func (ts *thingsService) ListChannels(key string, offset, limit uint64) (Channel
return ts.channels.RetrieveAll(res.GetValue(), offset, limit), nil return ts.channels.RetrieveAll(res.GetValue(), offset, limit), nil
} }
func (ts *thingsService) ListChannelsByThing(key, thing string, offset, limit uint64) (ChannelsPage, error) { func (ts *thingsService) ListChannelsByThing(token, thing string, offset, limit uint64) (ChannelsPage, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ChannelsPage{}, ErrUnauthorizedAccess return ChannelsPage{}, ErrUnauthorizedAccess
} }
@ -287,11 +312,11 @@ func (ts *thingsService) ListChannelsByThing(key, thing string, offset, limit ui
return ts.channels.RetrieveByThing(res.GetValue(), thing, offset, limit), nil return ts.channels.RetrieveByThing(res.GetValue(), thing, offset, limit), nil
} }
func (ts *thingsService) RemoveChannel(key, id string) error { func (ts *thingsService) RemoveChannel(token, id string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }
@ -300,11 +325,11 @@ func (ts *thingsService) RemoveChannel(key, id string) error {
return ts.channels.Remove(res.GetValue(), id) return ts.channels.Remove(res.GetValue(), id)
} }
func (ts *thingsService) Connect(key, chanID, thingID string) error { func (ts *thingsService) Connect(token, chanID, thingID string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }
@ -312,11 +337,11 @@ func (ts *thingsService) Connect(key, chanID, thingID string) error {
return ts.channels.Connect(res.GetValue(), chanID, thingID) return ts.channels.Connect(res.GetValue(), chanID, thingID)
} }
func (ts *thingsService) Disconnect(key, chanID, thingID string) error { func (ts *thingsService) Disconnect(token, chanID, thingID string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
res, err := ts.users.Identify(ctx, &mainflux.Token{Value: key}) res, err := ts.users.Identify(ctx, &mainflux.Token{Value: token})
if err != nil { if err != nil {
return ErrUnauthorizedAccess return ErrUnauthorizedAccess
} }

View File

@ -48,25 +48,25 @@ func TestAddThing(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
thing things.Thing thing things.Thing
key string token string
err error err error
}{ }{
{ {
desc: "add new thing", desc: "add new thing",
thing: things.Thing{Name: "a"}, thing: things.Thing{Name: "a"},
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "add thing with wrong credentials", desc: "add thing with wrong credentials",
thing: things.Thing{Name: "d"}, thing: things.Thing{Name: "d"},
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
_, err := svc.AddThing(tc.key, tc.thing) _, err := svc.AddThing(tc.token, tc.thing)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -79,31 +79,73 @@ func TestUpdateThing(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
thing things.Thing thing things.Thing
key string token string
err error err error
}{ }{
{ {
desc: "update existing thing", desc: "update existing thing",
thing: saved, thing: saved,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "update thing with wrong credentials", desc: "update thing with wrong credentials",
thing: saved, thing: saved,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "update non-existing thing", desc: "update non-existing thing",
thing: other, thing: other,
key: token, token: token,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.UpdateThing(tc.key, tc.thing) err := svc.UpdateThing(tc.token, tc.thing)
assert.Equal(t, tc.err, 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})
saved, err := svc.AddThing(token, thing)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
cases := []struct {
desc string
token string
id string
key string
err error
}{
{
desc: "update key of an existing thing",
token: token,
id: saved.ID,
key: key,
err: nil,
},
{
desc: "update key with invalid credentials",
token: wrongValue,
id: saved.ID,
key: key,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update key of non-existing thing",
token: token,
id: wrongID,
key: wrongValue,
err: things.ErrNotFound,
},
}
for _, tc := range cases {
err := svc.UpdateKey(tc.token, tc.id, tc.key)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -113,29 +155,29 @@ func TestViewThing(t *testing.T) {
saved, _ := svc.AddThing(token, thing) saved, _ := svc.AddThing(token, thing)
cases := map[string]struct { cases := map[string]struct {
id string id string
key string token string
err error err error
}{ }{
"view existing thing": { "view existing thing": {
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
"view thing with wrong credentials": { "view thing with wrong credentials": {
id: saved.ID, id: saved.ID,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"view non-existing thing": { "view non-existing thing": {
id: wrongID, id: wrongID,
key: token, token: token,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
_, err := svc.ViewThing(tc.key, tc.id) _, err := svc.ViewThing(tc.token, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
} }
} }
@ -149,49 +191,49 @@ func TestListThings(t *testing.T) {
} }
cases := map[string]struct { cases := map[string]struct {
key string token string
offset uint64 offset uint64
limit uint64 limit uint64
size uint64 size uint64
err error err error
}{ }{
"list all things": { "list all things": {
key: token, token: token,
offset: 0, offset: 0,
limit: n, limit: n,
size: n, size: n,
err: nil, err: nil,
}, },
"list half": { "list half": {
key: token, token: token,
offset: n / 2, offset: n / 2,
limit: n, limit: n,
size: n / 2, size: n / 2,
err: nil, err: nil,
}, },
"list last thing": { "list last thing": {
key: token, token: token,
offset: n - 1, offset: n - 1,
limit: n, limit: n,
size: 1, size: 1,
err: nil, err: nil,
}, },
"list empty set": { "list empty set": {
key: token, token: token,
offset: n + 1, offset: n + 1,
limit: n, limit: n,
size: 0, size: 0,
err: nil, err: nil,
}, },
"list with zero limit": { "list with zero limit": {
key: token, token: token,
offset: 1, offset: 1,
limit: 0, limit: 0,
size: 0, size: 0,
err: nil, err: nil,
}, },
"list with wrong credentials": { "list with wrong credentials": {
key: wrongValue, token: wrongValue,
offset: 0, offset: 0,
limit: 0, limit: 0,
size: 0, size: 0,
@ -200,7 +242,7 @@ func TestListThings(t *testing.T) {
} }
for desc, tc := range cases { for desc, tc := range cases {
page, err := svc.ListThings(tc.key, tc.offset, tc.limit) page, err := svc.ListThings(tc.token, tc.offset, tc.limit)
size := uint64(len(page.Things)) size := uint64(len(page.Things))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
@ -223,7 +265,7 @@ func TestListThingsByChannel(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
cases := map[string]struct { cases := map[string]struct {
key string token string
channel string channel string
offset uint64 offset uint64
limit uint64 limit uint64
@ -231,7 +273,7 @@ func TestListThingsByChannel(t *testing.T) {
err error err error
}{ }{
"list all things by existing channel": { "list all things by existing channel": {
key: token, token: token,
channel: sch.ID, channel: sch.ID,
offset: 0, offset: 0,
limit: n, limit: n,
@ -239,7 +281,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil, err: nil,
}, },
"list half of things by existing channel": { "list half of things by existing channel": {
key: token, token: token,
channel: sch.ID, channel: sch.ID,
offset: n / 2, offset: n / 2,
limit: n, limit: n,
@ -247,7 +289,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil, err: nil,
}, },
"list last thing by existing channel": { "list last thing by existing channel": {
key: token, token: token,
channel: sch.ID, channel: sch.ID,
offset: n - 1, offset: n - 1,
limit: n, limit: n,
@ -255,7 +297,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil, err: nil,
}, },
"list empty set of things by existing channel": { "list empty set of things by existing channel": {
key: token, token: token,
channel: sch.ID, channel: sch.ID,
offset: n + 1, offset: n + 1,
limit: n, limit: n,
@ -263,7 +305,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil, err: nil,
}, },
"list things by existing channel with zero limit": { "list things by existing channel with zero limit": {
key: token, token: token,
channel: sch.ID, channel: sch.ID,
offset: 1, offset: 1,
limit: 0, limit: 0,
@ -271,7 +313,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil, err: nil,
}, },
"list things by existing channel with wrong credentials": { "list things by existing channel with wrong credentials": {
key: wrongValue, token: wrongValue,
channel: sch.ID, channel: sch.ID,
offset: 0, offset: 0,
limit: 0, limit: 0,
@ -279,7 +321,7 @@ func TestListThingsByChannel(t *testing.T) {
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"list things by non-existent channel with wrong credentials": { "list things by non-existent channel with wrong credentials": {
key: token, token: token,
channel: "non-existent", channel: "non-existent",
offset: 0, offset: 0,
limit: 10, limit: 10,
@ -289,7 +331,7 @@ func TestListThingsByChannel(t *testing.T) {
} }
for desc, tc := range cases { for desc, tc := range cases {
page, err := svc.ListThingsByChannel(tc.key, tc.channel, tc.offset, tc.limit) page, err := svc.ListThingsByChannel(tc.token, tc.channel, tc.offset, tc.limit)
size := uint64(len(page.Things)) size := uint64(len(page.Things))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
@ -301,39 +343,39 @@ func TestRemoveThing(t *testing.T) {
saved, _ := svc.AddThing(token, thing) saved, _ := svc.AddThing(token, thing)
cases := []struct { cases := []struct {
desc string desc string
id string id string
key string token string
err error err error
}{ }{
{ {
desc: "remove thing with wrong credentials", desc: "remove thing with wrong credentials",
id: saved.ID, id: saved.ID,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "remove existing thing", desc: "remove existing thing",
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "remove removed thing", desc: "remove removed thing",
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "remove non-existing thing", desc: "remove non-existing thing",
id: wrongID, id: wrongID,
key: token, token: token,
err: nil, err: nil,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.RemoveThing(tc.key, tc.id) err := svc.RemoveThing(tc.token, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -344,25 +386,25 @@ func TestCreateChannel(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
channel things.Channel channel things.Channel
key string token string
err error err error
}{ }{
{ {
desc: "create channel", desc: "create channel",
channel: channel, channel: channel,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "create channel with wrong credentials", desc: "create channel with wrong credentials",
channel: channel, channel: channel,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
_, err := svc.CreateChannel(tc.key, tc.channel) _, err := svc.CreateChannel(tc.token, tc.channel)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -375,31 +417,31 @@ func TestUpdateChannel(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
channel things.Channel channel things.Channel
key string token string
err error err error
}{ }{
{ {
desc: "update existing channel", desc: "update existing channel",
channel: saved, channel: saved,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "update channel with wrong credentials", desc: "update channel with wrong credentials",
channel: saved, channel: saved,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "update non-existing channel", desc: "update non-existing channel",
channel: other, channel: other,
key: token, token: token,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.UpdateChannel(tc.key, tc.channel) err := svc.UpdateChannel(tc.token, tc.channel)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -409,29 +451,29 @@ func TestViewChannel(t *testing.T) {
saved, _ := svc.CreateChannel(token, channel) saved, _ := svc.CreateChannel(token, channel)
cases := map[string]struct { cases := map[string]struct {
id string id string
key string token string
err error err error
}{ }{
"view existing channel": { "view existing channel": {
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
"view channel with wrong credentials": { "view channel with wrong credentials": {
id: saved.ID, id: saved.ID,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"view non-existing channel": { "view non-existing channel": {
id: wrongID, id: wrongID,
key: token, token: token,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
_, err := svc.ViewChannel(tc.key, tc.id) _, err := svc.ViewChannel(tc.token, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
} }
} }
@ -444,49 +486,49 @@ func TestListChannels(t *testing.T) {
svc.CreateChannel(token, channel) svc.CreateChannel(token, channel)
} }
cases := map[string]struct { cases := map[string]struct {
key string token string
offset uint64 offset uint64
limit uint64 limit uint64
size uint64 size uint64
err error err error
}{ }{
"list all channels": { "list all channels": {
key: token, token: token,
offset: 0, offset: 0,
limit: n, limit: n,
size: n, size: n,
err: nil, err: nil,
}, },
"list half": { "list half": {
key: token, token: token,
offset: n / 2, offset: n / 2,
limit: n, limit: n,
size: n / 2, size: n / 2,
err: nil, err: nil,
}, },
"list last channel": { "list last channel": {
key: token, token: token,
offset: n - 1, offset: n - 1,
limit: n, limit: n,
size: 1, size: 1,
err: nil, err: nil,
}, },
"list empty set": { "list empty set": {
key: token, token: token,
offset: n + 1, offset: n + 1,
limit: n, limit: n,
size: 0, size: 0,
err: nil, err: nil,
}, },
"list with zero limit": { "list with zero limit": {
key: token, token: token,
offset: 1, offset: 1,
limit: 0, limit: 0,
size: 0, size: 0,
err: nil, err: nil,
}, },
"list with wrong credentials": { "list with wrong credentials": {
key: wrongValue, token: wrongValue,
offset: 0, offset: 0,
limit: 0, limit: 0,
size: 0, size: 0,
@ -495,7 +537,7 @@ func TestListChannels(t *testing.T) {
} }
for desc, tc := range cases { for desc, tc := range cases {
page, err := svc.ListChannels(tc.key, tc.offset, tc.limit) page, err := svc.ListChannels(tc.token, tc.offset, tc.limit)
size := uint64(len(page.Channels)) size := uint64(len(page.Channels))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
@ -518,7 +560,7 @@ func TestListChannelsByThing(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
cases := map[string]struct { cases := map[string]struct {
key string token string
thing string thing string
offset uint64 offset uint64
limit uint64 limit uint64
@ -526,7 +568,7 @@ func TestListChannelsByThing(t *testing.T) {
err error err error
}{ }{
"list all channels by existing thing": { "list all channels by existing thing": {
key: token, token: token,
thing: sth.ID, thing: sth.ID,
offset: 0, offset: 0,
limit: n, limit: n,
@ -534,7 +576,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil, err: nil,
}, },
"list half of channels by existing thing": { "list half of channels by existing thing": {
key: token, token: token,
thing: sth.ID, thing: sth.ID,
offset: n / 2, offset: n / 2,
limit: n, limit: n,
@ -542,7 +584,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil, err: nil,
}, },
"list last channel by existing thing": { "list last channel by existing thing": {
key: token, token: token,
thing: sth.ID, thing: sth.ID,
offset: n - 1, offset: n - 1,
limit: n, limit: n,
@ -550,7 +592,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil, err: nil,
}, },
"list empty set of channels by existing thing": { "list empty set of channels by existing thing": {
key: token, token: token,
thing: sth.ID, thing: sth.ID,
offset: n + 1, offset: n + 1,
limit: n, limit: n,
@ -558,7 +600,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil, err: nil,
}, },
"list channels by existing thing with zero limit": { "list channels by existing thing with zero limit": {
key: token, token: token,
thing: sth.ID, thing: sth.ID,
offset: 1, offset: 1,
limit: 0, limit: 0,
@ -566,7 +608,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil, err: nil,
}, },
"list channels by existing thing with wrong credentials": { "list channels by existing thing with wrong credentials": {
key: wrongValue, token: wrongValue,
thing: sth.ID, thing: sth.ID,
offset: 0, offset: 0,
limit: 0, limit: 0,
@ -574,7 +616,7 @@ func TestListChannelsByThing(t *testing.T) {
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"list channels by non-existent thing": { "list channels by non-existent thing": {
key: token, token: token,
thing: "non-existent", thing: "non-existent",
offset: 0, offset: 0,
limit: 10, limit: 10,
@ -584,7 +626,7 @@ func TestListChannelsByThing(t *testing.T) {
} }
for desc, tc := range cases { for desc, tc := range cases {
page, err := svc.ListChannelsByThing(tc.key, tc.thing, tc.offset, tc.limit) page, err := svc.ListChannelsByThing(tc.token, tc.thing, tc.offset, tc.limit)
size := uint64(len(page.Channels)) size := uint64(len(page.Channels))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size)) assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
@ -596,39 +638,39 @@ func TestRemoveChannel(t *testing.T) {
saved, _ := svc.CreateChannel(token, channel) saved, _ := svc.CreateChannel(token, channel)
cases := []struct { cases := []struct {
desc string desc string
id string id string
key string token string
err error err error
}{ }{
{ {
desc: "remove channel with wrong credentials", desc: "remove channel with wrong credentials",
id: saved.ID, id: saved.ID,
key: wrongValue, token: wrongValue,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "remove existing channel", desc: "remove existing channel",
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "remove removed channel", desc: "remove removed channel",
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
{ {
desc: "remove non-existing channel", desc: "remove non-existing channel",
id: saved.ID, id: saved.ID,
key: token, token: token,
err: nil, err: nil,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.RemoveChannel(tc.key, tc.id) err := svc.RemoveChannel(tc.token, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -641,35 +683,35 @@ func TestConnect(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
key string token string
chanID string chanID string
thingID string thingID string
err error err error
}{ }{
{ {
desc: "connect thing", desc: "connect thing",
key: token, token: token,
chanID: sch.ID, chanID: sch.ID,
thingID: sth.ID, thingID: sth.ID,
err: nil, err: nil,
}, },
{ {
desc: "connect thing with wrong credentials", desc: "connect thing with wrong credentials",
key: wrongValue, token: wrongValue,
chanID: sch.ID, chanID: sch.ID,
thingID: sth.ID, thingID: sth.ID,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "connect thing to non-existing channel", desc: "connect thing to non-existing channel",
key: token, token: token,
chanID: wrongID, chanID: wrongID,
thingID: sth.ID, thingID: sth.ID,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
{ {
desc: "connect non-existing thing to channel", desc: "connect non-existing thing to channel",
key: token, token: token,
chanID: sch.ID, chanID: sch.ID,
thingID: wrongID, thingID: wrongID,
err: things.ErrNotFound, err: things.ErrNotFound,
@ -677,7 +719,7 @@ func TestConnect(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.Connect(tc.key, tc.chanID, tc.thingID) err := svc.Connect(tc.token, tc.chanID, tc.thingID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
} }
@ -691,42 +733,42 @@ func TestDisconnect(t *testing.T) {
cases := []struct { cases := []struct {
desc string desc string
key string token string
chanID string chanID string
thingID string thingID string
err error err error
}{ }{
{ {
desc: "disconnect connected thing", desc: "disconnect connected thing",
key: token, token: token,
chanID: sch.ID, chanID: sch.ID,
thingID: sth.ID, thingID: sth.ID,
err: nil, err: nil,
}, },
{ {
desc: "disconnect disconnected thing", desc: "disconnect disconnected thing",
key: token, token: token,
chanID: sch.ID, chanID: sch.ID,
thingID: sth.ID, thingID: sth.ID,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
{ {
desc: "disconnect with wrong credentials", desc: "disconnect with wrong credentials",
key: wrongValue, token: wrongValue,
chanID: sch.ID, chanID: sch.ID,
thingID: sth.ID, thingID: sth.ID,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
{ {
desc: "disconnect from non-existing channel", desc: "disconnect from non-existing channel",
key: token, token: token,
chanID: wrongID, chanID: wrongID,
thingID: sth.ID, thingID: sth.ID,
err: things.ErrNotFound, err: things.ErrNotFound,
}, },
{ {
desc: "disconnect non-existing thing", desc: "disconnect non-existing thing",
key: token, token: token,
chanID: sch.ID, chanID: sch.ID,
thingID: wrongID, thingID: wrongID,
err: things.ErrNotFound, err: things.ErrNotFound,
@ -734,7 +776,7 @@ func TestDisconnect(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
err := svc.Disconnect(tc.key, tc.chanID, tc.thingID) err := svc.Disconnect(tc.token, tc.chanID, tc.thingID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} }
@ -748,29 +790,29 @@ func TestCanAccess(t *testing.T) {
svc.Connect(token, sch.ID, sth.ID) svc.Connect(token, sch.ID, sth.ID)
cases := map[string]struct { cases := map[string]struct {
key string token string
channel string channel string
err error err error
}{ }{
"allowed access": { "allowed access": {
key: sth.Key, token: sth.Key,
channel: sch.ID, channel: sch.ID,
err: nil, err: nil,
}, },
"not-connected cannot access": { "not-connected cannot access": {
key: wrongValue, token: wrongValue,
channel: sch.ID, channel: sch.ID,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
"access to non-existing channel": { "access to non-existing channel": {
key: sth.Key, token: sth.Key,
channel: wrongID, channel: wrongID,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
_, err := svc.CanAccess(tc.channel, tc.key) _, err := svc.CanAccess(tc.channel, tc.token)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
} }
} }
@ -781,24 +823,24 @@ func TestIdentify(t *testing.T) {
sth, _ := svc.AddThing(token, thing) sth, _ := svc.AddThing(token, thing)
cases := map[string]struct { cases := map[string]struct {
key string token string
id string id string
err error err error
}{ }{
"identify existing thing": { "identify existing thing": {
key: sth.Key, token: sth.Key,
id: sth.ID, id: sth.ID,
err: nil, err: nil,
}, },
"identify non-existing thing": { "identify non-existing thing": {
key: wrongValue, token: wrongValue,
id: wrongID, id: wrongID,
err: things.ErrUnauthorizedAccess, err: things.ErrUnauthorizedAccess,
}, },
} }
for desc, tc := range cases { for desc, tc := range cases {
id, err := svc.Identify(tc.key) id, err := svc.Identify(tc.token)
assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.id, id)) assert.Equal(t, tc.id, id, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.id, id))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err)) assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
} }

View File

@ -22,7 +22,7 @@ paths:
description: JSON-formatted document describing the new thing. description: JSON-formatted document describing the new thing.
in: body in: body
schema: schema:
$ref: "#/definitions/ThingReq" $ref: "#/definitions/CreateThingReq"
required: true required: true
responses: responses:
201: 201:
@ -120,7 +120,7 @@ paths:
description: JSON-formatted document describing the updated thing. description: JSON-formatted document describing the updated thing.
in: body in: body
schema: schema:
$ref: "#/definitions/ThingReq" $ref: "#/definitions/UpdateThingReq"
required: true required: true
responses: responses:
200: 200:
@ -154,6 +154,37 @@ paths:
description: Missing or invalid access token provided. description: Missing or invalid access token provided.
500: 500:
$ref: "#/responses/ServiceError" $ref: "#/responses/ServiceError"
/things/{thingId}/key:
patch:
summary: Updates thing key
description: |
Update is performed by replacing current key with a new one.
tags:
- things
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ThingId"
- name: key
description: JSON-formatted document describing updated key.
in: body
schema:
$ref: "#/definitions/UpdateKeyReq"
required: true
responses:
200:
description: Thing key updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Thing does not exist.
409:
description: Specified key already exists.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
/channels: /channels:
post: post:
summary: Creates new channel summary: Creates new channel
@ -461,14 +492,33 @@ definitions:
- id - id
- type - type
- key - key
ThingReq: CreateThingReq:
type: object
properties:
key:
type: string
description: |
Thing key that is used for thing auth. If there is
not one provided service will generate one in UUID
format.
name:
type: string
description: Free-form thing name.
metadata:
type: object
description: Custom thing's data in JSON format.
UpdateThingReq:
type: object type: object
properties: properties:
name: name:
type: string type: string
description: Free-form thing name. description: Free-form thing name.
metadata: metadata:
type: object
description: Custom thing's data in JSON format.
UpdateKeyReq:
type: object
properties:
key:
type: string type: string
description: Arbitrary, string-encoded thing's data. description: Thing key that is used for thing auth.
required:
- type

View File

@ -39,6 +39,10 @@ type ThingRepository interface {
// returned to indicate operation failure. // returned to indicate operation failure.
Update(Thing) error Update(Thing) error
// UpdateKey updates key value of the existing thing. A non-nil error is
// returned to indicate operation failure.
UpdateKey(string, string, string) error
// RetrieveByID retrieves the thing having the provided identifier, that is owned // RetrieveByID retrieves the thing having the provided identifier, that is owned
// by the specified user. // by the specified user.
RetrieveByID(string, string) (Thing, error) RetrieveByID(string, string) (Thing, error)