refactor: ennoblement - cursor pagination (#20)
Reviewed-on: twhelp/corev3#20
This commit is contained in:
parent
eb1890d90b
commit
5fa7c27d6d
|
@ -54,17 +54,22 @@ func (repo *EnnoblementBunRepository) Create(ctx context.Context, params ...doma
|
|||
func (repo *EnnoblementBunRepository) List(
|
||||
ctx context.Context,
|
||||
params domain.ListEnnoblementsParams,
|
||||
) (domain.Ennoblements, error) {
|
||||
) (domain.ListEnnoblementsResult, error) {
|
||||
var ennoblements bunmodel.Ennoblements
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&ennoblements).
|
||||
Apply(listEnnoblementsParamsApplier{params: params}.apply).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("couldn't select ennoblements from the db: %w", err)
|
||||
return domain.ListEnnoblementsResult{}, fmt.Errorf("couldn't select ennoblements from the db: %w", err)
|
||||
}
|
||||
|
||||
return ennoblements.ToDomain()
|
||||
converted, err := ennoblements.ToDomain()
|
||||
if err != nil {
|
||||
return domain.ListEnnoblementsResult{}, err
|
||||
}
|
||||
|
||||
return domain.NewListEnnoblementsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
type listEnnoblementsParamsApplier struct {
|
||||
|
@ -96,5 +101,57 @@ func (a listEnnoblementsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuer
|
|||
}
|
||||
}
|
||||
|
||||
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
|
||||
}
|
||||
|
||||
func (a listEnnoblementsParamsApplier) 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 el cursorPaginationApplierDataElement
|
||||
|
||||
switch s {
|
||||
case domain.EnnoblementSortIDASC:
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "ennoblement.id"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.EnnoblementSortIDDESC:
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
el.column = "ennoblement.id"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.EnnoblementSortServerKeyASC:
|
||||
el.value = cursor.ServerKey()
|
||||
el.column = "ennoblement.server_key"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.EnnoblementSortServerKeyDESC:
|
||||
el.value = cursor.ServerKey()
|
||||
el.column = "ennoblement.server_key"
|
||||
el.direction = sortDirectionDESC
|
||||
case domain.EnnoblementSortCreatedAtASC:
|
||||
el.value = cursor.CreatedAt()
|
||||
el.column = "ennoblement.created_at"
|
||||
el.direction = sortDirectionASC
|
||||
case domain.EnnoblementSortCreatedAtDESC:
|
||||
el.value = cursor.CreatedAt()
|
||||
el.column = "ennoblement.created_at"
|
||||
el.direction = sortDirectionDESC
|
||||
default:
|
||||
return q.Err(fmt.Errorf("%s: %w", s.String(), errInvalidSortValue))
|
||||
}
|
||||
|
||||
cursorApplier.data = append(cursorApplier.data, el)
|
||||
}
|
||||
|
||||
return q.Apply(cursorApplier.apply)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
listParams := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
|
||||
|
||||
ennoblements, err := repos.ennoblement.List(ctx, listParams)
|
||||
res, err := repos.ennoblement.List(ctx, listParams)
|
||||
ennoblements := res.Ennoblements()
|
||||
require.NoError(t, err)
|
||||
for i, p := range params {
|
||||
idx := slices.IndexFunc(ennoblements, func(ennoblement domain.Ennoblement) bool {
|
||||
|
@ -61,8 +62,9 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
listParams := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
|
||||
|
||||
ennoblements, err := repos.ennoblement.List(ctx, listParams)
|
||||
res, err := repos.ennoblement.List(ctx, listParams)
|
||||
require.NoError(t, err)
|
||||
ennoblements := res.Ennoblements()
|
||||
|
||||
m := make(map[string][]int)
|
||||
|
||||
|
@ -121,17 +123,12 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
|
||||
repos := newRepos(t)
|
||||
|
||||
ennoblements, listEnnoblementsErr := repos.ennoblement.List(ctx, domain.NewListEnnoblementsParams())
|
||||
require.NoError(t, listEnnoblementsErr)
|
||||
require.NotEmpty(t, ennoblements)
|
||||
randEnnoblement := ennoblements[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListEnnoblementsParams
|
||||
assertEnnoblements func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, params domain.ListEnnoblementsParams, total int)
|
||||
name string
|
||||
params func(t *testing.T) domain.ListEnnoblementsParams
|
||||
assertResult func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, params domain.ListEnnoblementsParams, total int)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
|
@ -139,8 +136,9 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
t.Helper()
|
||||
return domain.NewListEnnoblementsParams()
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, _ domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, _ domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Or(
|
||||
|
@ -149,6 +147,8 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
|
@ -160,24 +160,27 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[serverKey DESC, createdAt DESC]",
|
||||
name: "OK: sort=[serverKey DESC, createdAt DESC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortServerKeyDESC,
|
||||
domain.EnnoblementSortCreatedAtDESC,
|
||||
domain.EnnoblementSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, _ domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, _ domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
a.CreatedAt().Compare(b.CreatedAt()),
|
||||
) * -1
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey())*-1,
|
||||
a.CreatedAt().Compare(b.CreatedAt())*-1,
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
|
@ -199,8 +202,9 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
}))
|
||||
return params
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, _ domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, _ domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
|
@ -225,8 +229,9 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
}))
|
||||
return params
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, _ domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, _ domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Compare(a.ID(), b.ID()) * -1
|
||||
|
@ -242,18 +247,28 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("OK: serverKeys=[%s]", randEnnoblement.ServerKey()),
|
||||
name: "OK: serverKeys",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.Ennoblements())
|
||||
randEnnoblement := res.Ennoblements()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randEnnoblement.ServerKey()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotZero(t, ennoblements)
|
||||
for _, e := range ennoblements {
|
||||
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||
}
|
||||
|
@ -268,17 +283,150 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "OK: offset=1 limit=2",
|
||||
name: "OK: cursor serverKeys sort=[id ASC]",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Ennoblements()), 2)
|
||||
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{domain.EnnoblementSortIDASC}))
|
||||
require.NoError(t, params.SetServerKeys([]string{res.Ennoblements()[1].ServerKey()}))
|
||||
require.NoError(
|
||||
t,
|
||||
params.SetCursor(domaintest.NewEnnoblementCursor(t, func(cfg *domaintest.EnnoblementCursorConfig) {
|
||||
cfg.ID = res.Ennoblements()[1].ID()
|
||||
})),
|
||||
)
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
for _, e := range ennoblements {
|
||||
assert.GreaterOrEqual(t, e.ID(), params.Cursor().ID())
|
||||
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||
}
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortServerKeyASC,
|
||||
domain.EnnoblementSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Ennoblements()), 2)
|
||||
|
||||
require.NoError(
|
||||
t,
|
||||
params.SetCursor(domaintest.NewEnnoblementCursor(t, func(cfg *domaintest.EnnoblementCursorConfig) {
|
||||
cfg.ID = res.Ennoblements()[1].ID()
|
||||
cfg.ServerKey = res.Ennoblements()[1].ServerKey()
|
||||
})),
|
||||
)
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.GreaterOrEqual(t, ennoblements[0].ID(), params.Cursor().ID())
|
||||
for _, e := range ennoblements {
|
||||
assert.GreaterOrEqual(t, e.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.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
|
||||
domain.EnnoblementSortServerKeyDESC,
|
||||
domain.EnnoblementSortIDDESC,
|
||||
}))
|
||||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Ennoblements()), 2)
|
||||
|
||||
require.NoError(
|
||||
t,
|
||||
params.SetCursor(domaintest.NewEnnoblementCursor(t, func(cfg *domaintest.EnnoblementCursorConfig) {
|
||||
cfg.ID = res.Ennoblements()[1].ID()
|
||||
cfg.ServerKey = res.Ennoblements()[1].ServerKey()
|
||||
})),
|
||||
)
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
ennoblements := res.Ennoblements()
|
||||
assert.NotEmpty(t, len(ennoblements))
|
||||
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
) * -1
|
||||
}))
|
||||
assert.LessOrEqual(t, ennoblements[0].ID(), params.Cursor().ID())
|
||||
for _, e := range ennoblements {
|
||||
assert.LessOrEqual(t, e.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.ListEnnoblementsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
require.NoError(t, params.SetOffset(1))
|
||||
require.NoError(t, params.SetLimit(2))
|
||||
return params
|
||||
},
|
||||
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
|
||||
assertResult: func(t *testing.T, params domain.ListEnnoblementsParams, res domain.ListEnnoblementsResult) {
|
||||
t.Helper()
|
||||
assert.Len(t, ennoblements, params.Limit())
|
||||
assert.Len(t, res.Ennoblements(), params.Limit())
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.False(t, res.Next().IsZero())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
|
@ -299,7 +447,7 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
|||
|
||||
res, err := repos.ennoblement.List(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertEnnoblements(t, params, res)
|
||||
tt.assertResult(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -47,7 +47,7 @@ type villageRepository interface {
|
|||
|
||||
type ennoblementRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error
|
||||
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
|
||||
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.ListEnnoblementsResult, error)
|
||||
}
|
||||
|
||||
type tribeChangeRepository interface {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
type EnnoblementRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error
|
||||
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
|
||||
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.ListEnnoblementsResult, error)
|
||||
}
|
||||
|
||||
type EnnoblementService struct {
|
||||
|
@ -80,10 +80,12 @@ func (svc *EnnoblementService) getLatest(ctx context.Context, serverKey string)
|
|||
return domain.Ennoblement{}, err
|
||||
}
|
||||
|
||||
ennoblements, err := svc.repo.List(ctx, params)
|
||||
res, err := svc.repo.List(ctx, params)
|
||||
if err != nil {
|
||||
return domain.Ennoblement{}, err
|
||||
}
|
||||
ennoblements := res.Ennoblements()
|
||||
|
||||
if len(ennoblements) == 0 {
|
||||
return domain.Ennoblement{}, errEnnoblementNotFound
|
||||
}
|
||||
|
@ -116,3 +118,10 @@ func (svc *EnnoblementService) create(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *EnnoblementService) List(
|
||||
ctx context.Context,
|
||||
params domain.ListEnnoblementsParams,
|
||||
) (domain.ListEnnoblementsResult, error) {
|
||||
return svc.repo.List(ctx, params)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,35 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type EnnoblementCursorConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func NewEnnoblementCursor(tb TestingTB, opts ...func(cfg *EnnoblementCursorConfig)) domain.EnnoblementCursor {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &EnnoblementCursorConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
CreatedAt: gofakeit.Date(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
ec, err := domain.NewEnnoblementCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.CreatedAt,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return ec
|
||||
}
|
||||
|
||||
type EnnoblementConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
|
|
|
@ -25,13 +25,13 @@ func NewVillageCursor(tb TestingTB, opts ...func(cfg *VillageCursorConfig)) doma
|
|||
opt(cfg)
|
||||
}
|
||||
|
||||
pc, err := domain.NewVillageCursor(
|
||||
vc, err := domain.NewVillageCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return pc
|
||||
return vc
|
||||
}
|
||||
|
||||
type VillageConfig struct {
|
||||
|
|
|
@ -101,6 +101,10 @@ func (e Ennoblement) CreatedAt() time.Time {
|
|||
return e.createdAt
|
||||
}
|
||||
|
||||
func (e Ennoblement) ToCursor() (EnnoblementCursor, error) {
|
||||
return NewEnnoblementCursor(e.id, e.serverKey, e.createdAt)
|
||||
}
|
||||
|
||||
func (e Ennoblement) Base() BaseEnnoblement {
|
||||
return BaseEnnoblement{
|
||||
villageID: e.villageID,
|
||||
|
@ -113,6 +117,10 @@ func (e Ennoblement) Base() BaseEnnoblement {
|
|||
}
|
||||
}
|
||||
|
||||
func (e Ennoblement) IsZero() bool {
|
||||
return e == Ennoblement{}
|
||||
}
|
||||
|
||||
type Ennoblements []Ennoblement
|
||||
|
||||
type CreateEnnoblementParams struct {
|
||||
|
@ -194,11 +202,105 @@ func (s EnnoblementSort) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
type EnnoblementCursor struct {
|
||||
id int
|
||||
serverKey string
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
const ennoblementCursorModelName = "EnnoblementCursor"
|
||||
|
||||
func NewEnnoblementCursor(id int, serverKey string, createdAt time.Time) (EnnoblementCursor, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return EnnoblementCursor{}, ValidationError{
|
||||
Model: ennoblementCursorModelName,
|
||||
Field: "id",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateServerKey(serverKey); err != nil {
|
||||
return EnnoblementCursor{}, ValidationError{
|
||||
Model: ennoblementCursorModelName,
|
||||
Field: "serverKey",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return EnnoblementCursor{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
createdAt: createdAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func decodeEnnoblementCursor(encoded string) (EnnoblementCursor, error) {
|
||||
m, err := decodeCursor(encoded)
|
||||
if err != nil {
|
||||
return EnnoblementCursor{}, err
|
||||
}
|
||||
|
||||
id, err := m.int("id")
|
||||
if err != nil {
|
||||
return EnnoblementCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
serverKey, err := m.string("serverKey")
|
||||
if err != nil {
|
||||
return EnnoblementCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
createdAt, err := m.time("createdAt")
|
||||
if err != nil {
|
||||
return EnnoblementCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
ec, err := NewEnnoblementCursor(
|
||||
id,
|
||||
serverKey,
|
||||
createdAt,
|
||||
)
|
||||
if err != nil {
|
||||
return EnnoblementCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (ec EnnoblementCursor) ID() int {
|
||||
return ec.id
|
||||
}
|
||||
|
||||
func (ec EnnoblementCursor) ServerKey() string {
|
||||
return ec.serverKey
|
||||
}
|
||||
|
||||
func (ec EnnoblementCursor) CreatedAt() time.Time {
|
||||
return ec.createdAt
|
||||
}
|
||||
|
||||
func (ec EnnoblementCursor) IsZero() bool {
|
||||
return ec == EnnoblementCursor{}
|
||||
}
|
||||
|
||||
func (ec EnnoblementCursor) Encode() string {
|
||||
if ec.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"id", ec.id},
|
||||
{"serverKey", ec.serverKey},
|
||||
{"createdAt", ec.createdAt},
|
||||
})
|
||||
}
|
||||
|
||||
type ListEnnoblementsParams struct {
|
||||
serverKeys []string
|
||||
sort []EnnoblementSort
|
||||
cursor EnnoblementCursor
|
||||
limit int
|
||||
offset int
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -261,6 +363,30 @@ func (params *ListEnnoblementsParams) SetSort(sort []EnnoblementSort) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Cursor() EnnoblementCursor {
|
||||
return params.cursor
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetCursor(cursor EnnoblementCursor) error {
|
||||
params.cursor = cursor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetEncodedCursor(encoded string) error {
|
||||
decoded, err := decodeEnnoblementCursor(encoded)
|
||||
if err != nil {
|
||||
return ValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "cursor",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.cursor = decoded
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
@ -279,20 +405,53 @@ func (params *ListEnnoblementsParams) SetLimit(limit int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) Offset() int {
|
||||
return params.offset
|
||||
type ListEnnoblementsResult struct {
|
||||
ennoblements Ennoblements
|
||||
self EnnoblementCursor
|
||||
next EnnoblementCursor
|
||||
}
|
||||
|
||||
func (params *ListEnnoblementsParams) SetOffset(offset int) error {
|
||||
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
||||
return ValidationError{
|
||||
Model: listEnnoblementsParamsModelName,
|
||||
Field: "offset",
|
||||
Err: err,
|
||||
const listEnnoblementsResultModelName = "ListEnnoblementsResult"
|
||||
|
||||
func NewListEnnoblementsResult(ennoblements Ennoblements, next Ennoblement) (ListEnnoblementsResult, error) {
|
||||
var err error
|
||||
res := ListEnnoblementsResult{
|
||||
ennoblements: ennoblements,
|
||||
}
|
||||
|
||||
if len(ennoblements) > 0 {
|
||||
res.self, err = ennoblements[0].ToCursor()
|
||||
if err != nil {
|
||||
return ListEnnoblementsResult{}, ValidationError{
|
||||
Model: listEnnoblementsResultModelName,
|
||||
Field: "self",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.offset = offset
|
||||
if !next.IsZero() {
|
||||
res.next, err = next.ToCursor()
|
||||
if err != nil {
|
||||
return ListEnnoblementsResult{}, ValidationError{
|
||||
Model: listEnnoblementsResultModelName,
|
||||
Field: "next",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (res ListEnnoblementsResult) Ennoblements() Ennoblements {
|
||||
return res.ennoblements
|
||||
}
|
||||
|
||||
func (res ListEnnoblementsResult) Self() EnnoblementCursor {
|
||||
return res.self
|
||||
}
|
||||
|
||||
func (res ListEnnoblementsResult) Next() EnnoblementCursor {
|
||||
return res.next
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
@ -102,6 +104,87 @@ func TestEnnoblementSort_IsInConflict(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewEnnoblementCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validEnnoblementCursor := domaintest.NewEnnoblementCursor(t)
|
||||
|
||||
type args struct {
|
||||
id int
|
||||
serverKey string
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
id: validEnnoblementCursor.ID(),
|
||||
serverKey: validEnnoblementCursor.ServerKey(),
|
||||
createdAt: validEnnoblementCursor.CreatedAt(),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "ERR: id < 1",
|
||||
args: args{
|
||||
id: 0,
|
||||
serverKey: validEnnoblementCursor.ServerKey(),
|
||||
createdAt: validEnnoblementCursor.CreatedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "EnnoblementCursor",
|
||||
Field: "id",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
id: validEnnoblementCursor.ID(),
|
||||
serverKey: serverKeyTest.key,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "EnnoblementCursor",
|
||||
Field: "serverKey",
|
||||
Err: serverKeyTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ec, err := domain.NewEnnoblementCursor(
|
||||
tt.args.id,
|
||||
tt.args.serverKey,
|
||||
tt.args.createdAt,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.id, ec.ID())
|
||||
assert.Equal(t, tt.args.serverKey, ec.ServerKey())
|
||||
assert.Equal(t, tt.args.createdAt, ec.CreatedAt())
|
||||
assert.NotEmpty(t, ec.Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetServerKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -246,6 +329,86 @@ func TestListEnnoblementsParams_SetSort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetEncodedCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validCursor := domaintest.NewEnnoblementCursor(t)
|
||||
|
||||
type args struct {
|
||||
cursor string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedCursor domain.EnnoblementCursor
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
cursor: validCursor.Encode(),
|
||||
},
|
||||
expectedCursor: validCursor,
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) < 1",
|
||||
args: args{
|
||||
cursor: "",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
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: "ListEnnoblementsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1000,
|
||||
Current: 1001,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: malformed base64",
|
||||
args: args{
|
||||
cursor: "112345",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListEnnoblementsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.ErrInvalidCursor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
|
||||
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 TestListEnnoblementsParams_SetLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -309,51 +472,46 @@ func TestListEnnoblementsParams_SetLimit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListEnnoblementsParams_SetOffset(t *testing.T) {
|
||||
func TestNewListEnnoblementsResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
offset int
|
||||
ennoblements := domain.Ennoblements{
|
||||
domaintest.NewEnnoblement(t),
|
||||
domaintest.NewEnnoblement(t),
|
||||
domaintest.NewEnnoblement(t),
|
||||
}
|
||||
next := domaintest.NewEnnoblement(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: "ListEnnoblementsParams",
|
||||
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.NewListEnnoblementsResult(ennoblements, next)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ennoblements, res.Ennoblements())
|
||||
assert.Equal(t, ennoblements[0].ID(), res.Self().ID())
|
||||
assert.Equal(t, ennoblements[0].ServerKey(), res.Self().ServerKey())
|
||||
assert.Equal(t, next.ID(), res.Next().ID())
|
||||
assert.Equal(t, next.ServerKey(), res.Next().ServerKey())
|
||||
})
|
||||
|
||||
params := domain.NewListEnnoblementsParams()
|
||||
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.NewListEnnoblementsResult(ennoblements, domain.Ennoblement{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ennoblements, res.Ennoblements())
|
||||
assert.Equal(t, ennoblements[0].ID(), res.Self().ID())
|
||||
assert.Equal(t, ennoblements[0].ServerKey(), res.Self().ServerKey())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
|
||||
t.Run("OK: 0 ennoblements", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListEnnoblementsResult(nil, domain.Ennoblement{})
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, res.Ennoblements())
|
||||
assert.True(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -204,16 +204,16 @@ func TestEnnoblementSync(t *testing.T) {
|
|||
allEnnoblements := make(domain.Ennoblements, 0, len(expectedEnnoblements))
|
||||
|
||||
for {
|
||||
ennoblements, err := ennoblementRepo.List(ctx, listParams)
|
||||
res, err := ennoblementRepo.List(ctx, listParams)
|
||||
require.NoError(collect, err)
|
||||
|
||||
if len(ennoblements) == 0 {
|
||||
allEnnoblements = append(allEnnoblements, res.Ennoblements()...)
|
||||
|
||||
if res.Next().IsZero() {
|
||||
break
|
||||
}
|
||||
|
||||
allEnnoblements = append(allEnnoblements, ennoblements...)
|
||||
|
||||
require.NoError(collect, listParams.SetOffset(listParams.Offset()+domain.EnnoblementListMaxLimit))
|
||||
require.NoError(collect, listParams.SetCursor(res.Next()))
|
||||
}
|
||||
|
||||
if !assert.Len(collect, allEnnoblements, len(expectedEnnoblements)) {
|
||||
|
|
Loading…
Reference in New Issue