Skip to content

Commit

Permalink
NOISSUE - Refactor Provision API tests (absmach#192)
Browse files Browse the repository at this point in the history
- Refactored the code snippets related to API requests and provisioning services
- Added test cases for error handling and functionality
- Created mock services for testing purposes
- Utilized external packages Magistrala and mockery

Signed-off-by: Rodney Osodo <[email protected]>
  • Loading branch information
rodneyosodo authored Jan 8, 2024
1 parent f703739 commit 2fb14e2
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 88 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/check-generated-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
- "auth/keys.go"
- "auth/policies.go"
- "pkg/events/events.go"
- "provision/service.go"
- name: Set up protoc
if: steps.changes.outputs.proto == 'true'
Expand Down Expand Up @@ -109,6 +110,7 @@ jobs:
mv ./auth/mocks/keys.go ./auth/mocks/keys.go.tmp
mv ./pkg/events/mocks/publisher.go ./pkg/events/mocks/publisher.go.tmp
mv ./pkg/events/mocks/subscriber.go ./pkg/events/mocks/subscriber.go.tmp
mv ./provision/mocks/service.go ./provision/mocks/service.go.tmp
make mocks
Expand Down Expand Up @@ -137,3 +139,4 @@ jobs:
check_mock_changes ./auth/mocks/keys.go "Auth Keys ./auth/mocks/keys.go"
check_mock_changes ./pkg/events/mocks/publisher.go "ES Publisher ./pkg/events/mocks/publisher.go"
check_mock_changes ./pkg/events/mocks/subscriber.go "EE Subscriber ./pkg/events/mocks/subscriber.go"
check_mock_changes ./provision/mocks/service.go "Provision Service ./provision/mocks/service.go"
1 change: 1 addition & 0 deletions internal/api/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ 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.ErrBearerKey),
errors.Contains(err, apiutil.ErrNameSize):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, svcerr.ErrAuthentication),
Expand Down
6 changes: 3 additions & 3 deletions provision/api/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ func doProvision(svc provision.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}
token := req.token

res, err := svc.Provision(token, req.Name, req.ExternalID, req.ExternalKey)
res, err := svc.Provision(req.token, req.Name, req.ExternalID, req.ExternalKey)
if err != nil {
return provisionRes{Error: err.Error()}, nil
return nil, err
}

provisionResponse := provisionRes{
Expand All @@ -44,6 +43,7 @@ func getMapping(svc provision.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return nil, errors.Wrap(apiutil.ErrValidation, err)
}

return svc.Mapping(req.token)
}
}
210 changes: 210 additions & 0 deletions provision/api/endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package api_test

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/absmach/magistrala/internal/apiutil"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/errors"
"github.com/absmach/magistrala/provision"
"github.com/absmach/magistrala/provision/api"
"github.com/absmach/magistrala/provision/mocks"
"github.com/stretchr/testify/assert"
)

var (
validToken = "valid"
validContenType = "application/json"
validID = testsutil.GenerateUUID(&testing.T{})
)

type testRequest struct {
client *http.Client
method string
url string
token string
contentType string
body io.Reader
}

func (tr testRequest) make() (*http.Response, error) {
req, err := http.NewRequest(tr.method, tr.url, tr.body)
if err != nil {
return nil, err
}

if tr.token != "" {
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
}

if tr.contentType != "" {
req.Header.Set("Content-Type", tr.contentType)
}

return tr.client.Do(req)
}

func newProvisionServer() (*httptest.Server, *mocks.Service) {
svc := new(mocks.Service)

logger := mglog.NewMock()
mux := api.MakeHandler(svc, logger, "test")
return httptest.NewServer(mux), svc
}

func TestProvision(t *testing.T) {
is, svc := newProvisionServer()

cases := []struct {
desc string
token string
data string
contentType string
status int
svcErr error
}{
{
desc: "valid request",
token: validToken,
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusCreated,
contentType: validContenType,
svcErr: nil,
},
{
desc: "request with empty external id",
token: validToken,
data: fmt.Sprintf(`{"name": "test", "external_key": "%s"}`, validID),
status: http.StatusBadRequest,
contentType: validContenType,
svcErr: nil,
},
{
desc: "request with empty external key",
token: validToken,
data: fmt.Sprintf(`{"name": "test", "external_id": "%s"}`, validID),
status: http.StatusBadRequest,
contentType: validContenType,
svcErr: nil,
},
{
desc: "empty token",
token: "",
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusCreated,
contentType: validContenType,
svcErr: nil,
},
{
desc: "invalid content type",
token: validToken,
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusUnsupportedMediaType,
contentType: "text/plain",
svcErr: nil,
},
{
desc: "invalid request",
token: validToken,
data: `data`,
status: http.StatusBadRequest,
contentType: validContenType,
svcErr: nil,
},
{
desc: "service error",
token: validToken,
data: fmt.Sprintf(`{"name": "test", "external_id": "%s", "external_key": "%s"}`, validID, validID),
status: http.StatusForbidden,
contentType: validContenType,
svcErr: errors.ErrAuthorization,
},
}

for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repocall := svc.On("Provision", tc.token, "test", validID, validID).Return(provision.Result{}, tc.svcErr)
req := testRequest{
client: is.Client(),
method: http.MethodPost,
url: is.URL + "/mapping",
token: tc.token,
contentType: tc.contentType,
body: strings.NewReader(tc.data),
}

resp, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
repocall.Unset()
})
}
}

func TestMapping(t *testing.T) {
is, svc := newProvisionServer()

cases := []struct {
desc string
token string
contentType string
status int
svcErr error
}{
{
desc: "valid request",
token: validToken,
status: http.StatusOK,
contentType: validContenType,
svcErr: nil,
},
{
desc: "empty token",
token: "",
status: http.StatusUnauthorized,
contentType: validContenType,
svcErr: nil,
},
{
desc: "invalid content type",
token: validToken,
status: http.StatusUnsupportedMediaType,
contentType: "text/plain",
svcErr: nil,
},
{
desc: "service error",
token: validToken,
status: http.StatusForbidden,
contentType: validContenType,
svcErr: errors.ErrAuthorization,
},
}

for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repocall := svc.On("Mapping", tc.token).Return(map[string]interface{}{}, tc.svcErr)
req := testRequest{
client: is.Client(),
method: http.MethodGet,
url: is.URL + "/mapping",
token: tc.token,
contentType: tc.contentType,
}

resp, err := req.make()
assert.Nil(t, err, tc.desc)
assert.Equal(t, tc.status, resp.StatusCode, tc.desc)
repocall.Unset()
})
}
}
80 changes: 63 additions & 17 deletions provision/api/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,79 @@ import (
"testing"

"github.com/absmach/magistrala/internal/apiutil"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
)

func TestValidate(t *testing.T) {
cases := map[string]struct {
ExternalID string
ExternalKey string
err error
func TestProvisioReq(t *testing.T) {
cases := []struct {
desc string
req provisionReq
err error
}{
"mac address for device": {
ExternalID: "11:22:33:44:55:66",
ExternalKey: "key12345678",
err: nil,
{
desc: "valid request",
req: provisionReq{
token: "token",
Name: "name",
ExternalID: testsutil.GenerateUUID(t),
ExternalKey: testsutil.GenerateUUID(t),
},
err: nil,
},
"external id for device empty": {
{
desc: "empty external id",
req: provisionReq{
token: "token",
Name: "name",
ExternalID: "",
ExternalKey: testsutil.GenerateUUID(t),
},
err: apiutil.ErrMissingID,
},
{
desc: "empty external key",
req: provisionReq{
token: "token",
Name: "name",
ExternalID: testsutil.GenerateUUID(t),
ExternalKey: "",
},
err: apiutil.ErrBearerKey,
},
}

for _, tc := range cases {
err := tc.req.validate()
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
}
}

for desc, tc := range cases {
req := provisionReq{
ExternalID: tc.ExternalID,
ExternalKey: tc.ExternalKey,
}
func TestMappingReq(t *testing.T) {
cases := []struct {
desc string
req mappingReq
err error
}{
{
desc: "valid request",
req: mappingReq{
token: "token",
},
err: nil,
},
{
desc: "empty token",
req: mappingReq{
token: "",
},
err: apiutil.ErrBearerToken,
},
}

err := req.validate()
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", desc, tc.err, err))
for _, tc := range cases {
err := tc.req.validate()
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected `%v` got `%v`", tc.desc, tc.err, err))
}
}
4 changes: 3 additions & 1 deletion provision/api/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ package api
import (
"net/http"

"github.com/absmach/magistrala"
sdk "github.com/absmach/magistrala/pkg/sdk/go"
)

var _ magistrala.Response = (*provisionRes)(nil)

type provisionRes struct {
Things []sdk.Thing `json:"things"`
Channels []sdk.Channel `json:"channels"`
ClientCert map[string]string `json:"client_cert,omitempty"`
ClientKey map[string]string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
Whitelisted map[string]bool `json:"whitelisted,omitempty"`
Error string `json:"error,omitempty"`
}

func (res provisionRes) Code() int {
Expand Down
Loading

0 comments on commit 2fb14e2

Please sign in to comment.