diff --git a/internal/adapter/repository_bun_player.go b/internal/adapter/repository_bun_player.go index 76ea71b..1c55c50 100644 --- a/internal/adapter/repository_bun_player.go +++ b/internal/adapter/repository_bun_player.go @@ -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 } diff --git a/internal/adapter/repository_ennoblement_test.go b/internal/adapter/repository_ennoblement_test.go index 47ca103..df34139 100644 --- a/internal/adapter/repository_ennoblement_test.go +++ b/internal/adapter/repository_ennoblement_test.go @@ -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) diff --git a/internal/adapter/repository_player_snapshot_test.go b/internal/adapter/repository_player_snapshot_test.go index ea7dc12..fe58e9a 100644 --- a/internal/adapter/repository_player_snapshot_test.go +++ b/internal/adapter/repository_player_snapshot_test.go @@ -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) diff --git a/internal/adapter/repository_player_test.go b/internal/adapter/repository_player_test.go index 580e870..dcc827b 100644 --- a/internal/adapter/repository_player_test.go +++ b/internal/adapter/repository_player_test.go @@ -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) diff --git a/internal/adapter/repository_test.go b/internal/adapter/repository_test.go index 9f7616d..1a75c8c 100644 --- a/internal/adapter/repository_test.go +++ b/internal/adapter/repository_test.go @@ -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 } diff --git a/internal/adapter/repository_tribe_change_test.go b/internal/adapter/repository_tribe_change_test.go index c27d5e7..606136f 100644 --- a/internal/adapter/repository_tribe_change_test.go +++ b/internal/adapter/repository_tribe_change_test.go @@ -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) diff --git a/internal/adapter/repository_tribe_snapshot_test.go b/internal/adapter/repository_tribe_snapshot_test.go index 20220d0..a958080 100644 --- a/internal/adapter/repository_tribe_snapshot_test.go +++ b/internal/adapter/repository_tribe_snapshot_test.go @@ -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) diff --git a/internal/adapter/repository_tribe_test.go b/internal/adapter/repository_tribe_test.go index a49c132..e8b9c0f 100644 --- a/internal/adapter/repository_tribe_test.go +++ b/internal/adapter/repository_tribe_test.go @@ -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) diff --git a/internal/adapter/repository_village_test.go b/internal/adapter/repository_village_test.go index 046dec7..eff7bc7 100644 --- a/internal/adapter/repository_village_test.go +++ b/internal/adapter/repository_village_test.go @@ -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) diff --git a/internal/app/service_player.go b/internal/app/service_player.go index 2474581..9751f7f 100644 --- a/internal/app/service_player.go +++ b/internal/app/service_player.go @@ -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) } diff --git a/internal/app/service_player_snapshot.go b/internal/app/service_player_snapshot.go index 13c1dff..95c54bb 100644 --- a/internal/app/service_player_snapshot.go +++ b/internal/app/service_player_snapshot.go @@ -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) } } diff --git a/internal/domain/domaintest/player.go b/internal/domain/domaintest/player.go index 2d7b7af..4d7ebd2 100644 --- a/internal/domain/domaintest/player.go +++ b/internal/domain/domaintest/player.go @@ -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 diff --git a/internal/domain/player.go b/internal/domain/player.go index 345452b..2604ab6 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -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 } diff --git a/internal/domain/player_test.go b/internal/domain/player_test.go index 88ac021..b039099 100644 --- a/internal/domain/player_test.go +++ b/internal/domain/player_test.go @@ -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()) + }) } diff --git a/internal/domain/server_test.go b/internal/domain/server_test.go index e9e4ee8..980a647 100644 --- a/internal/domain/server_test.go +++ b/internal/domain/server_test.go @@ -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{}) diff --git a/internal/domain/tribe_test.go b/internal/domain/tribe_test.go index ee449d7..5c8821d 100644 --- a/internal/domain/tribe_test.go +++ b/internal/domain/tribe_test.go @@ -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{}) diff --git a/internal/port/consumer_data_sync_test.go b/internal/port/consumer_data_sync_test.go index 272a43c..60bcfde 100644 --- a/internal/port/consumer_data_sync_test.go +++ b/internal/port/consumer_data_sync_test.go @@ -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)) { diff --git a/internal/port/consumer_snapshot_creation_test.go b/internal/port/consumer_snapshot_creation_test.go index e27258d..61b6c98 100644 --- a/internal/port/consumer_snapshot_creation_test.go +++ b/internal/port/consumer_snapshot_creation_test.go @@ -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()