feat: api - add a new endpoint - GET /api/v2/versions/{versionCode}/servers/{serverKey}/villages (#17)
Reviewed-on: twhelp/corev3#17
This commit is contained in:
parent
847cf220da
commit
34a5385224
|
@ -14,6 +14,7 @@ tags:
|
|||
- name: servers
|
||||
- name: tribes
|
||||
- name: players
|
||||
- name: villages
|
||||
servers:
|
||||
- url: "{scheme}://{hostname}/api"
|
||||
variables:
|
||||
|
@ -226,7 +227,24 @@ paths:
|
|||
$ref: "#/components/responses/ListPlayersResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
|
||||
/v2/versions/{versionCode}/servers/{serverKey}/villages:
|
||||
get:
|
||||
operationId: listVillages
|
||||
tags:
|
||||
- versions
|
||||
- servers
|
||||
- villages
|
||||
description: List villages
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/VersionCodePathParam"
|
||||
- $ref: "#/components/parameters/ServerKeyPathParam"
|
||||
- $ref: "#/components/parameters/CursorQueryParam"
|
||||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListVillagesResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
components:
|
||||
schemas:
|
||||
Error:
|
||||
|
@ -1079,6 +1097,58 @@ components:
|
|||
deletedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
Village:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- fullName
|
||||
- x
|
||||
- y
|
||||
- points
|
||||
- profileUrl
|
||||
- continent
|
||||
- bonus
|
||||
- createdAt
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/IntId"
|
||||
name:
|
||||
type: string
|
||||
example: Village
|
||||
fullName:
|
||||
type: string
|
||||
example: Village (450|450) K44
|
||||
x:
|
||||
type: integer
|
||||
example: 450
|
||||
y:
|
||||
type: integer
|
||||
example: 450
|
||||
points:
|
||||
type: integer
|
||||
profileUrl:
|
||||
type: string
|
||||
format: uri
|
||||
continent:
|
||||
type: string
|
||||
example: K44
|
||||
bonus:
|
||||
type: integer
|
||||
description: |
|
||||
Some of the bonuses:
|
||||
1 - 100% higher wood production
|
||||
2 - 100% higher clay production
|
||||
3 - 100% higher iron production
|
||||
4 - 10% more population
|
||||
5 - 33% faster recruitment in the Barracks
|
||||
6 - 33% faster recruitment in the Stable
|
||||
7 - 50% faster recruitment in the Workshop
|
||||
8 - 30% more resources are produced (all resource types)
|
||||
9 - 50% more storage capacity and merchants
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
Cursor:
|
||||
type: object
|
||||
x-go-type-skip-optional-pointer: true
|
||||
|
@ -1364,6 +1434,21 @@ components:
|
|||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/Player"
|
||||
ListVillagesResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Village"
|
||||
ErrorResponse:
|
||||
description: Default error response.
|
||||
content:
|
||||
|
|
|
@ -108,12 +108,14 @@ var cmdServe = &cli.Command{
|
|||
serverRepo := adapter.NewServerBunRepository(bunDB)
|
||||
tribeRepo := adapter.NewTribeBunRepository(bunDB)
|
||||
playerRepo := adapter.NewPlayerBunRepository(bunDB)
|
||||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||
|
||||
// services
|
||||
versionSvc := app.NewVersionService(versionRepo)
|
||||
serverSvc := app.NewServerService(serverRepo, nil, nil)
|
||||
tribeSvc := app.NewTribeService(tribeRepo, nil, nil)
|
||||
playerSvc := app.NewPlayerService(playerRepo, nil, nil, nil)
|
||||
villageSvc := app.NewVillageService(villageRepo, nil, nil)
|
||||
|
||||
// health
|
||||
h := health.New()
|
||||
|
@ -141,6 +143,7 @@ var cmdServe = &cli.Command{
|
|||
serverSvc,
|
||||
tribeSvc,
|
||||
playerSvc,
|
||||
villageSvc,
|
||||
port.WithOpenAPIConfig(oapiCfg),
|
||||
))
|
||||
|
||||
|
|
|
@ -101,6 +101,10 @@ func (v Village) Name() string {
|
|||
return v.name
|
||||
}
|
||||
|
||||
func (v Village) FullName() string {
|
||||
return fmt.Sprintf("%s (%d|%d) %s", v.name, v.x, v.y, v.continent)
|
||||
}
|
||||
|
||||
func (v Village) Points() int {
|
||||
return v.points
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ type apiHTTPHandler struct {
|
|||
serverSvc *app.ServerService
|
||||
tribeSvc *app.TribeService
|
||||
playerSvc *app.PlayerService
|
||||
villageSvc *app.VillageService
|
||||
errorRenderer apiErrorRenderer
|
||||
openAPISchema func() (*openapi3.T, error)
|
||||
}
|
||||
|
@ -32,6 +33,7 @@ func NewAPIHTTPHandler(
|
|||
serverSvc *app.ServerService,
|
||||
tribeSvc *app.TribeService,
|
||||
playerSvc *app.PlayerService,
|
||||
villageSvc *app.VillageService,
|
||||
opts ...APIHTTPHandlerOption,
|
||||
) http.Handler {
|
||||
cfg := newAPIHTTPHandlerConfig(opts...)
|
||||
|
@ -41,6 +43,7 @@ func NewAPIHTTPHandler(
|
|||
serverSvc: serverSvc,
|
||||
tribeSvc: tribeSvc,
|
||||
playerSvc: playerSvc,
|
||||
villageSvc: villageSvc,
|
||||
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||
return getOpenAPISchema(cfg.openAPI)
|
||||
}),
|
||||
|
|
|
@ -36,12 +36,14 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
|||
serverRepo := adapter.NewServerBunRepository(bunDB)
|
||||
tribeRepo := adapter.NewTribeBunRepository(bunDB)
|
||||
playerRepo := adapter.NewPlayerBunRepository(bunDB)
|
||||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||
|
||||
return port.NewAPIHTTPHandler(
|
||||
app.NewVersionService(versionRepo),
|
||||
app.NewServerService(serverRepo, nil, nil),
|
||||
app.NewTribeService(tribeRepo, nil, nil),
|
||||
app.NewPlayerService(playerRepo, nil, nil, nil),
|
||||
app.NewVillageService(villageRepo, nil, nil),
|
||||
cfg.options...,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
)
|
||||
|
||||
func (h *apiHTTPHandler) ListVillages(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
_ apimodel.VersionCodePathParam,
|
||||
serverKey apimodel.ServerKeyPathParam,
|
||||
params apimodel.ListVillagesParams,
|
||||
) {
|
||||
domainParams := domain.NewListVillagesParams()
|
||||
|
||||
if err := domainParams.SetSort([]domain.VillageSort{domain.VillageSortIDASC}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListVillagesErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListVillagesErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if params.Limit != nil {
|
||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListVillagesErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Cursor != nil {
|
||||
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListVillagesErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.villageSvc.List(r.Context(), domainParams)
|
||||
if err != nil {
|
||||
h.errorRenderer.render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
renderJSON(w, r, http.StatusOK, apimodel.NewListVillagesResponse(res))
|
||||
}
|
||||
|
||||
func formatListVillagesErrorPath(segments []domain.ErrorPathSegment) []string {
|
||||
if segments[0].Model != "ListVillagesParams" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch segments[0].Field {
|
||||
case "cursor":
|
||||
return []string{"$query", "cursor"}
|
||||
case "limit":
|
||||
return []string{"$query", "limit"}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
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 endpointListVillages = "/v2/versions/%s/servers/%s/villages"
|
||||
|
||||
func TestListVillages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newAPIHTTPHandler(t)
|
||||
villages := getAllVillages(t, handler)
|
||||
server := villages[0].Server
|
||||
|
||||
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.ListVillagesResponse](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.Village) int {
|
||||
return cmp.Compare(a.Id, b.Id)
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=1",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("limit", "1")
|
||||
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.ListVillagesResponse](t, resp.Body)
|
||||
assert.NotZero(t, body.Cursor.Next)
|
||||
assert.NotZero(t, body.Cursor.Self)
|
||||
assert.NotZero(t, body.Data)
|
||||
limit, err := strconv.Atoi(req.URL.Query().Get("limit"))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, body.Data, limit)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=1 cursor",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("limit", "1")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp := doCustomRequest(handler, req.Clone(req.Context()))
|
||||
defer resp.Body.Close()
|
||||
body := decodeJSON[apimodel.ListVillagesResponse](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.ListVillagesResponse](t, resp.Body)
|
||||
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 a string",
|
||||
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: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"current": float64(domainErr.Current),
|
||||
"min": float64(domainErr.Min),
|
||||
},
|
||||
Path: []string{"$query", "limit"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit > 500",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("limit", "501")
|
||||
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: 500,
|
||||
Current: limit,
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: 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: 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: 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: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Path: []string{"$query", "cursor"},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: version not found",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
req.URL.Path = fmt.Sprintf(endpointListVillages, domaintest.RandVersionCode(), server.Key)
|
||||
},
|
||||
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, 7)
|
||||
domainErr := domain.VersionNotFoundError{
|
||||
VersionCode: pathSegments[3],
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"code": domainErr.VersionCode,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: server not found",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
req.URL.Path = fmt.Sprintf(endpointListVillages, server.Version.Code, domaintest.RandServerKey())
|
||||
},
|
||||
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, 7)
|
||||
domainErr := domain.ServerNotFoundError{
|
||||
Key: pathSegments[5],
|
||||
}
|
||||
assert.Equal(t, apimodel.ErrorResponse{
|
||||
Errors: []apimodel.Error{
|
||||
{
|
||||
Code: domainErr.Code(),
|
||||
Message: domainErr.Error(),
|
||||
Params: map[string]any{
|
||||
"key": domainErr.Key,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, body)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
fmt.Sprintf(endpointListVillages, server.Version.Code, server.Key),
|
||||
nil,
|
||||
)
|
||||
if tt.reqModifier != nil {
|
||||
tt.reqModifier(t, req)
|
||||
}
|
||||
|
||||
resp := doCustomRequest(handler, req)
|
||||
defer resp.Body.Close()
|
||||
tt.assertResp(t, req, resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type villageWithServer struct {
|
||||
apimodel.Village
|
||||
Server serverWithVersion
|
||||
}
|
||||
|
||||
func getAllVillages(tb testing.TB, h http.Handler) []villageWithServer {
|
||||
tb.Helper()
|
||||
|
||||
servers := getAllServers(tb, h)
|
||||
|
||||
var villages []villageWithServer
|
||||
|
||||
for _, s := range servers {
|
||||
resp := doRequest(h, http.MethodGet, fmt.Sprintf(endpointListVillages, s.Version.Code, s.Key), nil)
|
||||
require.Equal(tb, http.StatusOK, resp.StatusCode)
|
||||
|
||||
for _, v := range decodeJSON[apimodel.ListVillagesResponse](tb, resp.Body).Data {
|
||||
villages = append(villages, villageWithServer{
|
||||
Village: v,
|
||||
Server: s,
|
||||
})
|
||||
}
|
||||
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
require.NotZero(tb, villages)
|
||||
|
||||
return villages
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package apimodel
|
||||
|
||||
import (
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
func NewVillage(v domain.Village) Village {
|
||||
return Village{
|
||||
Bonus: v.Bonus(),
|
||||
Continent: v.Continent(),
|
||||
CreatedAt: v.CreatedAt(),
|
||||
FullName: v.FullName(),
|
||||
Id: v.ID(),
|
||||
Name: v.Name(),
|
||||
Points: v.Points(),
|
||||
ProfileUrl: v.ProfileURL().String(),
|
||||
X: v.X(),
|
||||
Y: v.Y(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewListVillagesResponse(res domain.ListVillagesResult) ListVillagesResponse {
|
||||
versions := res.Villages()
|
||||
|
||||
resp := ListVillagesResponse{
|
||||
Data: make([]Village, 0, len(versions)),
|
||||
Cursor: Cursor{
|
||||
Next: res.Next().Encode(),
|
||||
Self: res.Self().Encode(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
resp.Data = append(resp.Data, NewVillage(v))
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
Loading…
Reference in New Issue