1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 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")
}
func (svc *mainfluxThings) UpdateKey(string, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListThings(string, uint64, uint64) (things.ThingsPage, error) {
panic("not implemented")
}

View File

@ -23,10 +23,11 @@ func addThingEndpoint(svc things.Service) endpoint.Endpoint {
}
thing := things.Thing{
Key: req.Key,
Name: req.Name,
Metadata: req.Metadata,
}
saved, err := svc.AddThing(req.key, thing)
saved, err := svc.AddThing(req.token, thing)
if err != nil {
return nil, err
}
@ -53,7 +54,24 @@ func updateThingEndpoint(svc things.Service) endpoint.Endpoint {
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
}
@ -70,7 +88,7 @@ func viewThingEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err
}
thing, err := svc.ViewThing(req.key, req.id)
thing, err := svc.ViewThing(req.token, req.id)
if err != nil {
return nil, err
}
@ -94,7 +112,7 @@ func listThingsEndpoint(svc things.Service) endpoint.Endpoint {
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 {
return nil, err
}
@ -130,7 +148,7 @@ func listThingsByChannelEndpoint(svc things.Service) endpoint.Endpoint {
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 {
return nil, err
}
@ -171,7 +189,7 @@ func removeThingEndpoint(svc things.Service) endpoint.Endpoint {
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
}
@ -188,7 +206,7 @@ func createChannelEndpoint(svc things.Service) endpoint.Endpoint {
}
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 {
return nil, err
}
@ -214,7 +232,7 @@ func updateChannelEndpoint(svc things.Service) endpoint.Endpoint {
Name: req.Name,
Metadata: req.Metadata,
}
if err := svc.UpdateChannel(req.key, channel); err != nil {
if err := svc.UpdateChannel(req.token, channel); err != nil {
return nil, err
}
@ -234,7 +252,7 @@ func viewChannelEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err
}
channel, err := svc.ViewChannel(req.key, req.id)
channel, err := svc.ViewChannel(req.token, req.id)
if err != nil {
return nil, err
}
@ -258,7 +276,7 @@ func listChannelsEndpoint(svc things.Service) endpoint.Endpoint {
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 {
return nil, err
}
@ -295,7 +313,7 @@ func listChannelsByThingEndpoint(svc things.Service) endpoint.Endpoint {
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 {
return nil, err
}
@ -333,7 +351,7 @@ func removeChannelEndpoint(svc things.Service) endpoint.Endpoint {
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
}
@ -349,7 +367,7 @@ func connectEndpoint(svc things.Service) endpoint.Endpoint {
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
}
@ -365,7 +383,7 @@ func disconnectEndpoint(svc things.Service) endpoint.Endpoint {
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
}

View File

@ -89,7 +89,9 @@ func TestAddThing(t *testing.T) {
ts := newServer(svc)
defer ts.Close()
data := toJSON(thing)
th := thing
th.Key = "key"
data := toJSON(th)
cases := []struct {
desc string
@ -107,6 +109,14 @@ func TestAddThing(t *testing.T) {
status: http.StatusCreated,
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",
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) {
svc := newService(map[string]string{token: email})
ts := newServer(svc)

View File

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

View File

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

View File

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

View File

@ -29,9 +29,9 @@ func LoggingMiddleware(svc things.Service, logger log.Logger) things.Service {
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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
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))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
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))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
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))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -78,10 +78,23 @@ func (lm *loggingMiddleware) ListThings(key string, offset, limit uint64) (_ thi
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
message := fmt.Sprintf("Method list_things_by_channel for channel %s took %s to complete", id, time.Since(begin))
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))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -103,12 +116,12 @@ func (lm *loggingMiddleware) RemoveThing(key, id string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -116,12 +129,12 @@ func (lm *loggingMiddleware) CreateChannel(key string, channel things.Channel) (
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -129,12 +142,12 @@ func (lm *loggingMiddleware) UpdateChannel(key string, channel things.Channel) (
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -142,12 +155,12 @@ func (lm *loggingMiddleware) ViewChannel(key, id string) (channel things.Channel
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -155,10 +168,10 @@ func (lm *loggingMiddleware) ListChannels(key string, offset, limit uint64) (_ t
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
message := fmt.Sprintf("Method list_channels_by_thing for thing %s took %s to complete", id, time.Since(begin))
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))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -180,12 +193,12 @@ func (lm *loggingMiddleware) RemoveChannel(key, id string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -193,12 +206,12 @@ func (lm *loggingMiddleware) Connect(key, chanID, thingID string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {
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 {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -206,7 +219,7 @@ func (lm *loggingMiddleware) Disconnect(key, chanID, thingID string) (err error)
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(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) {

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) {
ms.counter.With("method", "add_thing").Add(1)
ms.latency.With("method", "add_thing").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "update_thing").Add(1)
ms.latency.With("method", "update_thing").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "view_thing").Add(1)
ms.latency.With("method", "view_thing").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "list_things").Add(1)
ms.latency.With("method", "list_things").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "list_things_by_channel").Add(1)
ms.latency.With("method", "list_things_by_channel").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "remove_thing").Add(1)
ms.latency.With("method", "remove_thing").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "create_channel").Add(1)
ms.latency.With("method", "create_channel").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "update_channel").Add(1)
ms.latency.With("method", "update_channel").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "view_channel").Add(1)
ms.latency.With("method", "view_channel").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "list_channels").Add(1)
ms.latency.With("method", "list_channels").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "list_channels_by_thing").Add(1)
ms.latency.With("method", "list_channels_by_thing").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "remove_channel").Add(1)
ms.latency.With("method", "remove_channel").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "connect").Add(1)
ms.latency.With("method", "connect").Observe(time.Since(begin).Seconds())
}(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) {
ms.counter.With("method", "disconnect").Add(1)
ms.latency.With("method", "disconnect").Observe(time.Since(begin).Seconds())
}(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) {

View File

@ -51,6 +51,12 @@ func (trm *thingRepositoryMock) Save(thing things.Thing) (string, error) {
trm.mu.Lock()
defer trm.mu.Unlock()
for _, th := range trm.things {
if th.Key == thing.Key {
return "", things.ErrConflict
}
}
trm.counter++
thing.ID = strconv.FormatUint(trm.counter, 10)
trm.things[key(thing.Owner, thing.ID)] = thing
@ -73,6 +79,29 @@ func (trm *thingRepositoryMock) Update(thing things.Thing) error {
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) {
trm.mu.Lock()
defer trm.mu.Unlock()

View File

@ -55,7 +55,7 @@ func migrateDB(db *sqlx.DB) error {
`CREATE TABLE IF NOT EXISTS things (
id UUID,
owner VARCHAR(254),
key CHAR(36) UNIQUE NOT NULL,
key VARCHAR(4096) UNIQUE NOT NULL,
name TEXT,
metadata JSON,
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)
if err != nil {
pqErr, ok := err.(*pq.Error)
if ok && errInvalid == pqErr.Code.Name() {
return "", things.ErrMalformedEntity
if ok {
switch pqErr.Code.Name() {
case errInvalid:
return "", things.ErrMalformedEntity
case errDuplicate:
return "", things.ErrConflict
}
}
return "", err
@ -83,6 +88,41 @@ func (tr thingRepository) Update(thing things.Thing) error {
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) {
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,
},
{
desc: "create thing with conflicting key",
thing: thing,
err: things.ErrConflict,
},
}
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) {
email := "thing-single-retrieval@example.com"
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) {
sth, err := es.svc.AddThing(key, thing)
func (es eventStore) AddThing(token string, thing things.Thing) (things.Thing, error) {
sth, err := es.svc.AddThing(token, thing)
if err != nil {
return sth, err
}
@ -55,8 +55,8 @@ func (es eventStore) AddThing(key string, thing things.Thing) (things.Thing, err
return sth, err
}
func (es eventStore) UpdateThing(key string, thing things.Thing) error {
if err := es.svc.UpdateThing(key, thing); err != nil {
func (es eventStore) UpdateThing(token string, thing things.Thing) error {
if err := es.svc.UpdateThing(token, thing); err != nil {
return err
}
@ -75,20 +75,27 @@ func (es eventStore) UpdateThing(key string, thing things.Thing) error {
return nil
}
func (es eventStore) ViewThing(key, id string) (things.Thing, error) {
return es.svc.ViewThing(key, id)
// UpdateKey doesn't send event because key shouldn't be sent over stream.
// 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) {
return es.svc.ListThings(key, offset, limit)
func (es eventStore) ViewThing(token, id string) (things.Thing, error) {
return es.svc.ViewThing(token, id)
}
func (es eventStore) ListThingsByChannel(key, id string, offset, limit uint64) (things.ThingsPage, error) {
return es.svc.ListThingsByChannel(key, id, offset, limit)
func (es eventStore) ListThings(token string, offset, limit uint64) (things.ThingsPage, error) {
return es.svc.ListThings(token, offset, limit)
}
func (es eventStore) RemoveThing(key, id string) error {
if err := es.svc.RemoveThing(key, id); err != nil {
func (es eventStore) ListThingsByChannel(token, id string, offset, limit uint64) (things.ThingsPage, error) {
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
}
@ -105,8 +112,8 @@ func (es eventStore) RemoveThing(key, id string) error {
return nil
}
func (es eventStore) CreateChannel(key string, channel things.Channel) (things.Channel, error) {
sch, err := es.svc.CreateChannel(key, channel)
func (es eventStore) CreateChannel(token string, channel things.Channel) (things.Channel, error) {
sch, err := es.svc.CreateChannel(token, channel)
if err != nil {
return sch, err
}
@ -127,8 +134,8 @@ func (es eventStore) CreateChannel(key string, channel things.Channel) (things.C
return sch, err
}
func (es eventStore) UpdateChannel(key string, channel things.Channel) error {
if err := es.svc.UpdateChannel(key, channel); err != nil {
func (es eventStore) UpdateChannel(token string, channel things.Channel) error {
if err := es.svc.UpdateChannel(token, channel); err != nil {
return err
}
@ -147,20 +154,20 @@ func (es eventStore) UpdateChannel(key string, channel things.Channel) error {
return nil
}
func (es eventStore) ViewChannel(key, id string) (things.Channel, error) {
return es.svc.ViewChannel(key, id)
func (es eventStore) ViewChannel(token, id string) (things.Channel, error) {
return es.svc.ViewChannel(token, id)
}
func (es eventStore) ListChannels(key string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannels(key, offset, limit)
func (es eventStore) ListChannels(token string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannels(token, offset, limit)
}
func (es eventStore) ListChannelsByThing(key, id string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannelsByThing(key, id, offset, limit)
func (es eventStore) ListChannelsByThing(token, id string, offset, limit uint64) (things.ChannelsPage, error) {
return es.svc.ListChannelsByThing(token, id, offset, limit)
}
func (es eventStore) RemoveChannel(key, id string) error {
if err := es.svc.RemoveChannel(key, id); err != nil {
func (es eventStore) RemoveChannel(token, id string) error {
if err := es.svc.RemoveChannel(token, id); err != nil {
return err
}
@ -177,8 +184,8 @@ func (es eventStore) RemoveChannel(key, id string) error {
return nil
}
func (es eventStore) Connect(key, chanID, thingID string) error {
if err := es.svc.Connect(key, chanID, thingID); err != nil {
func (es eventStore) Connect(token, chanID, thingID string) error {
if err := es.svc.Connect(token, chanID, thingID); err != nil {
return err
}
@ -196,8 +203,8 @@ func (es eventStore) Connect(key, chanID, thingID string) error {
return nil
}
func (es eventStore) Disconnect(key, chanID, thingID string) error {
if err := es.svc.Disconnect(key, chanID, thingID); err != nil {
func (es eventStore) Disconnect(token, chanID, thingID string) error {
if err := es.svc.Disconnect(token, chanID, thingID); err != nil {
return err
}

View File

@ -26,6 +26,9 @@ var (
// ErrNotFound indicates a non-existent entity request.
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
@ -38,6 +41,10 @@ type Service interface {
// belongs to the user identified by the provided key.
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
// ID, that belongs to the user identified by the provided key.
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 {
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)
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 Thing{}, ErrUnauthorizedAccess
}
thing.ID = ts.idp.ID()
thing.Owner = res.GetValue()
thing.Key = ts.idp.ID()
if thing.Key == "" {
thing.Key = ts.idp.ID()
}
id, err := ts.things.Save(thing)
if err != nil {
@ -150,7 +160,7 @@ func (ts *thingsService) AddThing(key string, thing Thing) (Thing, error) {
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 {
return ErrMalformedEntity
}
@ -158,7 +168,7 @@ func (ts *thingsService) UpdateThing(key string, thing Thing) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
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
}
@ -168,11 +178,26 @@ func (ts *thingsService) UpdateThing(key string, thing Thing) error {
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)
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 {
return Thing{}, ErrUnauthorizedAccess
}
@ -180,11 +205,11 @@ func (ts *thingsService) ViewThing(key, id string) (Thing, error) {
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)
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 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
}
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)
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 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
}
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)
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
}
@ -217,11 +242,11 @@ func (ts *thingsService) RemoveThing(key string, id string) error {
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)
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 Channel{}, ErrUnauthorizedAccess
}
@ -238,11 +263,11 @@ func (ts *thingsService) CreateChannel(key string, channel Channel) (Channel, er
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)
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
}
@ -251,11 +276,11 @@ func (ts *thingsService) UpdateChannel(key string, channel Channel) error {
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)
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 Channel{}, ErrUnauthorizedAccess
}
@ -263,11 +288,11 @@ func (ts *thingsService) ViewChannel(key, id string) (Channel, error) {
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)
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 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
}
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)
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 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
}
func (ts *thingsService) RemoveChannel(key, id string) error {
func (ts *thingsService) RemoveChannel(token, id string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
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
}
@ -300,11 +325,11 @@ func (ts *thingsService) RemoveChannel(key, id string) error {
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)
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
}
@ -312,11 +337,11 @@ func (ts *thingsService) Connect(key, chanID, thingID string) error {
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)
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
}

View File

@ -48,25 +48,25 @@ func TestAddThing(t *testing.T) {
cases := []struct {
desc string
thing things.Thing
key string
token string
err error
}{
{
desc: "add new thing",
thing: things.Thing{Name: "a"},
key: token,
token: token,
err: nil,
},
{
desc: "add thing with wrong credentials",
thing: things.Thing{Name: "d"},
key: wrongValue,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
}
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))
}
}
@ -79,31 +79,73 @@ func TestUpdateThing(t *testing.T) {
cases := []struct {
desc string
thing things.Thing
key string
token string
err error
}{
{
desc: "update existing thing",
thing: saved,
key: token,
token: token,
err: nil,
},
{
desc: "update thing with wrong credentials",
thing: saved,
key: wrongValue,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update non-existing thing",
thing: other,
key: token,
token: token,
err: things.ErrNotFound,
},
}
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))
}
}
@ -113,29 +155,29 @@ func TestViewThing(t *testing.T) {
saved, _ := svc.AddThing(token, thing)
cases := map[string]struct {
id string
key string
err error
id string
token string
err error
}{
"view existing thing": {
id: saved.ID,
key: token,
err: nil,
id: saved.ID,
token: token,
err: nil,
},
"view thing with wrong credentials": {
id: saved.ID,
key: wrongValue,
err: things.ErrUnauthorizedAccess,
id: saved.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
"view non-existing thing": {
id: wrongID,
key: token,
err: things.ErrNotFound,
id: wrongID,
token: token,
err: things.ErrNotFound,
},
}
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))
}
}
@ -149,49 +191,49 @@ func TestListThings(t *testing.T) {
}
cases := map[string]struct {
key string
token string
offset uint64
limit uint64
size uint64
err error
}{
"list all things": {
key: token,
token: token,
offset: 0,
limit: n,
size: n,
err: nil,
},
"list half": {
key: token,
token: token,
offset: n / 2,
limit: n,
size: n / 2,
err: nil,
},
"list last thing": {
key: token,
token: token,
offset: n - 1,
limit: n,
size: 1,
err: nil,
},
"list empty set": {
key: token,
token: token,
offset: n + 1,
limit: n,
size: 0,
err: nil,
},
"list with zero limit": {
key: token,
token: token,
offset: 1,
limit: 0,
size: 0,
err: nil,
},
"list with wrong credentials": {
key: wrongValue,
token: wrongValue,
offset: 0,
limit: 0,
size: 0,
@ -200,7 +242,7 @@ func TestListThings(t *testing.T) {
}
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))
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))
@ -223,7 +265,7 @@ func TestListThingsByChannel(t *testing.T) {
time.Sleep(time.Second)
cases := map[string]struct {
key string
token string
channel string
offset uint64
limit uint64
@ -231,7 +273,7 @@ func TestListThingsByChannel(t *testing.T) {
err error
}{
"list all things by existing channel": {
key: token,
token: token,
channel: sch.ID,
offset: 0,
limit: n,
@ -239,7 +281,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil,
},
"list half of things by existing channel": {
key: token,
token: token,
channel: sch.ID,
offset: n / 2,
limit: n,
@ -247,7 +289,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil,
},
"list last thing by existing channel": {
key: token,
token: token,
channel: sch.ID,
offset: n - 1,
limit: n,
@ -255,7 +297,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil,
},
"list empty set of things by existing channel": {
key: token,
token: token,
channel: sch.ID,
offset: n + 1,
limit: n,
@ -263,7 +305,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil,
},
"list things by existing channel with zero limit": {
key: token,
token: token,
channel: sch.ID,
offset: 1,
limit: 0,
@ -271,7 +313,7 @@ func TestListThingsByChannel(t *testing.T) {
err: nil,
},
"list things by existing channel with wrong credentials": {
key: wrongValue,
token: wrongValue,
channel: sch.ID,
offset: 0,
limit: 0,
@ -279,7 +321,7 @@ func TestListThingsByChannel(t *testing.T) {
err: things.ErrUnauthorizedAccess,
},
"list things by non-existent channel with wrong credentials": {
key: token,
token: token,
channel: "non-existent",
offset: 0,
limit: 10,
@ -289,7 +331,7 @@ func TestListThingsByChannel(t *testing.T) {
}
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))
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))
@ -301,39 +343,39 @@ func TestRemoveThing(t *testing.T) {
saved, _ := svc.AddThing(token, thing)
cases := []struct {
desc string
id string
key string
err error
desc string
id string
token string
err error
}{
{
desc: "remove thing with wrong credentials",
id: saved.ID,
key: wrongValue,
err: things.ErrUnauthorizedAccess,
desc: "remove thing with wrong credentials",
id: saved.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "remove existing thing",
id: saved.ID,
key: token,
err: nil,
desc: "remove existing thing",
id: saved.ID,
token: token,
err: nil,
},
{
desc: "remove removed thing",
id: saved.ID,
key: token,
err: nil,
desc: "remove removed thing",
id: saved.ID,
token: token,
err: nil,
},
{
desc: "remove non-existing thing",
id: wrongID,
key: token,
err: nil,
desc: "remove non-existing thing",
id: wrongID,
token: token,
err: nil,
},
}
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))
}
}
@ -344,25 +386,25 @@ func TestCreateChannel(t *testing.T) {
cases := []struct {
desc string
channel things.Channel
key string
token string
err error
}{
{
desc: "create channel",
channel: channel,
key: token,
token: token,
err: nil,
},
{
desc: "create channel with wrong credentials",
channel: channel,
key: wrongValue,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
}
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))
}
}
@ -375,31 +417,31 @@ func TestUpdateChannel(t *testing.T) {
cases := []struct {
desc string
channel things.Channel
key string
token string
err error
}{
{
desc: "update existing channel",
channel: saved,
key: token,
token: token,
err: nil,
},
{
desc: "update channel with wrong credentials",
channel: saved,
key: wrongValue,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "update non-existing channel",
channel: other,
key: token,
token: token,
err: things.ErrNotFound,
},
}
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))
}
}
@ -409,29 +451,29 @@ func TestViewChannel(t *testing.T) {
saved, _ := svc.CreateChannel(token, channel)
cases := map[string]struct {
id string
key string
err error
id string
token string
err error
}{
"view existing channel": {
id: saved.ID,
key: token,
err: nil,
id: saved.ID,
token: token,
err: nil,
},
"view channel with wrong credentials": {
id: saved.ID,
key: wrongValue,
err: things.ErrUnauthorizedAccess,
id: saved.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
"view non-existing channel": {
id: wrongID,
key: token,
err: things.ErrNotFound,
id: wrongID,
token: token,
err: things.ErrNotFound,
},
}
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))
}
}
@ -444,49 +486,49 @@ func TestListChannels(t *testing.T) {
svc.CreateChannel(token, channel)
}
cases := map[string]struct {
key string
token string
offset uint64
limit uint64
size uint64
err error
}{
"list all channels": {
key: token,
token: token,
offset: 0,
limit: n,
size: n,
err: nil,
},
"list half": {
key: token,
token: token,
offset: n / 2,
limit: n,
size: n / 2,
err: nil,
},
"list last channel": {
key: token,
token: token,
offset: n - 1,
limit: n,
size: 1,
err: nil,
},
"list empty set": {
key: token,
token: token,
offset: n + 1,
limit: n,
size: 0,
err: nil,
},
"list with zero limit": {
key: token,
token: token,
offset: 1,
limit: 0,
size: 0,
err: nil,
},
"list with wrong credentials": {
key: wrongValue,
token: wrongValue,
offset: 0,
limit: 0,
size: 0,
@ -495,7 +537,7 @@ func TestListChannels(t *testing.T) {
}
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))
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))
@ -518,7 +560,7 @@ func TestListChannelsByThing(t *testing.T) {
time.Sleep(time.Second)
cases := map[string]struct {
key string
token string
thing string
offset uint64
limit uint64
@ -526,7 +568,7 @@ func TestListChannelsByThing(t *testing.T) {
err error
}{
"list all channels by existing thing": {
key: token,
token: token,
thing: sth.ID,
offset: 0,
limit: n,
@ -534,7 +576,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil,
},
"list half of channels by existing thing": {
key: token,
token: token,
thing: sth.ID,
offset: n / 2,
limit: n,
@ -542,7 +584,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil,
},
"list last channel by existing thing": {
key: token,
token: token,
thing: sth.ID,
offset: n - 1,
limit: n,
@ -550,7 +592,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil,
},
"list empty set of channels by existing thing": {
key: token,
token: token,
thing: sth.ID,
offset: n + 1,
limit: n,
@ -558,7 +600,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil,
},
"list channels by existing thing with zero limit": {
key: token,
token: token,
thing: sth.ID,
offset: 1,
limit: 0,
@ -566,7 +608,7 @@ func TestListChannelsByThing(t *testing.T) {
err: nil,
},
"list channels by existing thing with wrong credentials": {
key: wrongValue,
token: wrongValue,
thing: sth.ID,
offset: 0,
limit: 0,
@ -574,7 +616,7 @@ func TestListChannelsByThing(t *testing.T) {
err: things.ErrUnauthorizedAccess,
},
"list channels by non-existent thing": {
key: token,
token: token,
thing: "non-existent",
offset: 0,
limit: 10,
@ -584,7 +626,7 @@ func TestListChannelsByThing(t *testing.T) {
}
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))
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))
@ -596,39 +638,39 @@ func TestRemoveChannel(t *testing.T) {
saved, _ := svc.CreateChannel(token, channel)
cases := []struct {
desc string
id string
key string
err error
desc string
id string
token string
err error
}{
{
desc: "remove channel with wrong credentials",
id: saved.ID,
key: wrongValue,
err: things.ErrUnauthorizedAccess,
desc: "remove channel with wrong credentials",
id: saved.ID,
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "remove existing channel",
id: saved.ID,
key: token,
err: nil,
desc: "remove existing channel",
id: saved.ID,
token: token,
err: nil,
},
{
desc: "remove removed channel",
id: saved.ID,
key: token,
err: nil,
desc: "remove removed channel",
id: saved.ID,
token: token,
err: nil,
},
{
desc: "remove non-existing channel",
id: saved.ID,
key: token,
err: nil,
desc: "remove non-existing channel",
id: saved.ID,
token: token,
err: nil,
},
}
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))
}
}
@ -641,35 +683,35 @@ func TestConnect(t *testing.T) {
cases := []struct {
desc string
key string
token string
chanID string
thingID string
err error
}{
{
desc: "connect thing",
key: token,
token: token,
chanID: sch.ID,
thingID: sth.ID,
err: nil,
},
{
desc: "connect thing with wrong credentials",
key: wrongValue,
token: wrongValue,
chanID: sch.ID,
thingID: sth.ID,
err: things.ErrUnauthorizedAccess,
},
{
desc: "connect thing to non-existing channel",
key: token,
token: token,
chanID: wrongID,
thingID: sth.ID,
err: things.ErrNotFound,
},
{
desc: "connect non-existing thing to channel",
key: token,
token: token,
chanID: sch.ID,
thingID: wrongID,
err: things.ErrNotFound,
@ -677,7 +719,7 @@ func TestConnect(t *testing.T) {
}
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))
}
}
@ -691,42 +733,42 @@ func TestDisconnect(t *testing.T) {
cases := []struct {
desc string
key string
token string
chanID string
thingID string
err error
}{
{
desc: "disconnect connected thing",
key: token,
token: token,
chanID: sch.ID,
thingID: sth.ID,
err: nil,
},
{
desc: "disconnect disconnected thing",
key: token,
token: token,
chanID: sch.ID,
thingID: sth.ID,
err: things.ErrNotFound,
},
{
desc: "disconnect with wrong credentials",
key: wrongValue,
token: wrongValue,
chanID: sch.ID,
thingID: sth.ID,
err: things.ErrUnauthorizedAccess,
},
{
desc: "disconnect from non-existing channel",
key: token,
token: token,
chanID: wrongID,
thingID: sth.ID,
err: things.ErrNotFound,
},
{
desc: "disconnect non-existing thing",
key: token,
token: token,
chanID: sch.ID,
thingID: wrongID,
err: things.ErrNotFound,
@ -734,7 +776,7 @@ func TestDisconnect(t *testing.T) {
}
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))
}
@ -748,29 +790,29 @@ func TestCanAccess(t *testing.T) {
svc.Connect(token, sch.ID, sth.ID)
cases := map[string]struct {
key string
token string
channel string
err error
}{
"allowed access": {
key: sth.Key,
token: sth.Key,
channel: sch.ID,
err: nil,
},
"not-connected cannot access": {
key: wrongValue,
token: wrongValue,
channel: sch.ID,
err: things.ErrUnauthorizedAccess,
},
"access to non-existing channel": {
key: sth.Key,
token: sth.Key,
channel: wrongID,
err: things.ErrUnauthorizedAccess,
},
}
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))
}
}
@ -781,24 +823,24 @@ func TestIdentify(t *testing.T) {
sth, _ := svc.AddThing(token, thing)
cases := map[string]struct {
key string
id string
err error
token string
id string
err error
}{
"identify existing thing": {
key: sth.Key,
id: sth.ID,
err: nil,
token: sth.Key,
id: sth.ID,
err: nil,
},
"identify non-existing thing": {
key: wrongValue,
id: wrongID,
err: things.ErrUnauthorizedAccess,
token: wrongValue,
id: wrongID,
err: things.ErrUnauthorizedAccess,
},
}
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.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.
in: body
schema:
$ref: "#/definitions/ThingReq"
$ref: "#/definitions/CreateThingReq"
required: true
responses:
201:
@ -120,7 +120,7 @@ paths:
description: JSON-formatted document describing the updated thing.
in: body
schema:
$ref: "#/definitions/ThingReq"
$ref: "#/definitions/UpdateThingReq"
required: true
responses:
200:
@ -154,6 +154,37 @@ paths:
description: Missing or invalid access token provided.
500:
$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:
post:
summary: Creates new channel
@ -461,14 +492,33 @@ definitions:
- id
- type
- 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
properties:
name:
type: string
description: Free-form thing name.
metadata:
type: object
description: Custom thing's data in JSON format.
UpdateKeyReq:
type: object
properties:
key:
type: string
description: Arbitrary, string-encoded thing's data.
required:
- type
description: Thing key that is used for thing auth.

View File

@ -39,6 +39,10 @@ type ThingRepository interface {
// returned to indicate operation failure.
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
// by the specified user.
RetrieveByID(string, string) (Thing, error)