feat: add a new model - TribeChangeWithRelations (#26)
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

Reviewed-on: twhelp/corev3#26
This commit is contained in:
Dawid Wysokiński 2024-03-12 06:52:41 +00:00
parent 53596dcdad
commit 48f6a56a5e
24 changed files with 388 additions and 182 deletions

View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"log/slog"
"slices"
"github.com/urfave/cli/v2"
)
@ -45,7 +46,7 @@ func newApp(name, version string) *appWrapper {
app.Version = version
app.Commands = []*cli.Command{cmdDB, cmdJob, cmdConsumer, cmdServe}
app.DefaultCommand = cmdServe.Name
app.Flags = concatSlices(appFlags, logFlags)
app.Flags = slices.Concat(appFlags, logFlags)
app.Before = app.handleBefore
return app
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"slices"
"sync"
"time"
@ -28,7 +29,7 @@ var cmdConsumer = &cli.Command{
{
Name: "server",
Usage: "Run the worker responsible for consuming server-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Flags: slices.Concat(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
@ -78,7 +79,7 @@ var cmdConsumer = &cli.Command{
{
Name: "tribe",
Usage: "Run the worker responsible for consuming tribe-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Flags: slices.Concat(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
@ -135,7 +136,7 @@ var cmdConsumer = &cli.Command{
{
Name: "player",
Usage: "Run the worker responsible for consuming player-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Flags: slices.Concat(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
@ -198,7 +199,7 @@ var cmdConsumer = &cli.Command{
{
Name: "village",
Usage: "Run the worker responsible for consuming village-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Flags: slices.Concat(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
@ -240,7 +241,7 @@ var cmdConsumer = &cli.Command{
{
Name: "ennoblement",
Usage: "Run the worker responsible for consuming ennoblement-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Flags: slices.Concat(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,

View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"log/slog"
"slices"
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
@ -21,7 +22,7 @@ var (
{
Name: "data",
Usage: "Trigger data sync (servers, players, tribes, villages)",
Flags: concatSlices(dbFlags, rmqFlags),
Flags: slices.Concat(dbFlags, rmqFlags),
Action: func(c *cli.Context) error {
logger := loggerFromCtx(c.Context)
watermillLogger := newWatermillLogger(logger)
@ -84,7 +85,7 @@ var (
{
Name: "ennoblements",
Usage: "Trigger ennoblement sync",
Flags: concatSlices(dbFlags, rmqFlags),
Flags: slices.Concat(dbFlags, rmqFlags),
Action: func(c *cli.Context) error {
logger := loggerFromCtx(c.Context)
watermillLogger := newWatermillLogger(logger)
@ -153,7 +154,7 @@ var (
{
Name: "snapshots",
Usage: "Trigger snapshot creation (players/tribes)",
Flags: concatSlices(dbFlags, rmqFlags),
Flags: slices.Concat(dbFlags, rmqFlags),
Action: func(c *cli.Context) error {
logger := loggerFromCtx(c.Context)
watermillLogger := newWatermillLogger(logger)

View File

@ -7,6 +7,7 @@ import (
"log/slog"
"net/http"
"net/url"
"slices"
"strings"
"time"
@ -85,7 +86,7 @@ const (
var cmdServe = &cli.Command{
Name: "serve",
Usage: "Run the HTTP server",
Flags: concatSlices(apiServerFlags, dbFlags),
Flags: slices.Concat(apiServerFlags, dbFlags),
Action: func(c *cli.Context) error {
logger := loggerFromCtx(c.Context)

View File

@ -7,24 +7,6 @@ import (
"syscall"
)
func concatSlices[T any](slices ...[]T) []T {
var totalLen int
for _, s := range slices {
totalLen += len(s)
}
result := make([]T, totalLen)
var i int
for _, s := range slices {
i += copy(result[i:], s)
}
return result
}
var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
// newShutdownSignalContext returns a copy of the parent context that is marked done

View File

@ -70,6 +70,36 @@ func (repo *TribeChangeBunRepository) List(
return domain.NewListTribeChangesResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *TribeChangeBunRepository) ListWithRelations(
ctx context.Context,
params domain.ListTribeChangesParams,
) (domain.ListTribeChangesWithRelationsResult, error) {
var tribeChanges bunmodel.TribeChanges
if err := repo.db.NewSelect().
Model(&tribeChanges).
Apply(listTribeChangesParamsApplier{params: params}.apply).
Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column(bunmodel.PlayerMetaColumns...)
}).
Relation("NewTribe", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column(bunmodel.TribeMetaColumns...)
}).
Relation("OldTribe", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Column(bunmodel.TribeMetaColumns...)
}).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return domain.ListTribeChangesWithRelationsResult{}, fmt.Errorf("couldn't select tribe changes from the db: %w", err)
}
converted, err := tribeChanges.ToDomainWithRelations()
if err != nil {
return domain.ListTribeChangesWithRelationsResult{}, err
}
return domain.NewListTribeChangesWithRelationsResult(separateListResultAndNext(converted, params.Limit()))
}
type listTribeChangesParamsApplier struct {
params domain.ListTribeChangesParams
}

View File

@ -57,6 +57,10 @@ type ennoblementRepository interface {
type tribeChangeRepository interface {
Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error
List(ctx context.Context, params domain.ListTribeChangesParams) (domain.ListTribeChangesResult, error)
ListWithRelations(
ctx context.Context,
params domain.ListTribeChangesParams,
) (domain.ListTribeChangesWithRelationsResult, error)
}
type tribeSnapshotRepository interface {

View File

@ -136,7 +136,7 @@ func testTribeChangeRepository(t *testing.T, newRepos func(t *testing.T) reposit
})
})
t.Run("List", func(t *testing.T) {
t.Run("List & ListWithRelations", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
@ -428,6 +428,18 @@ func testTribeChangeRepository(t *testing.T, newRepos func(t *testing.T) reposit
res, err := repos.tribeChange.List(ctx, params)
tt.assertError(t, err)
tt.assertResult(t, params, res)
resWithRelations, err := repos.tribeChange.ListWithRelations(ctx, params)
tt.assertError(t, err)
require.Len(t, resWithRelations.TribeChanges(), len(res.TribeChanges()))
for i, e := range resWithRelations.TribeChanges() {
assert.Equal(t, res.TribeChanges()[i], e.TribeChange())
assert.Equal(t, e.TribeChange().PlayerID(), e.Player().ID())
assert.Equal(t, e.TribeChange().NewTribeID(), e.NewTribe().V.ID())
assert.Equal(t, e.TribeChange().NewTribeID() != 0, e.NewTribe().Valid)
assert.Equal(t, e.TribeChange().OldTribeID(), e.OldTribe().V.ID())
assert.Equal(t, e.TribeChange().OldTribeID() != 0, e.OldTribe().Valid)
}
})
}
})

View File

@ -10,6 +10,11 @@ type TribeChangeRepository interface {
// Create persists tribe changes in a store (e.g. Postgres).
// Duplicates are ignored.
Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error
List(ctx context.Context, params domain.ListTribeChangesParams) (domain.ListTribeChangesResult, error)
ListWithRelations(
ctx context.Context,
params domain.ListTribeChangesParams,
) (domain.ListTribeChangesWithRelationsResult, error)
}
type TribeChangeService struct {
@ -38,3 +43,17 @@ func (svc *TribeChangeService) Create(ctx context.Context, params ...domain.Crea
return nil
}
func (svc *TribeChangeService) List(
ctx context.Context,
params domain.ListTribeChangesParams,
) (domain.ListTribeChangesResult, error) {
return svc.repo.List(ctx, params)
}
func (svc *TribeChangeService) ListWithRelations(
ctx context.Context,
params domain.ListTribeChangesParams,
) (domain.ListTribeChangesWithRelationsResult, error) {
return svc.repo.ListWithRelations(ctx, params)
}

View File

@ -109,31 +109,9 @@ func (e Ennoblement) ToDomainWithRelations() (domain.EnnoblementWithRelations, e
type Ennoblements []Ennoblement
func (es Ennoblements) ToDomain() (domain.Ennoblements, error) {
res := make(domain.Ennoblements, 0, len(es))
for _, e := range es {
converted, err := e.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(es)
}
func (es Ennoblements) ToDomainWithRelations() (domain.EnnoblementsWithRelations, error) {
res := make(domain.EnnoblementsWithRelations, 0, len(es))
for _, e := range es {
converted, err := e.ToDomainWithRelations()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomainWithRelations(es)
}

View File

@ -137,31 +137,9 @@ func (p Player) ToMetaWithRelations() (domain.PlayerMetaWithRelations, error) {
type Players []Player
func (ps Players) ToDomain() (domain.Players, error) {
res := make(domain.Players, 0, len(ps))
for _, p := range ps {
converted, err := p.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(ps)
}
func (ps Players) ToDomainWithRelations() (domain.PlayersWithRelations, error) {
res := make(domain.PlayersWithRelations, 0, len(ps))
for _, p := range ps {
converted, err := p.ToDomainWithRelations()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomainWithRelations(ps)
}

View File

@ -61,16 +61,5 @@ func (ps PlayerSnapshot) ToDomain() (domain.PlayerSnapshot, error) {
type PlayerSnapshots []PlayerSnapshot
func (pss PlayerSnapshots) ToDomain() (domain.PlayerSnapshots, error) {
res := make(domain.PlayerSnapshots, 0, len(pss))
for _, ps := range pss {
converted, err := ps.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(pss)
}

View File

@ -86,16 +86,5 @@ func (s Server) ToDomain() (domain.Server, error) {
type Servers []Server
func (ss Servers) ToDomain() (domain.Servers, error) {
res := make(domain.Servers, 0, len(ss))
for _, s := range ss {
converted, err := s.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(ss)
}

View File

@ -92,16 +92,5 @@ func (t Tribe) ToDomain() (domain.Tribe, error) {
type Tribes []Tribe
func (ts Tribes) ToDomain() (domain.Tribes, error) {
res := make(domain.Tribes, 0, len(ts))
for _, t := range ts {
converted, err := t.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(ts)
}

View File

@ -42,19 +42,45 @@ func (tc TribeChange) ToDomain() (domain.TribeChange, error) {
return converted, nil
}
//nolint:gocyclo
func (tc TribeChange) ToDomainWithRelations() (domain.TribeChangeWithRelations, error) {
converted, err := tc.ToDomain()
if err != nil {
return domain.TribeChangeWithRelations{}, err
}
player, err := tc.Player.ToMeta()
if err != nil {
return domain.TribeChangeWithRelations{}, err
}
var oldTribe domain.NullTribeMeta
if tc.OldTribe.ID > 0 {
oldTribe.Valid = true
oldTribe.V, err = tc.OldTribe.ToMeta()
if err != nil {
return domain.TribeChangeWithRelations{}, err
}
}
var newTribe domain.NullTribeMeta
if tc.NewTribe.ID > 0 {
newTribe.Valid = true
newTribe.V, err = tc.NewTribe.ToMeta()
if err != nil {
return domain.TribeChangeWithRelations{}, err
}
}
return converted.WithRelations(player, oldTribe, newTribe), nil
}
type TribeChanges []TribeChange
func (tcs TribeChanges) ToDomain() (domain.TribeChanges, error) {
res := make(domain.TribeChanges, 0, len(tcs))
for _, tc := range tcs {
converted, err := tc.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(tcs)
}
func (tcs TribeChanges) ToDomainWithRelations() (domain.TribeChangesWithRelations, error) {
return sliceToDomainWithRelations(tcs)
}

View File

@ -64,16 +64,5 @@ func (ts TribeSnapshot) ToDomain() (domain.TribeSnapshot, error) {
type TribeSnapshots []TribeSnapshot
func (tss TribeSnapshots) ToDomain() (domain.TribeSnapshots, error) {
res := make(domain.TribeSnapshots, 0, len(tss))
for _, ts := range tss {
converted, err := ts.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(tss)
}

View File

@ -0,0 +1,35 @@
package bunmodel
func sliceToDomain[T interface {
ToDomain() (V, error)
}, V any](s []T) ([]V, error) {
res := make([]V, 0, len(s))
for _, el := range s {
converted, err := el.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
}
func sliceToDomainWithRelations[T interface {
ToDomainWithRelations() (V, error)
}, V any](s []T) ([]V, error) {
res := make([]V, 0, len(s))
for _, el := range s {
converted, err := el.ToDomainWithRelations()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
}

View File

@ -1,8 +1,6 @@
package bunmodel
import (
"fmt"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/uptrace/bun"
)
@ -28,16 +26,5 @@ func (v Version) ToDomain() (domain.Version, error) {
type Versions []Version
func (vs Versions) ToDomain() (domain.Versions, error) {
res := make(domain.Versions, 0, len(vs))
for _, v := range vs {
converted, err := v.ToDomain()
if err != nil {
return nil, fmt.Errorf("couldn't construct domain.Version (version=%s): %w", v.Code, err)
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(vs)
}

View File

@ -95,31 +95,9 @@ func (v Village) ToMeta() (domain.VillageMeta, error) {
type Villages []Village
func (vs Villages) ToDomain() (domain.Villages, error) {
res := make(domain.Villages, 0, len(vs))
for _, v := range vs {
converted, err := v.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomain(vs)
}
func (vs Villages) ToDomainWithRelations() (domain.VillagesWithRelations, error) {
res := make(domain.VillagesWithRelations, 0, len(vs))
for _, v := range vs {
converted, err := v.ToDomainWithRelations()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
return sliceToDomainWithRelations(vs)
}

View File

@ -158,7 +158,7 @@ func NewEnnoblementWithRelations(
var oldTribe domain.TribeMeta
if len(cfg.OldTribeOptions) > 0 {
newTribe = NewTribe(tb, cfg.OldTribeOptions...).Meta()
oldTribe = NewTribe(tb, cfg.OldTribeOptions...).Meta()
}
return e.WithRelations(

View File

@ -74,3 +74,77 @@ func NewTribeChange(tb TestingTB, opts ...func(cfg *TribeChangeConfig)) domain.T
return tc
}
type TribeChangeWithRelationsConfig struct {
TribeChangeOptions []func(cfg *TribeChangeConfig)
PlayerOptions []func(cfg *PlayerConfig)
OldTribeOptions []func(cfg *TribeConfig)
NewTribeOptions []func(cfg *TribeConfig)
}
//nolint:gocyclo
func NewTribeChangeWithRelations(
tb TestingTB,
opts ...func(cfg *TribeChangeWithRelationsConfig),
) domain.TribeChangeWithRelations {
tb.Helper()
cfg := &TribeChangeWithRelationsConfig{}
for _, opt := range opts {
opt(cfg)
}
tc := NewTribeChange(tb, cfg.TribeChangeOptions...)
if tc.PlayerID() > 0 {
cfg.PlayerOptions = append([]func(cfg *PlayerConfig){
func(cfg *PlayerConfig) {
cfg.ID = tc.PlayerID()
},
}, cfg.PlayerOptions...)
}
if tc.OldTribeID() > 0 {
cfg.OldTribeOptions = append([]func(cfg *TribeConfig){
func(cfg *TribeConfig) {
cfg.ID = tc.OldTribeID()
},
}, cfg.OldTribeOptions...)
}
if tc.NewTribeID() > 0 {
cfg.NewTribeOptions = append([]func(cfg *TribeConfig){
func(cfg *TribeConfig) {
cfg.ID = tc.NewTribeID()
},
}, cfg.NewTribeOptions...)
}
var player domain.PlayerMeta
if len(cfg.PlayerOptions) > 0 {
player = NewPlayer(tb, cfg.PlayerOptions...).Meta()
}
var oldTribe domain.TribeMeta
if len(cfg.OldTribeOptions) > 0 {
oldTribe = NewTribe(tb, cfg.OldTribeOptions...).Meta()
}
var newTribe domain.TribeMeta
if len(cfg.NewTribeOptions) > 0 {
newTribe = NewTribe(tb, cfg.NewTribeOptions...).Meta()
}
return tc.WithRelations(
player,
domain.NullTribeMeta{
V: oldTribe,
Valid: !oldTribe.IsZero(),
},
domain.NullTribeMeta{
V: newTribe,
Valid: !newTribe.IsZero(),
},
)
}

View File

@ -875,9 +875,9 @@ func TestNewListEnnoblementsWithRelationsResult(t *testing.T) {
t.Run("OK: 0 ennoblements", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListPlayersWithRelationsResult(nil, domain.PlayerWithRelations{})
res, err := domain.NewListEnnoblementsWithRelationsResult(nil, domain.EnnoblementWithRelations{})
require.NoError(t, err)
assert.Zero(t, res.Players())
assert.Zero(t, res.Ennoblements())
assert.True(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
})

View File

@ -81,6 +81,19 @@ func (tc TribeChange) CreatedAt() time.Time {
return tc.createdAt
}
func (tc TribeChange) WithRelations(
player PlayerMeta,
oldTribe NullTribeMeta,
newTribe NullTribeMeta,
) TribeChangeWithRelations {
return TribeChangeWithRelations{
tribeChange: tc,
player: player,
oldTribe: oldTribe,
newTribe: newTribe,
}
}
func (tc TribeChange) ToCursor() (TribeChangeCursor, error) {
return NewTribeChangeCursor(tc.id, tc.serverKey, tc.createdAt)
}
@ -91,6 +104,35 @@ func (tc TribeChange) IsZero() bool {
type TribeChanges []TribeChange
type TribeChangeWithRelations struct {
tribeChange TribeChange
player PlayerMeta
oldTribe NullTribeMeta
newTribe NullTribeMeta
}
func (tc TribeChangeWithRelations) TribeChange() TribeChange {
return tc.tribeChange
}
func (tc TribeChangeWithRelations) Player() PlayerMeta {
return tc.player
}
func (tc TribeChangeWithRelations) OldTribe() NullTribeMeta {
return tc.oldTribe
}
func (tc TribeChangeWithRelations) NewTribe() NullTribeMeta {
return tc.newTribe
}
func (tc TribeChangeWithRelations) IsZero() bool {
return tc.tribeChange.IsZero()
}
type TribeChangesWithRelations []TribeChangeWithRelations
type CreateTribeChangeParams struct {
serverKey string
playerID int
@ -460,14 +502,14 @@ type ListTribeChangesResult struct {
const listTribeChangesResultModelName = "ListTribeChangesResult"
func NewListTribeChangesResult(ennoblements TribeChanges, next TribeChange) (ListTribeChangesResult, error) {
func NewListTribeChangesResult(tribeChanges TribeChanges, next TribeChange) (ListTribeChangesResult, error) {
var err error
res := ListTribeChangesResult{
tribeChanges: ennoblements,
tribeChanges: tribeChanges,
}
if len(ennoblements) > 0 {
res.self, err = ennoblements[0].ToCursor()
if len(tribeChanges) > 0 {
res.self, err = tribeChanges[0].ToCursor()
if err != nil {
return ListTribeChangesResult{}, ValidationError{
Model: listTribeChangesResultModelName,
@ -502,3 +544,57 @@ func (res ListTribeChangesResult) Self() TribeChangeCursor {
func (res ListTribeChangesResult) Next() TribeChangeCursor {
return res.next
}
type ListTribeChangesWithRelationsResult struct {
tribeChanges TribeChangesWithRelations
self TribeChangeCursor
next TribeChangeCursor
}
const listTribeChangesWithRelationsResultModelName = "ListTribeChangesWithRelationsResult"
func NewListTribeChangesWithRelationsResult(
tribeChanges TribeChangesWithRelations,
next TribeChangeWithRelations,
) (ListTribeChangesWithRelationsResult, error) {
var err error
res := ListTribeChangesWithRelationsResult{
tribeChanges: tribeChanges,
}
if len(tribeChanges) > 0 {
res.self, err = tribeChanges[0].TribeChange().ToCursor()
if err != nil {
return ListTribeChangesWithRelationsResult{}, ValidationError{
Model: listTribeChangesWithRelationsResultModelName,
Field: "self",
Err: err,
}
}
}
if !next.IsZero() {
res.next, err = next.TribeChange().ToCursor()
if err != nil {
return ListTribeChangesWithRelationsResult{}, ValidationError{
Model: listTribeChangesWithRelationsResultModelName,
Field: "next",
Err: err,
}
}
}
return res, nil
}
func (res ListTribeChangesWithRelationsResult) TribeChanges() TribeChangesWithRelations {
return res.tribeChanges
}
func (res ListTribeChangesWithRelationsResult) Self() TribeChangeCursor {
return res.self
}
func (res ListTribeChangesWithRelationsResult) Next() TribeChangeCursor {
return res.next
}

View File

@ -694,3 +694,50 @@ func TestNewListTribeChangesResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
}
func TestNewListTribeChangesWithRelationsResult(t *testing.T) {
t.Parallel()
tcs := domain.TribeChangesWithRelations{
domaintest.NewTribeChangeWithRelations(t),
domaintest.NewTribeChangeWithRelations(t),
domaintest.NewTribeChangeWithRelations(t),
}
next := domaintest.NewTribeChangeWithRelations(t)
t.Run("OK: with next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribeChangesWithRelationsResult(tcs, next)
require.NoError(t, err)
assert.Equal(t, tcs, res.TribeChanges())
assert.Equal(t, tcs[0].TribeChange().ID(), res.Self().ID())
assert.Equal(t, tcs[0].TribeChange().ServerKey(), res.Self().ServerKey())
assert.Equal(t, tcs[0].TribeChange().CreatedAt(), res.Self().CreatedAt())
assert.Equal(t, next.TribeChange().ID(), res.Next().ID())
assert.Equal(t, next.TribeChange().ServerKey(), res.Next().ServerKey())
assert.Equal(t, next.TribeChange().CreatedAt(), res.Next().CreatedAt())
})
t.Run("OK: without next", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribeChangesWithRelationsResult(tcs, domain.TribeChangeWithRelations{})
require.NoError(t, err)
assert.Equal(t, tcs, res.TribeChanges())
assert.Equal(t, tcs[0].TribeChange().ID(), res.Self().ID())
assert.Equal(t, tcs[0].TribeChange().ServerKey(), res.Self().ServerKey())
assert.Equal(t, tcs[0].TribeChange().CreatedAt(), res.Self().CreatedAt())
assert.True(t, res.Next().IsZero())
})
t.Run("OK: 0 tribe changes", func(t *testing.T) {
t.Parallel()
res, err := domain.NewListTribeChangesWithRelationsResult(nil, domain.TribeChangeWithRelations{})
require.NoError(t, err)
assert.Zero(t, res.TribeChanges())
assert.True(t, res.Self().IsZero())
assert.True(t, res.Next().IsZero())
})
}