458 lines
12 KiB
Go
458 lines
12 KiB
Go
package port_test
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
|
"github.com/brianvoe/gofakeit/v7"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const endpointListVersions = "/v2/versions"
|
|
|
|
func TestListVersions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := newAPIHTTPHandler(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
reqModifier func(t *testing.T, req *http.Request)
|
|
assertResp func(t *testing.T, req *http.Request, resp *http.Response)
|
|
}{
|
|
{
|
|
name: "OK: without params",
|
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ListVersionsResponse](t, resp.Body)
|
|
assert.Zero(t, body.Cursor.Next)
|
|
assert.NotZero(t, body.Cursor.Self)
|
|
assert.NotZero(t, body.Data)
|
|
assert.True(t, slices.IsSortedFunc(body.Data, func(a, b apimodel.Version) int {
|
|
return cmp.Compare(a.Code, b.Code)
|
|
}))
|
|
},
|
|
},
|
|
{
|
|
name: "OK: limit=3",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("limit", "3")
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ListVersionsResponse](t, resp.Body)
|
|
assert.NotZero(t, body.Cursor.Next)
|
|
assert.NotZero(t, body.Cursor.Self)
|
|
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
|
|
require.NoError(t, err)
|
|
assert.Len(t, body.Data, limit)
|
|
},
|
|
},
|
|
{
|
|
name: "OK: limit=3 cursor",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
|
|
q := req.URL.Query()
|
|
q.Set("limit", "3")
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
resp := doCustomRequest(handler, req.Clone(req.Context()))
|
|
defer resp.Body.Close()
|
|
body := decodeJSON[apimodel.ListVersionsResponse](t, resp.Body)
|
|
require.NotEmpty(t, body.Cursor.Next)
|
|
|
|
q.Set("cursor", body.Cursor.Next)
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ListVersionsResponse](t, resp.Body)
|
|
assert.NotZero(t, body.Cursor.Next)
|
|
assert.Equal(t, req.URL.Query().Get("cursor"), body.Cursor.Self)
|
|
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
|
|
require.NoError(t, err)
|
|
assert.Len(t, body.Data, limit)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: limit is not an integer",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("limit", "asd")
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: "invalid-param-format",
|
|
Message: fmt.Sprintf(
|
|
"error binding string parameter: strconv.ParseInt: parsing \"%s\": invalid syntax",
|
|
req.URL.Query().Get("limit"),
|
|
),
|
|
Path: []string{"$query", "limit"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: limit < 1",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("limit", "0")
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
|
|
require.NoError(t, err)
|
|
domainErr := domain.MinGreaterEqualError{
|
|
Min: 1,
|
|
Current: limit,
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"current": float64(domainErr.Current),
|
|
"min": float64(domainErr.Min),
|
|
},
|
|
Path: []string{"$query", "limit"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: fmt.Sprintf("ERR: limit > %d", domain.VersionListMaxLimit),
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("limit", strconv.Itoa(domain.VersionListMaxLimit+1))
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
|
|
require.NoError(t, err)
|
|
domainErr := domain.MaxLessEqualError{
|
|
Max: domain.VersionListMaxLimit,
|
|
Current: limit,
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"current": float64(domainErr.Current),
|
|
"max": float64(domainErr.Max),
|
|
},
|
|
Path: []string{"$query", "limit"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: len(cursor) < 1",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("cursor", "")
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
domainErr := domain.LenOutOfRangeError{
|
|
Min: 1,
|
|
Max: 1000,
|
|
Current: len(req.URL.Query().Get("cursor")),
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"current": float64(domainErr.Current),
|
|
"max": float64(domainErr.Max),
|
|
"min": float64(domainErr.Min),
|
|
},
|
|
Path: []string{"$query", "cursor"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: len(cursor) > 1000",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("cursor", gofakeit.LetterN(1001))
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
domainErr := domain.LenOutOfRangeError{
|
|
Min: 1,
|
|
Max: 1000,
|
|
Current: len(req.URL.Query().Get("cursor")),
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"current": float64(domainErr.Current),
|
|
"max": float64(domainErr.Max),
|
|
"min": float64(domainErr.Min),
|
|
},
|
|
Path: []string{"$query", "cursor"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: invalid cursor",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
q := req.URL.Query()
|
|
q.Set("cursor", gofakeit.LetterN(100))
|
|
req.URL.RawQuery = q.Encode()
|
|
},
|
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
var domainErr domain.Error
|
|
require.ErrorAs(t, domain.ErrInvalidCursor, &domainErr)
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Path: []string{"$query", "cursor"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, endpointListVersions, nil)
|
|
if tt.reqModifier != nil {
|
|
tt.reqModifier(t, req)
|
|
}
|
|
|
|
resp := doCustomRequest(handler, req)
|
|
defer resp.Body.Close()
|
|
tt.assertResp(t, req, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
const endpointGetVersion = "/v2/versions/%s"
|
|
|
|
func TestGetVersion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
handler := newAPIHTTPHandler(t)
|
|
version := randSliceElement(getAllVersions(t, handler))
|
|
|
|
tests := []struct {
|
|
name string
|
|
reqModifier func(t *testing.T, req *http.Request)
|
|
assertResp func(t *testing.T, req *http.Request, resp *http.Response)
|
|
}{
|
|
{
|
|
name: "OK",
|
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.GetVersionResponse](t, resp.Body)
|
|
assert.Equal(t, version, body.Data)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: len(versionCode) != 2",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
req.URL.Path = fmt.Sprintf(endpointGetVersion, domaintest.RandVersionCode()+domaintest.RandVersionCode())
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
pathSegments := strings.Split(req.URL.Path, "/")
|
|
require.Len(t, pathSegments, 4)
|
|
domainErr := domain.LenOutOfRangeError{
|
|
Min: 2,
|
|
Max: 2,
|
|
Current: len(pathSegments[3]),
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"min": float64(domainErr.Min),
|
|
"max": float64(domainErr.Max),
|
|
"current": float64(domainErr.Current),
|
|
},
|
|
Path: []string{"$path", "versionCode"},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
{
|
|
name: "ERR: version not found",
|
|
reqModifier: func(t *testing.T, req *http.Request) {
|
|
t.Helper()
|
|
req.URL.Path = fmt.Sprintf(endpointGetVersion, randInvalidVersionCode(t, handler))
|
|
},
|
|
assertResp: func(t *testing.T, req *http.Request, resp *http.Response) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
|
|
|
// body
|
|
body := decodeJSON[apimodel.ErrorResponse](t, resp.Body)
|
|
pathSegments := strings.Split(req.URL.Path, "/")
|
|
require.Len(t, pathSegments, 4)
|
|
domainErr := domain.VersionNotFoundError{
|
|
VersionCode: pathSegments[3],
|
|
}
|
|
assert.Equal(t, apimodel.ErrorResponse{
|
|
Errors: []apimodel.Error{
|
|
{
|
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
|
Message: domainErr.Error(),
|
|
Params: map[string]any{
|
|
"code": domainErr.VersionCode,
|
|
},
|
|
},
|
|
},
|
|
}, body)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf(endpointGetVersion, version.Code), nil)
|
|
if tt.reqModifier != nil {
|
|
tt.reqModifier(t, req)
|
|
}
|
|
|
|
resp := doCustomRequest(handler, req)
|
|
defer resp.Body.Close()
|
|
tt.assertResp(t, req, resp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func getAllVersions(tb testing.TB, h http.Handler) []apimodel.Version {
|
|
tb.Helper()
|
|
respVersions := doRequest(h, http.MethodGet, endpointListVersions, nil)
|
|
defer respVersions.Body.Close()
|
|
require.Equal(tb, http.StatusOK, respVersions.StatusCode)
|
|
bodyVersions := decodeJSON[apimodel.ListVersionsResponse](tb, respVersions.Body)
|
|
require.NotEmpty(tb, bodyVersions.Data)
|
|
return bodyVersions.Data
|
|
}
|
|
|
|
func randInvalidVersionCode(tb testing.TB, h http.Handler) string {
|
|
tb.Helper()
|
|
|
|
versions := getAllVersions(tb, h)
|
|
|
|
for range 3 {
|
|
code := domaintest.RandVersionCode()
|
|
if !slices.ContainsFunc(versions, func(version apimodel.Version) bool {
|
|
return version.Code == code
|
|
}) {
|
|
return code
|
|
}
|
|
}
|
|
|
|
return domaintest.RandVersionCode()
|
|
}
|