diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index cefe1c4d1..e7bf15846 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -7,9 +7,38 @@ on: push: branches: - main + paths: + - ".github/workflows/api-tests.yml" + - "api/**" + - "auth/api/http/**" + - "bootstrap/api**" + - "certs/api/**" + - "consumers/notifiers/api/**" + - "http/api/**" + - "invitations/api/**" + - "provision/api/**" + - "readers/api/**" + - "things/api/**" + - "twins/api/**" + - "users/api/**" + pull_request: branches: - main + paths: + - ".github/workflows/api-tests.yml" + - "api/**" + - "auth/api/http/**" + - "bootstrap/api**" + - "certs/api/**" + - "consumers/notifiers/api/**" + - "http/api/**" + - "invitations/api/**" + - "provision/api/**" + - "readers/api/**" + - "things/api/**" + - "twins/api/**" + - "users/api/**" env: TOKENS_URL: http://localhost:9002/users/tokens/issue @@ -17,6 +46,8 @@ env: USER_IDENTITY: admin@example.com USER_SECRET: 12345678 DOMAIN_NAME: demo-test + USERS_URL: http://localhost:9002 + THINGS_URL: http://localhost:9000 jobs: api-test: @@ -44,20 +75,82 @@ jobs: export USER_TOKEN=$(curl -sSX POST $TOKENS_URL -H "Content-Type: application/json" -d "{\"identity\": \"$USER_IDENTITY\",\"secret\": \"$USER_SECRET\",\"domain_id\": \"$DOMAIN_ID\"}" | jq -r .access_token) echo "USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV + - name: Check for changes in specific paths + uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + auth: + - ".github/workflows/api-tests.yml" + - "api/openapi/auth.yml" + - "auth/api/http/**" + + bootstrap: + - ".github/workflows/api-tests.yml" + - "api/openapi/bootstrap.yml" + - "bootstrap/api/**" + + certs: + - ".github/workflows/api-tests.yml" + - "api/openapi/certs.yml" + - "certs/api/**" + + notifiers: + - ".github/workflows/api-tests.yml" + - "api/openapi/notifiers.yml" + - "consumers/notifiers/api/**" + + http: + - ".github/workflows/api-tests.yml" + - "api/openapi/http.yml" + - "http/api/**" + + invitations: + - ".github/workflows/api-tests.yml" + - "api/openapi/invitations.yml" + - "invitations/api/**" + + provision: + - ".github/workflows/api-tests.yml" + - "api/openapi/provision.yml" + - "provision/api/**" + + readers: + - ".github/workflows/api-tests.yml" + - "api/openapi/readers.yml" + - "readers/api/**" + + things: + - ".github/workflows/api-tests.yml" + - "api/openapi/things.yml" + - "things/api/**" + + twins: + - ".github/workflows/api-tests.yml" + - "api/openapi/twins.yml" + - "twins/api/**" + + users: + - ".github/workflows/api-tests.yml" + - "api/openapi/users.yml" + - "users/api/**" + - name: Run Users API tests + if: steps.changes.outputs.users == 'true' uses: schemathesis/action@v1 with: schema: api/openapi/users.yml - base-url: http://localhost:9002 + base-url: ${{ env.USERS_URL }} 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: Run Things API tests + if: steps.changes.outputs.things == 'true' uses: schemathesis/action@v1 with: schema: api/openapi/things.yml - base-url: http://localhost:9000 + base-url: ${{ env.THINGS_URL }} 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' diff --git a/Makefile b/Makefile index e6579a3d0..6ef7655df 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ BUILD_DIR = build SERVICES = auth users things http coap ws lora influxdb-writer influxdb-reader mongodb-writer \ mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader timescale-writer timescale-reader cli \ bootstrap opcua twins mqtt provision certs smtp-notifier smpp-notifier invitations +TEST_API_SERVICES = auth bootstrap certs http invitations notifiers provision readers things twins users +TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES)) DOCKERS = $(addprefix docker_,$(SERVICES)) DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES)) CGO_ENABLED ?= 0 @@ -136,15 +138,30 @@ 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: +define test_api_service + $(eval svc=$(subst test_api_,,$(1))) @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 \ + + @if [ -z "$(USER_TOKEN)" ]; then \ + echo "USER_TOKEN is not set"; \ + echo "Please set it to a valid token"; \ + exit 1; \ + fi + + st run api/openapi/$(svc).yml \ --checks all \ - --base-url http://localhost:9002 \ + --base-url $(2) \ --header "Authorization: Bearer $(USER_TOKEN)" \ --contrib-unique-data --contrib-openapi-formats-uuid \ --hypothesis-suppress-health-check=filter_too_much \ --stateful=links +endef + +test_api_users: TEST_API_URL := http://localhost:9002 +test_api_things: TEST_API_URL := http://localhost:9000 + +$(TEST_API): + $(call test_api_service,$(@),$(TEST_API_URL)) proto: protoc -I. --go_out=. --go_opt=paths=source_relative pkg/messaging/*.proto diff --git a/api/openapi/things.yml b/api/openapi/things.yml index dde3c0e6a..422c18f43 100644 --- a/api/openapi/things.yml +++ b/api/openapi/things.yml @@ -94,6 +94,8 @@ paths: "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": @@ -113,7 +115,7 @@ paths: requestBody: $ref: "#/components/requestBodies/ThingsCreateReq" responses: - "201": + "200": $ref: "#/components/responses/ThingPageRes" "400": description: Failed due to malformed JSON. @@ -180,6 +182,8 @@ paths: description: Failed to perform authorization over the entity. "404": description: Failed due to non existing thing. + "409": + description: Failed due to using an existing identity. "415": description: Missing or invalid content type. "422": @@ -537,6 +541,8 @@ paths: description: Failed to perform authorization over the entity. "404": description: Channel does not exist. + "409": + description: Failed due to using an existing identity. "415": description: Missing or invalid content type. "422": @@ -665,7 +671,7 @@ paths: security: - bearerAuth: [] responses: - "200": + "204": description: Thing unshared. "400": description: Failed due to malformed thing's ID. @@ -725,7 +731,7 @@ paths: security: - bearerAuth: [] responses: - "200": + "204": description: Thing unshared. "400": description: Failed due to malformed thing's ID. @@ -1359,7 +1365,7 @@ components: ChannelsPage: type: object properties: - channels: + groups: type: array minItems: 0 uniqueItems: true @@ -1377,9 +1383,9 @@ components: example: 10 description: Maximum number of items to return in one page. required: - - channels + - groups - total - - level + - offset PoliciesPage: type: object diff --git a/internal/api/common_test.go b/internal/api/common_test.go index a29bba99c..a424d4d11 100644 --- a/internal/api/common_test.go +++ b/internal/api/common_test.go @@ -301,7 +301,7 @@ func TestEncodeError(t *testing.T) { svcerr.ErrViewEntity, svcerr.ErrRemoveEntity, }, - code: http.StatusInternalServerError, + code: http.StatusUnprocessableEntity, }, { desc: "InternalServerError", diff --git a/internal/groups/service_test.go b/internal/groups/service_test.go index 481482b5f..39e3807bf 100644 --- a/internal/groups/service_test.go +++ b/internal/groups/service_test.go @@ -17,7 +17,6 @@ import ( "github.com/absmach/magistrala/internal/groups" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/clients" - mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" mggroups "github.com/absmach/magistrala/pkg/groups" "github.com/absmach/magistrala/pkg/groups/mocks" @@ -585,7 +584,7 @@ func TestEnableGroup(t *testing.T) { retrieveResp: mggroups.Group{ Status: clients.Status(groups.EnabledStatus), }, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "with retrieve error", @@ -685,7 +684,7 @@ func TestDisableGroup(t *testing.T) { retrieveResp: mggroups.Group{ Status: clients.Status(groups.DisabledStatus), }, - err: mgclients.ErrStatusAlreadyAssigned, + err: errors.ErrStatusAlreadyAssigned, }, { desc: "with retrieve error", diff --git a/things/api/http/responses.go b/things/api/http/responses.go index 3987f182f..de7d8cb81 100644 --- a/things/api/http/responses.go +++ b/things/api/http/responses.go @@ -27,8 +27,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/things/service.go b/things/service.go index 6fc0efc71..cc3f2fe92 100644 --- a/things/service.go +++ b/things/service.go @@ -244,7 +244,7 @@ func (svc service) listUserThingPermission(ctx context.Context, userID, thingID ObjectType: auth.ThingType, }) if err != nil { - return []string{}, err + return []string{}, errors.Wrap(svcerr.ErrAuthorization, err) } return lp.GetPermissions(), nil } @@ -610,7 +610,7 @@ func (svc *service) authorize(ctx context.Context, domainID, subjType, subjKind, } res, err := svc.auth.Authorize(ctx, req) if err != nil { - return "", err + return "", errors.Wrap(errors.ErrAuthorization, err) } if !res.GetAuthorized() { return "", errors.Wrap(errors.ErrAuthorization, err)