refactor: player snapshot - cursor pagination
This commit is contained in:
parent
b6e46dee35
commit
41cb1d042e
|
@ -59,17 +59,22 @@ func (repo *PlayerSnapshotBunRepository) Create(
|
|||
func (repo *PlayerSnapshotBunRepository) List(
|
||||
ctx context.Context,
|
||||
params domain.ListPlayerSnapshotsParams,
|
||||
) (domain.PlayerSnapshots, error) {
|
||||
) (domain.ListPlayerSnapshotsResult, error) {
|
||||
var playerSnapshots bunmodel.PlayerSnapshots
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&playerSnapshots).
|
||||
Apply(listPlayerSnapshotsParamsApplier{params: params}.apply).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("couldn't select player snapshots from the db: %w", err)
|
||||
return domain.ListPlayerSnapshotsResult{}, fmt.Errorf("couldn't select player snapshots from the db: %w", err)
|
||||
}
|
||||
|
||||
return playerSnapshots.ToDomain()
|
||||
converted, err := playerSnapshots.ToDomain()
|
||||
if err != nil {
|
||||
return domain.ListPlayerSnapshotsResult{}, err
|
||||
}
|
||||
|
||||
return domain.NewListPlayerSnapshotsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
type listPlayerSnapshotsParamsApplier struct {
|
||||
|
@ -81,6 +86,10 @@ func (a listPlayerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQ
|
|||
q = q.Where("ps.server_key IN (?)", bun.In(serverKeys))
|
||||
}
|
||||
|
||||
if playerIDs := a.params.PlayerIDs(); len(playerIDs) > 0 {
|
||||
q = q.Where("ps.player_id IN (?)", bun.In(playerIDs))
|
||||
}
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
column, dir, err := a.sortToColumnAndDirection(s)
|
||||
if err != nil {
|
||||
|
@ -90,7 +99,49 @@ func (a listPlayerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQ
|
|||
q.OrderExpr("? ?", column, dir.Bun())
|
||||
}
|
||||
|
||||
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
|
||||
}
|
||||
|
||||
func (a listPlayerSnapshotsParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
cursor := a.params.Cursor()
|
||||
|
||||
if cursor.IsZero() {
|
||||
return q
|
||||
}
|
||||
|
||||
sort := a.params.Sort()
|
||||
cursorApplier := cursorPaginationApplier{
|
||||
data: make([]cursorPaginationApplierDataElement, 0, len(sort)),
|
||||
}
|
||||
|
||||
for _, s := range sort {
|
||||
var err error
|
||||
var el cursorPaginationApplierDataElement
|
||||
|
||||
el.column, el.direction, err = a.sortToColumnAndDirection(s)
|
||||
if err != nil {
|
||||
return q.Err(err)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case domain.PlayerSnapshotSortIDASC,
|
||||
domain.PlayerSnapshotSortIDDESC:
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
case domain.PlayerSnapshotSortServerKeyASC,
|
||||
domain.PlayerSnapshotSortServerKeyDESC:
|
||||
el.value = cursor.ServerKey()
|
||||
case domain.PlayerSnapshotSortDateASC,
|
||||
domain.PlayerSnapshotSortDateDESC:
|
||||
el.value = cursor.Date()
|
||||
default:
|
||||
return q.Err(fmt.Errorf("%s: %w", s.String(), errInvalidSortValue))
|
||||
}
|
||||
|
||||
cursorApplier.data = append(cursorApplier.data, el)
|
||||
}
|
||||
|
||||
return q.Apply(cursorApplier.apply)
|
||||
}
|
||||
|
||||
func (a listPlayerSnapshotsParamsApplier) sortToColumnAndDirection(
|
||||
|
|
|
@ -30,7 +30,8 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
|
||||
require.NotEmpty(t, params)
|
||||
|
||||
playerSnapshots, err := repos.playerSnapshot.List(ctx, domain.NewListPlayerSnapshotsParams())
|
||||
res, err := repos.playerSnapshot.List(ctx, domain.NewListPlayerSnapshotsParams())
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
require.NoError(t, err)
|
||||
for i, p := range params {
|
||||
date := p.Date().Format(dateFormat)
|
||||
|
@ -60,8 +61,9 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
|
||||
require.NotEmpty(t, params)
|
||||
|
||||
playerSnapshots, err := repos.playerSnapshot.List(ctx, domain.NewListPlayerSnapshotsParams())
|
||||
res, err := repos.playerSnapshot.List(ctx, domain.NewListPlayerSnapshotsParams())
|
||||
require.NoError(t, err)
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
|
||||
m := make(map[string][]int)
|
||||
|
||||
|
@ -119,18 +121,13 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
|
||||
repos := newRepos(t)
|
||||
|
||||
playerSnapshots, listPlayerSnapshotsErr := repos.playerSnapshot.List(ctx, domain.NewListPlayerSnapshotsParams())
|
||||
require.NoError(t, listPlayerSnapshotsErr)
|
||||
require.NotEmpty(t, playerSnapshots)
|
||||
randPlayerSnapshot := playerSnapshots[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListPlayerSnapshotsParams
|
||||
assertPlayerSnapshots func(
|
||||
name string
|
||||
params func(t *testing.T) domain.ListPlayerSnapshotsParams
|
||||
assertResult func(
|
||||
t *testing.T,
|
||||
params domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
)
|
||||
assertError func(t *testing.T, err error)
|
||||
}{
|
||||
|
@ -140,12 +137,13 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
t.Helper()
|
||||
return domain.NewListPlayerSnapshotsParams()
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotEmpty(t, len(playerSnapshots))
|
||||
assert.True(t, slices.IsSortedFunc(playerSnapshots, func(a, b domain.PlayerSnapshot) int {
|
||||
return cmp.Or(
|
||||
|
@ -154,6 +152,8 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -167,12 +167,13 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
}))
|
||||
return params
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotEmpty(t, len(playerSnapshots))
|
||||
assert.True(t, slices.IsSortedFunc(playerSnapshots, func(a, b domain.PlayerSnapshot) int {
|
||||
return cmp.Or(
|
||||
|
@ -192,12 +193,13 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
}))
|
||||
return params
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotEmpty(t, len(playerSnapshots))
|
||||
assert.True(t, slices.IsSortedFunc(playerSnapshots, func(a, b domain.PlayerSnapshot) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
|
@ -214,12 +216,13 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
}))
|
||||
return params
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotEmpty(t, len(playerSnapshots))
|
||||
assert.True(t, slices.IsSortedFunc(playerSnapshots, func(a, b domain.PlayerSnapshot) int {
|
||||
return cmp.Compare(a.ID(), b.ID()) * -1
|
||||
|
@ -227,43 +230,89 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("OK: serverKeys=[%s]", randPlayerSnapshot.ServerKey()),
|
||||
name: "OK: serverKeys",
|
||||
params: func(t *testing.T) domain.ListPlayerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListPlayerSnapshotsParams()
|
||||
|
||||
res, err := repos.playerSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.PlayerSnapshots())
|
||||
randPlayerSnapshot := res.PlayerSnapshots()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randPlayerSnapshot.ServerKey()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
params domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotZero(t, playerSnapshots)
|
||||
for _, ps := range playerSnapshots {
|
||||
assert.True(t, slices.Contains(serverKeys, ps.ServerKey()))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: offset=1 limit=2",
|
||||
name: "OK: playerIDs serverKeys",
|
||||
params: func(t *testing.T) domain.ListPlayerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListPlayerSnapshotsParams()
|
||||
|
||||
res, err := repos.playerSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.PlayerSnapshots())
|
||||
randPlayerSnapshot := res.PlayerSnapshots()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randPlayerSnapshot.ServerKey()}))
|
||||
require.NoError(t, params.SetPlayerIDs([]int{randPlayerSnapshot.PlayerID()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
params domain.ListPlayerSnapshotsParams,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
playerIDs := params.PlayerIDs()
|
||||
|
||||
playerSnapshots := res.PlayerSnapshots()
|
||||
assert.NotZero(t, playerSnapshots)
|
||||
for _, ps := range playerSnapshots {
|
||||
assert.True(t, slices.Contains(serverKeys, ps.ServerKey()))
|
||||
assert.True(t, slices.Contains(playerIDs, ps.PlayerID()))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=2",
|
||||
params: func(t *testing.T) domain.ListPlayerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListPlayerSnapshotsParams()
|
||||
require.NoError(t, params.SetOffset(1))
|
||||
require.NoError(t, params.SetLimit(2))
|
||||
return params
|
||||
},
|
||||
assertPlayerSnapshots: func(
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
params domain.ListPlayerSnapshotsParams,
|
||||
playerSnapshots domain.PlayerSnapshots,
|
||||
res domain.ListPlayerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
assert.Len(t, playerSnapshots, params.Limit())
|
||||
assert.Len(t, res.PlayerSnapshots(), params.Limit())
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.False(t, res.Next().IsZero())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -284,7 +333,7 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
|
||||
res, err := repos.playerSnapshot.List(ctx, params)
|
||||
assertError(t, err)
|
||||
tt.assertPlayerSnapshots(t, params, res)
|
||||
tt.assertResult(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -70,7 +70,7 @@ type tribeSnapshotRepository interface {
|
|||
|
||||
type playerSnapshotRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreatePlayerSnapshotParams) error
|
||||
List(ctx context.Context, params domain.ListPlayerSnapshotsParams) (domain.PlayerSnapshots, error)
|
||||
List(ctx context.Context, params domain.ListPlayerSnapshotsParams) (domain.ListPlayerSnapshotsResult, error)
|
||||
}
|
||||
|
||||
type repositories struct {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type PlayerSnapshotCursorConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
func NewPlayerSnapshotCursor(tb TestingTB, opts ...func(cfg *PlayerSnapshotCursorConfig)) domain.PlayerSnapshotCursor {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &PlayerSnapshotCursorConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
Date: gofakeit.Date(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
psc, err := domain.NewPlayerSnapshotCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.Date,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return psc
|
||||
}
|
||||
|
||||
type PlayerSnapshotConfig struct {
|
||||
ID int
|
||||
PlayerID int
|
||||
ServerKey string
|
||||
NumVillages int
|
||||
Points int
|
||||
Rank int
|
||||
TribeID int
|
||||
OD domain.OpponentsDefeated
|
||||
Date time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func NewPlayerSnapshot(tb TestingTB, opts ...func(cfg *PlayerSnapshotConfig)) domain.PlayerSnapshot {
|
||||
tb.Helper()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
cfg := &PlayerSnapshotConfig{
|
||||
ID: RandID(),
|
||||
PlayerID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
NumVillages: gofakeit.IntRange(1, 10000),
|
||||
Points: gofakeit.IntRange(1, 10000),
|
||||
Rank: gofakeit.IntRange(1, 10000),
|
||||
TribeID: RandID(),
|
||||
OD: NewOpponentsDefeated(tb),
|
||||
Date: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
ps, err := domain.UnmarshalPlayerSnapshotFromDatabase(
|
||||
cfg.ID,
|
||||
cfg.PlayerID,
|
||||
cfg.ServerKey,
|
||||
cfg.NumVillages,
|
||||
cfg.Points,
|
||||
cfg.Rank,
|
||||
cfg.TribeID,
|
||||
cfg.OD,
|
||||
cfg.Date,
|
||||
cfg.CreatedAt,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return ps
|
||||
}
|
|
@ -115,6 +115,14 @@ func (ps PlayerSnapshot) CreatedAt() time.Time {
|
|||
return ps.createdAt
|
||||
}
|
||||
|
||||
func (ps PlayerSnapshot) ToCursor() (PlayerSnapshotCursor, error) {
|
||||
return NewPlayerSnapshotCursor(ps.id, ps.serverKey, ps.date)
|
||||
}
|
||||
|
||||
func (ps PlayerSnapshot) IsZero() bool {
|
||||
return ps == PlayerSnapshot{}
|
||||
}
|
||||
|
||||
type PlayerSnapshots []PlayerSnapshot
|
||||
|
||||
type CreatePlayerSnapshotParams struct {
|
||||
|
@ -224,16 +232,112 @@ func (s PlayerSnapshotSort) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
const PlayerSnapshotListMaxLimit = 500
|
||||
type PlayerSnapshotCursor struct {
|
||||
id int
|
||||
serverKey string
|
||||
date time.Time
|
||||
}
|
||||
|
||||
const playerSnapshotCursorModelName = "PlayerSnapshotCursor"
|
||||
|
||||
func NewPlayerSnapshotCursor(id int, serverKey string, date time.Time) (PlayerSnapshotCursor, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return PlayerSnapshotCursor{}, ValidationError{
|
||||
Model: playerSnapshotCursorModelName,
|
||||
Field: "id",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateServerKey(serverKey); err != nil {
|
||||
return PlayerSnapshotCursor{}, ValidationError{
|
||||
Model: playerSnapshotCursorModelName,
|
||||
Field: "serverKey",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return PlayerSnapshotCursor{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
date: date,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func decodePlayerSnapshotCursor(encoded string) (PlayerSnapshotCursor, error) {
|
||||
m, err := decodeCursor(encoded)
|
||||
if err != nil {
|
||||
return PlayerSnapshotCursor{}, err
|
||||
}
|
||||
|
||||
id, err := m.int("id")
|
||||
if err != nil {
|
||||
return PlayerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
serverKey, err := m.string("serverKey")
|
||||
if err != nil {
|
||||
return PlayerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
date, err := m.time("date")
|
||||
if err != nil {
|
||||
return PlayerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
psc, err := NewPlayerSnapshotCursor(
|
||||
id,
|
||||
serverKey,
|
||||
date,
|
||||
)
|
||||
if err != nil {
|
||||
return PlayerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
return psc, nil
|
||||
}
|
||||
|
||||
func (psc PlayerSnapshotCursor) ID() int {
|
||||
return psc.id
|
||||
}
|
||||
|
||||
func (psc PlayerSnapshotCursor) ServerKey() string {
|
||||
return psc.serverKey
|
||||
}
|
||||
|
||||
func (psc PlayerSnapshotCursor) Date() time.Time {
|
||||
return psc.date
|
||||
}
|
||||
|
||||
func (psc PlayerSnapshotCursor) IsZero() bool {
|
||||
return psc == PlayerSnapshotCursor{}
|
||||
}
|
||||
|
||||
func (psc PlayerSnapshotCursor) Encode() string {
|
||||
if psc.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"id", psc.id},
|
||||
{"serverKey", psc.serverKey},
|
||||
{"date", psc.date},
|
||||
})
|
||||
}
|
||||
|
||||
type ListPlayerSnapshotsParams struct {
|
||||
serverKeys []string
|
||||
playerIDs []int
|
||||
sort []PlayerSnapshotSort
|
||||
cursor PlayerSnapshotCursor
|
||||
limit int
|
||||
offset int
|
||||
}
|
||||
|
||||
const listPlayerSnapshotsParamsModelName = "ListPlayerSnapshotsParams"
|
||||
const (
|
||||
PlayerSnapshotListMaxLimit = 500
|
||||
listPlayerSnapshotsParamsModelName = "ListPlayerSnapshotsParams"
|
||||
)
|
||||
|
||||
func NewListPlayerSnapshotsParams() ListPlayerSnapshotsParams {
|
||||
return ListPlayerSnapshotsParams{
|
||||
|
@ -267,6 +371,27 @@ func (params *ListPlayerSnapshotsParams) SetServerKeys(serverKeys []string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) PlayerIDs() []int {
|
||||
return params.playerIDs
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) SetPlayerIDs(playerIDs []int) error {
|
||||
for i, id := range playerIDs {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listPlayerSnapshotsParamsModelName,
|
||||
Field: "playerIDs",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.playerIDs = playerIDs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) Sort() []PlayerSnapshotSort {
|
||||
return params.sort
|
||||
}
|
||||
|
@ -290,6 +415,30 @@ func (params *ListPlayerSnapshotsParams) SetSort(sort []PlayerSnapshotSort) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) Cursor() PlayerSnapshotCursor {
|
||||
return params.cursor
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) SetCursor(cursor PlayerSnapshotCursor) error {
|
||||
params.cursor = cursor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) SetEncodedCursor(encoded string) error {
|
||||
decoded, err := decodePlayerSnapshotCursor(encoded)
|
||||
if err != nil {
|
||||
return ValidationError{
|
||||
Model: listPlayerSnapshotsParamsModelName,
|
||||
Field: "cursor",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.cursor = decoded
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
@ -308,20 +457,56 @@ func (params *ListPlayerSnapshotsParams) SetLimit(limit int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) Offset() int {
|
||||
return params.offset
|
||||
type ListPlayerSnapshotsResult struct {
|
||||
snapshots PlayerSnapshots
|
||||
self PlayerSnapshotCursor
|
||||
next PlayerSnapshotCursor
|
||||
}
|
||||
|
||||
func (params *ListPlayerSnapshotsParams) SetOffset(offset int) error {
|
||||
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
||||
return ValidationError{
|
||||
Model: listPlayerSnapshotsParamsModelName,
|
||||
Field: "offset",
|
||||
Err: err,
|
||||
const listPlayerSnapshotsResultModelName = "ListPlayerSnapshotsResult"
|
||||
|
||||
func NewListPlayerSnapshotsResult(
|
||||
snapshots PlayerSnapshots,
|
||||
next PlayerSnapshot,
|
||||
) (ListPlayerSnapshotsResult, error) {
|
||||
var err error
|
||||
res := ListPlayerSnapshotsResult{
|
||||
snapshots: snapshots,
|
||||
}
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
res.self, err = snapshots[0].ToCursor()
|
||||
if err != nil {
|
||||
return ListPlayerSnapshotsResult{}, ValidationError{
|
||||
Model: listPlayerSnapshotsResultModelName,
|
||||
Field: "self",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.offset = offset
|
||||
if !next.IsZero() {
|
||||
res.next, err = next.ToCursor()
|
||||
if err != nil {
|
||||
return ListPlayerSnapshotsResult{}, ValidationError{
|
||||
Model: listPlayerSnapshotsResultModelName,
|
||||
Field: "next",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (res ListPlayerSnapshotsResult) PlayerSnapshots() PlayerSnapshots {
|
||||
return res.snapshots
|
||||
}
|
||||
|
||||
func (res ListPlayerSnapshotsResult) Self() PlayerSnapshotCursor {
|
||||
return res.self
|
||||
}
|
||||
|
||||
func (res ListPlayerSnapshotsResult) Next() PlayerSnapshotCursor {
|
||||
return res.next
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -109,6 +110,87 @@ func TestPlayerSnapshotSort_IsInConflict(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewPlayerSnapshotCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validPlayerSnapshotCursor := domaintest.NewPlayerSnapshotCursor(t)
|
||||
|
||||
type args struct {
|
||||
id int
|
||||
serverKey string
|
||||
date time.Time
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
id: validPlayerSnapshotCursor.ID(),
|
||||
serverKey: validPlayerSnapshotCursor.ServerKey(),
|
||||
date: validPlayerSnapshotCursor.Date(),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "ERR: id < 1",
|
||||
args: args{
|
||||
id: 0,
|
||||
serverKey: validPlayerSnapshotCursor.ServerKey(),
|
||||
date: validPlayerSnapshotCursor.Date(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "PlayerSnapshotCursor",
|
||||
Field: "id",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
id: validPlayerSnapshotCursor.ID(),
|
||||
serverKey: serverKeyTest.key,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "PlayerSnapshotCursor",
|
||||
Field: "serverKey",
|
||||
Err: serverKeyTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
psc, err := domain.NewPlayerSnapshotCursor(
|
||||
tt.args.id,
|
||||
tt.args.serverKey,
|
||||
tt.args.date,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.id, psc.ID())
|
||||
assert.Equal(t, tt.args.serverKey, psc.ServerKey())
|
||||
assert.Equal(t, tt.args.date, psc.Date())
|
||||
assert.NotEmpty(t, psc.Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPlayerSnapshotsParams_SetServerKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -252,6 +334,86 @@ func TestListPlayerSnapshotsParams_SetSort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListPlayerSnapshotsParams_SetEncodedCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validCursor := domaintest.NewPlayerSnapshotCursor(t)
|
||||
|
||||
type args struct {
|
||||
cursor string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedCursor domain.PlayerSnapshotCursor
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
cursor: validCursor.Encode(),
|
||||
},
|
||||
expectedCursor: validCursor,
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) < 1",
|
||||
args: args{
|
||||
cursor: "",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayerSnapshotsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1000,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) > 1000",
|
||||
args: args{
|
||||
cursor: gofakeit.LetterN(1001),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayerSnapshotsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1000,
|
||||
Current: 1001,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: malformed base64",
|
||||
args: args{
|
||||
cursor: "112345",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayerSnapshotsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.ErrInvalidCursor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListPlayerSnapshotsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetEncodedCursor(tt.args.cursor), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPlayerSnapshotsParams_SetLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -315,51 +477,49 @@ func TestListPlayerSnapshotsParams_SetLimit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListPlayerSnapshotsParams_SetOffset(t *testing.T) {
|
||||
func TestNewListPlayerSnapshotsResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
offset int
|
||||
snapshots := domain.PlayerSnapshots{
|
||||
domaintest.NewPlayerSnapshot(t),
|
||||
domaintest.NewPlayerSnapshot(t),
|
||||
domaintest.NewPlayerSnapshot(t),
|
||||
}
|
||||
next := domaintest.NewPlayerSnapshot(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
offset: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: offset < 0",
|
||||
args: args{
|
||||
offset: -1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListPlayerSnapshotsParams",
|
||||
Field: "offset",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("OK: with next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := domain.NewListPlayerSnapshotsResult(snapshots, next)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.PlayerSnapshots())
|
||||
assert.Equal(t, snapshots[0].ID(), res.Self().ID())
|
||||
assert.Equal(t, snapshots[0].ServerKey(), res.Self().ServerKey())
|
||||
assert.Equal(t, snapshots[0].Date(), res.Self().Date())
|
||||
assert.Equal(t, next.ID(), res.Next().ID())
|
||||
assert.Equal(t, next.ServerKey(), res.Next().ServerKey())
|
||||
assert.Equal(t, next.Date(), res.Next().Date())
|
||||
})
|
||||
|
||||
params := domain.NewListPlayerSnapshotsParams()
|
||||
t.Run("OK: without next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.offset, params.Offset())
|
||||
})
|
||||
}
|
||||
res, err := domain.NewListPlayerSnapshotsResult(snapshots, domain.PlayerSnapshot{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.PlayerSnapshots())
|
||||
assert.Equal(t, snapshots[0].ID(), res.Self().ID())
|
||||
assert.Equal(t, snapshots[0].ServerKey(), res.Self().ServerKey())
|
||||
assert.Equal(t, snapshots[0].Date(), res.Self().Date())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
|
||||
t.Run("OK: 0 snapshots", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListPlayerSnapshotsResult(nil, domain.PlayerSnapshot{})
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, res.PlayerSnapshots())
|
||||
assert.True(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -316,14 +316,10 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
cnt := 0
|
||||
|
||||
for {
|
||||
snapshots, err := playerSnapshotRepo.List(ctx, listSnapshotsParams)
|
||||
res, err := playerSnapshotRepo.List(ctx, listSnapshotsParams)
|
||||
require.NoError(collect, err)
|
||||
|
||||
if len(snapshots) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, ps := range snapshots {
|
||||
for _, ps := range res.PlayerSnapshots() {
|
||||
cnt++
|
||||
msg := fmt.Sprintf("PlayerID=%d,ServerKey=%s", ps.PlayerID(), ps.ServerKey())
|
||||
|
||||
|
@ -353,7 +349,11 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
assert.WithinDuration(collect, time.Now(), ps.Date(), 24*time.Hour, msg)
|
||||
}
|
||||
|
||||
require.NoError(collect, listSnapshotsParams.SetOffset(listSnapshotsParams.Offset()+listSnapshotsParams.Limit()))
|
||||
if res.Next().IsZero() {
|
||||
break
|
||||
}
|
||||
|
||||
require.NoError(collect, listSnapshotsParams.SetCursor(res.Next()))
|
||||
}
|
||||
|
||||
//nolint:testifylint
|
||||
|
|
Loading…
Reference in New Issue