refactor: player snapshot - cursor pagination
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

This commit is contained in:
Dawid Wysokiński 2024-03-16 07:43:46 +01:00
parent b6e46dee35
commit 41cb1d042e
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
7 changed files with 628 additions and 93 deletions

View File

@ -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(

View File

@ -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)
})
}
})

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
})
}

View File

@ -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