refactor: ennoblement - cursor pagination (#20)
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

Reviewed-on: twhelp/corev3#20
This commit is contained in:
Dawid Wysokiński 2024-03-10 08:30:03 +00:00
parent eb1890d90b
commit 5fa7c27d6d
9 changed files with 652 additions and 92 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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