mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
a19cd9c97b
Signed-off-by: dusan <borovcanindusan1@gmail.com>
339 lines
7.2 KiB
Go
339 lines
7.2 KiB
Go
// Copyright (c) Abstract Machines
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package http_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/absmach/supermq"
|
|
api "github.com/absmach/supermq/api/http"
|
|
apiutil "github.com/absmach/supermq/api/http/util"
|
|
"github.com/absmach/supermq/internal/testsutil"
|
|
"github.com/absmach/supermq/pkg/errors"
|
|
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
var _ supermq.Response = (*response)(nil)
|
|
|
|
var validUUID = testsutil.GenerateUUID(&testing.T{})
|
|
|
|
type responseWriter struct {
|
|
body []byte
|
|
statusCode int
|
|
header http.Header
|
|
}
|
|
|
|
func newResponseWriter() *responseWriter {
|
|
return &responseWriter{
|
|
header: http.Header{},
|
|
}
|
|
}
|
|
|
|
func (w *responseWriter) Header() http.Header {
|
|
return w.header
|
|
}
|
|
|
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
|
w.body = b
|
|
return 0, nil
|
|
}
|
|
|
|
func (w *responseWriter) WriteHeader(statusCode int) {
|
|
w.statusCode = statusCode
|
|
}
|
|
|
|
func (w *responseWriter) StatusCode() int {
|
|
return w.statusCode
|
|
}
|
|
|
|
func (w *responseWriter) Body() []byte {
|
|
return w.body
|
|
}
|
|
|
|
type response struct {
|
|
code int
|
|
headers map[string]string
|
|
empty bool
|
|
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
func (res response) Code() int {
|
|
return res.code
|
|
}
|
|
|
|
func (res response) Headers() map[string]string {
|
|
return res.headers
|
|
}
|
|
|
|
func (res response) Empty() bool {
|
|
return res.empty
|
|
}
|
|
|
|
type body struct {
|
|
Error string `json:"error,omitempty"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func TestValidateUUID(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
uuid string
|
|
err error
|
|
}{
|
|
{
|
|
desc: "valid uuid",
|
|
uuid: validUUID,
|
|
err: nil,
|
|
},
|
|
{
|
|
desc: "invalid uuid",
|
|
uuid: "invalid",
|
|
err: apiutil.ErrInvalidIDFormat,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
err := api.ValidateUUID(c.uuid)
|
|
assert.Equal(t, c.err, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeResponse(t *testing.T) {
|
|
now := time.Now()
|
|
validBody := []byte(`{"id":"` + validUUID + `","name":"test","created_at":"` + now.Format(time.RFC3339Nano) + `"}` + "\n" + ``)
|
|
|
|
cases := []struct {
|
|
desc string
|
|
resp any
|
|
header http.Header
|
|
code int
|
|
body []byte
|
|
err error
|
|
}{
|
|
{
|
|
desc: "valid response",
|
|
resp: response{
|
|
code: http.StatusOK,
|
|
headers: map[string]string{
|
|
"Location": "/groups/" + validUUID,
|
|
},
|
|
ID: validUUID,
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
},
|
|
header: http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
"Location": []string{"/groups/" + validUUID},
|
|
},
|
|
code: http.StatusOK,
|
|
body: validBody,
|
|
err: nil,
|
|
},
|
|
{
|
|
desc: "valid response with no headers",
|
|
resp: response{
|
|
code: http.StatusOK,
|
|
ID: validUUID,
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
},
|
|
header: http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
},
|
|
code: http.StatusOK,
|
|
body: validBody,
|
|
err: nil,
|
|
},
|
|
{
|
|
desc: "valid response with many headers",
|
|
resp: response{
|
|
code: http.StatusOK,
|
|
headers: map[string]string{
|
|
"X-Test": "test",
|
|
"X-Test2": "test2",
|
|
},
|
|
ID: validUUID,
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
},
|
|
header: http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
"X-Test": []string{"test"},
|
|
"X-Test2": []string{"test2"},
|
|
},
|
|
code: http.StatusOK,
|
|
body: validBody,
|
|
err: nil,
|
|
},
|
|
{
|
|
desc: "valid response with empty body",
|
|
resp: response{
|
|
code: http.StatusOK,
|
|
empty: true,
|
|
ID: validUUID,
|
|
},
|
|
header: http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
},
|
|
code: http.StatusOK,
|
|
body: []byte(``),
|
|
err: nil,
|
|
},
|
|
{
|
|
desc: "invalid response",
|
|
resp: struct {
|
|
ID string `json:"id"`
|
|
}{
|
|
ID: validUUID,
|
|
},
|
|
header: http.Header{},
|
|
code: 0,
|
|
body: []byte(`{"id":"` + validUUID + `"}` + "\n" + ``),
|
|
err: nil,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
responseWriter := newResponseWriter()
|
|
err := api.EncodeResponse(context.Background(), responseWriter, c.resp)
|
|
assert.Equal(t, c.err, err)
|
|
assert.Equal(t, c.header, responseWriter.Header())
|
|
assert.Equal(t, c.code, responseWriter.StatusCode())
|
|
assert.Equal(t, string(c.body), string(responseWriter.Body()))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeError(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
errs []error
|
|
code int
|
|
}{
|
|
{
|
|
desc: "BadRequest",
|
|
errs: []error{
|
|
apiutil.ErrMissingSecret,
|
|
svcerr.ErrMalformedEntity,
|
|
errors.ErrMalformedEntity,
|
|
apiutil.ErrMissingID,
|
|
apiutil.ErrEmptyList,
|
|
apiutil.ErrMissingMemberType,
|
|
apiutil.ErrMissingMemberKind,
|
|
apiutil.ErrLimitSize,
|
|
apiutil.ErrNameSize,
|
|
svcerr.ErrViewEntity,
|
|
},
|
|
code: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "BadRequest with validation error",
|
|
errs: []error{
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingSecret),
|
|
errors.Wrap(apiutil.ErrValidation, svcerr.ErrMalformedEntity),
|
|
errors.Wrap(apiutil.ErrValidation, errors.ErrMalformedEntity),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrEmptyList),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingMemberType),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingMemberKind),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrLimitSize),
|
|
errors.Wrap(apiutil.ErrValidation, apiutil.ErrNameSize),
|
|
},
|
|
code: http.StatusBadRequest,
|
|
},
|
|
{
|
|
desc: "Unauthorized",
|
|
errs: []error{
|
|
svcerr.ErrAuthentication,
|
|
svcerr.ErrAuthentication,
|
|
apiutil.ErrBearerToken,
|
|
},
|
|
code: http.StatusUnauthorized,
|
|
},
|
|
|
|
{
|
|
desc: "NotFound",
|
|
errs: []error{
|
|
svcerr.ErrNotFound,
|
|
},
|
|
code: http.StatusNotFound,
|
|
},
|
|
{
|
|
desc: "Conflict",
|
|
errs: []error{
|
|
svcerr.ErrConflict,
|
|
svcerr.ErrConflict,
|
|
},
|
|
code: http.StatusConflict,
|
|
},
|
|
{
|
|
desc: "Forbidden",
|
|
errs: []error{
|
|
svcerr.ErrAuthorization,
|
|
svcerr.ErrAuthorization,
|
|
svcerr.ErrDomainAuthorization,
|
|
},
|
|
code: http.StatusForbidden,
|
|
},
|
|
{
|
|
desc: "UnsupportedMediaType",
|
|
errs: []error{
|
|
apiutil.ErrUnsupportedContentType,
|
|
},
|
|
code: http.StatusUnsupportedMediaType,
|
|
},
|
|
{
|
|
desc: "StatusUnprocessableEntity",
|
|
errs: []error{
|
|
svcerr.ErrCreateEntity,
|
|
svcerr.ErrUpdateEntity,
|
|
svcerr.ErrRemoveEntity,
|
|
},
|
|
code: http.StatusUnprocessableEntity,
|
|
},
|
|
{
|
|
desc: "InternalServerError",
|
|
errs: []error{
|
|
errors.New("test"),
|
|
},
|
|
code: http.StatusInternalServerError,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.desc, func(t *testing.T) {
|
|
responseWriter := newResponseWriter()
|
|
for _, err := range c.errs {
|
|
api.EncodeError(context.Background(), err, responseWriter)
|
|
assert.Equal(t, c.code, responseWriter.StatusCode())
|
|
|
|
message := body{}
|
|
jerr := json.Unmarshal(responseWriter.Body(), &message)
|
|
assert.NoError(t, jerr)
|
|
|
|
var wrapper error
|
|
switch errors.Contains(err, apiutil.ErrValidation) {
|
|
case true:
|
|
wrapper, err = errors.Unwrap(err)
|
|
assert.Equal(t, err.Error(), message.Error)
|
|
assert.Equal(t, wrapper.Error(), message.Message)
|
|
case false:
|
|
assert.Equal(t, err.Error(), message.Message)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|