From bee13c2e3a9400d81765dba84fde0c2137e4c6e9 Mon Sep 17 00:00:00 2001 From: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> Date: Fri, 15 Dec 2023 18:53:31 +0300 Subject: [PATCH] Add property based testing to boostrap API using schemathesis Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> --- .github/workflows/api-tests.yml | 13 ++- Makefile | 1 + api/openapi/bootstrap.yml | 198 +++++++++++++++++++++----------- bootstrap/api/endpoint_test.go | 2 +- bootstrap/api/transport.go | 92 ++------------- bootstrap/service.go | 56 ++++----- internal/api/common.go | 12 +- 7 files changed, 192 insertions(+), 182 deletions(-) diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index 9b6eb9bd23e..731aa5f8a4f 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -50,6 +50,7 @@ env: THINGS_URL: http://localhost:9000 INVITATIONS_URL: http://localhost:9020 AUTH_URL: http://localhost:8189 + BOOTSTRAP_URL: http://localhost:9013 jobs: api-test: @@ -68,7 +69,7 @@ jobs: run: make all -j $(nproc) && make dockers_dev -j $(nproc) - name: Start containers - run: make run up args="-d" && sleep 10 + run: make run up args="-d" && make run_addons up args="-d" - name: Set access token run: | @@ -177,6 +178,16 @@ jobs: 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 Bootstrap API tests + if: steps.changes.outputs.bootstrap == 'true' + uses: schemathesis/action@v1 + with: + schema: api/openapi/bootstrap.yml + base-url: ${{ env.BOOTSTRAP_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: Stop containers if: always() run: make run down args="-v" diff --git a/Makefile b/Makefile index aea5ee2808d..0c2e6410ea2 100644 --- a/Makefile +++ b/Makefile @@ -154,6 +154,7 @@ test_api_users: TEST_API_URL := http://localhost:9002 test_api_things: TEST_API_URL := http://localhost:9000 test_api_invitations: TEST_API_URL := http://localhost:9020 test_api_auth: TEST_API_URL := http://localhost:8189 +test_api_bootstrap: TEST_API_URL := http://localhost:9013 $(TEST_API): $(call test_api_service,$(@),$(TEST_API_URL)) diff --git a/api/openapi/bootstrap.yml b/api/openapi/bootstrap.yml index 2a8177a54fc..bf58d4d43fc 100644 --- a/api/openapi/bootstrap.yml +++ b/api/openapi/bootstrap.yml @@ -25,10 +25,11 @@ tags: externalDocs: description: Find out more about Configs url: https://docs.magistrala.abstractmachines.fr/ - + paths: /things/configs: post: + operationId: createConfig summary: Adds new config description: | Adds new config to the list of config owned by user identified using @@ -38,17 +39,28 @@ paths: requestBody: $ref: "#/components/requestBodies/ConfigCreateReq" responses: - '201': + "201": $ref: "#/components/responses/ConfigCreateRes" - '400': + "400": description: Failed due to malformed JSON. - '401': + "401": description: Missing or invalid access token provided. - '415': + "403": + description: Failed to perform authorization over the entity. + "404": + description: A non-existent entity request. + "409": + description: Failed due to using an existing identity. + "415": description: Missing or invalid content type. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" + "503": + description: Failed to receive response from the things service. get: + operationId: getConfigs summary: Retrieves managed configs description: | Retrieves a list of managed configs. Due to performance concerns, data @@ -63,31 +75,37 @@ paths: - $ref: "#/components/parameters/State" - $ref: "#/components/parameters/Name" responses: - '200': + "200": $ref: "#/components/responses/ConfigListRes" - '400': + "400": description: Failed due to malformed query parameters. - '401': + "401": description: Missing or invalid access token provided. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/configs/{configId}: get: + operationId: getConfig summary: Retrieves config info (with channels). tags: - configs parameters: - $ref: "#/components/parameters/ConfigId" responses: - '200': + "200": $ref: "#/components/responses/ConfigRes" - '401': + "401": description: Missing or invalid access token provided. - '404': + "404": description: Config does not exist. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" put: + operationId: updateConfig summary: Updates config info description: | Update is performed by replacing the current resource data with values @@ -98,21 +116,24 @@ paths: parameters: - $ref: "#/components/parameters/ConfigId" requestBody: - $ref: "#/components/requestBodies/ConfigUpdateReq" + $ref: "#/components/requestBodies/ConfigUpdateReq" responses: - '200': + "200": description: Config updated. - '400': + "400": description: Failed due to malformed JSON. - '401': + "401": description: Missing or invalid access token provided. - '404': + "404": description: Config does not exist. - '415': + "415": description: Missing or invalid content type. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" delete: + operationId: removeConfig summary: Removes a Config description: | Removes a Config. In case of successful removal the service will ensure @@ -122,16 +143,19 @@ paths: parameters: - $ref: "#/components/parameters/ConfigId" responses: - '204': + "204": description: Config removed. - '400': + "400": description: Failed due to malformed config ID. - '401': + "401": description: Missing or invalid access token provided. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/configs/certs/{configId}: patch: + operationId: updateConfigCerts summary: Updates certs description: | Update is performed by replacing the current certificate data with values @@ -143,21 +167,24 @@ paths: requestBody: $ref: "#/components/requestBodies/ConfigCertUpdateReq" responses: - '200': + "200": description: Config updated. $ref: "#/components/responses/ConfigUpdateCertsRes" - '400': + "400": description: Failed due to malformed JSON. - '401': + "401": description: Missing or invalid access token provided. - '404': + "404": description: Config does not exist. - '415': + "415": description: Missing or invalid content type. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/configs/connections/{configId}: put: + operationId: updateConfigConnections summary: Updates channels the thing is connected to description: | Update connections performs update of the channel list corresponding @@ -169,20 +196,23 @@ paths: requestBody: $ref: "#/components/requestBodies/ConfigConnUpdateReq" responses: - '200': + "200": description: Config updated. - '400': + "400": description: Failed due to malformed JSON. - '401': + "401": description: Missing or invalid access token provided. - '404': + "404": description: Config does not exist. - '415': + "415": description: Missing or invalid content type. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/bootstrap/{externalId}: get: + operationId: getBootstrapConfig summary: Retrieves configuration. description: | Retrieves a configuration with given external ID and external key. @@ -193,18 +223,21 @@ paths: parameters: - $ref: "#/components/parameters/ExternalId" responses: - '200': + "200": $ref: "#/components/responses/BootstrapConfigRes" - '400': + "400": description: Failed due to malformed JSON. - '401': + "401": description: Missing or invalid external key provided. - '404': + "404": description: Failed to retrieve corresponding config. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/bootstrap/secure/{externalId}: get: + operationId: getSecureBootstrapConfig summary: Retrieves configuration. description: | Retrieves a configuration with given external ID and encrypted external key. @@ -215,15 +248,22 @@ paths: parameters: - $ref: "#/components/parameters/ExternalId" responses: - '200': + "200": $ref: "#/components/responses/BootstrapConfigRes" - '404': + "400": + description: Failed due to malformed JSON. + "401": + description: Missing or invalid access token provided. + "404": description: | Failed to retrieve corresponding config. - '500': + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /things/state/{configId}: put: + operationId: updateConfigState summary: Updates Config state. description: | Updating state represents enabling/disabling Config, i.e. connecting @@ -233,15 +273,21 @@ paths: parameters: - $ref: "#/components/parameters/ConfigId" requestBody: - $ref: '#/components/requestBodies/ConfigStateUpdateReq' + $ref: "#/components/requestBodies/ConfigStateUpdateReq" responses: - '204': + "204": description: Config removed. - '400': + "400": description: Failed due to malformed config's ID. - '401': + "401": description: Missing or invalid access token provided. - '500': + "404": + description: A non-existent entity request. + "415": + description: Missing or invalid content type. + "422": + description: Database can't process request. + "500": $ref: "#/components/responses/ServiceError" /health: get: @@ -249,9 +295,9 @@ paths: tags: - health responses: - '200': + "200": $ref: "#/components/responses/HealthRes" - '500': + "500": $ref: "#/components/responses/ServiceError" components: @@ -453,12 +499,14 @@ components: description: External key. thing_id: type: string + format: uuid description: ID of the corresponding Magistrala Thing. channels: type: array minItems: 0 items: type: string + format: uuid content: type: string name: @@ -468,7 +516,7 @@ components: description: Thing Certificate. client_key: type: string - description: Thing Private Key. + description: Thing Private Key. ca_cert: type: string required: @@ -513,6 +561,7 @@ components: minItems: 0 items: type: string + format: uuid ConfigStateUpdateReq: description: Update the state of the Config. content: @@ -525,14 +574,14 @@ components: responses: ConfigCreateRes: - description: Config registered. - headers: - Location: - content: - text/plain: - schema: - type: string - description: Created configuration's relative URL (i.e. /things/configs/{configId}). + description: Config registered. + headers: + Location: + content: + text/plain: + schema: + type: string + description: Created configuration's relative URL (i.e. /things/configs/{configId}). ConfigListRes: description: Data retrieved. Configs from this list don't contain channels. content: @@ -545,10 +594,31 @@ components: application/json: schema: $ref: "#/components/schemas/Config" + links: + update: + operationId: updateConfig + parameters: + configId: $response.body#/id + updateCerts: + operationId: updateConfigCerts + parameters: + configId: $response.body#/id + updateConnections: + operationId: updateConfigConnections + parameters: + configId: $response.body#/id + updateState: + operationId: updateConfigState + parameters: + configId: $response.body#/id + delete: + operationId: removeConfig + parameters: + configId: $response.body#/id BootstrapConfigRes: description: | - Data retrieved. If secure, a response is encrypted using - the secret key, so the response is in the binary form. + Data retrieved. If secure, a response is encrypted using + the secret key, so the response is in the binary form. content: application/json: schema: @@ -558,7 +628,7 @@ components: HealthRes: description: Service Health Check. content: - application/json: + application/health+json: schema: $ref: "./schemas/HealthInfo.yml" ConfigUpdateCertsRes: diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 5f4c87b3611..13ce8e3d19b 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -1154,7 +1154,7 @@ func TestBootstrap(t *testing.T) { desc: "bootstrap a Thing with an empty key", externalID: c.ExternalID, externalKey: "", - status: http.StatusUnauthorized, + status: http.StatusBadRequest, res: missingKeyRes, secure: false, }, diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index 572a45126a5..a8346162c83 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -13,9 +13,9 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/bootstrap" + "github.com/absmach/magistrala/internal/api" "github.com/absmach/magistrala/internal/apiutil" "github.com/absmach/magistrala/pkg/errors" - svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -41,7 +41,7 @@ var ( // MakeHandler returns a HTTP handler for API endpoints. func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, encodeError)), + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } r := chi.NewRouter() @@ -51,43 +51,43 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger *s r.Post("/", otelhttp.NewHandler(kithttp.NewServer( addEndpoint(svc), decodeAddRequest, - encodeResponse, + api.EncodeResponse, opts...), "add").ServeHTTP) r.Get("/", otelhttp.NewHandler(kithttp.NewServer( listEndpoint(svc), decodeListRequest, - encodeResponse, + api.EncodeResponse, opts...), "list").ServeHTTP) r.Get("/{configID}", otelhttp.NewHandler(kithttp.NewServer( viewEndpoint(svc), decodeEntityRequest, - encodeResponse, + api.EncodeResponse, opts...), "view").ServeHTTP) r.Put("/{configID}", otelhttp.NewHandler(kithttp.NewServer( updateEndpoint(svc), decodeUpdateRequest, - encodeResponse, + api.EncodeResponse, opts...), "update").ServeHTTP) r.Delete("/{configID}", otelhttp.NewHandler(kithttp.NewServer( removeEndpoint(svc), decodeEntityRequest, - encodeResponse, + api.EncodeResponse, opts...), "remove").ServeHTTP) r.Patch("/certs/{certID}", otelhttp.NewHandler(kithttp.NewServer( updateCertEndpoint(svc), decodeUpdateCertRequest, - encodeResponse, + api.EncodeResponse, opts...), "update_cert").ServeHTTP) r.Put("/connections/{connID}", otelhttp.NewHandler(kithttp.NewServer( updateConnEndpoint(svc), decodeUpdateConnRequest, - encodeResponse, + api.EncodeResponse, opts...), "update_connections").ServeHTTP) }) @@ -95,12 +95,12 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger *s r.Get("/", otelhttp.NewHandler(kithttp.NewServer( bootstrapEndpoint(svc, reader, false), decodeBootstrapRequest, - encodeResponse, + api.EncodeResponse, opts...), "bootstrap").ServeHTTP) r.Get("/{externalID}", otelhttp.NewHandler(kithttp.NewServer( bootstrapEndpoint(svc, reader, false), decodeBootstrapRequest, - encodeResponse, + api.EncodeResponse, opts...), "bootstrap").ServeHTTP) r.Get("/secure/{externalID}", otelhttp.NewHandler(kithttp.NewServer( bootstrapEndpoint(svc, reader, true), @@ -112,7 +112,7 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader, logger *s r.Put("/state/{thingID}", otelhttp.NewHandler(kithttp.NewServer( stateEndpoint(svc), decodeStateRequest, - encodeResponse, + api.EncodeResponse, opts...), "update_state").ServeHTTP) }) r.Get("/health", magistrala.Health("bootstrap", instanceID)) @@ -242,23 +242,6 @@ func decodeEntityRequest(_ context.Context, r *http.Request) (interface{}, error return req, nil } -func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { - w.Header().Set("Content-Type", contentType) - if ar, ok := response.(magistrala.Response); ok { - for k, v := range ar.Headers() { - w.Header().Set(k, v) - } - - w.WriteHeader(ar.Code()) - - if ar.Empty() { - return nil - } - } - - return json.NewEncoder(w).Encode(response) -} - func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error { w.Header().Set("Content-Type", byteContentType) w.WriteHeader(http.StatusOK) @@ -270,57 +253,6 @@ func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interfac return nil } -func encodeError(_ context.Context, err error, w http.ResponseWriter) { - var wrapper error - if errors.Contains(err, apiutil.ErrValidation) { - wrapper, err = errors.Unwrap(err) - } - - switch { - case errors.Contains(err, svcerr.ErrAuthentication), - errors.Contains(err, apiutil.ErrBearerToken), - errors.Contains(err, apiutil.ErrBearerKey): - w.WriteHeader(http.StatusUnauthorized) - case errors.Contains(err, apiutil.ErrUnsupportedContentType): - w.WriteHeader(http.StatusUnsupportedMediaType) - case errors.Contains(err, apiutil.ErrInvalidQueryParams), - errors.Contains(err, svcerr.ErrMalformedEntity), - errors.Contains(err, apiutil.ErrMissingID), - errors.Contains(err, apiutil.ErrBootstrapState), - errors.Contains(err, apiutil.ErrLimitSize): - w.WriteHeader(http.StatusBadRequest) - case errors.Contains(err, svcerr.ErrNotFound): - w.WriteHeader(http.StatusNotFound) - case errors.Contains(err, bootstrap.ErrExternalKey), - errors.Contains(err, bootstrap.ErrExternalKeySecure), - errors.Contains(err, svcerr.ErrAuthorization): - w.WriteHeader(http.StatusForbidden) - case errors.Contains(err, bootstrap.ErrThings): - w.WriteHeader(http.StatusServiceUnavailable) - case errors.Contains(err, svcerr.ErrConflict): - w.WriteHeader(http.StatusConflict) - case errors.Contains(err, svcerr.ErrCreateEntity), - errors.Contains(err, svcerr.ErrUpdateEntity), - errors.Contains(err, svcerr.ErrViewEntity), - errors.Contains(err, svcerr.ErrRemoveEntity): - w.WriteHeader(http.StatusInternalServerError) - - default: - w.WriteHeader(http.StatusInternalServerError) - } - - if wrapper != nil { - err = errors.Wrap(wrapper, err) - } - - if errorVal, ok := err.(errors.Error); ok { - w.Header().Set("Content-Type", contentType) - if err := json.NewEncoder(w).Encode(errorVal); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - } -} - func parseFilter(values url.Values) bootstrap.Filter { ret := bootstrap.Filter{ FullMatch: make(map[string]string), diff --git a/bootstrap/service.go b/bootstrap/service.go index 2f87d1ae57e..21adb512ca4 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -31,19 +31,9 @@ var ( // ErrBootstrap indicates error in getting bootstrap configuration. ErrBootstrap = errors.New("failed to read bootstrap configuration") - errAddBootstrap = errors.New("failed to add bootstrap configuration") - errUpdateConnections = errors.New("failed to update connections") - errRemoveBootstrap = errors.New("failed to remove bootstrap configuration") - errChangeState = errors.New("failed to change state of bootstrap configuration") - errUpdateChannel = errors.New("failed to update channel") - errRemoveConfig = errors.New("failed to remove bootstrap configuration") - errRemoveChannel = errors.New("failed to remove channel") - errCreateThing = errors.New("failed to create thing") - errDisconnectThing = errors.New("failed to disconnect thing") - errCheckChannels = errors.New("failed to check if channels exists") - errConnectionChannels = errors.New("failed to check channels connections") - errThingNotFound = errors.New("failed to find thing") - errUpdateCert = errors.New("failed to update cert") + ErrUpdateConnections = errors.New("failed to update connections") + ErrCreateThing = errors.New("failed to create thing") + ErrConnectionChannels = errors.New("failed to check channels connections") ) var _ Service = (*bootstrapService)(nil) @@ -134,18 +124,18 @@ func (bs bootstrapService) Add(ctx context.Context, token string, cfg Config) (C // Check if channels exist. This is the way to prevent fetching channels that already exist. existing, err := bs.configs.ListExisting(ctx, owner, toConnect) if err != nil { - return Config{}, errors.Wrap(errCheckChannels, err) + return Config{}, errors.Wrap(svcerr.ErrNotFound, err) } cfg.Channels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), token) if err != nil { - return Config{}, errors.Wrap(errConnectionChannels, err) + return Config{}, err } id := cfg.ThingID mgThing, err := bs.thing(id, token) if err != nil { - return Config{}, errors.Wrap(errThingNotFound, err) + return Config{}, errors.Wrap(svcerr.ErrNotFound, err) } cfg.ThingID = mgThing.ID @@ -162,7 +152,7 @@ func (bs bootstrapService) Add(ctx context.Context, token string, cfg Config) (C err = errors.Wrap(err, errT) } } - return Config{}, errors.Wrap(errAddBootstrap, err) + return Config{}, errors.Wrap(svcerr.ErrCreateEntity, err) } cfg.ThingID = saved @@ -191,7 +181,7 @@ func (bs bootstrapService) Update(ctx context.Context, token string, cfg Config) cfg.Owner = owner if err = bs.configs.Update(ctx, cfg); err != nil { - return errors.Wrap(errUpdateConnections, err) + return errors.Wrap(ErrUpdateConnections, err) } return nil } @@ -203,7 +193,7 @@ func (bs bootstrapService) UpdateCert(ctx context.Context, token, thingID, clien } cfg, err := bs.configs.UpdateCert(ctx, owner, thingID, clientCert, clientKey, caCert) if err != nil { - return Config{}, errors.Wrap(errUpdateCert, err) + return Config{}, errors.Wrap(svcerr.ErrUpdateEntity, err) } return cfg, nil } @@ -216,7 +206,7 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri cfg, err := bs.configs.RetrieveByID(ctx, owner, id) if err != nil { - return errors.Wrap(errUpdateConnections, err) + return errors.Wrap(ErrUpdateConnections, err) } add, remove := bs.updateList(cfg, connections) @@ -224,12 +214,12 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri // Check if channels exist. This is the way to prevent fetching channels that already exist. existing, err := bs.configs.ListExisting(ctx, owner, connections) if err != nil { - return errors.Wrap(errUpdateConnections, err) + return errors.Wrap(ErrUpdateConnections, err) } channels, err := bs.connectionChannels(connections, bs.toIDList(existing), token) if err != nil { - return errors.Wrap(errUpdateConnections, err) + return err } cfg.Channels = channels @@ -259,7 +249,7 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri } } if err := bs.configs.UpdateConnections(ctx, owner, id, channels, connections); err != nil { - return errors.Wrap(errUpdateConnections, err) + return errors.Wrap(ErrUpdateConnections, err) } return nil } @@ -278,7 +268,7 @@ func (bs bootstrapService) Remove(ctx context.Context, token, id string) error { return errors.Wrap(svcerr.ErrAuthentication, err) } if err := bs.configs.Remove(ctx, owner, id); err != nil { - return errors.Wrap(errRemoveBootstrap, err) + return errors.Wrap(svcerr.ErrRemoveEntity, err) } return nil } @@ -310,7 +300,7 @@ func (bs bootstrapService) ChangeState(ctx context.Context, token, id string, st cfg, err := bs.configs.RetrieveByID(ctx, owner, id) if err != nil { - return errors.Wrap(errChangeState, err) + return errors.Wrap(svcerr.ErrUpdateEntity, err) } if cfg.State == state { @@ -339,35 +329,35 @@ func (bs bootstrapService) ChangeState(ctx context.Context, token, id string, st } } if err := bs.configs.ChangeState(ctx, owner, id, state); err != nil { - return errors.Wrap(errChangeState, err) + return errors.Wrap(svcerr.ErrUpdateEntity, err) } return nil } func (bs bootstrapService) UpdateChannelHandler(ctx context.Context, channel Channel) error { if err := bs.configs.UpdateChannel(ctx, channel); err != nil { - return errors.Wrap(errUpdateChannel, err) + return errors.Wrap(svcerr.ErrUpdateEntity, err) } return nil } func (bs bootstrapService) RemoveConfigHandler(ctx context.Context, id string) error { if err := bs.configs.RemoveThing(ctx, id); err != nil { - return errors.Wrap(errRemoveConfig, err) + return errors.Wrap(svcerr.ErrRemoveEntity, err) } return nil } func (bs bootstrapService) RemoveChannelHandler(ctx context.Context, id string) error { if err := bs.configs.RemoveChannel(ctx, id); err != nil { - return errors.Wrap(errRemoveChannel, err) + return errors.Wrap(svcerr.ErrRemoveEntity, err) } return nil } func (bs bootstrapService) DisconnectThingHandler(ctx context.Context, channelID, thingID string) error { if err := bs.configs.DisconnectThing(ctx, channelID, thingID); err != nil { - return errors.Wrap(errDisconnectThing, err) + return errors.Wrap(svcerr.ErrRemoveEntity, err) } return nil } @@ -390,11 +380,11 @@ func (bs bootstrapService) thing(id, token string) (mgsdk.Thing, error) { if id == "" { id, err := bs.idProvider.ID() if err != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, err) + return mgsdk.Thing{}, errors.Wrap(ErrCreateThing, err) } thing, sdkErr := bs.sdk.CreateThing(mgsdk.Thing{ID: id, Name: "Bootstrapped Thing " + id}, token) if sdkErr != nil { - return mgsdk.Thing{}, errors.Wrap(errCreateThing, errors.New(sdkErr.Err().Msg())) + return mgsdk.Thing{}, errors.Wrap(ErrCreateThing, errors.New(sdkErr.Err().Msg())) } return thing, nil } @@ -423,7 +413,7 @@ func (bs bootstrapService) connectionChannels(channels, existing []string, token for id := range add { ch, err := bs.sdk.Channel(id, token) if err != nil { - return nil, errors.Wrap(errors.ErrMalformedEntity, err) + return nil, errors.Wrap(svcerr.ErrNotFound, err) } ret = append(ret, Channel{ diff --git a/internal/api/common.go b/internal/api/common.go index 84f54b2132c..7dde3d36698 100644 --- a/internal/api/common.go +++ b/internal/api/common.go @@ -9,6 +9,7 @@ import ( "net/http" "github.com/absmach/magistrala" + "github.com/absmach/magistrala/bootstrap" "github.com/absmach/magistrala/internal/apiutil" mgclients "github.com/absmach/magistrala/pkg/clients" "github.com/absmach/magistrala/pkg/errors" @@ -132,10 +133,11 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrMissingRelation), errors.Contains(err, svcerr.ErrPasswordFormat), errors.Contains(err, apiutil.ErrInvalidLevel), - errors.Contains(err, apiutil.ErrInvalidQueryParams), errors.Contains(err, apiutil.ErrMalformedPolicy), errors.Contains(err, apiutil.ErrInvalidAPIKey), - errors.Contains(err, apiutil.ErrMissingName): + errors.Contains(err, apiutil.ErrMissingName), + errors.Contains(err, apiutil.ErrBootstrapState), + errors.Contains(err, apiutil.ErrInvalidQueryParams): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, svcerr.ErrLogin), @@ -147,7 +149,9 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, errors.ErrStatusAlreadyAssigned): w.WriteHeader(http.StatusConflict) case errors.Contains(err, svcerr.ErrAuthorization), - errors.Contains(err, svcerr.ErrDomainAuthorization): + errors.Contains(err, svcerr.ErrDomainAuthorization), + errors.Contains(err, bootstrap.ErrExternalKey), + errors.Contains(err, bootstrap.ErrExternalKeySecure): w.WriteHeader(http.StatusForbidden) case errors.Contains(err, apiutil.ErrUnsupportedContentType): w.WriteHeader(http.StatusUnsupportedMediaType) @@ -160,6 +164,8 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, repoerr.ErrFailedToRetrieveAllGroups), errors.Contains(err, svcerr.ErrRemoveEntity): w.WriteHeader(http.StatusUnprocessableEntity) + case errors.Contains(err, bootstrap.ErrThings): + w.WriteHeader(http.StatusServiceUnavailable) default: w.WriteHeader(http.StatusInternalServerError) }