mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
NOISSUE - Implement Thing Delete (#179)
* add: delete function in things interface Signed-off-by: Arvindh <arvindh91@gmail.com> * add: remove things Signed-off-by: Arvindh <arvindh91@gmail.com> * fix: things event streams Signed-off-by: Arvindh <arvindh91@gmail.com> * add: things delete test Signed-off-by: Arvindh <arvindh91@gmail.com> * add: delete thing http transport Signed-off-by: Arvindh <arvindh91@gmail.com> * add: delete thing sdk, sdk_test, cli Signed-off-by: Arvindh <arvindh91@gmail.com> * gofumpt -ed Signed-off-by: Arvindh <arvindh91@gmail.com> * add: openapi Signed-off-by: Arvindh <arvindh91@gmail.com> * fix: things change status response Signed-off-by: Arvindh <arvindh91@gmail.com> * add: openapi Signed-off-by: Arvindh <arvindh91@gmail.com> * rename events: from thing delete to thing remove Signed-off-by: Arvindh <arvindh91@gmail.com> * fix wordings Signed-off-by: Arvindh <arvindh91@gmail.com> * fix wordings in openapi Signed-off-by: Arvindh <arvindh91@gmail.com> * add: type check Signed-off-by: Arvindh <arvindh91@gmail.com> * update open api yaml Signed-off-by: Arvindh <arvindh91@gmail.com> * fix things mocks Signed-off-by: Arvindh <arvindh91@gmail.com> --------- Signed-off-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
+21
-5
@@ -169,7 +169,26 @@ paths:
|
||||
description: Missing or invalid content type.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
delete:
|
||||
summary: Delete thing for a thing with the given id.
|
||||
description: |
|
||||
Delete thing removes a thing with the given id from repo
|
||||
and removes all the policies related to this thing.
|
||||
tags:
|
||||
- Things
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ThingID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Thing deleted.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access to thing id.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
/things/{thingID}/tags:
|
||||
patch:
|
||||
summary: Updates tags the thing.
|
||||
@@ -479,7 +498,7 @@ paths:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/ChannelDeleteRes"
|
||||
description: Channel deleted.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
@@ -1771,9 +1790,6 @@ components:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Channel"
|
||||
|
||||
ChannelDeleteRes:
|
||||
description: Channel Deleted.
|
||||
|
||||
ChannelPageRes:
|
||||
description: Data retrieved.
|
||||
content:
|
||||
|
||||
@@ -571,6 +571,26 @@ paths:
|
||||
description: Group does not exist.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
delete:
|
||||
summary: Delete group for a group with the given id.
|
||||
description: |
|
||||
Delete group removes a group with the given id from repo
|
||||
and removes all the policies related to this group.
|
||||
tags:
|
||||
- Groups
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/GroupID"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Group deleted.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Unauthorized access to group id.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/groups/{groupID}/children:
|
||||
get:
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/bootstrap"
|
||||
"github.com/absmach/magistrala/pkg/clients"
|
||||
"github.com/absmach/magistrala/pkg/events"
|
||||
)
|
||||
|
||||
@@ -61,20 +60,8 @@ func (es *eventHandler) Handle(ctx context.Context, event events.Event) error {
|
||||
}
|
||||
|
||||
func decodeRemoveThing(event map[string]interface{}) removeEvent {
|
||||
status := read(event, "status", "")
|
||||
st, err := clients.ToStatus(status)
|
||||
if err != nil {
|
||||
return removeEvent{}
|
||||
}
|
||||
switch st {
|
||||
case clients.EnabledStatus:
|
||||
return removeEvent{}
|
||||
case clients.DisabledStatus:
|
||||
return removeEvent{
|
||||
id: read(event, "id", ""),
|
||||
}
|
||||
default:
|
||||
return removeEvent{}
|
||||
return removeEvent{
|
||||
id: read(event, "id", ""),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,24 @@ var cmdThings = []cobra.Command{
|
||||
logJSON(t)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "delete <thing_id> <user_auth_token>",
|
||||
Short: "Delete thing",
|
||||
Long: "Delete thing by id\n" +
|
||||
"Usage:\n" +
|
||||
"\tmagistrala-cli things delete <thing_id> $USERTOKEN - delete thing with <thing_id>\n",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
logUsage(cmd.Use)
|
||||
return
|
||||
}
|
||||
if err := sdk.DeleteThing(args[0], args[1]); err != nil {
|
||||
logError(err)
|
||||
return
|
||||
}
|
||||
logOK()
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "identify <thing_key>",
|
||||
Short: "Identify thing",
|
||||
|
||||
@@ -453,6 +453,13 @@ type SDK interface {
|
||||
// fmt.Println(users)
|
||||
ListThingUsers(thingID string, pm PageMetadata, token string) (UsersPage, errors.SDKError)
|
||||
|
||||
// DeleteThing deletes a thing with the given id.
|
||||
//
|
||||
// example:
|
||||
// err := sdk.DeleteThing("thingID", "token")
|
||||
// fmt.Println(err)
|
||||
DeleteThing(id, token string) errors.SDKError
|
||||
|
||||
// CreateGroup creates new group and returns its id.
|
||||
//
|
||||
// example:
|
||||
|
||||
@@ -294,3 +294,9 @@ func (sdk mgSDK) ListThingUsers(thingID string, pm PageMetadata, token string) (
|
||||
|
||||
return up, nil
|
||||
}
|
||||
|
||||
func (sdk mgSDK) DeleteThing(id, token string) errors.SDKError {
|
||||
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, thingsEndpoint, id)
|
||||
_, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, nil, nil, http.StatusNoContent)
|
||||
return sdkerr
|
||||
}
|
||||
|
||||
@@ -1276,3 +1276,46 @@ func TestShareThing(t *testing.T) {
|
||||
repoCall3.Unset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteThing(t *testing.T) {
|
||||
ts, cRepo, _, auth, cache := setupThings()
|
||||
|
||||
defer ts.Close()
|
||||
|
||||
conf := sdk.Config{
|
||||
ThingsURL: ts.URL,
|
||||
}
|
||||
mgsdk := sdk.NewSDK(conf)
|
||||
|
||||
thing := sdk.Thing{
|
||||
ID: generateUUID(t),
|
||||
Name: "clientname",
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Credentials: sdk.Credentials{Secret: generateUUID(t)},
|
||||
Status: mgclients.EnabledStatus.String(),
|
||||
}
|
||||
|
||||
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil)
|
||||
repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: false}, nil)
|
||||
repoCall2 := cRepo.On("Delete", mock.Anything, mock.Anything).Return(nil)
|
||||
repoCall4 := cache.On("Remove", mock.Anything, thing.ID).Return(nil)
|
||||
err := mgsdk.DeleteThing("wrongID", validToken)
|
||||
assert.Equal(t, err, errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), fmt.Sprintf("Delete thing with wrong id: expected %v got %v", svcerr.ErrNotFound, err))
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
repoCall2.Unset()
|
||||
|
||||
repoCall = auth.On("DeletePolicy", mock.Anything, mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil)
|
||||
repoCall1 = auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: validToken}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil)
|
||||
repoCall2 = auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil)
|
||||
repoCall3 := cRepo.On("Delete", mock.Anything, mock.Anything).Return(nil)
|
||||
err = mgsdk.DeleteThing(thing.ID, validToken)
|
||||
assert.Nil(t, err, fmt.Sprintf("Delete thing with correct id: expected %v got %v", nil, err))
|
||||
ok := repoCall3.Parent.AssertCalled(t, "Delete", mock.Anything, thing.ID)
|
||||
assert.True(t, ok, "Delete was not called on deleting thing with correct id")
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
repoCall2.Unset()
|
||||
repoCall3.Unset()
|
||||
repoCall4.Unset()
|
||||
}
|
||||
|
||||
@@ -778,6 +778,26 @@ func (_m *SDK) DeleteSubscription(id string, token string) errors.SDKError {
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteThing provides a mock function with given fields: id, token
|
||||
func (_m *SDK) DeleteThing(id string, token string) errors.SDKError {
|
||||
ret := _m.Called(id, token)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteThing")
|
||||
}
|
||||
|
||||
var r0 errors.SDKError
|
||||
if rf, ok := ret.Get(0).(func(string, string) errors.SDKError); ok {
|
||||
r0 = rf(id, token)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(errors.SDKError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DisableChannel provides a mock function with given fields: id, token
|
||||
func (_m *SDK) DisableChannel(id string, token string) (sdk.Channel, errors.SDKError) {
|
||||
ret := _m.Called(id, token)
|
||||
|
||||
@@ -108,6 +108,13 @@ func clientsHandler(svc things.Service, r *chi.Mux, logger mglog.Logger) http.Ha
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "unshare_thing").ServeHTTP)
|
||||
|
||||
r.Delete("/{thingID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteClientEndpoint(svc),
|
||||
decodeDeleteClientReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_thing").ServeHTTP)
|
||||
})
|
||||
|
||||
// Ideal location: things service, channels endpoint
|
||||
@@ -376,3 +383,12 @@ func decodeThingUnshareRequest(_ context.Context, r *http.Request) (interface{},
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteClientReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := deleteClientReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
id: chi.URLParam(r, "thingID"),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func enableClientEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return deleteClientRes{Client: client}, nil
|
||||
return changeClientStatusRes{Client: client}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ func disableClientEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return deleteClientRes{Client: client}, nil
|
||||
return changeClientStatusRes{Client: client}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,3 +401,18 @@ func thingUnshareEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return thingUnshareRes{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteClientEndpoint(svc things.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(deleteClientReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
if err := svc.DeleteClient(ctx, req.token, req.id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return deleteClientRes{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,3 +365,18 @@ func (req *thingUnshareRequest) validate() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type deleteClientReq struct {
|
||||
token string
|
||||
id string
|
||||
}
|
||||
|
||||
func (req deleteClientReq) validate() error {
|
||||
if req.token == "" {
|
||||
return apiutil.ErrBearerToken
|
||||
}
|
||||
if req.id == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ var (
|
||||
_ magistrala.Response = (*unassignUsersGroupsRes)(nil)
|
||||
_ magistrala.Response = (*connectChannelThingRes)(nil)
|
||||
_ magistrala.Response = (*disconnectChannelThingRes)(nil)
|
||||
_ magistrala.Response = (*changeClientStatusRes)(nil)
|
||||
)
|
||||
|
||||
type pageRes struct {
|
||||
@@ -138,20 +139,34 @@ func (res viewMembersRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type deleteClientRes struct {
|
||||
type changeClientStatusRes struct {
|
||||
mgclients.Client
|
||||
}
|
||||
|
||||
func (res deleteClientRes) Code() int {
|
||||
func (res changeClientStatusRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res changeClientStatusRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res changeClientStatusRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type deleteClientRes struct{}
|
||||
|
||||
func (res deleteClientRes) Code() int {
|
||||
return http.StatusNoContent
|
||||
}
|
||||
|
||||
func (res deleteClientRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res deleteClientRes) Empty() bool {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
type assignUsersGroupsRes struct{}
|
||||
|
||||
@@ -192,3 +192,15 @@ func (lm *loggingMiddleware) Unshare(ctx context.Context, token, id, relation st
|
||||
}(time.Now())
|
||||
return lm.svc.Unshare(ctx, token, id, relation, userids...)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) DeleteClient(ctx context.Context, token, id string) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method delete_client for thing id %s took %s to complete", id, 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.DeleteClient(ctx, token, id)
|
||||
}
|
||||
|
||||
@@ -141,3 +141,11 @@ func (ms *metricsMiddleware) Unshare(ctx context.Context, token, id, relation st
|
||||
}(time.Now())
|
||||
return ms.svc.Unshare(ctx, token, id, relation, userids...)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) DeleteClient(ctx context.Context, token, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "delete_client").Add(1)
|
||||
ms.latency.With("method", "delete_client").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
return ms.svc.DeleteClient(ctx, token, id)
|
||||
}
|
||||
|
||||
+27
-14
@@ -14,22 +14,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
clientPrefix = "thing."
|
||||
clientCreate = clientPrefix + "create"
|
||||
clientUpdate = clientPrefix + "update"
|
||||
clientRemove = clientPrefix + "remove"
|
||||
clientView = clientPrefix + "view"
|
||||
clientViewPerms = clientPrefix + "view_perms"
|
||||
clientList = clientPrefix + "list"
|
||||
clientListByGroup = clientPrefix + "list_by_channel"
|
||||
clientIdentify = clientPrefix + "identify"
|
||||
clientAuthorize = clientPrefix + "authorize"
|
||||
clientPrefix = "thing."
|
||||
clientCreate = clientPrefix + "create"
|
||||
clientUpdate = clientPrefix + "update"
|
||||
clientChangeStatus = clientPrefix + "change_status"
|
||||
clientRemove = clientPrefix + "remove"
|
||||
clientView = clientPrefix + "view"
|
||||
clientViewPerms = clientPrefix + "view_perms"
|
||||
clientList = clientPrefix + "list"
|
||||
clientListByGroup = clientPrefix + "list_by_channel"
|
||||
clientIdentify = clientPrefix + "identify"
|
||||
clientAuthorize = clientPrefix + "authorize"
|
||||
)
|
||||
|
||||
var (
|
||||
_ events.Event = (*createClientEvent)(nil)
|
||||
_ events.Event = (*updateClientEvent)(nil)
|
||||
_ events.Event = (*removeClientEvent)(nil)
|
||||
_ events.Event = (*changeStatusClientEvent)(nil)
|
||||
_ events.Event = (*viewClientEvent)(nil)
|
||||
_ events.Event = (*viewClientPermsEvent)(nil)
|
||||
_ events.Event = (*listClientEvent)(nil)
|
||||
@@ -37,6 +38,7 @@ var (
|
||||
_ events.Event = (*identifyClientEvent)(nil)
|
||||
_ events.Event = (*authorizeClientEvent)(nil)
|
||||
_ events.Event = (*shareClientEvent)(nil)
|
||||
_ events.Event = (*removeClientEvent)(nil)
|
||||
)
|
||||
|
||||
type createClientEvent struct {
|
||||
@@ -125,16 +127,16 @@ func (uce updateClientEvent) Encode() (map[string]interface{}, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
type removeClientEvent struct {
|
||||
type changeStatusClientEvent struct {
|
||||
id string
|
||||
status string
|
||||
updatedAt time.Time
|
||||
updatedBy string
|
||||
}
|
||||
|
||||
func (rce removeClientEvent) Encode() (map[string]interface{}, error) {
|
||||
func (rce changeStatusClientEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"operation": clientRemove,
|
||||
"operation": clientChangeStatus,
|
||||
"id": rce.id,
|
||||
"status": rce.status,
|
||||
"updated_at": rce.updatedAt,
|
||||
@@ -380,3 +382,14 @@ func (sce shareClientEvent) Encode() (map[string]interface{}, error) {
|
||||
"user_ids": strings.Join(sce.userIDs, ","),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type removeClientEvent struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (dce removeClientEvent) Encode() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"operation": clientRemove,
|
||||
"id": dce.id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (es *eventStore) EnableClient(ctx context.Context, token, id string) (mgcli
|
||||
return cli, err
|
||||
}
|
||||
|
||||
return es.delete(ctx, cli)
|
||||
return es.changeStatus(ctx, cli)
|
||||
}
|
||||
|
||||
func (es *eventStore) DisableClient(ctx context.Context, token, id string) (mgclients.Client, error) {
|
||||
@@ -171,11 +171,11 @@ func (es *eventStore) DisableClient(ctx context.Context, token, id string) (mgcl
|
||||
return cli, err
|
||||
}
|
||||
|
||||
return es.delete(ctx, cli)
|
||||
return es.changeStatus(ctx, cli)
|
||||
}
|
||||
|
||||
func (es *eventStore) delete(ctx context.Context, cli mgclients.Client) (mgclients.Client, error) {
|
||||
event := removeClientEvent{
|
||||
func (es *eventStore) changeStatus(ctx context.Context, cli mgclients.Client) (mgclients.Client, error) {
|
||||
event := changeStatusClientEvent{
|
||||
id: cli.ID,
|
||||
updatedAt: cli.UpdatedAt,
|
||||
updatedBy: cli.UpdatedBy,
|
||||
@@ -251,3 +251,17 @@ func (es *eventStore) Unshare(ctx context.Context, token, id, relation string, u
|
||||
|
||||
return es.Publish(ctx, event)
|
||||
}
|
||||
|
||||
func (es *eventStore) DeleteClient(ctx context.Context, token, id string) error {
|
||||
if err := es.svc.DeleteClient(ctx, token, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
event := removeClientEvent{id}
|
||||
|
||||
if err := es.Publish(ctx, event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -45,6 +45,24 @@ func (_m *Repository) ChangeStatus(ctx context.Context, client clients.Client) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, id
|
||||
func (_m *Repository) Delete(ctx context.Context, id string) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Delete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// RetrieveAll provides a mock function with given fields: ctx, pm
|
||||
func (_m *Repository) RetrieveAll(ctx context.Context, pm clients.Page) (clients.ClientsPage, error) {
|
||||
ret := _m.Called(ctx, pm)
|
||||
|
||||
@@ -34,6 +34,9 @@ type Repository interface {
|
||||
|
||||
// RetrieveBySecret retrieves a client based on the secret (key).
|
||||
RetrieveBySecret(ctx context.Context, key string) (mgclients.Client, error)
|
||||
|
||||
// Delete deletes client with given id
|
||||
Delete(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// NewRepository instantiates a PostgreSQL
|
||||
@@ -107,3 +110,11 @@ func (repo clientRepo) RetrieveBySecret(ctx context.Context, key string) (mgclie
|
||||
|
||||
return pgclients.ToClient(dbc)
|
||||
}
|
||||
|
||||
func (repo clientRepo) Delete(ctx context.Context, id string) error {
|
||||
q := "DELETE FROM clients AS c WHERE c.id = $1 ;"
|
||||
if _, err := repo.DB.ExecContext(ctx, q, id); err != nil {
|
||||
return postgres.HandleError(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@ import (
|
||||
const maxNameSize = 1024
|
||||
|
||||
var (
|
||||
invalidName = strings.Repeat("m", maxNameSize+10)
|
||||
clientIdentity = "client-identity@example.com"
|
||||
clientName = "client name"
|
||||
invalidName = strings.Repeat("m", maxNameSize+10)
|
||||
clientIdentity = "client-identity@example.com"
|
||||
clientName = "client name"
|
||||
invalidClientID = "invalidClientID"
|
||||
)
|
||||
|
||||
func TestClientsSave(t *testing.T) {
|
||||
@@ -192,3 +193,37 @@ func TestClientsRetrieveBySecret(t *testing.T) {
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM clients")
|
||||
require.Nil(t, err, fmt.Sprintf("clean clients unexpected error: %s", err))
|
||||
})
|
||||
repo := postgres.NewRepository(database)
|
||||
|
||||
client := clients.Client{
|
||||
ID: testsutil.GenerateUUID(t),
|
||||
Name: clientName,
|
||||
Credentials: clients.Credentials{
|
||||
Identity: clientIdentity,
|
||||
Secret: testsutil.GenerateUUID(t),
|
||||
},
|
||||
Status: clients.EnabledStatus,
|
||||
}
|
||||
|
||||
_, err := repo.Save(context.Background(), client)
|
||||
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
|
||||
|
||||
cases := map[string]struct {
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
"delete client id": {client.ID, nil},
|
||||
"delete invalid client id ": {invalidClientID, nil},
|
||||
}
|
||||
|
||||
for desc, tc := range cases {
|
||||
err := repo.Delete(context.Background(), tc.id)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +442,55 @@ func (svc service) Unshare(ctx context.Context, token, id, relation string, user
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) DeleteClient(ctx context.Context, token, id string) error {
|
||||
res, err := svc.identify(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := svc.authorize(ctx, auth.UserType, auth.UsersKind, res.GetId(), auth.DeletePermission, auth.ThingType, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove from cache
|
||||
if err := svc.clientCache.Remove(ctx, id); err != nil {
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
// Remove policy of groups
|
||||
if _, err := svc.auth.DeletePolicy(ctx, &magistrala.DeletePolicyReq{
|
||||
SubjectType: auth.GroupType,
|
||||
Object: id,
|
||||
ObjectType: auth.ThingType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove policy from domain
|
||||
if _, err := svc.auth.DeletePolicy(ctx, &magistrala.DeletePolicyReq{
|
||||
SubjectType: auth.DomainType,
|
||||
Object: id,
|
||||
ObjectType: auth.ThingType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove thing from database
|
||||
if err := svc.clients.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove policy of users
|
||||
if _, err := svc.auth.DeletePolicy(ctx, &magistrala.DeletePolicyReq{
|
||||
SubjectType: auth.UserType,
|
||||
Object: id,
|
||||
ObjectType: auth.ThingType,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc service) changeClientStatus(ctx context.Context, token string, client mgclients.Client) (mgclients.Client, error) {
|
||||
userID, err := svc.authorize(ctx, auth.UserType, auth.TokenKind, token, auth.DeletePermission, auth.ThingType, client.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -1132,6 +1132,83 @@ func TestListMembers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteClient(t *testing.T) {
|
||||
svc, cRepo, auth, cache := newService()
|
||||
|
||||
client := mgclients.Client{
|
||||
ID: testsutil.GenerateUUID(t),
|
||||
Name: "TestClient",
|
||||
Credentials: mgclients.Credentials{
|
||||
Identity: "TestClient@example.com",
|
||||
Secret: "password",
|
||||
},
|
||||
Tags: []string{"tag1", "tag2"},
|
||||
Metadata: mgclients.Metadata{"role": "client"},
|
||||
}
|
||||
invalidClientID := "invalidClientID"
|
||||
_ = invalidClientID
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
clientID string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "Delete client with authorized token",
|
||||
token: validToken,
|
||||
clientID: client.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "Delete client with unauthorized token",
|
||||
token: authmocks.InvalidValue,
|
||||
clientID: client.ID,
|
||||
err: errors.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "Delete invalid client",
|
||||
token: validToken,
|
||||
clientID: authmocks.InvalidValue,
|
||||
err: errors.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "Delete repo error ",
|
||||
token: validToken,
|
||||
clientID: client.ID,
|
||||
err: errors.ErrRemoveEntity,
|
||||
},
|
||||
{
|
||||
desc: "Delete policy error ",
|
||||
token: validToken,
|
||||
clientID: client.ID,
|
||||
err: errors.ErrUnidentified,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
repoCall := auth.On("Identify", mock.Anything, &magistrala.IdentityReq{Token: tc.token}).Return(&magistrala.IdentityRes{Id: validID, DomainId: testsutil.GenerateUUID(t)}, nil)
|
||||
repoCall1 := auth.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.AuthorizeRes{Authorized: true}, nil)
|
||||
repoCall2 := auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: true}, nil)
|
||||
repoCall3 := cache.On("Remove", mock.Anything, tc.clientID).Return(nil)
|
||||
repoCall4 := cRepo.On("Delete", context.Background(), tc.clientID).Return(nil)
|
||||
if tc.err == errors.ErrRemoveEntity {
|
||||
repoCall4.Unset()
|
||||
repoCall4 = cRepo.On("Delete", context.Background(), tc.clientID).Return(errors.ErrRemoveEntity)
|
||||
}
|
||||
if tc.err == errors.ErrUnidentified {
|
||||
repoCall2.Unset()
|
||||
repoCall2 = auth.On("DeletePolicy", mock.Anything, mock.Anything).Return(&magistrala.DeletePolicyRes{Deleted: false}, errors.ErrUnidentified)
|
||||
}
|
||||
err := svc.DeleteClient(context.Background(), tc.token, tc.clientID)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
repoCall2.Unset()
|
||||
repoCall3.Unset()
|
||||
repoCall4.Unset()
|
||||
}
|
||||
}
|
||||
|
||||
func getIDs(clients []mgclients.Client) []string {
|
||||
ids := []string{}
|
||||
for _, client := range clients {
|
||||
|
||||
@@ -57,6 +57,9 @@ type Service interface {
|
||||
|
||||
// Authorize used for AuthZ gRPC server implementation and Things authorization.
|
||||
Authorize(ctx context.Context, req *magistrala.AuthorizeReq) (string, error)
|
||||
|
||||
// DeleteClient deletes client with given ID.
|
||||
DeleteClient(ctx context.Context, token, id string) error
|
||||
}
|
||||
|
||||
// Cache contains thing caching interface.
|
||||
|
||||
@@ -133,3 +133,10 @@ func (tm *tracingMiddleware) Unshare(ctx context.Context, token, id, relation st
|
||||
defer span.End()
|
||||
return tm.svc.Unshare(ctx, token, id, relation, userids...)
|
||||
}
|
||||
|
||||
// DeleteClient traces the "DeleteClient" operation of the wrapped things.Service.
|
||||
func (tm *tracingMiddleware) DeleteClient(ctx context.Context, token, id string) error {
|
||||
ctx, span := tm.tracer.Start(ctx, "delete_client", trace.WithAttributes(attribute.String("id", id)))
|
||||
defer span.End()
|
||||
return tm.svc.DeleteClient(ctx, token, id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user