feat: new endpoint GET /api/v2/versions/{versionCode}/servers/{serverKey}/snapshots #52
116
api/openapi3.yml
116
api/openapi3.yml
|
@ -471,6 +471,25 @@ paths:
|
|||
$ref: "#/components/responses/ListTribeChangesResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/v2/versions/{versionCode}/servers/{serverKey}/snapshots:
|
||||
get:
|
||||
operationId: listServerServerSnapshots
|
||||
tags:
|
||||
- versions
|
||||
- servers
|
||||
- snapshots
|
||||
description: List the given server's snapshots
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/VersionCodePathParam"
|
||||
- $ref: "#/components/parameters/ServerKeyPathParam"
|
||||
- $ref: "#/components/parameters/CursorQueryParam"
|
||||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
- $ref: "#/components/parameters/ServerSnapshotSortQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListServerSnapshotsResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/snapshots:
|
||||
get:
|
||||
operationId: listTribeTribeSnapshots
|
||||
|
@ -1594,6 +1613,52 @@ components:
|
|||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
ServerSnapshot:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- server
|
||||
- numPlayers
|
||||
- numActivePlayers
|
||||
- numInactivePlayers
|
||||
- numTribes
|
||||
- numActiveTribes
|
||||
- numInactiveTribes
|
||||
- numVillages
|
||||
- numBarbarianVillages
|
||||
- numBonusVillages
|
||||
- numPlayerVillages
|
||||
- date
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/IntId"
|
||||
server:
|
||||
$ref: "#/components/schemas/ServerMeta"
|
||||
numPlayers:
|
||||
type: integer
|
||||
description: numActivePlayers+numInactivePlayers
|
||||
numActivePlayers:
|
||||
type: integer
|
||||
numInactivePlayers:
|
||||
type: integer
|
||||
numTribes:
|
||||
type: integer
|
||||
description: numActiveTribes+numInactiveTribes
|
||||
numActiveTribes:
|
||||
type: integer
|
||||
numInactiveTribes:
|
||||
type: integer
|
||||
numVillages:
|
||||
type: integer
|
||||
numBarbarianVillages:
|
||||
type: integer
|
||||
numBonusVillages:
|
||||
type: integer
|
||||
numPlayerVillages:
|
||||
type: integer
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
TribeSnapshot:
|
||||
type: object
|
||||
required:
|
||||
|
@ -1675,7 +1740,7 @@ components:
|
|||
x-go-type-skip-optional-pointer: true
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/CursorString"
|
||||
PaginationResponse:
|
||||
Pagination:
|
||||
type: object
|
||||
properties:
|
||||
cursor:
|
||||
|
@ -1840,6 +1905,20 @@ components:
|
|||
- date:ASC
|
||||
- date:DESC
|
||||
maxItems: 1
|
||||
ServerSnapshotSortQueryParam:
|
||||
name: sort
|
||||
in: query
|
||||
description: Order matters!
|
||||
schema:
|
||||
type: array
|
||||
default:
|
||||
- date:ASC
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- date:ASC
|
||||
- date:DESC
|
||||
maxItems: 1
|
||||
PlayerSnapshotSortQueryParam:
|
||||
name: sort
|
||||
in: query
|
||||
|
@ -1905,7 +1984,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -1931,7 +2010,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -1990,7 +2069,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2016,7 +2095,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2031,7 +2110,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2057,7 +2136,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2083,7 +2162,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2098,7 +2177,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2107,13 +2186,28 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TribeChange"
|
||||
ListServerSnapshotsResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ServerSnapshot"
|
||||
ListTribeSnapshotsResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
@ -2128,7 +2222,7 @@ components:
|
|||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- $ref: "#/components/schemas/Pagination"
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
|
|
|
@ -113,6 +113,7 @@ var cmdServe = &cli.Command{
|
|||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
||||
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
||||
serverSnapshotRepo := adapter.NewServerSnapshotBunRepository(bunDB)
|
||||
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(bunDB)
|
||||
playerSnapshotRepo := adapter.NewPlayerSnapshotBunRepository(bunDB)
|
||||
|
||||
|
@ -124,6 +125,7 @@ var cmdServe = &cli.Command{
|
|||
playerSvc := app.NewPlayerService(playerRepo, tribeChangeSvc, nil, nil)
|
||||
villageSvc := app.NewVillageService(villageRepo, nil, nil)
|
||||
ennoblementSvc := app.NewEnnoblementService(ennoblementRepo, nil, nil)
|
||||
serverSnapshotSvc := app.NewServerSnapshotService(serverSnapshotRepo, serverSvc, nil)
|
||||
tribeSnapshotSvc := app.NewTribeSnapshotService(tribeSnapshotRepo, tribeSvc, nil)
|
||||
playerSnapshotSvc := app.NewPlayerSnapshotService(playerSnapshotRepo, playerSvc, nil)
|
||||
|
||||
|
@ -160,6 +162,7 @@ var cmdServe = &cli.Command{
|
|||
villageSvc,
|
||||
ennoblementSvc,
|
||||
tribeChangeSvc,
|
||||
serverSnapshotSvc,
|
||||
tribeSnapshotSvc,
|
||||
playerSnapshotSvc,
|
||||
port.WithOpenAPIConfig(oapiCfg),
|
||||
|
|
|
@ -81,6 +81,33 @@ func (repo *ServerSnapshotBunRepository) List(
|
|||
return domain.NewListServerSnapshotsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
func (repo *ServerSnapshotBunRepository) ListWithRelations(
|
||||
ctx context.Context,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
) (domain.ListServerSnapshotsWithRelationsResult, error) {
|
||||
var serverSnapshots bunmodel.ServerSnapshots
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&serverSnapshots).
|
||||
Apply(listServerSnapshotsParamsApplier{params: params}.apply).
|
||||
Relation("Server", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
return q.Column(bunmodel.ServerMetaColumns...)
|
||||
}).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.ListServerSnapshotsWithRelationsResult{}, fmt.Errorf(
|
||||
"couldn't select server snapshots from the db: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
converted, err := serverSnapshots.ToDomainWithRelations()
|
||||
if err != nil {
|
||||
return domain.ListServerSnapshotsWithRelationsResult{}, err
|
||||
}
|
||||
|
||||
return domain.NewListServerSnapshotsWithRelationsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
func (repo *ServerSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
|
||||
if _, err := repo.db.NewDelete().
|
||||
Model((*bunmodel.ServerSnapshot)(nil)).
|
||||
|
|
|
@ -115,7 +115,7 @@ func testServerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
})
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Run("List & ListWithRelations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
@ -405,6 +405,14 @@ func testServerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
assertError(t, err)
|
||||
tt.assertResult(t, params, res)
|
||||
|
||||
resWithRelations, err := repos.serverSnapshot.ListWithRelations(ctx, params)
|
||||
assertError(t, err)
|
||||
require.Len(t, resWithRelations.ServerSnapshots(), len(res.ServerSnapshots()))
|
||||
for i, ss := range resWithRelations.ServerSnapshots() {
|
||||
assert.Equal(t, res.ServerSnapshots()[i], ss.ServerSnapshot())
|
||||
assert.Equal(t, ss.ServerSnapshot().ServerKey(), ss.Server().Key())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -70,6 +70,10 @@ type tribeChangeRepository interface {
|
|||
type serverSnapshotRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
|
||||
List(ctx context.Context, params domain.ListServerSnapshotsParams) (domain.ListServerSnapshotsResult, error)
|
||||
ListWithRelations(
|
||||
ctx context.Context,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
) (domain.ListServerSnapshotsWithRelationsResult, error)
|
||||
Delete(ctx context.Context, serverKey string, dateLTE time.Time) error
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ type ServerSnapshotRepository interface {
|
|||
// Create persists tribe snapshots in a store (e.g. Postgres).
|
||||
// Duplicates are ignored.
|
||||
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
|
||||
ListWithRelations(
|
||||
ctx context.Context,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
) (domain.ListServerSnapshotsWithRelationsResult, error)
|
||||
}
|
||||
|
||||
type ServerSnapshotService struct {
|
||||
|
@ -27,7 +31,6 @@ func NewServerSnapshotService(
|
|||
return &ServerSnapshotService{repo: repo, serverSvc: serverSvc, pub: pub}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (svc *ServerSnapshotService) Create(
|
||||
ctx context.Context,
|
||||
createSnapshotsCmdPayload domain.CreateSnapshotsCmdPayload,
|
||||
|
@ -60,3 +63,10 @@ func (svc *ServerSnapshotService) Create(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServerSnapshotService) ListWithRelations(
|
||||
ctx context.Context,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
) (domain.ListServerSnapshotsWithRelationsResult, error) {
|
||||
return svc.repo.ListWithRelations(ctx, params)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ type ServerSnapshot struct {
|
|||
|
||||
ID int `bun:"id,pk,autoincrement,identity"`
|
||||
ServerKey string `bun:"server_key,nullzero"`
|
||||
Server Server `bun:"server,rel:belongs-to,join:server_key=key"`
|
||||
NumPlayers int `bun:"num_players"`
|
||||
NumActivePlayers int `bun:"num_active_players"`
|
||||
NumInactivePlayers int `bun:"num_inactive_players"`
|
||||
|
@ -55,8 +56,26 @@ func (ss ServerSnapshot) ToDomain() (domain.ServerSnapshot, error) {
|
|||
return converted, nil
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ToDomainWithRelations() (domain.ServerSnapshotWithRelations, error) {
|
||||
converted, err := ss.ToDomain()
|
||||
if err != nil {
|
||||
return domain.ServerSnapshotWithRelations{}, err
|
||||
}
|
||||
|
||||
server, err := ss.Server.ToMeta()
|
||||
if err != nil {
|
||||
return domain.ServerSnapshotWithRelations{}, err
|
||||
}
|
||||
|
||||
return converted.WithRelations(server), nil
|
||||
}
|
||||
|
||||
type ServerSnapshots []ServerSnapshot
|
||||
|
||||
func (sss ServerSnapshots) ToDomain() (domain.ServerSnapshots, error) {
|
||||
return sliceToDomain(sss)
|
||||
}
|
||||
|
||||
func (sss ServerSnapshots) ToDomainWithRelations() (domain.ServerSnapshotsWithRelations, error) {
|
||||
return sliceToDomainWithRelations(sss)
|
||||
}
|
||||
|
|
|
@ -100,3 +100,38 @@ func NewServerSnapshot(tb TestingTB, opts ...func(cfg *ServerSnapshotConfig)) do
|
|||
|
||||
return ss
|
||||
}
|
||||
|
||||
type ServerSnapshotWithRelationsConfig struct {
|
||||
ServerSnapshotOptions []func(cfg *ServerSnapshotConfig)
|
||||
ServerOptions []func(cfg *ServerConfig)
|
||||
}
|
||||
|
||||
func NewServerSnapshotWithRelations(
|
||||
tb TestingTB,
|
||||
opts ...func(cfg *ServerSnapshotWithRelationsConfig),
|
||||
) domain.ServerSnapshotWithRelations {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &ServerSnapshotWithRelationsConfig{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
ss := NewServerSnapshot(tb, cfg.ServerSnapshotOptions...)
|
||||
|
||||
if ss.ServerKey() != "" {
|
||||
cfg.ServerOptions = append([]func(cfg *ServerConfig){
|
||||
func(cfg *ServerConfig) {
|
||||
cfg.Key = ss.ServerKey()
|
||||
},
|
||||
}, cfg.ServerOptions...)
|
||||
}
|
||||
|
||||
var tribe domain.ServerMeta
|
||||
if len(cfg.ServerOptions) > 0 {
|
||||
tribe = NewServer(tb, cfg.ServerOptions...).Meta()
|
||||
}
|
||||
|
||||
return ss.WithRelations(tribe)
|
||||
}
|
||||
|
|
|
@ -135,6 +135,13 @@ func (ss ServerSnapshot) CreatedAt() time.Time {
|
|||
return ss.createdAt
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) WithRelations(server ServerMeta) ServerSnapshotWithRelations {
|
||||
return ServerSnapshotWithRelations{
|
||||
snapshot: ss,
|
||||
server: server,
|
||||
}
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ToCursor() (ServerSnapshotCursor, error) {
|
||||
return NewServerSnapshotCursor(ss.id, ss.serverKey, ss.date)
|
||||
}
|
||||
|
@ -145,6 +152,25 @@ func (ss ServerSnapshot) IsZero() bool {
|
|||
|
||||
type ServerSnapshots []ServerSnapshot
|
||||
|
||||
type ServerSnapshotWithRelations struct {
|
||||
snapshot ServerSnapshot
|
||||
server ServerMeta
|
||||
}
|
||||
|
||||
func (ts ServerSnapshotWithRelations) ServerSnapshot() ServerSnapshot {
|
||||
return ts.snapshot
|
||||
}
|
||||
|
||||
func (ts ServerSnapshotWithRelations) Server() ServerMeta {
|
||||
return ts.server
|
||||
}
|
||||
|
||||
func (ts ServerSnapshotWithRelations) IsZero() bool {
|
||||
return ts.snapshot.IsZero()
|
||||
}
|
||||
|
||||
type ServerSnapshotsWithRelations []ServerSnapshotWithRelations
|
||||
|
||||
type CreateServerSnapshotParams struct {
|
||||
serverKey string
|
||||
numPlayers int
|
||||
|
@ -576,3 +602,57 @@ func (res ListServerSnapshotsResult) Self() ServerSnapshotCursor {
|
|||
func (res ListServerSnapshotsResult) Next() ServerSnapshotCursor {
|
||||
return res.next
|
||||
}
|
||||
|
||||
type ListServerSnapshotsWithRelationsResult struct {
|
||||
snapshots ServerSnapshotsWithRelations
|
||||
self ServerSnapshotCursor
|
||||
next ServerSnapshotCursor
|
||||
}
|
||||
|
||||
const listServerSnapshotsWithRelationsResultModelName = "ListServerSnapshotsWithRelationsResult"
|
||||
|
||||
func NewListServerSnapshotsWithRelationsResult(
|
||||
snapshots ServerSnapshotsWithRelations,
|
||||
next ServerSnapshotWithRelations,
|
||||
) (ListServerSnapshotsWithRelationsResult, error) {
|
||||
var err error
|
||||
res := ListServerSnapshotsWithRelationsResult{
|
||||
snapshots: snapshots,
|
||||
}
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
res.self, err = snapshots[0].ServerSnapshot().ToCursor()
|
||||
if err != nil {
|
||||
return ListServerSnapshotsWithRelationsResult{}, ValidationError{
|
||||
Model: listServerSnapshotsWithRelationsResultModelName,
|
||||
Field: "self",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !next.IsZero() {
|
||||
res.next, err = next.ServerSnapshot().ToCursor()
|
||||
if err != nil {
|
||||
return ListServerSnapshotsWithRelationsResult{}, ValidationError{
|
||||
Model: listServerSnapshotsWithRelationsResultModelName,
|
||||
Field: "next",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsWithRelationsResult) ServerSnapshots() ServerSnapshotsWithRelations {
|
||||
return res.snapshots
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsWithRelationsResult) Self() ServerSnapshotCursor {
|
||||
return res.self
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsWithRelationsResult) Next() ServerSnapshotCursor {
|
||||
return res.next
|
||||
}
|
||||
|
|
|
@ -852,3 +852,50 @@ func TestNewListServerSnapshotsResult(t *testing.T) {
|
|||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewListServerSnapshotsWithRelationsResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
snapshots := domain.ServerSnapshotsWithRelations{
|
||||
domaintest.NewServerSnapshotWithRelations(t),
|
||||
domaintest.NewServerSnapshotWithRelations(t),
|
||||
domaintest.NewServerSnapshotWithRelations(t),
|
||||
}
|
||||
next := domaintest.NewServerSnapshotWithRelations(t)
|
||||
|
||||
t.Run("OK: with next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListServerSnapshotsWithRelationsResult(snapshots, next)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.ServerSnapshots())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().ID(), res.Self().ID())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().ServerKey(), res.Self().ServerKey())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().Date(), res.Self().Date())
|
||||
assert.Equal(t, next.ServerSnapshot().ID(), res.Next().ID())
|
||||
assert.Equal(t, next.ServerSnapshot().ServerKey(), res.Next().ServerKey())
|
||||
assert.Equal(t, next.ServerSnapshot().Date(), res.Next().Date())
|
||||
})
|
||||
|
||||
t.Run("OK: without next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListServerSnapshotsWithRelationsResult(snapshots, domain.ServerSnapshotWithRelations{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.ServerSnapshots())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().ID(), res.Self().ID())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().ServerKey(), res.Self().ServerKey())
|
||||
assert.Equal(t, snapshots[0].ServerSnapshot().Date(), res.Self().Date())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
|
||||
t.Run("OK: 0 snapshots", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListServerSnapshotsWithRelationsResult(nil, domain.ServerSnapshotWithRelations{})
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, res.ServerSnapshots())
|
||||
assert.True(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ type apiHTTPHandler struct {
|
|||
villageSvc *app.VillageService
|
||||
ennoblementSvc *app.EnnoblementService
|
||||
tribeChangeSvc *app.TribeChangeService
|
||||
serverSnapshotSvc *app.ServerSnapshotService
|
||||
tribeSnapshotSvc *app.TribeSnapshotService
|
||||
playerSnapshotSvc *app.PlayerSnapshotService
|
||||
errorRenderer apiErrorRenderer
|
||||
|
@ -40,6 +41,7 @@ func NewAPIHTTPHandler(
|
|||
villageSvc *app.VillageService,
|
||||
ennoblementSvc *app.EnnoblementService,
|
||||
tribeChangeSvc *app.TribeChangeService,
|
||||
serverSnapshotSvc *app.ServerSnapshotService,
|
||||
tribeSnapshotSvc *app.TribeSnapshotService,
|
||||
playerSnapshotSvc *app.PlayerSnapshotService,
|
||||
opts ...APIHTTPHandlerOption,
|
||||
|
@ -54,6 +56,7 @@ func NewAPIHTTPHandler(
|
|||
villageSvc: villageSvc,
|
||||
ennoblementSvc: ennoblementSvc,
|
||||
tribeChangeSvc: tribeChangeSvc,
|
||||
serverSnapshotSvc: serverSnapshotSvc,
|
||||
tribeSnapshotSvc: tribeSnapshotSvc,
|
||||
playerSnapshotSvc: playerSnapshotSvc,
|
||||
openAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||
|
|
96
internal/port/handler_http_api_server_snapshot.go
Normal file
96
internal/port/handler_http_api_server_snapshot.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/port/internal/apimodel"
|
||||
)
|
||||
|
||||
const apiServerSnapshotSortMaxLength = 1
|
||||
|
||||
var apiServerSnapshotSortAllowedValues = []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateDESC,
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (h *apiHTTPHandler) ListServerServerSnapshots(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
_ apimodel.VersionCodePathParam,
|
||||
serverKey apimodel.ServerKeyPathParam,
|
||||
params apimodel.ListServerServerSnapshotsParams,
|
||||
) {
|
||||
domainParams := domain.NewListServerSnapshotsParams()
|
||||
|
||||
if err := domainParams.SetSort([]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if params.Sort != nil {
|
||||
if err := domainParams.PrependSortString(
|
||||
*params.Sort,
|
||||
apiServerSnapshotSortAllowedValues,
|
||||
apiServerSnapshotSortMaxLength,
|
||||
); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := domainParams.PrependSort([]domain.ServerSnapshotSort{domain.ServerSnapshotSortDateASC}); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Limit != nil {
|
||||
if err := domainParams.SetLimit(*params.Limit); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.Cursor != nil {
|
||||
if err := domainParams.SetEncodedCursor(*params.Cursor); err != nil {
|
||||
h.errorRenderer.withErrorPathFormatter(formatListServerSnapshotsErrorPath).render(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.serverSnapshotSvc.ListWithRelations(r.Context(), domainParams)
|
||||
if err != nil {
|
||||
h.errorRenderer.render(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
renderJSON(w, r, http.StatusOK, apimodel.NewListServerSnapshotsResponse(res))
|
||||
}
|
||||
|
||||
func formatListServerSnapshotsErrorPath(segments []domain.ErrorPathSegment) []string {
|
||||
if segments[0].Model != "ListServerSnapshotsParams" {
|
||||
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
|
||||
}
|
||||
}
|
542
internal/port/handler_http_api_server_snapshot_test.go
Normal file
542
internal/port/handler_http_api_server_snapshot_test.go
Normal file
|
@ -0,0 +1,542 @@
|
|||
package port_test
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain/domaintest"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/port/internal/apimodel"
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const endpointListServerServerSnapshots = "/v2/versions/%s/servers/%s/snapshots"
|
||||
|
||||
func TestListServerServerSnapshots(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newAPIHTTPHandler(t)
|
||||
sss := getAllServerSnapshots(t, handler)
|
||||
var server serverWithVersion
|
||||
|
||||
sssGroupedByServerKey := make(map[string][]serverSnapshotWithServer)
|
||||
for _, ss := range sss {
|
||||
key := ss.Server.Key
|
||||
sssGroupedByServerKey[key] = append(sssGroupedByServerKey[key], ss)
|
||||
}
|
||||
currentMax := -1
|
||||
for _, grouped := range sssGroupedByServerKey {
|
||||
if l := len(grouped); l > currentMax && l > 0 {
|
||||
currentMax = l
|
||||
server = grouped[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.ListServerSnapshotsResponse](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.ServerSnapshot) 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.ListServerSnapshotsResponse](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.ListServerSnapshotsResponse](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.ListServerSnapshotsResponse](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.ListServerSnapshotsResponse](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.ServerSnapshot) int {
|
||||
return cmp.Or(
|
||||
a.Date.Compare(b.Date.Time)*-1,
|
||||
cmp.Compare(a.Id, b.Id),
|
||||
)
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
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.ServerSnapshotListMaxLimit),
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
q := req.URL.Query()
|
||||
q.Set("limit", strconv.Itoa(domain.ServerSnapshotListMaxLimit+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.ServerSnapshotListMaxLimit,
|
||||
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) > 1",
|
||||
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)
|
||||
domainErr := domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 1,
|
||||
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: version not found",
|
||||
reqModifier: func(t *testing.T, req *http.Request) {
|
||||
t.Helper()
|
||||
req.URL.Path = fmt.Sprintf(
|
||||
endpointListServerServerSnapshots,
|
||||
randInvalidVersionCode(t, handler),
|
||||
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: 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(
|
||||
endpointListServerServerSnapshots,
|
||||
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: apimodel.ErrorCode(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(endpointListServerServerSnapshots, 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 serverSnapshotWithServer struct {
|
||||
apimodel.ServerSnapshot
|
||||
Server serverWithVersion
|
||||
}
|
||||
|
||||
func getAllServerSnapshots(tb testing.TB, h http.Handler) []serverSnapshotWithServer {
|
||||
tb.Helper()
|
||||
|
||||
servers := getAllServers(tb, h)
|
||||
|
||||
var sss []serverSnapshotWithServer
|
||||
|
||||
for _, s := range servers {
|
||||
resp := doRequest(h, http.MethodGet, fmt.Sprintf(
|
||||
endpointListServerServerSnapshots,
|
||||
s.Version.Code,
|
||||
s.Key,
|
||||
), nil)
|
||||
require.Equal(tb, http.StatusOK, resp.StatusCode)
|
||||
|
||||
for _, ts := range decodeJSON[apimodel.ListServerSnapshotsResponse](tb, resp.Body).Data {
|
||||
sss = append(sss, serverSnapshotWithServer{
|
||||
ServerSnapshot: ts,
|
||||
Server: s,
|
||||
})
|
||||
}
|
||||
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
require.NotZero(tb, sss)
|
||||
|
||||
return sss
|
||||
}
|
|
@ -40,6 +40,7 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
|||
villageRepo := adapter.NewVillageBunRepository(bunDB)
|
||||
ennoblementRepo := adapter.NewEnnoblementBunRepository(bunDB)
|
||||
tribeChangeRepo := adapter.NewTribeChangeBunRepository(bunDB)
|
||||
serverSnapshotRepo := adapter.NewServerSnapshotBunRepository(bunDB)
|
||||
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(bunDB)
|
||||
playerSnapshotRepo := adapter.NewPlayerSnapshotBunRepository(bunDB)
|
||||
|
||||
|
@ -51,6 +52,7 @@ func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) h
|
|||
app.NewVillageService(villageRepo, nil, nil),
|
||||
app.NewEnnoblementService(ennoblementRepo, nil, nil),
|
||||
app.NewTribeChangeService(tribeChangeRepo),
|
||||
app.NewServerSnapshotService(serverSnapshotRepo, nil, nil),
|
||||
app.NewTribeSnapshotService(tribeSnapshotRepo, nil, nil),
|
||||
app.NewPlayerSnapshotService(playerSnapshotRepo, nil, nil),
|
||||
cfg.options...,
|
||||
|
|
43
internal/port/internal/apimodel/server_snapshot.go
Normal file
43
internal/port/internal/apimodel/server_snapshot.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package apimodel
|
||||
|
||||
import (
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
oapitypes "github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
func NewServerSnapshot(withRelations domain.ServerSnapshotWithRelations) ServerSnapshot {
|
||||
ss := withRelations.ServerSnapshot()
|
||||
return ServerSnapshot{
|
||||
Date: oapitypes.Date{Time: ss.Date()},
|
||||
Id: ss.ID(),
|
||||
NumActivePlayers: ss.NumActivePlayers(),
|
||||
NumActiveTribes: ss.NumActiveTribes(),
|
||||
NumBarbarianVillages: ss.NumBarbarianVillages(),
|
||||
NumBonusVillages: ss.NumBonusVillages(),
|
||||
NumInactivePlayers: ss.NumInactivePlayers(),
|
||||
NumInactiveTribes: ss.NumInactiveTribes(),
|
||||
NumPlayerVillages: ss.NumPlayerVillages(),
|
||||
NumPlayers: ss.NumPlayers(),
|
||||
NumTribes: ss.NumTribes(),
|
||||
NumVillages: ss.NumVillages(),
|
||||
Server: NewServerMeta(withRelations.Server()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewListServerSnapshotsResponse(res domain.ListServerSnapshotsWithRelationsResult) ListServerSnapshotsResponse {
|
||||
sss := res.ServerSnapshots()
|
||||
|
||||
resp := ListServerSnapshotsResponse{
|
||||
Data: make([]ServerSnapshot, 0, len(sss)),
|
||||
Cursor: Cursor{
|
||||
Next: res.Next().Encode(),
|
||||
Self: res.Self().Encode(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, ss := range sss {
|
||||
resp.Data = append(resp.Data, NewServerSnapshot(ss))
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
58
internal/port/testdata/api/fixture.yml
vendored
58
internal/port/testdata/api/fixture.yml
vendored
|
@ -7393,6 +7393,64 @@
|
|||
new_tribe_id: 2
|
||||
server_key: pl169
|
||||
created_at: 2021-09-10T20:01:11.000Z
|
||||
- model: ServerSnapshot
|
||||
rows:
|
||||
- id: 10000
|
||||
server_key: de188
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-01T05:15:25.154992Z
|
||||
created_at: 2024-05-01T05:15:25.154994Z
|
||||
- id: 10001
|
||||
server_key: de188
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-02T05:15:25.154992Z
|
||||
created_at: 2024-05-02T05:15:25.154994Z
|
||||
- id: 20000
|
||||
server_key: it70
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-03T05:15:25.154992Z
|
||||
created_at: 2024-05-03T05:15:25.154994Z
|
||||
- id: 20001
|
||||
server_key: it70
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-04T05:15:25.154992Z
|
||||
created_at: 2024-05-04T05:15:25.154994Z
|
||||
- model: TribeSnapshot
|
||||
rows:
|
||||
- rank_att: 1
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
url: https://pl169.plemiona.pl
|
||||
open: true
|
||||
special: false
|
||||
num_players: 10000
|
||||
num_active_players: 2001
|
||||
num_inactive_players: 7999
|
||||
num_tribes: 500
|
||||
num_active_tribes: 214
|
||||
num_inactive_tribes: 286
|
||||
num_villages: 49074
|
||||
num_player_villages: 48500
|
||||
num_barbarian_villages: 1574
|
||||
|
|
|
@ -39,8 +39,6 @@ spec:
|
|||
key: rabbitmq-connection-string
|
||||
- name: AUTO_MAX_PROCS
|
||||
value: "true"
|
||||
- name: API_OPENAPI_SERVERS
|
||||
value: https://twhelp.app,https://tribalwarshelp.com
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
|
|
|
@ -29,3 +29,5 @@ spec:
|
|||
key: rabbitmq-connection-string
|
||||
- name: AUTO_MAX_PROCS
|
||||
value: "true"
|
||||
- name: API_OPENAPI_SERVERS
|
||||
value: https://twhelp.app,https://tribalwarshelp.com
|
||||
|
|
Loading…
Reference in New Issue
Block a user