From e896974b684a344479feecf573e2badd970e2947 Mon Sep 17 00:00:00 2001 From: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:39:30 +0300 Subject: [PATCH] Add property based testing to users API using schemathesis Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/api-tests.yml | 54 ++++++ .gitignore | 3 + Makefile | 20 +- api/openapi/users.yml | 287 ++++++++++++++++++++++++++++- auth/postgres/domains.go | 20 +- auth/service.go | 9 - internal/api/common.go | 27 ++- internal/apiutil/errors.go | 6 +- internal/groups/api/responses.go | 4 +- internal/groups/postgres/groups.go | 18 +- internal/groups/service.go | 22 +-- internal/postgres/common.go | 9 - invitations/api/endpoint_test.go | 16 +- invitations/api/requests_test.go | 11 +- invitations/invitations.go | 11 +- invitations/invitations_test.go | 11 +- invitations/service_test.go | 4 +- pkg/clients/postgres/clients.go | 12 +- pkg/clients/status.go | 4 - pkg/errors/repository/types.go | 9 + pkg/errors/service/types.go | 9 + pkg/errors/types.go | 6 + pkg/groups/errors.go | 3 - pkg/sdk/go/channels_test.go | 10 +- pkg/sdk/go/groups_test.go | 10 +- pkg/sdk/go/things_test.go | 8 +- pkg/sdk/go/tokens_test.go | 2 +- pkg/sdk/go/users_test.go | 13 +- things/service.go | 2 +- things/service_test.go | 4 +- users/api/clients.go | 33 ++-- users/api/responses.go | 4 +- users/postgres/clients.go | 4 +- users/service.go | 25 +-- users/service_test.go | 4 +- 35 files changed, 517 insertions(+), 177 deletions(-) create mode 100644 .github/workflows/api-tests.yml diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml new file mode 100644 index 000000000..98a313d6c --- /dev/null +++ b/.github/workflows/api-tests.yml @@ -0,0 +1,54 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +name: Property Based Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +env: + TOKEN_URL: http://localhost:9002/users/tokens/issue + USER_IDENTITY: admin@example.com + USER_SECRET: 12345678 + +jobs: + api-test: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + cache-dependency-path: "go.sum" + + - name: Build images + run: make all -j $(nproc) && make dockers_dev -j $(nproc) + + - name: Start containers + run: make run up args="-d" && sleep 10 + + - name: Set access token + run: | + export USER_TOKEN=$(curl -sSX POST $TOKEN_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\"}" | jq -r .access_token) + echo "USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV + + - name: Run Users API tests + uses: schemathesis/action@v1 + with: + schema: api/openapi/users.yml + base-url: http://localhost:9002 + checks: all + report: false + args: '--header "Authorization: Bearer ${{ env.USER_TOKEN }}" --contrib-unique-data --contrib-openapi-formats-uuid --hypothesis-suppress-health-check=filter_too_much --stateful=links' + + - name: Stop containers + if: always() + run: make run down args="-v" diff --git a/.gitignore b/.gitignore index 8d9ae5778..60286b531 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ tools/provision/mgconn.toml # coverage files coverage + +# Schemathesis +.hypothesis diff --git a/Makefile b/Makefile index f43285ff5..e6579a3d0 100644 --- a/Makefile +++ b/Makefile @@ -104,14 +104,14 @@ FILTERED_SERVICES = $(filter-out $(RUN_ADDON_ARGS), $(SERVICES)) all: $(SERVICES) -.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs +.PHONY: all $(SERVICES) dockers dockers_dev latest release run run_addons grpc_mtls_certs check_mtls check_certs test_api clean: rm -rf ${BUILD_DIR} cleandocker: # Stops containers and removes containers, networks, volumes, and images created by up - docker-compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans + docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) down --rmi all -v --remove-orphans ifdef pv # Remove unused volumes @@ -136,6 +136,16 @@ test: mocks done go test -v --race -count 1 -tags test -coverprofile=coverage/coverage.out $$(go list ./... | grep -v 'consumers\|readers\|postgres\|internal\|opcua\|cmd') +test_api: + @which st > /dev/null || (echo "schemathesis not found, please install it from https://github.com/schemathesis/schemathesis#getting-started" && exit 1) + st run api/openapi/users.yml \ + --checks all \ + --base-url http://localhost:9002 \ + --header "Authorization: Bearer $(USER_TOKEN)" \ + --contrib-unique-data --contrib-openapi-formats-uuid \ + --hypothesis-suppress-health-check=filter_too_much \ + --stateful=links + proto: protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./*.proto @@ -246,16 +256,16 @@ run: check_certs change_config ifeq ($(MG_ES_TYPE), redis) sed -i "s/MG_ES_TYPE=.*/MG_ES_TYPE=redis/" docker/.env sed -i "s/MG_ES_URL=.*/MG_ES_URL=$$\{MG_REDIS_URL}/" docker/.env - docker-compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) --profile redis -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) + docker compose -f docker/docker-compose.yml --profile $(DOCKER_PROFILE) --profile redis -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) else sed -i "s,MG_ES_TYPE=.*,MG_ES_TYPE=$$\{MG_MESSAGE_BROKER_TYPE}," docker/.env sed -i "s,MG_ES_URL=.*,MG_ES_URL=$$\{MG_$(shell echo ${MG_MESSAGE_BROKER_TYPE} | tr 'a-z' 'A-Z')_URL\}," docker/.env - docker-compose -f docker/docker-compose.yml --env-file docker/.env --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) + docker compose -f docker/docker-compose.yml --env-file docker/.env --profile $(DOCKER_PROFILE) -p $(DOCKER_PROJECT) $(DOCKER_COMPOSE_COMMAND) $(args) endif run_addons: check_certs $(call change_config) $(foreach SVC,$(RUN_ADDON_ARGS),$(if $(filter $(SVC),$(ADDON_SERVICES) $(EXTERNAL_SERVICES)),,$(error Invalid Service $(SVC)))) @for SVC in $(RUN_ADDON_ARGS); do \ - MG_ADDONS_CERTS_PATH_PREFIX="../." docker-compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ + MG_ADDONS_CERTS_PATH_PREFIX="../." docker compose -f docker/addons/$$SVC/docker-compose.yml -p $(DOCKER_PROJECT) --env-file ./docker/.env $(DOCKER_COMPOSE_COMMAND) $(args) & \ done diff --git a/api/openapi/users.yml b/api/openapi/users.yml index d7af7fd09..c5fa8e473 100644 --- a/api/openapi/users.yml +++ b/api/openapi/users.yml @@ -34,6 +34,7 @@ tags: paths: /users: post: + operationId: createUser tags: - Users summary: Registers user account @@ -49,14 +50,19 @@ paths: description: Failed due to malformed JSON. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "409": description: Failed due to using an existing identity. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" get: + operationId: listUsers tags: - Users summary: List users @@ -95,6 +101,7 @@ paths: /users/profile: get: + operationId: getProfile summary: Gets info on currently logged in user. description: | Gets info on currently logged in user. Info is obtained using @@ -115,6 +122,7 @@ paths: /users/{userID}: get: + operationId: getUser summary: Retrieves a user description: | Retrieves a specific user that is identifier by the user ID. @@ -131,6 +139,8 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. "422": @@ -139,6 +149,7 @@ paths: $ref: "#/components/responses/ServiceError" patch: + operationId: updateUser summary: Updates name and metadata of the user. description: | Updates name and metadata of the user with provided ID. Name and metadata @@ -156,15 +167,24 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/tags: patch: + operationId: updateUserTags summary: Updates tags the user. description: | Updates tags of the user with provided ID. Tags is updated using @@ -182,15 +202,22 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/identity: patch: + operationId: updateUserIdentity summary: Updates Identity of the user. description: | Updates identity of the user with provided ID. Identity is @@ -208,15 +235,24 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/role: patch: + operationId: updateUserRole summary: Updates the user role. description: | Updates role for the user with provided ID. @@ -233,15 +269,22 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. + "403": + description: Failed to perform authorization over the entity. "404": description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{userID}/disable: post: + operationId: disableUser summary: Disables a user description: | Disables a specific user that is identifier by the user ID. @@ -258,8 +301,14 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "409": + description: Failed due to already disabled user. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -267,6 +316,7 @@ paths: /users/{userID}/enable: post: + operationId: enableUser summary: Enables a user description: | Enables a specific user that is identifier by the user ID. @@ -283,8 +333,14 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "409": + description: Failed due to already enabled user. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -292,6 +348,7 @@ paths: /users/secret: patch: + operationId: updateUserSecret summary: Updates Secret of currently logged in user. description: | Updates secret of currently logged in user. Secret is updated using @@ -307,15 +364,20 @@ paths: $ref: "#/components/responses/UserRes" "400": description: Failed due to malformed JSON. - "404": - description: Failed due to non existing user. "401": description: Missing or invalid access token provided. + "404": + description: Failed due to non existing user. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /password/reset-request: post: + operationId: requestPasswordReset summary: User password reset request description: | Generates a reset token and sends and @@ -333,11 +395,14 @@ paths: description: Failed due to malformed JSON. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /password/reset: put: + operationId: resetPassword summary: User password reset endpoint description: | When user gets reset token, after he submitted @@ -352,13 +417,18 @@ paths: description: User link . "400": description: Failed due to malformed JSON. + "401": + description: Missing or invalid access token provided. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/users: get: + operationId: listUsersInGroup tags: - Users summary: List users in a group @@ -386,6 +456,8 @@ paths: description: | Missing or invalid access token provided. This endpoint is available only for administrators. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. "422": @@ -395,6 +467,7 @@ paths: /channels/{channelID}/users: get: + operationId: listUsersInChannel tags: - Users summary: List users in a channel @@ -422,6 +495,8 @@ paths: description: | Missing or invalid access token provided. This endpoint is available only for administrators. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. "422": @@ -431,6 +506,7 @@ paths: /users/tokens/issue: post: + operationId: issueToken summary: Issue Token description: | Issue Access and Refresh Token used for authenticating into the system. @@ -441,8 +517,12 @@ paths: responses: "200": $ref: "#/components/responses/TokenRes" + "400": + description: Failed due to malformed JSON. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -450,6 +530,7 @@ paths: /users/tokens/refresh: post: + operationId: refreshToken summary: Refresh Token description: | Refreshes Access and Refresh Token used for authenticating into the system. @@ -460,8 +541,12 @@ paths: responses: "200": $ref: "#/components/responses/TokenRes" + "400": + description: Failed due to malformed JSON. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -469,6 +554,7 @@ paths: /groups: post: + operationId: createGroup tags: - Groups summary: Creates new group @@ -486,14 +572,19 @@ paths: description: Failed due to malformed JSON. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "409": description: Failed due to using an existing identity. "415": description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" get: + operationId: listGroups summary: Lists groups. description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -520,13 +611,18 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}: get: + operationId: getGroup summary: Gets group info. description: | Gets info on a group specified by id. @@ -543,12 +639,17 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" put: + operationId: updateGroup summary: Updates group data. description: | Updates Name, Description or Metadata of a group. @@ -567,8 +668,16 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "409": + description: Failed due to using an existing identity. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" delete: @@ -594,6 +703,7 @@ paths: /groups/{groupID}/children: get: + operationId: listChildren summary: List children of a certain group description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -621,13 +731,18 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/parents: get: + operationId: listParents summary: List parents of a certain group description: | Lists groups up to a max level of hierarchy that can be fetched in one @@ -655,13 +770,18 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /groups/{groupID}/enable: post: + operationId: enableGroup summary: Enables a group description: | Enables a specific group that is identifier by the group ID. @@ -678,8 +798,14 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "409": + description: Failed due to already enabled group. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -687,6 +813,7 @@ paths: /groups/{groupID}/disable: post: + operationId: disableGroup summary: Disables a group description: | Disables a specific group that is identifier by the group ID. @@ -703,8 +830,14 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "409": + description: Failed due to already disabled group. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -712,6 +845,7 @@ paths: /groups/{groupID}/users/assign: post: + operationId: assignUser summary: Assigns a user to a group description: | Assigns a specific user to a group that is identifier by the group ID. @@ -730,8 +864,12 @@ paths: description: Failed due to malformed group's ID. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -739,6 +877,7 @@ paths: /groups/{groupID}/users/unassign: post: + operationId: unassignUser summary: Unassigns a user to a group description: | Unassigns a specific user to a group that is identifier by the group ID. @@ -757,8 +896,12 @@ paths: description: Failed due to malformed group's ID. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: A non-existent entity request. + "415": + description: Missing or invalid content type. "422": description: Database can't process request. "500": @@ -766,6 +909,7 @@ paths: /channels/{memberID}/groups: get: + operationId: listGroupsInChannel summary: Get group associated with the member description: | Gets groups associated with the channel member specified by id. @@ -787,13 +931,18 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /users/{memberID}/groups: get: + operationId: listGroupsByUser summary: Get group associated with the member description: | Gets groups associated with the user member specified by id. @@ -815,13 +964,18 @@ paths: description: Failed due to malformed query parameters. "401": description: Missing or invalid access token provided. + "403": + description: Failed to perform authorization over the entity. "404": description: Group does not exist. + "422": + description: Database can't process request. "500": $ref: "#/components/responses/ServiceError" /health: get: + operationId: health summary: Retrieves service health check info. tags: - health @@ -1177,7 +1331,7 @@ components: required: - groups - total - - level + - offset MembershipsPage: type: object @@ -1286,7 +1440,7 @@ components: properties: role: type: string - enum: ["admin","user"] + enum: ["admin", "user"] example: user description: User role example. required: @@ -1425,6 +1579,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1452,6 +1609,7 @@ components: in: query schema: type: string + pattern: "^[^\u0000-\u001F]*$" required: false example: "admin@example.com" @@ -1541,6 +1699,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1551,6 +1712,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1561,6 +1725,9 @@ components: schema: type: string format: uuid + minLength: 36 + maxLength: 36 + pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" required: true example: bb7edb32-2eac-4aad-aebe-ed96fe073879 @@ -1789,6 +1956,43 @@ components: application/json: schema: $ref: "#/components/schemas/User" + links: + get: + operationId: getUser + parameters: + userID: $response.body#/id + get_groups: + operationId: listUsersInGroup + parameters: + groupID: $response.body#/id + get_channels: + operationId: listUsersInChannel + parameters: + channelID: $response.body#/id + update: + operationId: updateUser + parameters: + userID: $response.body#/id + update_tags: + operationId: updateUserTags + parameters: + userID: $response.body#/id + update_identity: + operationId: updateUserIdentity + parameters: + userID: $response.body#/id + update_role: + operationId: updateUserRole + parameters: + userID: $response.body#/id + disable: + operationId: disableUser + parameters: + userID: $response.body#/id + enable: + operationId: enableUser + parameters: + userID: $response.body#/id UserRes: description: Data retrieved. @@ -1796,6 +2000,15 @@ components: application/json: schema: $ref: "#/components/schemas/User" + links: + get_groups: + operationId: listUsersInGroup + parameters: + groupID: $response.body#/id + get_channels: + operationId: listUsersInChannel + parameters: + channelID: $response.body#/id UserPageRes: description: Data retrieved. @@ -1823,6 +2036,47 @@ components: application/json: schema: $ref: "#/components/schemas/Group" + links: + get: + operationId: getGroup + parameters: + groupID: $response.body#/id + get_children: + operationId: listChildren + parameters: + groupID: $response.body#/id + get_parent: + operationId: listParents + parameters: + groupID: $response.body#/id + get_channels: + operationId: listGroupsInChannel + parameters: + memberID: $response.body#/id + get_users: + operationId: listGroupsByUser + parameters: + memberID: $response.body#/id + update: + operationId: updateGroup + parameters: + groupID: $response.body#/id + disable: + operationId: disableGroup + parameters: + groupID: $response.body#/id + enable: + operationId: enableGroup + parameters: + groupID: $response.body#/id + assign: + operationId: assignUser + parameters: + groupID: $response.body#/id + unassign: + operationId: unassignUser + parameters: + groupID: $response.body#/id GroupRes: description: Data retrieved. @@ -1830,6 +2084,31 @@ components: application/json: schema: $ref: "#/components/schemas/Group" + links: + get_children: + operationId: listChildren + parameters: + groupID: $response.body#/id + get_parent: + operationId: listParents + parameters: + groupID: $response.body#/id + get_channels: + operationId: listGroupsInChannel + parameters: + memberID: $response.body#/id + get_users: + operationId: listGroupsByUser + parameters: + memberID: $response.body#/id + assign: + operationId: assignUser + parameters: + groupID: $response.body#/id + unassign: + operationId: unassignUser + parameters: + groupID: $response.body#/id GroupPageRes: description: Data retrieved. diff --git a/auth/postgres/domains.go b/auth/postgres/domains.go index 56f4b8a56..4e605f69e 100644 --- a/auth/postgres/domains.go +++ b/auth/postgres/domains.go @@ -103,13 +103,13 @@ func (repo domainRepo) RetrievePermissions(ctx context.Context, subject, id stri rows, err := repo.db.QueryxContext(ctx, q, id, subject) if err != nil { - return []string{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return []string{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return []string{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } permissions := []string{} @@ -141,18 +141,18 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM domains d" @@ -162,7 +162,7 @@ func (repo domainRepo) RetrieveAllByIDs(ctx context.Context, pm auth.Page) (auth total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } pm.Total = total @@ -201,18 +201,18 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma dbPage, err := toDBClientsPage(pm) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() domains, err := repo.processRows(rows) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM domains d JOIN policies pc ON pc.object_id = d.id" @@ -222,7 +222,7 @@ func (repo domainRepo) ListDomains(ctx context.Context, pm auth.Page) (auth.Doma total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return auth.DomainsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return auth.DomainsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } pm.Total = total diff --git a/auth/service.go b/auth/service.go index 3938a05ac..3d670029d 100644 --- a/auth/service.go +++ b/auth/service.go @@ -30,15 +30,6 @@ var ( // ErrFailedToRetrieveMembership failed to retrieve memberships. ErrFailedToRetrieveMembership = errors.New("failed to retrieve memberships") - // ErrFailedToRetrieveAll failed to retrieve groups. - ErrFailedToRetrieveAll = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveParents failed to retrieve groups. - ErrFailedToRetrieveParents = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveChildren failed to retrieve groups. - ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups") - errIssueUser = errors.New("failed to issue new login key") errIssueTmp = errors.New("failed to issue new temporary key") errRevoke = errors.New("failed to remove key") diff --git a/internal/api/common.go b/internal/api/common.go index 90b086c4b..f896562bc 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -13,6 +13,7 @@ import ( "github.com/absmach/magistrala/internal/postgres" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/gofrs/uuid" ) @@ -114,17 +115,33 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrMissingMemberType), errors.Contains(err, apiutil.ErrMissingMemberKind), errors.Contains(err, apiutil.ErrLimitSize), - errors.Contains(err, apiutil.ErrNameSize): + errors.Contains(err, apiutil.ErrNameSize), + errors.Contains(err, apiutil.ErrInvalidStatus), + errors.Contains(err, apiutil.ErrInvitationState), + errors.Contains(err, apiutil.ErrInvalidRole), + errors.Contains(err, apiutil.ErrMissingEmail), + errors.Contains(err, apiutil.ErrMissingHost), + errors.Contains(err, apiutil.ErrMissingIdentity), + errors.Contains(err, apiutil.ErrMissingSecret), + errors.Contains(err, apiutil.ErrMissingPass), + errors.Contains(err, apiutil.ErrMissingConfPass), + errors.Contains(err, apiutil.ErrInvalidResetPass), + errors.Contains(err, apiutil.ErrMissingRelation), + errors.Contains(err, errors.ErrPasswordFormat), + errors.Contains(err, apiutil.ErrInvalidLevel), + errors.Contains(err, apiutil.ErrInvalidQueryParams): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, errors.ErrAuthentication), + errors.Contains(err, errors.ErrLogin), errors.Contains(err, apiutil.ErrBearerToken): w.WriteHeader(http.StatusUnauthorized) case errors.Contains(err, svcerr.ErrNotFound): w.WriteHeader(http.StatusNotFound) case errors.Contains(err, svcerr.ErrConflict), errors.Contains(err, postgres.ErrMemberAlreadyAssigned), - errors.Contains(err, errors.ErrConflict): + errors.Contains(err, errors.ErrConflict), + errors.Contains(err, errors.ErrStatusAlreadyAssigned): w.WriteHeader(http.StatusConflict) case errors.Contains(err, svcerr.ErrAuthorization), errors.Contains(err, errors.ErrAuthorization), @@ -134,9 +151,13 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(http.StatusUnsupportedMediaType) case errors.Contains(err, svcerr.ErrCreateEntity), errors.Contains(err, svcerr.ErrUpdateEntity), + errors.Contains(err, svcerr.ErrFailedOwnerUpdate), errors.Contains(err, svcerr.ErrViewEntity), + errors.Contains(err, repoerr.ErrFailedToRetrieveAllGroups), + errors.Contains(err, repoerr.ErrAddPolicies), + errors.Contains(err, repoerr.ErrDeletePolicies), errors.Contains(err, svcerr.ErrRemoveEntity): - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusUnprocessableEntity) default: w.WriteHeader(http.StatusInternalServerError) } diff --git a/internal/apiutil/errors.go b/internal/apiutil/errors.go index bca1c710e..e2346edd2 100644 --- a/internal/apiutil/errors.go +++ b/internal/apiutil/errors.go @@ -114,12 +114,12 @@ var ( // ErrMissingRelation indicates missing relation. ErrMissingRelation = errors.New("missing relation") + // ErrInvalidRelation indicates an invalid relation. + ErrInvalidRelation = errors.New("invalid relation") + // ErrInvalidAPIKey indicates an invalid API key type. ErrInvalidAPIKey = errors.New("invalid api key type") - // ErrMaxLevelExceeded indicates an invalid group level. - ErrMaxLevelExceeded = errors.New("invalid group level (should be lower than 5)") - // ErrBootstrapState indicates an invalid bootstrap state. ErrBootstrapState = errors.New("invalid bootstrap state") diff --git a/internal/groups/api/responses.go b/internal/groups/api/responses.go index 11651448e..15ebc327a 100644 --- a/internal/groups/api/responses.go +++ b/internal/groups/api/responses.go @@ -121,9 +121,9 @@ type groupPageRes struct { } type pageRes struct { - Limit uint64 `json:"limit"` + Limit uint64 `json:"limit,omitempty"` Offset uint64 `json:"offset"` - Total uint64 `json:"total,omitempty"` + Total uint64 `json:"total"` Level uint64 `json:"level,omitempty"` } diff --git a/internal/groups/postgres/groups.go b/internal/groups/postgres/groups.go index 48eea426a..7b1b4e489 100644 --- a/internal/groups/postgres/groups.go +++ b/internal/groups/postgres/groups.go @@ -166,17 +166,17 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( dbPage, err := toDBGroupPage(gm) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() items, err := repo.processRows(rows) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM groups g" @@ -186,7 +186,7 @@ func (repo groupRepository) RetrieveAll(ctx context.Context, gm mggroups.Page) ( total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } page := gm @@ -215,21 +215,19 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, } q = fmt.Sprintf("%s %s ORDER BY g.updated_at LIMIT :limit OFFSET :offset;", q, query) - fmt.Println(q) - fmt.Printf("%+v\n", gm) dbPage, err := toDBGroupPage(gm) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.db.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() items, err := repo.processRows(rows) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } cq := "SELECT COUNT(*) FROM groups g" @@ -239,7 +237,7 @@ func (repo groupRepository) RetrieveByIDs(ctx context.Context, gm mggroups.Page, total, err := postgres.Total(ctx, repo.db, cq, dbPage) if err != nil { - return mggroups.Page{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mggroups.Page{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } page := gm diff --git a/internal/groups/service.go b/internal/groups/service.go index 0af806f40..0a89923dc 100644 --- a/internal/groups/service.go +++ b/internal/groups/service.go @@ -13,6 +13,8 @@ import ( "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" + repoerr "github.com/absmach/magistrala/pkg/errors/repository" + svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/groups" "golang.org/x/sync/errgroup" ) @@ -20,8 +22,6 @@ import ( var ( errParentUnAuthz = errors.New("failed to authorize parent group") errMemberKind = errors.New("invalid member kind") - errAddPolicies = errors.New("failed to add policies") - errDeletePolicies = errors.New("failed to delete policies") errRetrieveGroups = errors.New("failed to retrieve groups") errGroupIDs = errors.New("invalid group ids") ) @@ -292,10 +292,10 @@ func (svc service) checkSuperAdmin(ctx context.Context, userID string) error { Object: auth.MagistralaObject, }) if err != nil { - return err + return errors.Wrap(svcerr.ErrAuthorization, err) } if !res.Authorized { - return errors.ErrAuthorization + return svcerr.ErrAuthorization } return nil } @@ -453,7 +453,7 @@ func (svc service) Assign(ctx context.Context, token, groupID, relation, memberK } if _, err := svc.auth.AddPolicies(ctx, &policies); err != nil { - return errors.Wrap(errAddPolicies, err) + return errors.Wrap(repoerr.ErrAddPolicies, err) } return nil @@ -604,7 +604,7 @@ func (svc service) Unassign(ctx context.Context, token, groupID, relation, membe } if _, err := svc.auth.DeletePolicies(ctx, &policies); err != nil { - return errors.Wrap(errDeletePolicies, err) + return errors.Wrap(repoerr.ErrDeletePolicies, err) } return nil } @@ -702,7 +702,7 @@ func (svc service) changeGroupStatus(ctx context.Context, token string, group gr return groups.Group{}, err } if dbGroup.Status == group.Status { - return groups.Group{}, mgclients.ErrStatusAlreadyAssigned + return groups.Group{}, errors.ErrStatusAlreadyAssigned } group.UpdatedBy = id @@ -731,10 +731,10 @@ func (svc service) authorizeToken(ctx context.Context, subjectType, subject, per } res, err := svc.auth.Authorize(ctx, req) if err != nil { - return "", err + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return res.GetId(), nil } @@ -751,10 +751,10 @@ func (svc service) authorizeKind(ctx context.Context, domainID, subjectType, sub } res, err := svc.auth.Authorize(ctx, req) if err != nil { - return "", err + return "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return "", errors.ErrAuthorization + return "", svcerr.ErrAuthorization } return res.GetId(), nil } diff --git a/internal/postgres/common.go b/internal/postgres/common.go index 1c5b18371..e924b8897 100644 --- a/internal/postgres/common.go +++ b/internal/postgres/common.go @@ -31,15 +31,6 @@ var ( // ErrFailedToRetrieveMembership failed to retrieve memberships. ErrFailedToRetrieveMembership = errors.New("failed to retrieve memberships") - - // ErrFailedToRetrieveAll failed to retrieve groups. - ErrFailedToRetrieveAll = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveParents failed to retrieve groups. - ErrFailedToRetrieveParents = errors.New("failed to retrieve all groups") - - // ErrFailedToRetrieveChildren failed to retrieve groups. - ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups") ) func CreateMetadataQuery(entity string, um map[string]interface{}) (string, []byte, error) { diff --git a/invitations/api/endpoint_test.go b/invitations/api/endpoint_test.go index b03cb2fef..65b0900ff 100644 --- a/invitations/api/endpoint_test.go +++ b/invitations/api/endpoint_test.go @@ -170,7 +170,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid offset", token: validToken, query: "offset=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -186,7 +186,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid limit", token: validToken, query: "limit=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -202,7 +202,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate user_id", token: validToken, query: "user_id=1&user_id=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -218,7 +218,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate invited_by", token: validToken, query: "invited_by=1&invited_by=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -234,7 +234,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate relation", token: validToken, query: "relation=1&relation=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -250,7 +250,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate domain_id", token: validToken, query: "domain_id=1&domain_id=2", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -266,7 +266,7 @@ func TestListInvitation(t *testing.T) { desc: "with invalid state", token: validToken, query: "state=invalid", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, @@ -274,7 +274,7 @@ func TestListInvitation(t *testing.T) { desc: "with duplicate state", token: validToken, query: "state=all&state=all", - status: http.StatusInternalServerError, + status: http.StatusBadRequest, contentType: validContenType, svcErr: nil, }, diff --git a/invitations/api/requests_test.go b/invitations/api/requests_test.go index 1f1192a92..a2e22660a 100644 --- a/invitations/api/requests_test.go +++ b/invitations/api/requests_test.go @@ -4,7 +4,6 @@ package api import ( - "errors" "fmt" "testing" @@ -14,11 +13,7 @@ import ( "github.com/stretchr/testify/assert" ) -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") - valid = "valid" -) +var valid = "valid" func TestSendInvitationReqValidation(t *testing.T) { cases := []struct { @@ -79,7 +74,7 @@ func TestSendInvitationReqValidation(t *testing.T) { Relation: "", Resend: true, }, - err: errMissingRelation, + err: apiutil.ErrMissingRelation, }, { desc: "invalid relation", @@ -90,7 +85,7 @@ func TestSendInvitationReqValidation(t *testing.T) { Relation: "invalid", Resend: true, }, - err: errInvalidRelation, + err: apiutil.ErrInvalidRelation, }, } diff --git a/invitations/invitations.go b/invitations/invitations.go index 354dd3108..0cf6c5a2a 100644 --- a/invitations/invitations.go +++ b/invitations/invitations.go @@ -6,15 +6,10 @@ package invitations import ( "context" "encoding/json" - "errors" "time" "github.com/absmach/magistrala/auth" -) - -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") + "github.com/absmach/magistrala/internal/apiutil" ) // Invitation is an invitation to join a domain. @@ -122,7 +117,7 @@ type Repository interface { // It returns an error if the relation is empty or invalid. func CheckRelation(relation string) error { if relation == "" { - return errMissingRelation + return apiutil.ErrMissingRelation } if relation != auth.AdministratorRelation && relation != auth.EditorRelation && @@ -133,7 +128,7 @@ func CheckRelation(relation string) error { relation != auth.RoleGroupRelation && relation != auth.GroupRelation && relation != auth.PlatformRelation { - return errInvalidRelation + return apiutil.ErrInvalidRelation } return nil diff --git a/invitations/invitations_test.go b/invitations/invitations_test.go index 942eaec76..2c367d7a7 100644 --- a/invitations/invitations_test.go +++ b/invitations/invitations_test.go @@ -4,19 +4,14 @@ package invitations_test import ( - "errors" "fmt" "testing" + "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/invitations" "github.com/stretchr/testify/assert" ) -var ( - errMissingRelation = errors.New("missing relation") - errInvalidRelation = errors.New("invalid relation") -) - func TestInvitation_MarshalJSON(t *testing.T) { cases := []struct { desc string @@ -60,8 +55,8 @@ func TestCheckRelation(t *testing.T) { relation string err error }{ - {"", errMissingRelation}, - {"admin", errInvalidRelation}, + {"", apiutil.ErrMissingRelation}, + {"admin", apiutil.ErrInvalidRelation}, {"editor", nil}, {"viewer", nil}, {"member", nil}, diff --git a/invitations/service_test.go b/invitations/service_test.go index c1c1da521..ee6c3215d 100644 --- a/invitations/service_test.go +++ b/invitations/service_test.go @@ -5,7 +5,6 @@ package invitations_test import ( "context" - "errors" "math/rand" "testing" "time" @@ -13,6 +12,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/auth" authmocks "github.com/absmach/magistrala/auth/mocks" + "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/invitations" "github.com/absmach/magistrala/invitations/mocks" @@ -79,7 +79,7 @@ func TestSendInvitation(t *testing.T) { token: validToken, tokenUserID: testsutil.GenerateUUID(t), req: invitations.Invitation{Relation: "invalid"}, - err: errors.New("invalid relation"), + err: apiutil.ErrInvalidRelation, authNErr: nil, domainErr: nil, adminErr: nil, diff --git a/pkg/clients/postgres/clients.go b/pkg/clients/postgres/clients.go index 057b79e04..9c03df5ca 100644 --- a/pkg/clients/postgres/clients.go +++ b/pkg/clients/postgres/clients.go @@ -157,11 +157,11 @@ func (repo ClientRepository) RetrieveAll(ctx context.Context, pm clients.Page) ( dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() @@ -205,12 +205,12 @@ func (repo ClientRepository) RetrieveAllBasicInfo(ctx context.Context, pm client dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() @@ -263,11 +263,11 @@ func (repo ClientRepository) RetrieveAllByIDs(ctx context.Context, pm clients.Pa dbPage, err := ToDBClientsPage(pm) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return clients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return clients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() diff --git a/pkg/clients/status.go b/pkg/clients/status.go index 9d4c59e2f..82a716d51 100644 --- a/pkg/clients/status.go +++ b/pkg/clients/status.go @@ -5,7 +5,6 @@ package clients import ( "encoding/json" - "errors" "strings" "github.com/absmach/magistrala/internal/apiutil" @@ -36,9 +35,6 @@ const ( Unknown = "unknown" ) -// ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. -var ErrStatusAlreadyAssigned = errors.New("status already assigned") - // String converts client/group status to string literal. func (s Status) String() string { switch s { diff --git a/pkg/errors/repository/types.go b/pkg/errors/repository/types.go index 2637d79b3..a9bb4fb70 100644 --- a/pkg/errors/repository/types.go +++ b/pkg/errors/repository/types.go @@ -48,4 +48,13 @@ var ( // ErrInvalidSecret indicates invalid secret. ErrInvalidSecret = errors.New("missing secret") + + // ErrAddPolicies indicates failed to add policies. + ErrAddPolicies = errors.New("failed to add policies") + + // ErrDeletePolicies indicates failed to delete policies. + ErrDeletePolicies = errors.New("failed to delete policies") + + // ErrFailedToRetrieveAllGroups failed to retrieve groups. + ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") ) diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index a0a3049f2..5c175fe5b 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -45,4 +45,13 @@ var ( // ErrInvalidPolicy indicates that an invalid policy. ErrInvalidPolicy = errors.New("invalid policy") + + // ErrRecoveryToken indicates error in generating password recovery token. + ErrRecoveryToken = errors.New("failed to generate password recovery token") + + // ErrFailedPolicyUpdate indicates a failure to update user policy. + ErrFailedPolicyUpdate = errors.New("failed to update user policy") + + // ErrFailedOwnerUpdate indicates a failure to update user policy. + ErrFailedOwnerUpdate = errors.New("failed to update user owner") ) diff --git a/pkg/errors/types.go b/pkg/errors/types.go index 684c4dec6..f49e71a79 100644 --- a/pkg/errors/types.go +++ b/pkg/errors/types.go @@ -45,6 +45,9 @@ var ( // ErrLogin indicates wrong login credentials. ErrLogin = New("invalid user id or secret") + // ErrPasswordFormat indicates weak password. + ErrPasswordFormat = errors.New("password does not meet the requirements") + // ErrUnsupportedContentType indicates invalid content type. ErrUnsupportedContentType = errors.New("invalid content type") @@ -53,4 +56,7 @@ var ( // ErrEmptyPath indicates empty file path. ErrEmptyPath = errors.New("empty file path") + + // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. + ErrStatusAlreadyAssigned = errors.New("status already assigned") ) diff --git a/pkg/groups/errors.go b/pkg/groups/errors.go index 24b1a29d9..b6665fa0b 100644 --- a/pkg/groups/errors.go +++ b/pkg/groups/errors.go @@ -14,7 +14,4 @@ var ( // ErrDisableGroup indicates error in disabling group. ErrDisableGroup = errors.New("failed to disable group") - - // ErrStatusAlreadyAssigned indicated that the group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") ) diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go index 8b02d84da..562c0bd29 100644 --- a/pkg/sdk/go/channels_test.go +++ b/pkg/sdk/go/channels_test.go @@ -109,7 +109,7 @@ func TestCreateChannel(t *testing.T) { Status: mgclients.EnabledStatus.String(), }, token: token, - err: errors.NewSDKErrorWithStatus(errors.ErrCreateEntity, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.ErrCreateEntity, http.StatusUnprocessableEntity), }, { desc: "create channel with invalid owner", @@ -119,7 +119,7 @@ func TestCreateChannel(t *testing.T) { Status: mgclients.EnabledStatus.String(), }, token: token, - err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusUnprocessableEntity), }, { desc: "create channel with missing name", @@ -475,7 +475,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel description with invalid token", @@ -485,7 +485,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel metadata with invalid token", @@ -497,7 +497,7 @@ func TestUpdateChannel(t *testing.T) { }, response: sdk.Channel{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update channel that can't be marshalled", diff --git a/pkg/sdk/go/groups_test.go b/pkg/sdk/go/groups_test.go index dfb19d0d1..dd8a98e30 100644 --- a/pkg/sdk/go/groups_test.go +++ b/pkg/sdk/go/groups_test.go @@ -94,7 +94,7 @@ func TestCreateGroup(t *testing.T) { ParentID: mocks.WrongID, Status: clients.EnabledStatus.String(), }, - err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(svcerr.ErrCreateEntity, http.StatusUnprocessableEntity), }, { desc: "create group with invalid owner", @@ -104,7 +104,7 @@ func TestCreateGroup(t *testing.T) { OwnerID: mocks.WrongID, Status: clients.EnabledStatus.String(), }, - err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(sdk.ErrFailedCreation, http.StatusUnprocessableEntity), }, { desc: "create group with missing name", @@ -731,7 +731,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update group description with invalid token", @@ -741,7 +741,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update group metadata with invalid token", @@ -753,7 +753,7 @@ func TestUpdateGroup(t *testing.T) { }, response: sdk.Group{}, token: invalidToken, - err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusForbidden), }, { desc: "update a group that can't be marshalled", diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go index a31e1851f..0d24fe740 100644 --- a/pkg/sdk/go/things_test.go +++ b/pkg/sdk/go/things_test.go @@ -97,7 +97,7 @@ func TestCreateThing(t *testing.T) { response: sdk.Thing{}, token: token, repoErr: sdk.ErrFailedCreation, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, repoerr.ErrCreateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, repoerr.ErrCreateEntity), http.StatusUnprocessableEntity), }, { desc: "register empty thing", @@ -247,7 +247,7 @@ func TestCreateThings(t *testing.T) { things: thingsList, response: []sdk.Thing{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusUnprocessableEntity), }, { desc: "register empty things", @@ -751,7 +751,7 @@ func TestUpdateThing(t *testing.T) { thing: thing2, response: sdk.Thing{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update thing that can't be marshalled", @@ -836,7 +836,7 @@ func TestUpdateThingTags(t *testing.T) { thing: thing2, response: sdk.Thing{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update thing that can't be marshalled", diff --git a/pkg/sdk/go/tokens_test.go b/pkg/sdk/go/tokens_test.go index ebf6cfbd5..ad5f07a95 100644 --- a/pkg/sdk/go/tokens_test.go +++ b/pkg/sdk/go/tokens_test.go @@ -62,7 +62,7 @@ func TestIssueToken(t *testing.T) { desc: "issue token for an empty user", login: sdk.Login{}, token: &magistrala.Token{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingIdentity), http.StatusBadRequest), }, { desc: "issue token for invalid identity", diff --git a/pkg/sdk/go/users_test.go b/pkg/sdk/go/users_test.go index 3285a3037..0aaadc450 100644 --- a/pkg/sdk/go/users_test.go +++ b/pkg/sdk/go/users_test.go @@ -86,7 +86,7 @@ func TestCreateClient(t *testing.T) { client: user, response: sdk.User{}, token: token, - err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(sdk.ErrFailedCreation, sdk.ErrFailedCreation), http.StatusUnprocessableEntity), }, { desc: "register empty user", @@ -367,7 +367,6 @@ func TestListClients(t *testing.T) { for _, tc := range cases { pm := sdk.PageMetadata{ Status: tc.status, - Total: total, Offset: tc.offset, Limit: tc.limit, Name: tc.name, @@ -570,7 +569,7 @@ func TestUpdateClient(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -661,7 +660,7 @@ func TestUpdateClientTags(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -751,7 +750,7 @@ func TestUpdateClientIdentity(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", @@ -767,7 +766,7 @@ func TestUpdateClientIdentity(t *testing.T) { }, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrUpdateEntity, svcerr.ErrUpdateEntity), http.StatusUnprocessableEntity), }, } @@ -920,7 +919,7 @@ func TestUpdateClientRole(t *testing.T) { client: client2, response: sdk.User{}, token: validToken, - err: errors.NewSDKErrorWithStatus(errors.Wrap(users.ErrFailedOwnerUpdate, users.ErrFailedOwnerUpdate), http.StatusInternalServerError), + err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrFailedOwnerUpdate, svcerr.ErrFailedOwnerUpdate), http.StatusUnprocessableEntity), }, { desc: "update a user that can't be marshalled", diff --git a/things/service.go b/things/service.go index 1da19c87e..f67dc78ac 100644 --- a/things/service.go +++ b/things/service.go @@ -510,7 +510,7 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) } if dbClient.Status == client.Status { - return mgclients.Client{}, mgclients.ErrStatusAlreadyAssigned + return mgclients.Client{}, errors.ErrStatusAlreadyAssigned } client.UpdatedBy = userID diff --git a/things/service_test.go b/things/service_test.go index cdc0433bd..2803b71d9 100644 --- a/things/service_test.go +++ b/things/service_test.go @@ -786,7 +786,7 @@ func TestEnableClient(t *testing.T) { token: validToken, client: enabledClient1, response: enabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "enable non-existing client", @@ -909,7 +909,7 @@ func TestDisableClient(t *testing.T) { token: validToken, client: disabledClient1, response: mgclients.Client{}, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "disable non-existing client", diff --git a/users/api/clients.go b/users/api/clients.go index 32dea1a2b..0208569e3 100644 --- a/users/api/clients.go +++ b/users/api/clients.go @@ -26,6 +26,7 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mglog.Logger) http.Han opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } + r.Route("/users", func(r chi.Router) { r.Post("/", otelhttp.NewHandler(kithttp.NewServer( registrationEndpoint(svc), @@ -83,20 +84,6 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mglog.Logger) http.Han opts..., ), "update_client_identity").ServeHTTP) - r.Post("/password/reset-request", otelhttp.NewHandler(kithttp.NewServer( - passwordResetRequestEndpoint(svc), - decodePasswordResetRequest, - api.EncodeResponse, - opts..., - ), "password_reset_req").ServeHTTP) - - r.Put("/password/reset", otelhttp.NewHandler(kithttp.NewServer( - passwordResetEndpoint(svc), - decodePasswordReset, - api.EncodeResponse, - opts..., - ), "password_reset").ServeHTTP) - r.Patch("/{id}/role", otelhttp.NewHandler(kithttp.NewServer( updateClientRoleEndpoint(svc), decodeUpdateClientRole, @@ -133,6 +120,22 @@ func clientsHandler(svc users.Service, r *chi.Mux, logger mglog.Logger) http.Han ), "disable_client").ServeHTTP) }) + r.Route("/password", func(r chi.Router) { + r.Post("/reset-request", otelhttp.NewHandler(kithttp.NewServer( + passwordResetRequestEndpoint(svc), + decodePasswordResetRequest, + api.EncodeResponse, + opts..., + ), "password_reset_req").ServeHTTP) + + r.Put("/reset", otelhttp.NewHandler(kithttp.NewServer( + passwordResetEndpoint(svc), + decodePasswordReset, + api.EncodeResponse, + opts..., + ), "password_reset").ServeHTTP) + }) + // Ideal location: users service, groups endpoint. // Reason for placing here : // SpiceDB provides list of user ids in given user_group_id @@ -220,7 +223,7 @@ func decodeListClients(_ context.Context, r *http.Request) (interface{}, error) } oid, err := apiutil.ReadStringQuery(r, api.OwnerKey, "") if err != nil { - return nil, err + return nil, errors.Wrap(apiutil.ErrValidation, err) } visibility, err := apiutil.ReadStringQuery(r, api.VisibilityKey, "") if err != nil { diff --git a/users/api/responses.go b/users/api/responses.go index 716f5a912..fb05de493 100644 --- a/users/api/responses.go +++ b/users/api/responses.go @@ -25,8 +25,8 @@ var ( type pageRes struct { Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` - Total uint64 `json:"total,omitempty"` + Offset uint64 `json:"offset"` + Total uint64 `json:"total"` } type createClientRes struct { diff --git a/users/postgres/clients.go b/users/postgres/clients.go index 7f5ebb53d..fb1a46247 100644 --- a/users/postgres/clients.go +++ b/users/postgres/clients.go @@ -131,11 +131,11 @@ func (repo clientRepo) RetrieveAll(ctx context.Context, pm mgclients.Page) (mgcl dbPage, err := pgclients.ToDBClientsPage(pm) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } rows, err := repo.DB.NamedQueryContext(ctx, q, dbPage) if err != nil { - return mgclients.ClientsPage{}, errors.Wrap(postgres.ErrFailedToRetrieveAll, err) + return mgclients.ClientsPage{}, errors.Wrap(repoerr.ErrFailedToRetrieveAllGroups, err) } defer rows.Close() diff --git a/users/service.go b/users/service.go index e40ce0aad..bb57432f9 100644 --- a/users/service.go +++ b/users/service.go @@ -18,17 +18,6 @@ import ( ) var ( - // ErrRecoveryToken indicates error in generating password recovery token. - ErrRecoveryToken = errors.New("failed to generate password recovery token") - - // ErrPasswordFormat indicates weak password. - ErrPasswordFormat = errors.New("password does not meet the requirements") - - // ErrFailedPolicyUpdate indicates a failure to update user policy. - ErrFailedPolicyUpdate = errors.New("failed to update user policy") - - // ErrFailedOwnerUpdate indicates a failure to update user policy. - ErrFailedOwnerUpdate = errors.New("failed to update user owner") // ErrAddPolicies indictaed a failre to add policies. errAddPolicies = errors.New("failed to add policies") @@ -291,7 +280,7 @@ func (svc service) GenerateResetToken(ctx context.Context, email, host string) e } token, err := svc.auth.Issue(ctx, issueReq) if err != nil { - return errors.Wrap(ErrRecoveryToken, err) + return errors.Wrap(svcerr.ErrRecoveryToken, err) } return svc.SendPasswordReset(ctx, host, email, client.Name, token.AccessToken) @@ -310,7 +299,7 @@ func (svc service) ResetSecret(ctx context.Context, resetToken, secret string) e return errors.ErrNotFound } if !svc.passRegex.MatchString(secret) { - return ErrPasswordFormat + return errors.ErrPasswordFormat } secret, err = svc.hasher.Hash(secret) if err != nil { @@ -336,7 +325,7 @@ func (svc service) UpdateClientSecret(ctx context.Context, token, oldSecret, new return mgclients.Client{}, err } if !svc.passRegex.MatchString(newSecret) { - return mgclients.Client{}, ErrPasswordFormat + return mgclients.Client{}, errors.ErrPasswordFormat } dbClient, err := svc.clients.RetrieveByID(ctx, id) if err != nil { @@ -383,7 +372,7 @@ func (svc service) UpdateClientRole(ctx context.Context, token string, cli mgcli } if err := svc.updateClientPolicy(ctx, cli.ID, cli.Role); err != nil { - return mgclients.Client{}, errors.Wrap(ErrFailedPolicyUpdate, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrFailedPolicyUpdate, err) } client, err = svc.clients.UpdateRole(ctx, client) if err != nil { @@ -391,7 +380,7 @@ func (svc service) UpdateClientRole(ctx context.Context, token string, cli mgcli if errRollback := svc.updateClientPolicy(ctx, cli.ID, mgclients.UserRole); errRollback != nil { return mgclients.Client{}, errors.Wrap(err, errors.Wrap(repoerr.ErrRollbackTx, errRollback)) } - return mgclients.Client{}, errors.Wrap(ErrFailedOwnerUpdate, err) + return mgclients.Client{}, errors.Wrap(svcerr.ErrFailedOwnerUpdate, err) } return client, nil } @@ -437,7 +426,7 @@ func (svc service) changeClientStatus(ctx context.Context, token string, client return mgclients.Client{}, errors.Wrap(repoerr.ErrNotFound, err) } if dbClient.Status == client.Status { - return mgclients.Client{}, mgclients.ErrStatusAlreadyAssigned + return mgclients.Client{}, errors.ErrStatusAlreadyAssigned } client.UpdatedBy = tokenUserID @@ -528,7 +517,7 @@ func (svc *service) authorize(ctx context.Context, subjType, subjKind, subj, per } if !res.GetAuthorized() { - return "", errors.Wrap(svcerr.ErrAuthorization, err) + return "", svcerr.ErrAuthorization } return res.GetId(), nil } diff --git a/users/service_test.go b/users/service_test.go index 5c0e48731..3667a6f6f 100644 --- a/users/service_test.go +++ b/users/service_test.go @@ -989,7 +989,7 @@ func TestEnableClient(t *testing.T) { token: validToken, client: enabledClient1, response: enabledClient1, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "enable non-existing client", @@ -1119,7 +1119,7 @@ func TestDisableClient(t *testing.T) { token: validToken, client: disabledClient1, response: mgclients.Client{}, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "disable non-existing client",