refactor: player - cursor pagination (#5)
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline failed Details

Reviewed-on: twhelp/corev3#5
This commit is contained in:
Dawid Wysokiński 2024-02-26 06:44:39 +00:00
parent 3fd654d2ce
commit 084bb5aa85
18 changed files with 930 additions and 268 deletions

View File

@ -89,17 +89,25 @@ func (repo *PlayerBunRepository) CreateOrUpdate(ctx context.Context, params ...d
return nil
}
func (repo *PlayerBunRepository) List(ctx context.Context, params domain.ListPlayersParams) (domain.Players, error) {
func (repo *PlayerBunRepository) List(
ctx context.Context,
params domain.ListPlayersParams,
) (domain.ListPlayersResult, error) {
var players bunmodel.Players
if err := repo.db.NewSelect().
Model(&players).
Apply(listPlayersParamsApplier{params: params}.apply).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("couldn't select players from the db: %w", err)
return domain.ListPlayersResult{}, fmt.Errorf("couldn't select players from the db: %w", err)
}
return players.ToDomain()
converted, err := players.ToDomain()
if err != nil {
return domain.ListPlayersResult{}, err
}
return domain.NewListPlayersResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *PlayerBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
@ -132,10 +140,6 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
q = q.Where("player.id IN (?)", bun.In(ids))
}
if idGT := a.params.IDGT(); idGT.Valid {
q = q.Where("player.id > ?", idGT.Value)
}
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("player.server_key IN (?)", bun.In(serverKeys))
}
@ -163,5 +167,90 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
}
}
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
}
//nolint:gocyclo
func (a listPlayersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuery {
if a.params.Cursor().IsZero() {
return q
}
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
cursor := a.params.Cursor()
cursorID := cursor.ID()
cursorServerKey := cursor.ServerKey()
sort := a.params.Sort()
sortLen := len(sort)
// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245
switch {
case sortLen == 1:
switch sort[0] {
case domain.PlayerSortIDASC:
q = q.Where("player.id >= ?", cursorID)
case domain.PlayerSortIDDESC:
q = q.Where("player.id <= ?", cursorID)
case domain.PlayerSortServerKeyASC,
domain.PlayerSortServerKeyDESC:
return q.Err(errSortNoUniqueField)
default:
return q.Err(errUnsupportedSortValue)
}
case sortLen > 1:
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
for i := 0; i < sortLen; i++ {
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
current := sort[i]
for j := 0; j < i; j++ {
s := sort[j]
switch s {
case domain.PlayerSortIDASC,
domain.PlayerSortIDDESC:
q = q.Where("player.id = ?", cursorID)
case domain.PlayerSortServerKeyASC,
domain.PlayerSortServerKeyDESC:
q = q.Where("player.server_key = ?", cursorServerKey)
default:
return q.Err(errUnsupportedSortValue)
}
}
greaterSymbol := bun.Safe(">")
lessSymbol := bun.Safe("<")
if i == sortLen-1 {
greaterSymbol = ">="
lessSymbol = "<="
}
switch current {
case domain.PlayerSortIDASC:
q = q.Where("player.id ? ?", greaterSymbol, cursorID)
case domain.PlayerSortIDDESC:
q = q.Where("player.id ? ?", lessSymbol, cursorID)
case domain.PlayerSortServerKeyASC:
q = q.Where("player.server_key ? ?", greaterSymbol, cursorServerKey)
case domain.PlayerSortServerKeyDESC:
q = q.Where("player.server_key ? ?", lessSymbol, cursorServerKey)
default:
return q.Err(errUnsupportedSortValue)
}
return q
})
}
return q
})
}
return q
})
return q
}

View File

@ -116,7 +116,7 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -90,8 +90,9 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
}))
require.NoError(t, listPlayersParams.SetLimit(domain.PlayerSnapshotListMaxLimit/2))
players, err := repos.player.List(ctx, listPlayersParams)
res, err := repos.player.List(ctx, listPlayersParams)
require.NoError(t, err)
players := res.Players()
require.NotEmpty(t, players)
date := time.Now()
@ -113,7 +114,7 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -3,7 +3,6 @@ package adapter_test
import (
"cmp"
"context"
"fmt"
"math"
"slices"
"testing"
@ -39,8 +38,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
require.NoError(t, listParams.SetIDs(ids))
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
players, err := repos.player.List(ctx, listParams)
res, err := repos.player.List(ctx, listParams)
require.NoError(t, err)
players := res.Players()
assert.Len(t, players, len(params))
for i, p := range params {
idx := slices.IndexFunc(players, func(player domain.Player) bool {
@ -100,22 +100,16 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
players, listPlayersErr := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, listPlayersErr)
require.NotEmpty(t, players)
randPlayer := players[0]
tests := []struct {
name string
params func(t *testing.T) domain.ListPlayersParams
assertPlayers func(t *testing.T, params domain.ListPlayersParams, players domain.Players)
assertError func(t *testing.T, err error)
assertTotal func(t *testing.T, params domain.ListPlayersParams, total int)
name string
params func(t *testing.T) domain.ListPlayersParams
assertResult func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
@ -123,8 +117,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
t.Helper()
return domain.NewListPlayersParams()
},
assertPlayers: func(t *testing.T, _ domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, len(players))
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
@ -132,15 +127,13 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
cmp.Compare(a.ID(), b.ID()),
)
}))
assert.False(t, res.Self().IsZero())
assert.False(t, res.Next().IsZero())
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: sort=[serverKey DESC, id DESC]",
@ -150,8 +143,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
require.NoError(t, params.SetSort([]domain.PlayerSort{domain.PlayerSortServerKeyDESC, domain.PlayerSortIDDESC}))
return params
},
assertPlayers: func(t *testing.T, _ domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, len(players))
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
@ -164,26 +158,32 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: fmt.Sprintf("OK: ids=[%d] serverKeys=[%s]", randPlayer.ID(), randPlayer.ServerKey()),
name: "OK: ids serverKeys",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
res, err := repos.player.List(ctx, params)
require.NoError(t, err)
require.NotEmpty(t, len(res.Players()))
randPlayer := res.Players()[0]
require.NoError(t, params.SetIDs([]int{randPlayer.ID()}))
require.NoError(t, params.SetServerKeys([]string{randPlayer.ServerKey()}))
return params
},
assertPlayers: func(t *testing.T, params domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
ids := params.IDs()
serverKeys := params.ServerKeys()
players := res.Players()
assert.NotEmpty(t, players)
for _, p := range players {
assert.True(t, slices.Contains(ids, p.ID()))
assert.True(t, slices.Contains(serverKeys, p.ServerKey()))
@ -193,37 +193,6 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: fmt.Sprintf("OK: idGT=%d", randPlayer.ID()),
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetIDGT(domain.NullInt{
Value: randPlayer.ID(),
Valid: true,
}))
return params
},
assertPlayers: func(t *testing.T, params domain.ListPlayersParams, players domain.Players) {
t.Helper()
assert.NotEmpty(t, players)
for _, p := range players {
assert.Greater(t, p.ID(), params.IDGT().Value, p.ID())
}
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: deleted=true",
@ -236,8 +205,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}))
return params
},
assertPlayers: func(t *testing.T, _ domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
for _, s := range players {
assert.True(t, s.IsDeleted())
@ -247,10 +217,6 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: deleted=false",
@ -263,8 +229,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}))
return params
},
assertPlayers: func(t *testing.T, _ domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, _ domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, players)
for _, s := range players {
assert.False(t, s.IsDeleted())
@ -274,31 +241,145 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: offset=1 limit=2",
name: "OK: cursor serverKeys sort=[id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetOffset(1))
require.NoError(t, params.SetLimit(2))
res, err := repos.player.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.Players()), 2)
require.NoError(t, params.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}))
require.NoError(t, params.SetServerKeys([]string{res.Players()[1].ServerKey()}))
require.NoError(t, params.SetCursor(domaintest.NewPlayerCursor(t, func(cfg *domaintest.PlayerCursorConfig) {
cfg.ID = res.Players()[1].ID()
})))
return params
},
assertPlayers: func(t *testing.T, params domain.ListPlayersParams, players domain.Players) {
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
assert.Len(t, players, params.Limit())
serverKeys := params.ServerKeys()
players := res.Players()
assert.NotEmpty(t, len(players))
for _, p := range res.Players() {
assert.GreaterOrEqual(t, p.ID(), params.Cursor().ID())
assert.True(t, slices.Contains(serverKeys, p.ServerKey()))
}
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Compare(a.ID(), b.ID())
}))
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, _ domain.ListPlayersParams, total int) {
},
{
name: "OK: cursor sort=[serverKey ASC, id ASC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
assert.NotEmpty(t, total)
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortServerKeyASC,
domain.PlayerSortIDASC,
}))
res, err := repos.player.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.Players()), 2)
require.NoError(t, params.SetCursor(domaintest.NewPlayerCursor(t, func(cfg *domaintest.PlayerCursorConfig) {
cfg.ID = res.Players()[1].ID()
cfg.ServerKey = res.Players()[1].ServerKey()
})))
return params
},
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, len(players))
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
)
}))
assert.GreaterOrEqual(t, players[0].ID(), params.Cursor().ID())
for _, p := range res.Players() {
assert.GreaterOrEqual(t, p.ServerKey(), params.Cursor().ServerKey())
}
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
},
{
name: "OK: cursor sort=[serverKey DESC, id DESC]",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetSort([]domain.PlayerSort{
domain.PlayerSortServerKeyDESC,
domain.PlayerSortIDDESC,
}))
res, err := repos.player.List(ctx, params)
require.NoError(t, err)
require.Greater(t, len(res.Players()), 2)
require.NoError(t, params.SetCursor(domaintest.NewPlayerCursor(t, func(cfg *domaintest.PlayerCursorConfig) {
cfg.ID = res.Players()[1].ID()
cfg.ServerKey = res.Players()[1].ServerKey()
})))
return params
},
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
players := res.Players()
assert.NotEmpty(t, len(players))
assert.True(t, slices.IsSortedFunc(players, func(a, b domain.Player) int {
return cmp.Or(
cmp.Compare(a.ServerKey(), b.ServerKey()),
cmp.Compare(a.ID(), b.ID()),
) * -1
}))
assert.LessOrEqual(t, players[0].ID(), params.Cursor().ID())
for _, p := range res.Players() {
assert.LessOrEqual(t, p.ServerKey(), params.Cursor().ServerKey())
}
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
},
{
name: "OK: limit=2",
params: func(t *testing.T) domain.ListPlayersParams {
t.Helper()
params := domain.NewListPlayersParams()
require.NoError(t, params.SetLimit(2))
return params
},
assertResult: func(t *testing.T, params domain.ListPlayersParams, res domain.ListPlayersResult) {
t.Helper()
assert.Len(t, res.Players(), params.Limit())
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
},
}
@ -311,7 +392,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
res, err := repos.player.List(ctx, params)
tt.assertError(t, err)
tt.assertPlayers(t, params, res)
tt.assertResult(t, params, res)
})
}
})
@ -339,8 +420,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
require.NoError(t, listPlayersParams.SetDeleted(domain.NullBool{Value: false, Valid: true}))
require.NoError(t, listPlayersParams.SetServerKeys(serverKeys))
players, err := repos.player.List(ctx, listPlayersParams)
res, err := repos.player.List(ctx, listPlayersParams)
require.NoError(t, err)
players := res.Players()
var serverKey string
var ids []int
@ -363,9 +445,9 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
require.NoError(t, listPlayersParams.SetDeleted(domain.NullBool{Valid: false}))
require.NoError(t, listPlayersParams.SetServerKeys(serverKeys))
players, err = repos.player.List(ctx, listPlayersParams)
res, err = repos.player.List(ctx, listPlayersParams)
require.NoError(t, err)
for _, p := range players {
for _, p := range res.Players() {
if p.ServerKey() == serverKey && slices.Contains(ids, p.ID()) {
if slices.Contains(idsToDelete, p.ID()) {
assert.WithinDuration(t, time.Now(), p.DeletedAt(), time.Minute)

View File

@ -30,7 +30,7 @@ type tribeRepository interface {
type playerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.Players, error)
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
Delete(ctx context.Context, serverKey string, ids ...int) error
}

View File

@ -84,8 +84,9 @@ func testTribeChangeRepository(t *testing.T, newRepos func(t *testing.T) reposit
t.Run("OK", func(t *testing.T) {
t.Parallel()
players, err := repos.player.List(ctx, domain.NewListPlayersParams())
res, err := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, err)
players := res.Players()
require.NotEmpty(t, players)
player := players[0]
@ -133,7 +134,7 @@ func testTribeChangeRepository(t *testing.T, newRepos func(t *testing.T) reposit
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -115,7 +115,7 @@ func testTribeSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repos
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -163,7 +163,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
}
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -92,7 +92,7 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Run("List", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)

View File

@ -9,7 +9,7 @@ import (
type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.Players, error)
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
// In addition, Delete sets TribeID to null.
//
@ -104,10 +104,11 @@ func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey str
return err
}
storedPlayers, err := svc.repo.List(ctx, listParams)
res, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
storedPlayers := res.Players()
createParams, err := domain.NewCreatePlayerParams(serverKey, players, storedPlayers)
if err != nil {
@ -149,16 +150,12 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
var tribeChangesParams []domain.CreateTribeChangeParams
for {
storedPlayers, err := svc.repo.List(ctx, listParams)
res, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
if len(storedPlayers) == 0 {
break
}
ids, params, err := storedPlayers.Delete(serverKey, players)
ids, params, err := res.Players().Delete(serverKey, players)
if err != nil {
return err
}
@ -166,10 +163,11 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
toDelete = append(toDelete, ids...)
tribeChangesParams = append(tribeChangesParams, params...)
if err = listParams.SetIDGT(domain.NullInt{
Value: storedPlayers[len(storedPlayers)-1].ID(),
Valid: true,
}); err != nil {
if res.Next().IsZero() {
break
}
if err = listParams.SetCursor(res.Next()); err != nil {
return err
}
}
@ -181,6 +179,6 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
}
func (svc *PlayerService) List(ctx context.Context, params domain.ListPlayersParams) (domain.Players, error) {
func (svc *PlayerService) List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error) {
return svc.repo.List(ctx, params)
}

View File

@ -53,10 +53,11 @@ func (svc *PlayerSnapshotService) Create(
}
for {
players, err := svc.playerSvc.List(ctx, listPlayersParams)
res, err := svc.playerSvc.List(ctx, listPlayersParams)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
players := res.Players()
if len(players) == 0 {
break
@ -71,10 +72,11 @@ func (svc *PlayerSnapshotService) Create(
return fmt.Errorf("%s: %w", serverKey, err)
}
if err = listPlayersParams.SetIDGT(domain.NullInt{
Value: players[len(players)-1].ID(),
Valid: true,
}); err != nil {
if res.Next().IsZero() {
break
}
if err = listPlayersParams.SetCursor(res.Next()); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
}

View File

@ -1,6 +1,7 @@
package domaintest
import (
"math"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
@ -8,6 +9,47 @@ import (
"github.com/stretchr/testify/require"
)
type PlayerCursorConfig struct {
ID int
ServerKey string
ODScoreAtt int
ODScoreDef int
ODScoreTotal int
Points int
DeletedAt time.Time
}
func NewPlayerCursor(tb TestingTB, opts ...func(cfg *PlayerCursorConfig)) domain.PlayerCursor {
tb.Helper()
cfg := &PlayerCursorConfig{
ID: RandID(),
ServerKey: RandServerKey(),
ODScoreAtt: gofakeit.IntRange(0, math.MaxInt),
ODScoreDef: gofakeit.IntRange(0, math.MaxInt),
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
Points: gofakeit.IntRange(0, math.MaxInt),
DeletedAt: time.Time{},
}
for _, opt := range opts {
opt(cfg)
}
pc, err := domain.NewPlayerCursor(
cfg.ID,
cfg.ServerKey,
cfg.ODScoreAtt,
cfg.ODScoreDef,
cfg.ODScoreTotal,
cfg.Points,
cfg.DeletedAt,
)
require.NoError(tb, err)
return pc
}
type PlayerConfig struct {
ID int
Points int

View File

@ -373,14 +373,199 @@ const (
PlayerSortServerKeyDESC
)
type PlayerCursor struct {
id int
serverKey string
odScoreAtt int
odScoreDef int
odScoreTotal int
points int
deletedAt time.Time
}
const playerCursorModelName = "PlayerCursor"
func NewPlayerCursor(
id int,
serverKey string,
odScoreAtt int,
odScoreDef int,
odScoreTotal int,
points int,
deletedAt time.Time,
) (PlayerCursor, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "serverKey",
Err: err,
}
}
if err := validateIntInRange(odScoreAtt, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreAtt",
Err: err,
}
}
if err := validateIntInRange(odScoreDef, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreDef",
Err: err,
}
}
if err := validateIntInRange(odScoreTotal, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "odScoreTotal",
Err: err,
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
return PlayerCursor{}, ValidationError{
Model: playerCursorModelName,
Field: "points",
Err: err,
}
}
return PlayerCursor{
id: id,
serverKey: serverKey,
odScoreAtt: odScoreAtt,
odScoreDef: odScoreDef,
odScoreTotal: odScoreTotal,
points: points,
deletedAt: deletedAt,
}, nil
}
//nolint:gocyclo
func decodePlayerCursor(encoded string) (PlayerCursor, error) {
m, err := decodeCursor(encoded)
if err != nil {
return PlayerCursor{}, err
}
id, err := m.int("id")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
serverKey, err := m.string("serverKey")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
odScoreAtt, err := m.int("odScoreAtt")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
odScoreDef, err := m.int("odScoreDef")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
odScoreTotal, err := m.int("odScoreTotal")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
points, err := m.int("points")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
deletedAt, err := m.time("deletedAt")
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
pc, err := NewPlayerCursor(
id,
serverKey,
odScoreAtt,
odScoreDef,
odScoreTotal,
points,
deletedAt,
)
if err != nil {
return PlayerCursor{}, ErrInvalidCursor
}
return pc, nil
}
func (pc PlayerCursor) ID() int {
return pc.id
}
func (pc PlayerCursor) ServerKey() string {
return pc.serverKey
}
func (pc PlayerCursor) ODScoreAtt() int {
return pc.odScoreAtt
}
func (pc PlayerCursor) ODScoreDef() int {
return pc.odScoreDef
}
func (pc PlayerCursor) ODScoreTotal() int {
return pc.odScoreTotal
}
func (pc PlayerCursor) Points() int {
return pc.points
}
func (pc PlayerCursor) DeletedAt() time.Time {
return pc.deletedAt
}
func (pc PlayerCursor) IsZero() bool {
return pc == PlayerCursor{}
}
func (pc PlayerCursor) Encode() string {
if pc.IsZero() {
return ""
}
return encodeCursor([]keyValuePair{
{"id", pc.id},
{"serverKey", pc.serverKey},
{"odScoreAtt", pc.odScoreAtt},
{"odScoreDef", pc.odScoreDef},
{"odScoreTotal", pc.odScoreTotal},
{"points", pc.points},
{"deletedAt", pc.deletedAt},
})
}
type ListPlayersParams struct {
ids []int
idGT NullInt
serverKeys []string
deleted NullBool
sort []PlayerSort
cursor PlayerCursor
limit int
offset int
}
const (
@ -419,26 +604,6 @@ func (params *ListPlayersParams) SetIDs(ids []int) error {
return nil
}
func (params *ListPlayersParams) IDGT() NullInt {
return params.idGT
}
func (params *ListPlayersParams) SetIDGT(idGT NullInt) error {
if idGT.Valid {
if err := validateIntInRange(idGT.Value, 0, math.MaxInt); err != nil {
return ValidationError{
Model: listPlayersParamsModelName,
Field: "idGT",
Err: err,
}
}
}
params.idGT = idGT
return nil
}
func (params *ListPlayersParams) ServerKeys() []string {
return params.serverKeys
}
@ -480,6 +645,30 @@ func (params *ListPlayersParams) SetSort(sort []PlayerSort) error {
return nil
}
func (params *ListPlayersParams) Cursor() PlayerCursor {
return params.cursor
}
func (params *ListPlayersParams) SetCursor(cursor PlayerCursor) error {
params.cursor = cursor
return nil
}
func (params *ListPlayersParams) SetEncodedCursor(encoded string) error {
decoded, err := decodePlayerCursor(encoded)
if err != nil {
return ValidationError{
Model: listPlayersParamsModelName,
Field: "cursor",
Err: err,
}
}
params.cursor = decoded
return nil
}
func (params *ListPlayersParams) Limit() int {
return params.limit
}
@ -498,20 +687,71 @@ func (params *ListPlayersParams) SetLimit(limit int) error {
return nil
}
func (params *ListPlayersParams) Offset() int {
return params.offset
type ListPlayersResult struct {
players Players
self PlayerCursor
next PlayerCursor
}
func (params *ListPlayersParams) SetOffset(offset int) error {
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
return ValidationError{
Model: listPlayersParamsModelName,
Field: "offset",
Err: err,
const listPlayersResultModelName = "ListPlayersResult"
func NewListPlayersResult(players Players, next Player) (ListPlayersResult, error) {
var err error
res := ListPlayersResult{
players: players,
}
if len(players) > 0 {
od := players[0].OD()
res.self, err = NewPlayerCursor(
players[0].ID(),
players[0].ServerKey(),
od.ScoreAtt(),
od.ScoreDef(),
od.ScoreTotal(),
players[0].Points(),
players[0].DeletedAt(),
)
if err != nil {
return ListPlayersResult{}, ValidationError{
Model: listPlayersResultModelName,
Field: "self",
Err: err,
}
}
}
params.offset = offset
if !next.IsZero() {
od := next.OD()
res.next, err = NewPlayerCursor(
next.ID(),
next.ServerKey(),
od.ScoreAtt(),
od.ScoreDef(),
od.ScoreTotal(),
next.Points(),
next.DeletedAt(),
)
if err != nil {
return ListPlayersResult{}, ValidationError{
Model: listPlayersResultModelName,
Field: "next",
Err: err,
}
}
}
return nil
return res, nil
}
func (res ListPlayersResult) Players() Players {
return res.players
}
func (res ListPlayersResult) Self() PlayerCursor {
return res.self
}
func (res ListPlayersResult) Next() PlayerCursor {
return res.next
}

View File

@ -9,6 +9,7 @@ import (
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -227,6 +228,192 @@ func TestNewCreatePlayerParams(t *testing.T) {
}
}
func TestNewPlayerCursor(t *testing.T) {
t.Parallel()
validPlayerCursor := domaintest.NewPlayerCursor(t)
type args struct {
id int
serverKey string
odScoreAtt int
odScoreDef int
odScoreTotal int
points int
deletedAt time.Time
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: nil,
},
{
name: "ERR: id < 1",
args: args{
id: 0,
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "id",
Err: domain.MinGreaterEqualError{
Min: 1,
Current: 0,
},
},
},
{
name: "ERR: odScoreAtt < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: -1,
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreAtt",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: odScoreDef < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: -1,
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreDef",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: odScoreTotal < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: -1,
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "odScoreTotal",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: points < 0",
args: args{
id: validPlayerCursor.ID(),
serverKey: validPlayerCursor.ServerKey(),
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: -1,
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "points",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
id: validPlayerCursor.ID(),
serverKey: serverKeyTest.key,
odScoreAtt: validPlayerCursor.ODScoreAtt(),
odScoreDef: validPlayerCursor.ODScoreDef(),
odScoreTotal: validPlayerCursor.ODScoreTotal(),
points: validPlayerCursor.Points(),
deletedAt: validPlayerCursor.DeletedAt(),
},
expectedErr: domain.ValidationError{
Model: "PlayerCursor",
Field: "serverKey",
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tc, err := domain.NewPlayerCursor(
tt.args.id,
tt.args.serverKey,
tt.args.odScoreAtt,
tt.args.odScoreDef,
tt.args.odScoreTotal,
tt.args.points,
tt.args.deletedAt,
)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.id, tc.ID())
assert.Equal(t, tt.args.serverKey, tc.ServerKey())
assert.Equal(t, tt.args.odScoreAtt, tc.ODScoreAtt())
assert.Equal(t, tt.args.odScoreDef, tc.ODScoreDef())
assert.Equal(t, tt.args.odScoreTotal, tc.ODScoreTotal())
assert.Equal(t, tt.args.points, tc.Points())
assert.Equal(t, tt.args.deletedAt, tc.DeletedAt())
assert.NotEmpty(t, tc.Encode())
})
}
}
func TestListPlayersParams_SetIDs(t *testing.T) {
t.Parallel()
@ -287,61 +474,6 @@ func TestListPlayersParams_SetIDs(t *testing.T) {
}
}
func TestListPlayersParams_SetIDGT(t *testing.T) {
t.Parallel()
type args struct {
idGT domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
idGT: domain.NullInt{
Value: domaintest.RandID(),
Valid: true,
},
},
},
{
name: "ERR: value < 0",
args: args{
idGT: domain.NullInt{
Value: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "ListPlayersParams",
Field: "idGT",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListPlayersParams()
require.ErrorIs(t, params.SetIDGT(tt.args.idGT), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.idGT, params.IDGT())
})
}
}
func TestListPlayersParams_SetSort(t *testing.T) {
t.Parallel()
@ -414,6 +546,87 @@ func TestListPlayersParams_SetSort(t *testing.T) {
}
}
func TestListPlayersParams_SetEncodedCursor(t *testing.T) {
t.Parallel()
validCursor := domaintest.NewPlayerCursor(t)
type args struct {
cursor string
}
tests := []struct {
name string
args args
expectedCursor domain.PlayerCursor
expectedErr error
}{
{
name: "OK",
args: args{
cursor: validCursor.Encode(),
},
expectedCursor: validCursor,
},
{
name: "ERR: len(cursor) < 1",
args: args{
cursor: "",
},
expectedErr: domain.ValidationError{
Model: "ListPlayersParams",
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: "ListPlayersParams",
Field: "cursor",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 1000,
Current: 1001,
},
},
},
{
name: "ERR: malformed base64",
args: args{
cursor: "112345",
},
expectedErr: domain.ValidationError{
Model: "ListPlayersParams",
Field: "cursor",
Err: domain.ErrInvalidCursor,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListPlayersParams()
require.ErrorIs(t, params.SetEncodedCursor(tt.args.cursor), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.expectedCursor, params.Cursor())
assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
})
}
}
func TestListPlayersParams_SetLimit(t *testing.T) {
t.Parallel()
@ -477,51 +690,46 @@ func TestListPlayersParams_SetLimit(t *testing.T) {
}
}
func TestListPlayersParams_SetOffset(t *testing.T) {
func TestNewListPlayersResult(t *testing.T) {
t.Parallel()
type args struct {
offset int
players := domain.Players{
domaintest.NewPlayer(t),
domaintest.NewPlayer(t),
domaintest.NewPlayer(t),
}
next := domaintest.NewPlayer(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: "ListPlayersParams",
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.NewListPlayersResult(players, next)
require.NoError(t, err)
assert.Equal(t, players, res.Players())
assert.Equal(t, players[0].ID(), res.Self().ID())
assert.Equal(t, players[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, next.ID(), res.Next().ID())
assert.Equal(t, next.ServerKey(), res.Next().ServerKey())
})
params := domain.NewListPlayersParams()
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.NewListPlayersResult(players, domain.Player{})
require.NoError(t, err)
assert.Equal(t, players, res.Players())
assert.Equal(t, players[0].ID(), res.Self().ID())
assert.Equal(t, players[0].ServerKey(), res.Self().ServerKey())
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 players", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListPlayersResult(nil, domain.Player{})
require.NoError(t, err)
assert.Zero(t, res.Players())
assert.True(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
})
}

View File

@ -489,7 +489,7 @@ func TestNewListServersResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 versions", func(t *testing.T) {
t.Run("OK: 0 servers", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListServersResult(nil, domain.Server{})

View File

@ -365,6 +365,12 @@ func TestNewTribeCursor(t *testing.T) {
}
assert.Equal(t, tt.args.id, tc.ID())
assert.Equal(t, tt.args.serverKey, tc.ServerKey())
assert.Equal(t, tt.args.odScoreAtt, tc.ODScoreAtt())
assert.Equal(t, tt.args.odScoreDef, tc.ODScoreDef())
assert.Equal(t, tt.args.odScoreTotal, tc.ODScoreTotal())
assert.Equal(t, tt.args.points, tc.Points())
assert.InDelta(t, tt.args.dominance, tc.Dominance(), 0.001)
assert.Equal(t, tt.args.deletedAt, tc.DeletedAt())
assert.NotEmpty(t, tc.Encode())
})
}
@ -835,8 +841,7 @@ func TestListTribesParams_SetEncodedCursor(t *testing.T) {
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.expectedCursor.ID(), params.Cursor().ID())
assert.Equal(t, tt.expectedCursor.ServerKey(), params.Cursor().ServerKey())
assert.Equal(t, tt.expectedCursor, params.Cursor())
assert.Equal(t, tt.args.cursor, params.Cursor().Encode())
})
}
@ -908,7 +913,7 @@ func TestListTribesParams_SetLimit(t *testing.T) {
func TestNewListTribesResult(t *testing.T) {
t.Parallel()
servers := domain.Tribes{
tribes := domain.Tribes{
domaintest.NewTribe(t),
domaintest.NewTribe(t),
domaintest.NewTribe(t),
@ -918,11 +923,11 @@ func TestNewListTribesResult(t *testing.T) {
t.Run("OK: with next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribesResult(servers, next)
res, err := domain.NewListTribesResult(tribes, next)
require.NoError(t, err)
assert.Equal(t, servers, res.Tribes())
assert.Equal(t, servers[0].ID(), res.Self().ID())
assert.Equal(t, servers[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, tribes, res.Tribes())
assert.Equal(t, tribes[0].ID(), res.Self().ID())
assert.Equal(t, tribes[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, next.ID(), res.Next().ID())
assert.Equal(t, next.ServerKey(), res.Next().ServerKey())
})
@ -930,15 +935,15 @@ func TestNewListTribesResult(t *testing.T) {
t.Run("OK: without next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribesResult(servers, domain.Tribe{})
res, err := domain.NewListTribesResult(tribes, domain.Tribe{})
require.NoError(t, err)
assert.Equal(t, servers, res.Tribes())
assert.Equal(t, servers[0].ID(), res.Self().ID())
assert.Equal(t, servers[0].ServerKey(), res.Self().ServerKey())
assert.Equal(t, tribes, res.Tribes())
assert.Equal(t, tribes[0].ID(), res.Self().ID())
assert.Equal(t, tribes[0].ServerKey(), res.Self().ServerKey())
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 versions", func(t *testing.T) {
t.Run("OK: 0 tribes", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribesResult(nil, domain.Tribe{})

View File

@ -357,19 +357,16 @@ func TestDataSync(t *testing.T) {
allPlayers := make(domain.Players, 0, len(expectedPlayers))
for {
players, err := playerRepo.List(ctx, listParams)
res, err := playerRepo.List(ctx, listParams)
require.NoError(collect, err)
if len(players) == 0 {
allPlayers = append(allPlayers, res.Players()...)
if res.Next().IsZero() {
break
}
allPlayers = append(allPlayers, players...)
require.NoError(collect, listParams.SetIDGT(domain.NullInt{
Value: players[len(players)-1].ID(),
Valid: true,
}))
require.NoError(collect, listParams.SetCursor(res.Next()))
}
if !assert.Len(collect, allPlayers, len(expectedPlayers)) {

View File

@ -276,19 +276,16 @@ func TestSnapshotCreation(t *testing.T) {
var allPlayers domain.Players
for {
players, err := playerRepo.List(ctx, listPlayersParams)
res, err := playerRepo.List(ctx, listPlayersParams)
require.NoError(collect, err)
if len(players) == 0 {
allPlayers = append(allPlayers, res.Players()...)
if res.Next().IsZero() {
break
}
allPlayers = append(allPlayers, players...)
require.NoError(collect, listPlayersParams.SetIDGT(domain.NullInt{
Value: players[len(players)-1].ID(),
Valid: true,
}))
require.NoError(collect, listPlayersParams.SetCursor(res.Next()))
}
listSnapshotsParams := domain.NewListPlayerSnapshotsParams()