feat: new API endpoint /api/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/snapshots #30
|
@ -17,6 +17,7 @@ tags:
|
||||||
- name: villages
|
- name: villages
|
||||||
- name: ennoblements
|
- name: ennoblements
|
||||||
- name: tribe-changes
|
- name: tribe-changes
|
||||||
|
- name: snapshots
|
||||||
servers:
|
servers:
|
||||||
- url: "{scheme}://{hostname}/api"
|
- url: "{scheme}://{hostname}/api"
|
||||||
variables:
|
variables:
|
||||||
|
@ -441,6 +442,27 @@ paths:
|
||||||
$ref: "#/components/responses/ListTribeChangesResponse"
|
$ref: "#/components/responses/ListTribeChangesResponse"
|
||||||
default:
|
default:
|
||||||
$ref: "#/components/responses/ErrorResponse"
|
$ref: "#/components/responses/ErrorResponse"
|
||||||
|
/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/snapshots:
|
||||||
|
get:
|
||||||
|
operationId: listTribeTribeSnapshots
|
||||||
|
tags:
|
||||||
|
- versions
|
||||||
|
- servers
|
||||||
|
- tribes
|
||||||
|
- snapshots
|
||||||
|
description: List the given tribe's snapshots
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/VersionCodePathParam"
|
||||||
|
- $ref: "#/components/parameters/ServerKeyPathParam"
|
||||||
|
- $ref: "#/components/parameters/TribeIdPathParam"
|
||||||
|
- $ref: "#/components/parameters/CursorQueryParam"
|
||||||
|
- $ref: "#/components/parameters/LimitQueryParam"
|
||||||
|
- $ref: "#/components/parameters/TribeSnapshotSortQueryParam"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/ListTribeSnapshotsResponse"
|
||||||
|
default:
|
||||||
|
$ref: "#/components/responses/ErrorResponse"
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
DomainErrorCode:
|
DomainErrorCode:
|
||||||
|
@ -1484,6 +1506,42 @@ components:
|
||||||
createdAt:
|
createdAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
TribeSnapshot:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- tribe
|
||||||
|
- allPoints
|
||||||
|
- date
|
||||||
|
- dominance
|
||||||
|
- numMembers
|
||||||
|
- numVillages
|
||||||
|
- points
|
||||||
|
- rank
|
||||||
|
- opponentsDefeated
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/IntId"
|
||||||
|
tribe:
|
||||||
|
$ref: "#/components/schemas/TribeMeta"
|
||||||
|
allPoints:
|
||||||
|
type: integer
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
dominance:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
numMembers:
|
||||||
|
type: integer
|
||||||
|
numVillages:
|
||||||
|
type: integer
|
||||||
|
points:
|
||||||
|
type: integer
|
||||||
|
rank:
|
||||||
|
type: integer
|
||||||
|
opponentsDefeated:
|
||||||
|
$ref: "#/components/schemas/TribeOpponentsDefeated"
|
||||||
CursorString:
|
CursorString:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -1632,7 +1690,7 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- createdAt:ASC
|
- createdAt:ASC
|
||||||
- createdAt:DESC
|
- createdAt:DESC
|
||||||
maxItems: 1
|
maxItems: 2
|
||||||
TribeChangeSortQueryParam:
|
TribeChangeSortQueryParam:
|
||||||
name: sort
|
name: sort
|
||||||
in: query
|
in: query
|
||||||
|
@ -1646,7 +1704,21 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- createdAt:ASC
|
- createdAt:ASC
|
||||||
- createdAt:DESC
|
- createdAt:DESC
|
||||||
maxItems: 1
|
maxItems: 2
|
||||||
|
TribeSnapshotSortQueryParam:
|
||||||
|
name: sort
|
||||||
|
in: query
|
||||||
|
description: Order matters!
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
default:
|
||||||
|
- date:ASC
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- date:ASC
|
||||||
|
- date:DESC
|
||||||
|
maxItems: 2
|
||||||
SinceQueryParam:
|
SinceQueryParam:
|
||||||
name: since
|
name: since
|
||||||
in: query
|
in: query
|
||||||
|
@ -1885,6 +1957,21 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/TribeChange"
|
$ref: "#/components/schemas/TribeChange"
|
||||||
|
ListTribeSnapshotsResponse:
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PaginationResponse"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/TribeSnapshot"
|
||||||
ErrorResponse:
|
ErrorResponse:
|
||||||
description: Default error response.
|
description: Default error response.
|
||||||
content:
|
content:
|
||||||
|
|
|
@ -113,6 +113,7 @@ var cmdServe = &cli.Command{
|
||||||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||||
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
||||||
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
||||||
|
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(bunDB)
|
||||||
|
|
||||||
// services
|
// services
|
||||||
versionSvc := app.NewVersionService(versionRepo)
|
versionSvc := app.NewVersionService(versionRepo)
|
||||||
|
@ -122,6 +123,7 @@ var cmdServe = &cli.Command{
|
||||||
playerSvc := app.NewPlayerService(playerRepo, tribeChangeSvc, nil, nil)
|
playerSvc := app.NewPlayerService(playerRepo, tribeChangeSvc, nil, nil)
|
||||||
villageSvc := app.NewVillageService(villageRepo, nil, nil)
|
villageSvc := app.NewVillageService(villageRepo, nil, nil)
|
||||||
ennoblementSvc := app.NewEnnoblementService(ennoblementRepo, nil, nil)
|
ennoblementSvc := app.NewEnnoblementService(ennoblementRepo, nil, nil)
|
||||||
|
tribeSnapshotSvc := app.NewTribeSnapshotService(tribeSnapshotRepo, tribeSvc, nil)
|
||||||
|
|
||||||
// health
|
// health
|
||||||
h := health.New()
|
h := health.New()
|
||||||
|
@ -156,6 +158,7 @@ var cmdServe = &cli.Command{
|
||||||
villageSvc,
|
villageSvc,
|
||||||
ennoblementSvc,
|
ennoblementSvc,
|
||||||
tribeChangeSvc,
|
tribeChangeSvc,
|
||||||
|
tribeSnapshotSvc,
|
||||||
port.WithOpenAPIConfig(oapiCfg),
|
port.WithOpenAPIConfig(oapiCfg),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,10 @@ type TribeSnapshotRepository interface {
|
||||||
// Create persists tribe snapshots in a store (e.g. Postgres).
|
// Create persists tribe snapshots in a store (e.g. Postgres).
|
||||||
// Duplicates are ignored.
|
// Duplicates are ignored.
|
||||||
Create(ctx context.Context, params ...domain.CreateTribeSnapshotParams) error
|
Create(ctx context.Context, params ...domain.CreateTribeSnapshotParams) error
|
||||||
|
ListWithRelations(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListTribeSnapshotsParams,
|
||||||
|
) (domain.ListTribeSnapshotsWithRelationsResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TribeSnapshotService struct {
|
type TribeSnapshotService struct {
|
||||||
|
@ -92,3 +96,10 @@ func (svc *TribeSnapshotService) Create(
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *TribeSnapshotService) ListWithRelations(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListTribeSnapshotsParams,
|
||||||
|
) (domain.ListTribeSnapshotsWithRelationsResult, error) {
|
||||||
|
return svc.repo.ListWithRelations(ctx, params)
|
||||||
|
}
|
||||||
|
|
|
@ -13,15 +13,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiHTTPHandler struct {
|
type apiHTTPHandler struct {
|
||||||
versionSvc *app.VersionService
|
versionSvc *app.VersionService
|
||||||
serverSvc *app.ServerService
|
serverSvc *app.ServerService
|
||||||
tribeSvc *app.TribeService
|
tribeSvc *app.TribeService
|
||||||
playerSvc *app.PlayerService
|
playerSvc *app.PlayerService
|
||||||
villageSvc *app.VillageService
|
villageSvc *app.VillageService
|
||||||
ennoblementSvc *app.EnnoblementService
|
ennoblementSvc *app.EnnoblementService
|
||||||
tribeChangeSvc *app.TribeChangeService
|
tribeChangeSvc *app.TribeChangeService
|
||||||
errorRenderer apiErrorRenderer
|
tribeSnapshotSvc *app.TribeSnapshotService
|
||||||
openAPISchema func() (*openapi3.T, error)
|
errorRenderer apiErrorRenderer
|
||||||
|
openAPISchema func() (*openapi3.T, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithOpenAPIConfig(oapiCfg OpenAPIConfig) APIHTTPHandlerOption {
|
func WithOpenAPIConfig(oapiCfg OpenAPIConfig) APIHTTPHandlerOption {
|
||||||
|
@ -38,18 +39,20 @@ func NewAPIHTTPHandler(
|
||||||
villageSvc *app.VillageService,
|
villageSvc *app.VillageService,
|
||||||
ennoblementSvc *app.EnnoblementService,
|
ennoblementSvc *app.EnnoblementService,
|
||||||
tribeChangeSvc *app.TribeChangeService,
|
tribeChangeSvc *app.TribeChangeService,
|
||||||
|
tribeSnapshotSvc *app.TribeSnapshotService,
|
||||||
opts ...APIHTTPHandlerOption,
|
opts ...APIHTTPHandlerOption,
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
cfg := newAPIHTTPHandlerConfig(opts...)
|
cfg := newAPIHTTPHandlerConfig(opts...)
|
||||||
|
|
||||||
h := &apiHTTPHandler{
|
h := &apiHTTPHandler{
|
||||||
versionSvc: versionSvc,
|
versionSvc: versionSvc,
|
||||||
serverSvc: serverSvc,
|
serverSvc: serverSvc,
|
||||||
tribeSvc: tribeSvc,
|
tribeSvc: tribeSvc,
|
||||||
playerSvc: playerSvc,
|
playerSvc: playerSvc,
|
||||||
villageSvc: villageSvc,
|
villageSvc: villageSvc,
|
||||||
ennoblementSvc: ennoblementSvc,
|
ennoblementSvc: ennoblementSvc,
|
||||||
tribeChangeSvc: tribeChangeSvc,
|
tribeChangeSvc: tribeChangeSvc,
|
||||||
|
tribeSnapshotSvc: tribeSnapshotSvc,
|
||||||
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||||
return getOpenAPISchema(cfg.openAPI)
|
return getOpenAPISchema(cfg.openAPI)
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -40,6 +40,7 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
||||||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||||
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
||||||
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
||||||
|
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(bunDB)
|
||||||
|
|
||||||
return port.NewAPIHTTPHandler(
|
return port.NewAPIHTTPHandler(
|
||||||
app.NewVersionService(versionRepo),
|
app.NewVersionService(versionRepo),
|
||||||
|
@ -49,6 +50,7 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
||||||
app.NewVillageService(villageRepo, nil, nil),
|
app.NewVillageService(villageRepo, nil, nil),
|
||||||
app.NewEnnoblementService(ennoblementRepo, nil, nil),
|
app.NewEnnoblementService(ennoblementRepo, nil, nil),
|
||||||
app.NewTribeChangeService(tribeChangeRepo),
|
app.NewTribeChangeService(tribeChangeRepo),
|
||||||
|
app.NewTribeSnapshotService(tribeSnapshotRepo, nil, nil),
|
||||||
cfg.options...,
|
cfg.options...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
91
internal/port/handler_http_api_tribe_snapshot.go
Normal file
91
internal/port/handler_http_api_tribe_snapshot.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package port
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func (h *apiHTTPHandler) ListTribeTribeSnapshots(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
_ apimodel.VersionCodePathParam,
|
||||||
|
serverKey apimodel.ServerKeyPathParam,
|
||||||
|
tribeID apimodel.TribeIdPathParam,
|
||||||
|
params apimodel.ListTribeTribeSnapshotsParams,
|
||||||
|
) {
|
||||||
|
domainParams := domain.NewListTribeSnapshotsParams()
|
||||||
|
|
||||||
|
if err := domainParams.SetSort([]domain.TribeSnapshotSort{domain.TribeSnapshotSortIDASC}); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := domainParams.SetTribeIDs([]int{tribeID}); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Sort != nil {
|
||||||
|
if err := domainParams.PrependSortString(*params.Sort); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := domainParams.PrependSortString([]string{domain.TribeSnapshotSortDateASC.String()}); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Limit != nil {
|
||||||
|
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Cursor != nil {
|
||||||
|
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||||
|
h.errorRenderer.withErrorPathFormatter(formatListTribeSnapshotsErrorPath).render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.tribeSnapshotSvc.ListWithRelations(r.Context(), domainParams)
|
||||||
|
if err != nil {
|
||||||
|
h.errorRenderer.render(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJSON(w, r, http.StatusOK, apimodel.NewListTribeSnapshotsResponse(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatListTribeSnapshotsErrorPath(segments []domain.ErrorPathSegment) []string {
|
||||||
|
if segments[0].Model != "ListTribeSnapshotsParams" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch segments[0].Field {
|
||||||
|
case "cursor":
|
||||||
|
return []string{"$query", "cursor"}
|
||||||
|
case "limit":
|
||||||
|
return []string{"$query", "limit"}
|
||||||
|
case "sort":
|
||||||
|
path := []string{"$query", "sort"}
|
||||||
|
if segments[0].Index >= 0 {
|
||||||
|
path = append(path, strconv.Itoa(segments[0].Index))
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
626
internal/port/handler_http_api_tribe_snapshot_test.go
Normal file
626
internal/port/handler_http_api_tribe_snapshot_test.go
Normal file
|
@ -0,0 +1,626 @@
|
||||||
|
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 endpointListTribeTribeSnapshots = "/v2/versions/%s/servers/%s/tribes/%d/snapshots"
|
||||||
|
|
||||||
|
func TestListTribeTribeSnapshots(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
handler := newAPIHTTPHandler(t)
|
||||||
|
tss := getAllTribeSnapshots(t, handler)
|
||||||
|
var server serverWithVersion
|
||||||
|
var tribe apimodel.TribeMeta
|
||||||
|
|
||||||
|
tssGroupedByTribeIDAndServerKey := make(map[string][]tribeSnapshotWithServer)
|
||||||
|
for _, ts := range tss {
|
||||||
|
key := fmt.Sprintf("%d-%s", ts.Tribe.Id, ts.Server.Key)
|
||||||
|
tssGroupedByTribeIDAndServerKey[key] = append(tssGroupedByTribeIDAndServerKey[key], ts)
|
||||||
|
}
|
||||||
|
currentMax := -1
|
||||||
|
for _, grouped := range tssGroupedByTribeIDAndServerKey {
|
||||||
|
if l := len(grouped); l > currentMax && l > 0 {
|
||||||
|
currentMax = l
|
||||||
|
server = grouped[0].Server
|
||||||
|
tribe = grouped[0].Tribe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ListTribeSnapshotsResponse](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.TribeSnapshot) int {
|
||||||
|
return cmp.Or(
|
||||||
|
a.Date.Compare(b.Date.Time),
|
||||||
|
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.ListTribeSnapshotsResponse](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.ListTribeSnapshotsResponse](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.ListTribeSnapshotsResponse](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: "OK: sort=[date:DESC]",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Set("sort", "date:DESC")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
},
|
||||||
|
assertResp: func(t *testing.T, _ *http.Request, resp *http.Response) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
// body
|
||||||
|
body := decodeJSON[apimodel.ListTribeSnapshotsResponse](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.TribeSnapshot) int {
|
||||||
|
return cmp.Or(
|
||||||
|
a.Date.Compare(b.Date.Time)*-1,
|
||||||
|
cmp.Compare(a.Id, b.Id),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: 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.TribeSnapshotListMaxLimit),
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Set("limit", strconv.Itoa(domain.TribeSnapshotListMaxLimit+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.TribeSnapshotListMaxLimit,
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) > 2",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "date:DESC")
|
||||||
|
q.Add("sort", "date:ASC")
|
||||||
|
q.Add("sort", "date:ASC")
|
||||||
|
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: 2,
|
||||||
|
Current: len(req.URL.Query()["sort"]),
|
||||||
|
}
|
||||||
|
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", "sort"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: invalid sort",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "date:")
|
||||||
|
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.UnsupportedSortStringError{
|
||||||
|
Sort: req.URL.Query()["sort"][0],
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"sort": domainErr.Sort,
|
||||||
|
},
|
||||||
|
Path: []string{"$query", "sort", "0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: sort conflict",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("sort", "date:DESC")
|
||||||
|
q.Add("sort", "date:ASC")
|
||||||
|
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)
|
||||||
|
q := req.URL.Query()
|
||||||
|
domainErr := domain.SortConflictError{
|
||||||
|
Sort: [2]string{q["sort"][0], q["sort"][1]},
|
||||||
|
}
|
||||||
|
paramSort := make([]any, len(domainErr.Sort))
|
||||||
|
for i, s := range domainErr.Sort {
|
||||||
|
paramSort[i] = s
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"sort": paramSort,
|
||||||
|
},
|
||||||
|
Path: []string{"$query", "sort"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: version not found",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
req.URL.Path = fmt.Sprintf(
|
||||||
|
endpointListTribeTribeSnapshots,
|
||||||
|
randInvalidVersionCode(t, handler),
|
||||||
|
server.Key,
|
||||||
|
tribe.Id,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
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, 9)
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: server not found",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
req.URL.Path = fmt.Sprintf(
|
||||||
|
endpointListTribeTribeSnapshots,
|
||||||
|
server.Version.Code,
|
||||||
|
domaintest.RandServerKey(),
|
||||||
|
tribe.Id,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
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, 9)
|
||||||
|
domainErr := domain.ServerNotFoundError{
|
||||||
|
Key: pathSegments[5],
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"key": domainErr.Key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: tribe not found",
|
||||||
|
reqModifier: func(t *testing.T, req *http.Request) {
|
||||||
|
t.Helper()
|
||||||
|
req.URL.Path = fmt.Sprintf(
|
||||||
|
endpointListTribeTribeSnapshots,
|
||||||
|
server.Version.Code,
|
||||||
|
server.Key,
|
||||||
|
domaintest.RandID(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
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, 9)
|
||||||
|
id, err := strconv.Atoi(pathSegments[7])
|
||||||
|
require.NoError(t, err)
|
||||||
|
domainErr := domain.TribeNotFoundError{
|
||||||
|
ID: id,
|
||||||
|
ServerKey: pathSegments[5],
|
||||||
|
}
|
||||||
|
assert.Equal(t, apimodel.ErrorResponse{
|
||||||
|
Errors: []apimodel.Error{
|
||||||
|
{
|
||||||
|
Code: apimodel.ErrorCode(domainErr.Code()),
|
||||||
|
Message: domainErr.Error(),
|
||||||
|
Params: map[string]any{
|
||||||
|
"id": float64(domainErr.ID),
|
||||||
|
"serverKey": domainErr.ServerKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, body)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf(endpointListTribeTribeSnapshots, server.Version.Code, server.Key, tribe.Id),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if tt.reqModifier != nil {
|
||||||
|
tt.reqModifier(t, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := doCustomRequest(handler, req)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
tt.assertResp(t, req, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tribeSnapshotWithServer struct {
|
||||||
|
apimodel.TribeSnapshot
|
||||||
|
Server serverWithVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllTribeSnapshots(tb testing.TB, h http.Handler) []tribeSnapshotWithServer {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
tribes := getAllTribes(tb, h)
|
||||||
|
|
||||||
|
var tss []tribeSnapshotWithServer
|
||||||
|
|
||||||
|
for _, t := range tribes {
|
||||||
|
resp := doRequest(h, http.MethodGet, fmt.Sprintf(
|
||||||
|
endpointListTribeTribeSnapshots,
|
||||||
|
t.Server.Version.Code,
|
||||||
|
t.Server.Key,
|
||||||
|
t.Id,
|
||||||
|
), nil)
|
||||||
|
require.Equal(tb, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
for _, ts := range decodeJSON[apimodel.ListTribeSnapshotsResponse](tb, resp.Body).Data {
|
||||||
|
tss = append(tss, tribeSnapshotWithServer{
|
||||||
|
TribeSnapshot: ts,
|
||||||
|
Server: t.Server,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotZero(tb, tss)
|
||||||
|
|
||||||
|
return tss
|
||||||
|
}
|
40
internal/port/internal/apimodel/tribe_snapshot.go
Normal file
40
internal/port/internal/apimodel/tribe_snapshot.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package apimodel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
oapitypes "github.com/oapi-codegen/runtime/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTribeSnapshot(withRelations domain.TribeSnapshotWithRelations) TribeSnapshot {
|
||||||
|
tc := withRelations.TribeSnapshot()
|
||||||
|
return TribeSnapshot{
|
||||||
|
AllPoints: tc.AllPoints(),
|
||||||
|
Date: oapitypes.Date{Time: tc.Date()},
|
||||||
|
Dominance: tc.Dominance(),
|
||||||
|
Id: tc.ID(),
|
||||||
|
NumMembers: tc.NumMembers(),
|
||||||
|
NumVillages: tc.NumVillages(),
|
||||||
|
OpponentsDefeated: NewTribeOpponentsDefeated(tc.OD()),
|
||||||
|
Points: tc.Points(),
|
||||||
|
Rank: tc.Rank(),
|
||||||
|
Tribe: NewTribeMeta(withRelations.Tribe()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListTribeSnapshotsResponse(res domain.ListTribeSnapshotsWithRelationsResult) ListTribeSnapshotsResponse {
|
||||||
|
tcs := res.TribeSnapshots()
|
||||||
|
|
||||||
|
resp := ListTribeSnapshotsResponse{
|
||||||
|
Data: make([]TribeSnapshot, 0, len(tcs)),
|
||||||
|
Cursor: Cursor{
|
||||||
|
Next: res.Next().Encode(),
|
||||||
|
Self: res.Self().Encode(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
resp.Data = append(resp.Data, NewTribeSnapshot(tc))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user