feat: server snapshots
This commit is contained in:
parent
e3fa23d0c4
commit
3eef71ced3
|
@ -509,7 +509,7 @@ issues:
|
|||
text: add-constant
|
||||
- path: bun/migrations
|
||||
linters:
|
||||
- init
|
||||
- gochecknoinits
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
|
|
|
@ -6,7 +6,7 @@ repos:
|
|||
stages: [commit-msg]
|
||||
additional_dependencies: ["@commitlint/config-conventional"]
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.58.0
|
||||
rev: v1.58.1
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/hadolint/hadolint
|
||||
|
|
2
Makefile
2
Makefile
|
@ -16,7 +16,7 @@ install-git-hooks:
|
|||
.PHONY: install-golangci-lint
|
||||
install-golangci-lint:
|
||||
@echo "Installing github.com/golangci/golangci-lint..."
|
||||
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.58.0
|
||||
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.58.1
|
||||
|
||||
.PHONY: install-oapi-codegen
|
||||
install-oapi-codegen:
|
||||
|
|
|
@ -49,18 +49,32 @@ var cmdConsumer = &cli.Command{
|
|||
c.String(rmqFlagTopicSyncServersCmd.Name),
|
||||
c.String(rmqFlagTopicServerSyncedEvent.Name),
|
||||
)
|
||||
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
publisher,
|
||||
marshaler,
|
||||
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
|
||||
"",
|
||||
)
|
||||
|
||||
twSvc, err := newTWServiceFromFlags(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverSvc := app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher)
|
||||
serverSnapshotSvc := app.NewServerSnapshotService(
|
||||
adapter.NewServerSnapshotBunRepository(db),
|
||||
serverSvc,
|
||||
serverSnapshotPublisher,
|
||||
)
|
||||
|
||||
consumer := port.NewServerWatermillConsumer(
|
||||
app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher),
|
||||
serverSvc,
|
||||
serverSnapshotSvc,
|
||||
subscriber,
|
||||
logger,
|
||||
marshaler,
|
||||
c.String(rmqFlagTopicSyncServersCmd.Name),
|
||||
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
|
||||
c.String(rmqFlagTopicServerSyncedEvent.Name),
|
||||
c.String(rmqFlagTopicTribesSyncedEvent.Name),
|
||||
c.String(rmqFlagTopicPlayersSyncedEvent.Name),
|
||||
|
|
|
@ -171,13 +171,18 @@ var (
|
|||
}
|
||||
defer closeBunDB(bunDB, logger)
|
||||
|
||||
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
publisher,
|
||||
newWatermillMarshaler(),
|
||||
c.String(rmqFlagTopicCreateServerSnapshotCmd.Name),
|
||||
"",
|
||||
)
|
||||
tribeSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
publisher,
|
||||
newWatermillMarshaler(),
|
||||
c.String(rmqFlagTopicCreateTribeSnapshotsCmd.Name),
|
||||
c.String(rmqFlagTopicTribeSnapshotsCreatedEvent.Name),
|
||||
)
|
||||
|
||||
playerSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
publisher,
|
||||
newWatermillMarshaler(),
|
||||
|
@ -187,7 +192,13 @@ var (
|
|||
|
||||
versionSvc := app.NewVersionService(adapter.NewVersionBunRepository(bunDB))
|
||||
serverSvc := app.NewServerService(adapter.NewServerBunRepository(bunDB), nil, nil)
|
||||
snapshotSvc := app.NewSnapshotService(versionSvc, serverSvc, tribeSnapshotPublisher, playerSnapshotPublisher)
|
||||
snapshotSvc := app.NewSnapshotService(
|
||||
versionSvc,
|
||||
serverSvc,
|
||||
serverSnapshotPublisher,
|
||||
tribeSnapshotPublisher,
|
||||
playerSnapshotPublisher,
|
||||
)
|
||||
|
||||
shutdownSignalCtx, stop := newShutdownSignalContext(c.Context)
|
||||
defer stop()
|
||||
|
|
|
@ -31,6 +31,11 @@ var (
|
|||
Value: "tribes.event.synced",
|
||||
EnvVars: []string{"RABBITMQ_TOPIC_TRIBES_SYNCED_EVENT"},
|
||||
}
|
||||
rmqFlagTopicCreateServerSnapshotCmd = &cli.StringFlag{
|
||||
Name: "rabbitmq.topic.createServerSnapshotCmd",
|
||||
Value: "servers.cmd.create_snapshot",
|
||||
EnvVars: []string{"RABBITMQ_TOPIC_CREATE_SERVER_SNAPSHOT_CMD"},
|
||||
}
|
||||
rmqFlagTopicCreateTribeSnapshotsCmd = &cli.StringFlag{
|
||||
Name: "rabbitmq.topic.createTribeSnapshotsCmd",
|
||||
Value: "tribes.cmd.create_snapshots",
|
||||
|
@ -79,6 +84,7 @@ var (
|
|||
rmqFlags = []cli.Flag{
|
||||
rmqFlagConnectionString,
|
||||
rmqFlagTopicSyncServersCmd,
|
||||
rmqFlagTopicCreateServerSnapshotCmd,
|
||||
rmqFlagTopicServerSyncedEvent,
|
||||
rmqFlagTopicTribesSyncedEvent,
|
||||
rmqFlagTopicCreateTribeSnapshotsCmd,
|
||||
|
|
|
@ -111,7 +111,7 @@ func (repo *EnnoblementBunRepository) ListWithRelations(
|
|||
|
||||
func (repo *EnnoblementBunRepository) Delete(ctx context.Context, serverKey string, createdAtLTE time.Time) error {
|
||||
if _, err := repo.db.NewDelete().
|
||||
Model(&bunmodel.Ennoblement{}).
|
||||
Model((*bunmodel.Ennoblement)(nil)).
|
||||
Where("server_key = ?", serverKey).
|
||||
Where("created_at <= ?", createdAtLTE).
|
||||
Returning("NULL").
|
||||
|
|
|
@ -109,7 +109,7 @@ func (repo *PlayerSnapshotBunRepository) ListWithRelations(
|
|||
|
||||
func (repo *PlayerSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
|
||||
if _, err := repo.db.NewDelete().
|
||||
Model(&bunmodel.PlayerSnapshot{}).
|
||||
Model((*bunmodel.PlayerSnapshot)(nil)).
|
||||
Where("server_key = ?", serverKey).
|
||||
Where("date <= ?", dateLTE).
|
||||
Returning("NULL").
|
||||
|
|
|
@ -182,6 +182,10 @@ func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
|
|||
q = q.Set("ennoblement_data_synced_at = ?", ennoblementDataSyncedAt.V)
|
||||
}
|
||||
|
||||
if snapshotCreatedAt := a.params.SnapshotCreatedAt(); snapshotCreatedAt.Valid {
|
||||
q = q.Set("snapshot_created_at = ?", snapshotCreatedAt.V)
|
||||
}
|
||||
|
||||
if tribeSnapshotsCreatedAt := a.params.TribeSnapshotsCreatedAt(); tribeSnapshotsCreatedAt.Valid {
|
||||
q = q.Set("tribe_snapshots_created_at = ?", tribeSnapshotsCreatedAt.V)
|
||||
}
|
||||
|
@ -228,6 +232,13 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
)
|
||||
}
|
||||
|
||||
if snapshotCreatedAt := a.params.SnapshotCreatedAtLT(); snapshotCreatedAt.Valid {
|
||||
q = q.Where(
|
||||
"server.snapshot_created_at < ? OR server.snapshot_created_at is null",
|
||||
snapshotCreatedAt.V,
|
||||
)
|
||||
}
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
column, dir, err := a.sortToColumnAndDirection(s)
|
||||
if err != nil {
|
||||
|
|
179
internal/adapter/repository_bun_server_snapshot.go
Normal file
179
internal/adapter/repository_bun_server_snapshot.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bun/bunmodel"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type ServerSnapshotBunRepository struct {
|
||||
db bun.IDB
|
||||
}
|
||||
|
||||
func NewServerSnapshotBunRepository(db bun.IDB) *ServerSnapshotBunRepository {
|
||||
return &ServerSnapshotBunRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *ServerSnapshotBunRepository) Create(
|
||||
ctx context.Context,
|
||||
params ...domain.CreateServerSnapshotParams,
|
||||
) error {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
snapshots := make(bunmodel.ServerSnapshots, 0, len(params))
|
||||
|
||||
for _, p := range params {
|
||||
snapshots = append(snapshots, bunmodel.ServerSnapshot{
|
||||
ServerKey: p.ServerKey(),
|
||||
NumPlayers: p.NumPlayers(),
|
||||
NumActivePlayers: p.NumActivePlayers(),
|
||||
NumInactivePlayers: p.NumInactivePlayers(),
|
||||
NumTribes: p.NumTribes(),
|
||||
NumActiveTribes: p.NumActiveTribes(),
|
||||
NumInactiveTribes: p.NumInactiveTribes(),
|
||||
NumVillages: p.NumVillages(),
|
||||
NumPlayerVillages: p.NumPlayerVillages(),
|
||||
NumBarbarianVillages: p.NumBarbarianVillages(),
|
||||
NumBonusVillages: p.NumBonusVillages(),
|
||||
Date: p.Date(),
|
||||
CreatedAt: now,
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := repo.db.NewInsert().
|
||||
Model(&snapshots).
|
||||
Ignore().
|
||||
Returning("").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("something went wrong while inserting server snapshots into the db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *ServerSnapshotBunRepository) List(
|
||||
ctx context.Context,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
) (domain.ListServerSnapshotsResult, error) {
|
||||
var serverSnapshots bunmodel.ServerSnapshots
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&serverSnapshots).
|
||||
Apply(listServerSnapshotsParamsApplier{params: params}.apply).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.ListServerSnapshotsResult{}, fmt.Errorf("couldn't select server snapshots from the db: %w", err)
|
||||
}
|
||||
|
||||
converted, err := serverSnapshots.ToDomain()
|
||||
if err != nil {
|
||||
return domain.ListServerSnapshotsResult{}, err
|
||||
}
|
||||
|
||||
return domain.NewListServerSnapshotsResult(separateListResultAndNext(converted, params.Limit()))
|
||||
}
|
||||
|
||||
func (repo *ServerSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
|
||||
if _, err := repo.db.NewDelete().
|
||||
Model((*bunmodel.ServerSnapshot)(nil)).
|
||||
Where("server_key = ?", serverKey).
|
||||
Where("date <= ?", dateLTE).
|
||||
Returning("NULL").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't delete server snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listServerSnapshotsParamsApplier struct {
|
||||
params domain.ListServerSnapshotsParams
|
||||
}
|
||||
|
||||
func (a listServerSnapshotsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
|
||||
q = q.Where("ss.server_key IN (?)", bun.In(serverKeys))
|
||||
}
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
column, dir, err := a.sortToColumnAndDirection(s)
|
||||
if err != nil {
|
||||
return q.Err(err)
|
||||
}
|
||||
|
||||
q.OrderExpr("? ?", column, dir.Bun())
|
||||
}
|
||||
|
||||
return q.Limit(a.params.Limit() + 1).Apply(a.applyCursor)
|
||||
}
|
||||
|
||||
func (a listServerSnapshotsParamsApplier) 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.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortIDDESC:
|
||||
el.value = cursor.ID()
|
||||
el.unique = true
|
||||
case domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortServerKeyDESC:
|
||||
el.value = cursor.ServerKey()
|
||||
case domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateDESC:
|
||||
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 listServerSnapshotsParamsApplier) sortToColumnAndDirection(
|
||||
s domain.ServerSnapshotSort,
|
||||
) (bun.Safe, sortDirection, error) {
|
||||
switch s {
|
||||
case domain.ServerSnapshotSortDateASC:
|
||||
return "ss.date", sortDirectionASC, nil
|
||||
case domain.ServerSnapshotSortDateDESC:
|
||||
return "ss.date", sortDirectionDESC, nil
|
||||
case domain.ServerSnapshotSortIDASC:
|
||||
return "ss.id", sortDirectionASC, nil
|
||||
case domain.ServerSnapshotSortIDDESC:
|
||||
return "ss.id", sortDirectionDESC, nil
|
||||
case domain.ServerSnapshotSortServerKeyASC:
|
||||
return "ss.server_key", sortDirectionASC, nil
|
||||
case domain.ServerSnapshotSortServerKeyDESC:
|
||||
return "ss.server_key", sortDirectionDESC, nil
|
||||
default:
|
||||
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
|
||||
}
|
||||
}
|
29
internal/adapter/repository_bun_server_snapshot_test.go
Normal file
29
internal/adapter/repository_bun_server_snapshot_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bun/buntest"
|
||||
)
|
||||
|
||||
func TestServerSnapshotBunRepository_Postgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping long-running test")
|
||||
}
|
||||
|
||||
testServerSnapshotRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, postgres.NewDB(t))
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerSnapshotBunRepository_SQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testServerSnapshotRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, buntest.NewSQLiteDB(t))
|
||||
})
|
||||
}
|
|
@ -105,7 +105,7 @@ func (repo *TribeSnapshotBunRepository) ListWithRelations(
|
|||
|
||||
func (repo *TribeSnapshotBunRepository) Delete(ctx context.Context, serverKey string, dateLTE time.Time) error {
|
||||
if _, err := repo.db.NewDelete().
|
||||
Model(&bunmodel.TribeSnapshot{}).
|
||||
Model((*bunmodel.TribeSnapshot)(nil)).
|
||||
Where("server_key = ?", serverKey).
|
||||
Where("date <= ?", dateLTE).
|
||||
Returning("NULL").
|
||||
|
|
|
@ -71,12 +71,14 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo
|
|||
key := fmt.Sprintf("%s-%d-%s", p.ServerKey(), p.PlayerID(), p.Date().Format(dateFormat))
|
||||
|
||||
for i, ps := range playerSnapshots {
|
||||
if ps.ServerKey() == p.ServerKey() && ps.PlayerID() == p.PlayerID() && ps.Date().Equal(p.Date()) {
|
||||
//nolint:lll
|
||||
if ps.ServerKey() == p.ServerKey() && ps.PlayerID() == p.PlayerID() && ps.Date().Format(dateFormat) == p.Date().Format(dateFormat) {
|
||||
m[key] = append(m[key], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, m)
|
||||
for key, indexes := range m {
|
||||
assert.Len(t, indexes, 1, key)
|
||||
}
|
||||
|
|
445
internal/adapter/repository_server_snapshot_test.go
Normal file
445
internal/adapter/repository_server_snapshot_test.go
Normal file
|
@ -0,0 +1,445 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testServerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const dateFormat = "2006-01-02"
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
assertCreated := func(t *testing.T, params domain.CreateServerSnapshotParams) {
|
||||
t.Helper()
|
||||
|
||||
require.NotEmpty(t, params)
|
||||
|
||||
listParams := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, listParams.SetServerKeys([]string{params.ServerKey()}))
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, listParams)
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
require.NoError(t, err)
|
||||
|
||||
date := params.Date().Format(dateFormat)
|
||||
|
||||
idx := slices.IndexFunc(serverSnapshots, func(ss domain.ServerSnapshot) bool {
|
||||
return ss.ServerKey() == params.ServerKey() &&
|
||||
ss.Date().Format(dateFormat) == date
|
||||
})
|
||||
require.GreaterOrEqual(t, idx, 0)
|
||||
serverSnapshot := serverSnapshots[idx]
|
||||
|
||||
assert.Equal(t, params.ServerKey(), serverSnapshot.ServerKey())
|
||||
assert.Equal(t, params.NumPlayers(), serverSnapshot.NumPlayers())
|
||||
assert.Equal(t, params.NumActivePlayers(), serverSnapshot.NumActivePlayers())
|
||||
assert.Equal(t, params.NumInactivePlayers(), serverSnapshot.NumInactivePlayers())
|
||||
assert.Equal(t, params.NumTribes(), serverSnapshot.NumTribes())
|
||||
assert.Equal(t, params.NumActiveTribes(), serverSnapshot.NumActiveTribes())
|
||||
assert.Equal(t, params.NumInactiveTribes(), serverSnapshot.NumInactiveTribes())
|
||||
assert.Equal(t, params.NumVillages(), serverSnapshot.NumVillages())
|
||||
assert.Equal(t, params.NumPlayerVillages(), serverSnapshot.NumPlayerVillages())
|
||||
assert.Equal(t, params.NumBarbarianVillages(), serverSnapshot.NumBarbarianVillages())
|
||||
assert.Equal(t, params.NumBonusVillages(), serverSnapshot.NumBonusVillages())
|
||||
assert.Equal(t, date, serverSnapshot.Date().Format(dateFormat))
|
||||
assert.WithinDuration(t, time.Now(), serverSnapshot.CreatedAt(), time.Minute)
|
||||
}
|
||||
|
||||
assertNoDuplicates := func(t *testing.T, params domain.CreateServerSnapshotParams) {
|
||||
t.Helper()
|
||||
|
||||
listParams := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, listParams.SetServerKeys([]string{params.ServerKey()}))
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, listParams)
|
||||
require.NoError(t, err)
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
|
||||
var indexes []int
|
||||
|
||||
for i, ss := range serverSnapshots {
|
||||
if ss.ServerKey() == params.ServerKey() && ss.Date().Format(dateFormat) == params.Date().Format(dateFormat) {
|
||||
indexes = append(indexes, i)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Len(t, indexes, 1)
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
listServersParams := domain.NewListServersParams()
|
||||
require.NoError(t, listServersParams.SetOpen(domain.NullBool{
|
||||
V: true,
|
||||
Valid: true,
|
||||
}))
|
||||
require.NoError(t, listServersParams.SetLimit(1))
|
||||
|
||||
res, err := repos.server.List(ctx, listServersParams)
|
||||
require.NoError(t, err)
|
||||
servers := res.Servers()
|
||||
require.NotEmpty(t, servers)
|
||||
|
||||
date := time.Now()
|
||||
|
||||
createParams, err := domain.NewCreateServerSnapshotParams(servers[0], date)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.serverSnapshot.Create(ctx, createParams))
|
||||
assertCreated(t, createParams)
|
||||
|
||||
require.NoError(t, repos.serverSnapshot.Create(ctx, createParams))
|
||||
assertNoDuplicates(t, createParams)
|
||||
})
|
||||
|
||||
t.Run("OK: len(params) == 0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.NoError(t, repos.serverSnapshot.Create(ctx))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListServerSnapshotsParams
|
||||
assertResult func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult)
|
||||
assertError func(t *testing.T, err error)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
return domain.NewListServerSnapshotsParams()
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
a.Date().Compare(b.Date()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[serverKey DESC, date DESC, id DESC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyDESC,
|
||||
domain.ServerSnapshotSortDateDESC,
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
a.Date().Compare(b.Date()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
) * -1
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[id ASC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[id DESC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
_ domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Compare(a.ID(), b.ID()) * -1
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: serverKeys",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.ServerSnapshots())
|
||||
randServerSnapshot := res.ServerSnapshots()[0]
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randServerSnapshot.ServerKey()}))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotZero(t, serverSnapshots)
|
||||
for _, ss := range serverSnapshots {
|
||||
assert.True(t, slices.Contains(serverKeys, ss.ServerKey()))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor serverKeys sort=[id ASC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.ServerSnapshots()), 2)
|
||||
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC}))
|
||||
require.NoError(t, params.SetServerKeys([]string{res.ServerSnapshots()[1].ServerKey()}))
|
||||
cursor, err := res.ServerSnapshots()[1].ToCursor()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, params.SetCursor(cursor))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
|
||||
t.Helper()
|
||||
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
for _, ss := range serverSnapshots {
|
||||
assert.GreaterOrEqual(t, ss.ID(), params.Cursor().ID())
|
||||
assert.True(t, slices.Contains(serverKeys, ss.ServerKey()))
|
||||
}
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.ServerSnapshots()), 2)
|
||||
|
||||
cursor, err := res.ServerSnapshots()[1].ToCursor()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, params.SetCursor(cursor))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.GreaterOrEqual(t, serverSnapshots[0].ID(), params.Cursor().ID())
|
||||
for _, ss := range serverSnapshots {
|
||||
assert.GreaterOrEqual(t, ss.ServerKey(), params.Cursor().ServerKey())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[serverKey DESC, id DESC]",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyDESC,
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
}))
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.ServerSnapshots()), 2)
|
||||
|
||||
cursor, err := res.ServerSnapshots()[1].ToCursor()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, params.SetCursor(cursor))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListServerSnapshotsParams, res domain.ListServerSnapshotsResult) {
|
||||
t.Helper()
|
||||
serverSnapshots := res.ServerSnapshots()
|
||||
assert.NotEmpty(t, serverSnapshots)
|
||||
assert.True(t, slices.IsSortedFunc(serverSnapshots, func(a, b domain.ServerSnapshot) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
) * -1
|
||||
}))
|
||||
assert.LessOrEqual(t, serverSnapshots[0].ID(), params.Cursor().ID())
|
||||
for _, ss := range serverSnapshots {
|
||||
assert.LessOrEqual(t, ss.ServerKey(), params.Cursor().ServerKey())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=2",
|
||||
params: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetLimit(2))
|
||||
return params
|
||||
},
|
||||
assertResult: func(
|
||||
t *testing.T,
|
||||
params domain.ListServerSnapshotsParams,
|
||||
res domain.ListServerSnapshotsResult,
|
||||
) {
|
||||
t.Helper()
|
||||
assert.Len(t, res.ServerSnapshots(), params.Limit())
|
||||
assert.False(t, res.Self().IsZero())
|
||||
assert.False(t, res.Next().IsZero())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assertError := tt.assertError
|
||||
if assertError == nil {
|
||||
assertError = func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
params := tt.params(t)
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
assertError(t, err)
|
||||
tt.assertResult(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, res.ServerSnapshots())
|
||||
|
||||
randSnapshot := res.ServerSnapshots()[0]
|
||||
|
||||
require.NoError(t, repos.serverSnapshot.Delete(ctx, randSnapshot.ServerKey(), randSnapshot.Date()))
|
||||
|
||||
require.NoError(t, params.SetServerKeys([]string{randSnapshot.ServerKey()}))
|
||||
|
||||
res, err = repos.serverSnapshot.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, res.ServerSnapshots())
|
||||
for _, ss := range res.ServerSnapshots() {
|
||||
assert.True(t, ss.Date().After(randSnapshot.Date()))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -281,6 +281,26 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: snapshotCreatedAt=" + snapshotsCreatedAtLT.Format(time.RFC3339),
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetSnapshotCreatedAtLT(domain.NullTime{
|
||||
V: snapshotsCreatedAtLT,
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListServersParams, res domain.ListServersResult) {
|
||||
t.Helper()
|
||||
servers := res.Servers()
|
||||
assert.NotEmpty(t, servers)
|
||||
for _, s := range servers {
|
||||
assert.True(t, s.SnapshotCreatedAt().Before(snapshotsCreatedAtLT))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: playerSnapshotsCreatedAtLt=" + snapshotsCreatedAtLT.Format(time.RFC3339),
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
|
@ -550,6 +570,10 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
V: time.Now(),
|
||||
Valid: true,
|
||||
}))
|
||||
require.NoError(t, updateParams.SetSnapshotCreatedAt(domain.NullTime{
|
||||
V: time.Now(),
|
||||
Valid: true,
|
||||
}))
|
||||
require.NoError(t, updateParams.SetTribeSnapshotsCreatedAt(domain.NullTime{
|
||||
V: time.Now(),
|
||||
Valid: true,
|
||||
|
@ -606,6 +630,12 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
serverAfterUpdate.EnnoblementDataSyncedAt(),
|
||||
time.Minute,
|
||||
)
|
||||
assert.WithinDuration(
|
||||
t,
|
||||
updateParams.SnapshotCreatedAt().V,
|
||||
serverAfterUpdate.SnapshotCreatedAt(),
|
||||
time.Minute,
|
||||
)
|
||||
assert.WithinDuration(
|
||||
t,
|
||||
updateParams.TribeSnapshotsCreatedAt().V,
|
||||
|
|
|
@ -67,6 +67,12 @@ type tribeChangeRepository interface {
|
|||
) (domain.ListTribeChangesWithRelationsResult, error)
|
||||
}
|
||||
|
||||
type serverSnapshotRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
|
||||
List(ctx context.Context, params domain.ListServerSnapshotsParams) (domain.ListServerSnapshotsResult, error)
|
||||
Delete(ctx context.Context, serverKey string, dateLTE time.Time) error
|
||||
}
|
||||
|
||||
type tribeSnapshotRepository interface {
|
||||
Create(ctx context.Context, params ...domain.CreateTribeSnapshotParams) error
|
||||
List(ctx context.Context, params domain.ListTribeSnapshotsParams) (domain.ListTribeSnapshotsResult, error)
|
||||
|
@ -95,6 +101,7 @@ type repositories struct {
|
|||
village villageRepository
|
||||
ennoblement ennoblementRepository
|
||||
tribeChange tribeChangeRepository
|
||||
serverSnapshot serverSnapshotRepository
|
||||
tribeSnapshot tribeSnapshotRepository
|
||||
playerSnapshot playerSnapshotRepository
|
||||
}
|
||||
|
@ -112,6 +119,7 @@ func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
|||
village: adapter.NewVillageBunRepository(bunDB),
|
||||
ennoblement: adapter.NewEnnoblementBunRepository(bunDB),
|
||||
tribeChange: adapter.NewTribeChangeBunRepository(bunDB),
|
||||
serverSnapshot: adapter.NewServerSnapshotBunRepository(bunDB),
|
||||
tribeSnapshot: adapter.NewTribeSnapshotBunRepository(bunDB),
|
||||
playerSnapshot: adapter.NewPlayerSnapshotBunRepository(bunDB),
|
||||
}
|
||||
|
|
|
@ -73,12 +73,14 @@ func testTribeSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repos
|
|||
key := fmt.Sprintf("%s-%d-%s", p.ServerKey(), p.TribeID(), p.Date().Format(dateFormat))
|
||||
|
||||
for i, ts := range tribeSnapshots {
|
||||
if ts.ServerKey() == p.ServerKey() && ts.TribeID() == p.TribeID() && ts.Date().Equal(p.Date()) {
|
||||
//nolint:lll
|
||||
if ts.ServerKey() == p.ServerKey() && ts.TribeID() == p.TribeID() && ts.Date().Format(dateFormat) == p.Date().Format(dateFormat) {
|
||||
m[key] = append(m[key], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, m)
|
||||
for key, indexes := range m {
|
||||
assert.Len(t, indexes, 1, key)
|
||||
}
|
||||
|
|
62
internal/adapter/testdata/fixture.yml
vendored
62
internal/adapter/testdata/fixture.yml
vendored
|
@ -12,6 +12,7 @@
|
|||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
created_at: 2022-03-19T12:00:54.000Z
|
||||
snapshot_created_at: 2022-03-19T12:00:54.000Z
|
||||
player_data_synced_at: 2022-03-19T12:00:54.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:00:54.000Z
|
||||
tribe_data_synced_at: 2022-03-19T12:00:54.000Z
|
||||
|
@ -31,6 +32,7 @@
|
|||
num_barbarian_villages: 700
|
||||
num_bonus_villages: 1024
|
||||
created_at: 2021-04-02T16:01:25.000Z
|
||||
snapshot_created_at: 2021-04-02T16:01:25.000Z
|
||||
player_data_synced_at: 2021-04-02T16:01:25.000Z
|
||||
player_snapshots_created_at: 2021-04-02T16:01:25.000Z
|
||||
tribe_data_synced_at: 2021-04-02T16:01:25.000Z
|
||||
|
@ -50,6 +52,7 @@
|
|||
num_barbarian_villages: 1682
|
||||
num_bonus_villages: 256
|
||||
created_at: 2022-03-19T12:00:04.000Z
|
||||
snapshot_created_at: 2022-03-19T12:00:04.000Z
|
||||
player_data_synced_at: 2022-03-19T12:00:04.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:00:04.000Z
|
||||
tribe_data_synced_at: 2022-03-19T12:00:04.000Z
|
||||
|
@ -69,6 +72,7 @@
|
|||
num_barbarian_villages: 1574
|
||||
num_bonus_villages: 2048
|
||||
created_at: 2022-03-19T12:01:39.000Z
|
||||
snapshot_created_at: 2022-03-19T12:01:39.000Z
|
||||
player_data_synced_at: 2022-03-19T12:01:39.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:01:39.000Z
|
||||
tribe_data_synced_at: 2022-03-19T12:01:39.000Z
|
||||
|
@ -7374,6 +7378,64 @@
|
|||
new_tribe_id: 2
|
||||
server_key: pl169
|
||||
created_at: 2021-09-10T20:01:11.000Z
|
||||
- model: ServerSnapshot
|
||||
rows:
|
||||
- id: 10000
|
||||
server_key: de188
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-01T05:15:25.154992Z
|
||||
created_at: 2024-05-01T05:15:25.154994Z
|
||||
- id: 10001
|
||||
server_key: de188
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-02T05:15:25.154992Z
|
||||
created_at: 2024-05-02T05:15:25.154994Z
|
||||
- id: 20000
|
||||
server_key: it70
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-03T05:15:25.154992Z
|
||||
created_at: 2024-05-03T05:15:25.154994Z
|
||||
- id: 20001
|
||||
server_key: it70
|
||||
num_players: 0
|
||||
num_active_players: 180
|
||||
num_inactive_players: 0
|
||||
num_tribes: 0
|
||||
num_active_tribes: 76
|
||||
num_inactive_tribes: 0
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
date: 2024-05-04T05:15:25.154992Z
|
||||
created_at: 2024-05-04T05:15:25.154994Z
|
||||
- model: TribeSnapshot
|
||||
rows:
|
||||
- rank_att: 1
|
||||
|
|
|
@ -298,6 +298,21 @@ func (svc *ServerService) UpdateEnnoblementDataSyncedAt(
|
|||
return svc.repo.Update(ctx, key, updateParams)
|
||||
}
|
||||
|
||||
func (svc *ServerService) UpdateSnapshotCreatedAt(
|
||||
ctx context.Context,
|
||||
key string,
|
||||
) error {
|
||||
var updateParams domain.UpdateServerParams
|
||||
if err := updateParams.SetSnapshotCreatedAt(domain.NullTime{
|
||||
V: time.Now(),
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("%s: %w", key, err)
|
||||
}
|
||||
|
||||
return svc.repo.Update(ctx, key, updateParams)
|
||||
}
|
||||
|
||||
func (svc *ServerService) UpdateTribeSnapshotsCreatedAt(
|
||||
ctx context.Context,
|
||||
payload domain.SnapshotsCreatedEventPayload,
|
||||
|
|
62
internal/app/service_server_snapshot.go
Normal file
62
internal/app/service_server_snapshot.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
)
|
||||
|
||||
type ServerSnapshotRepository interface {
|
||||
// Create persists tribe snapshots in a store (e.g. Postgres).
|
||||
// Duplicates are ignored.
|
||||
Create(ctx context.Context, params ...domain.CreateServerSnapshotParams) error
|
||||
}
|
||||
|
||||
type ServerSnapshotService struct {
|
||||
repo ServerSnapshotRepository
|
||||
serverSvc *ServerService
|
||||
pub SnapshotPublisher
|
||||
}
|
||||
|
||||
func NewServerSnapshotService(
|
||||
repo ServerSnapshotRepository,
|
||||
serverSvc *ServerService,
|
||||
pub SnapshotPublisher,
|
||||
) *ServerSnapshotService {
|
||||
return &ServerSnapshotService{repo: repo, serverSvc: serverSvc, pub: pub}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (svc *ServerSnapshotService) Create(
|
||||
ctx context.Context,
|
||||
createSnapshotsCmdPayload domain.CreateSnapshotsCmdPayload,
|
||||
) error {
|
||||
versionCode := createSnapshotsCmdPayload.VersionCode()
|
||||
serverKey := createSnapshotsCmdPayload.ServerKey()
|
||||
date := createSnapshotsCmdPayload.Date()
|
||||
|
||||
server, err := svc.serverSvc.GetNormalByVersionCodeAndServerKey(ctx, versionCode, serverKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", serverKey, err)
|
||||
}
|
||||
|
||||
if !server.Open() {
|
||||
return nil
|
||||
}
|
||||
|
||||
params, err := domain.NewCreateServerSnapshotParams(server, date)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", serverKey, err)
|
||||
}
|
||||
|
||||
if err = svc.repo.Create(ctx, params); err != nil {
|
||||
return fmt.Errorf("%s: %w", serverKey, err)
|
||||
}
|
||||
|
||||
if err = svc.serverSvc.UpdateSnapshotCreatedAt(ctx, serverKey); err != nil {
|
||||
return fmt.Errorf("%s: %w", serverKey, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
type SnapshotService struct {
|
||||
versionSvc *VersionService
|
||||
serverSvc *ServerService
|
||||
serverSnapshotPub SnapshotPublisher
|
||||
tribeSnapshotPub SnapshotPublisher
|
||||
playerSnapshotPub SnapshotPublisher
|
||||
}
|
||||
|
@ -19,14 +20,16 @@ type SnapshotService struct {
|
|||
func NewSnapshotService(
|
||||
versionSvc *VersionService,
|
||||
serverSvc *ServerService,
|
||||
tribeSnapshotPublisher SnapshotPublisher,
|
||||
playerSnapshotPublisher SnapshotPublisher,
|
||||
serverSnapshotPub SnapshotPublisher,
|
||||
tribeSnapshotPub SnapshotPublisher,
|
||||
playerSnapshotPub SnapshotPublisher,
|
||||
) *SnapshotService {
|
||||
return &SnapshotService{
|
||||
versionSvc: versionSvc,
|
||||
serverSvc: serverSvc,
|
||||
tribeSnapshotPub: tribeSnapshotPublisher,
|
||||
playerSnapshotPub: playerSnapshotPublisher,
|
||||
serverSnapshotPub: serverSnapshotPub,
|
||||
tribeSnapshotPub: tribeSnapshotPub,
|
||||
playerSnapshotPub: playerSnapshotPub,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +51,7 @@ func (svc *SnapshotService) Create(ctx context.Context) error {
|
|||
date := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
if loopErr = errors.Join(
|
||||
svc.publishServer(ctx, v, snapshotsCreatedAtLT, date),
|
||||
svc.publishTribe(ctx, v, snapshotsCreatedAtLT, date),
|
||||
svc.publishPlayer(ctx, v, snapshotsCreatedAtLT, date),
|
||||
); loopErr != nil {
|
||||
|
@ -58,23 +62,47 @@ func (svc *SnapshotService) Create(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (svc *SnapshotService) publishServer(
|
||||
ctx context.Context,
|
||||
v domain.Version,
|
||||
snapshotCreatedAtLT time.Time,
|
||||
date time.Time,
|
||||
) error {
|
||||
params, err := svc.baseParams(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = params.SetSnapshotCreatedAtLT(domain.NullTime{
|
||||
V: snapshotCreatedAtLT,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
servers, err := svc.serverSvc.ListAll(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payloads, err := svc.toPayload(v, servers, date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.serverSnapshotPub.CmdCreate(ctx, payloads...)
|
||||
}
|
||||
|
||||
func (svc *SnapshotService) publishTribe(
|
||||
ctx context.Context,
|
||||
v domain.Version,
|
||||
snapshotsCreatedAtLT time.Time,
|
||||
date time.Time,
|
||||
) error {
|
||||
params := domain.NewListServersParams()
|
||||
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
|
||||
params, err := svc.baseParams(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := params.SetOpen(domain.NullBool{
|
||||
V: true,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{
|
||||
if err = params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{
|
||||
V: snapshotsCreatedAtLT,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
|
@ -100,17 +128,11 @@ func (svc *SnapshotService) publishPlayer(
|
|||
snapshotsCreatedAtLT time.Time,
|
||||
date time.Time,
|
||||
) error {
|
||||
params := domain.NewListServersParams()
|
||||
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
|
||||
params, err := svc.baseParams(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := params.SetOpen(domain.NullBool{
|
||||
V: true,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{
|
||||
if err = params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{
|
||||
V: snapshotsCreatedAtLT,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
|
@ -130,6 +152,20 @@ func (svc *SnapshotService) publishPlayer(
|
|||
return svc.playerSnapshotPub.CmdCreate(ctx, payloads...)
|
||||
}
|
||||
|
||||
func (svc *SnapshotService) baseParams(v domain.Version) (domain.ListServersParams, error) {
|
||||
params := domain.NewListServersParams()
|
||||
if err := params.SetVersionCodes([]string{v.Code()}); err != nil {
|
||||
return domain.ListServersParams{}, err
|
||||
}
|
||||
if err := params.SetOpen(domain.NullBool{
|
||||
V: true,
|
||||
Valid: true,
|
||||
}); err != nil {
|
||||
return domain.ListServersParams{}, nil
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (svc *SnapshotService) toPayload(
|
||||
v domain.Version,
|
||||
servers domain.Servers,
|
||||
|
|
|
@ -30,6 +30,7 @@ type Server struct {
|
|||
BuildingInfo BuildingInfo `bun:"building_info"`
|
||||
UnitInfo UnitInfo `bun:"unit_info"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero"`
|
||||
SnapshotCreatedAt time.Time `bun:"snapshot_created_at,nullzero"`
|
||||
PlayerDataUpdatedAt time.Time `bun:"player_data_synced_at,nullzero"`
|
||||
PlayerSnapshotsCreatedAt time.Time `bun:"player_snapshots_created_at,nullzero"`
|
||||
TribeDataUpdatedAt time.Time `bun:"tribe_data_synced_at,nullzero"`
|
||||
|
@ -75,6 +76,7 @@ func (s Server) ToDomain() (domain.Server, error) {
|
|||
buildingInfo,
|
||||
unitInfo,
|
||||
s.CreatedAt,
|
||||
s.SnapshotCreatedAt,
|
||||
s.PlayerDataUpdatedAt,
|
||||
s.PlayerSnapshotsCreatedAt,
|
||||
s.TribeDataUpdatedAt,
|
||||
|
|
62
internal/bun/bunmodel/server_snapshot.go
Normal file
62
internal/bun/bunmodel/server_snapshot.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type ServerSnapshot struct {
|
||||
bun.BaseModel `bun:"table:server_snapshots,alias:ss"`
|
||||
|
||||
ID int `bun:"id,pk,autoincrement,identity"`
|
||||
ServerKey string `bun:"server_key,nullzero"`
|
||||
NumPlayers int `bun:"num_players"`
|
||||
NumActivePlayers int `bun:"num_active_players"`
|
||||
NumInactivePlayers int `bun:"num_inactive_players"`
|
||||
NumTribes int `bun:"num_tribes"`
|
||||
NumActiveTribes int `bun:"num_active_tribes"`
|
||||
NumInactiveTribes int `bun:"num_inactive_tribes"`
|
||||
NumVillages int `bun:"num_villages"`
|
||||
NumPlayerVillages int `bun:"num_player_villages"`
|
||||
NumBarbarianVillages int `bun:"num_barbarian_villages"`
|
||||
NumBonusVillages int `bun:"num_bonus_villages"`
|
||||
Date time.Time `bun:"date,nullzero"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero"`
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ToDomain() (domain.ServerSnapshot, error) {
|
||||
converted, err := domain.UnmarshalServerSnapshotFromDatabase(
|
||||
ss.ID,
|
||||
ss.ServerKey,
|
||||
ss.NumPlayers,
|
||||
ss.NumActivePlayers,
|
||||
ss.NumInactivePlayers,
|
||||
ss.NumTribes,
|
||||
ss.NumActiveTribes,
|
||||
ss.NumInactiveTribes,
|
||||
ss.NumVillages,
|
||||
ss.NumPlayerVillages,
|
||||
ss.NumBarbarianVillages,
|
||||
ss.NumBonusVillages,
|
||||
ss.Date,
|
||||
ss.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ServerSnapshot{}, fmt.Errorf(
|
||||
"couldn't construct domain.ServerSnapshot (id=%d): %w",
|
||||
ss.ID,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
type ServerSnapshots []ServerSnapshot
|
||||
|
||||
func (sss ServerSnapshots) ToDomain() (domain.ServerSnapshots, error) {
|
||||
return sliceToDomain(sss)
|
||||
}
|
|
@ -23,6 +23,7 @@ func NewFixture(bunDB *bun.DB) *Fixture {
|
|||
(*bunmodel.Village)(nil),
|
||||
(*bunmodel.Ennoblement)(nil),
|
||||
(*bunmodel.TribeChange)(nil),
|
||||
(*bunmodel.ServerSnapshot)(nil),
|
||||
(*bunmodel.TribeSnapshot)(nil),
|
||||
(*bunmodel.PlayerSnapshot)(nil),
|
||||
)
|
||||
|
|
|
@ -12,7 +12,11 @@ func init() {
|
|||
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
||||
var servers bunmodel.Servers
|
||||
|
||||
if err := db.NewSelect().Model(&servers).Where("special = false").Scan(ctx); err != nil {
|
||||
if err := db.NewSelect().
|
||||
Model(&servers).
|
||||
ExcludeColumn("snapshot_created_at").
|
||||
Where("special = false").
|
||||
Scan(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't select servers from the db: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE servers ADD snapshot_created_at timestamp with time zone")
|
||||
return err
|
||||
}, func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE servers DROP COLUMN snapshot_created_at")
|
||||
return err
|
||||
})
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, `
|
||||
create table if not exists server_snapshots
|
||||
(
|
||||
?ID_COL,
|
||||
server_key varchar(100) not null
|
||||
references servers,
|
||||
date date not null,
|
||||
created_at timestamp with time zone default CURRENT_TIMESTAMP not null,
|
||||
num_players bigint default 0,
|
||||
num_active_players bigint default 0,
|
||||
num_inactive_players bigint default 0,
|
||||
num_tribes bigint default 0,
|
||||
num_active_tribes bigint default 0,
|
||||
num_inactive_tribes bigint default 0,
|
||||
num_villages bigint default 0,
|
||||
num_player_villages bigint default 0,
|
||||
num_barbarian_villages bigint default 0,
|
||||
num_bonus_villages bigint default 0,
|
||||
unique (server_key, date)
|
||||
);
|
||||
`)
|
||||
return err
|
||||
}, func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "drop table if exists server_snapshots cascade;")
|
||||
return err
|
||||
})
|
||||
}
|
|
@ -97,6 +97,7 @@ func NewServer(tb TestingTB, opts ...func(cfg *ServerConfig)) domain.Server {
|
|||
NewBuildingInfo(tb),
|
||||
NewUnitInfo(tb),
|
||||
time.Now(),
|
||||
time.Now(),
|
||||
cfg.PlayerDataSyncedAt,
|
||||
cfg.PlayerSnapshotsCreatedAt,
|
||||
cfg.TribeDataSyncedAt,
|
||||
|
|
102
internal/domain/domaintest/server_snapshot.go
Normal file
102
internal/domain/domaintest/server_snapshot.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type ServerSnapshotCursorConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
func NewServerSnapshotCursor(tb TestingTB, opts ...func(cfg *ServerSnapshotCursorConfig)) domain.ServerSnapshotCursor {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &ServerSnapshotCursorConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
Date: gofakeit.Date(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
ssc, err := domain.NewServerSnapshotCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.Date,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return ssc
|
||||
}
|
||||
|
||||
type ServerSnapshotConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
NumPlayers int
|
||||
NumActivePlayers int
|
||||
NumInactivePlayers int
|
||||
NumTribes int
|
||||
NumActiveTribes int
|
||||
NumInactiveTribes int
|
||||
NumVillages int
|
||||
NumPlayerVillages int
|
||||
NumBarbarianVillages int
|
||||
NumBonusVillages int
|
||||
Date time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func NewServerSnapshot(tb TestingTB, opts ...func(cfg *ServerSnapshotConfig)) domain.ServerSnapshot {
|
||||
tb.Helper()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
cfg := &ServerSnapshotConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
NumPlayers: gofakeit.IntRange(1, 10000),
|
||||
NumActivePlayers: gofakeit.IntRange(1, 10000),
|
||||
NumInactivePlayers: gofakeit.IntRange(1, 10000),
|
||||
NumTribes: gofakeit.IntRange(1, 10000),
|
||||
NumActiveTribes: gofakeit.IntRange(1, 10000),
|
||||
NumInactiveTribes: gofakeit.IntRange(1, 10000),
|
||||
NumVillages: gofakeit.IntRange(1, 10000),
|
||||
NumPlayerVillages: gofakeit.IntRange(1, 10000),
|
||||
NumBarbarianVillages: gofakeit.IntRange(1, 10000),
|
||||
NumBonusVillages: gofakeit.IntRange(1, 10000),
|
||||
Date: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
ss, err := domain.UnmarshalServerSnapshotFromDatabase(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.NumPlayers,
|
||||
cfg.NumActivePlayers,
|
||||
cfg.NumInactivePlayers,
|
||||
cfg.NumTribes,
|
||||
cfg.NumActiveTribes,
|
||||
cfg.NumInactiveTribes,
|
||||
cfg.NumVillages,
|
||||
cfg.NumPlayerVillages,
|
||||
cfg.NumBarbarianVillages,
|
||||
cfg.NumBonusVillages,
|
||||
cfg.Date,
|
||||
cfg.CreatedAt,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return ss
|
||||
}
|
|
@ -27,14 +27,14 @@ func NewTribeSnapshotCursor(tb TestingTB, opts ...func(cfg *TribeSnapshotCursorC
|
|||
opt(cfg)
|
||||
}
|
||||
|
||||
psc, err := domain.NewTribeSnapshotCursor(
|
||||
tsc, err := domain.NewTribeSnapshotCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.Date,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return psc
|
||||
return tsc
|
||||
}
|
||||
|
||||
type TribeSnapshotConfig struct {
|
||||
|
|
|
@ -28,6 +28,7 @@ type Server struct {
|
|||
buildingInfo BuildingInfo
|
||||
unitInfo UnitInfo
|
||||
createdAt time.Time
|
||||
snapshotCreatedAt time.Time
|
||||
playerDataSyncedAt time.Time
|
||||
playerSnapshotsCreatedAt time.Time
|
||||
tribeDataSyncedAt time.Time
|
||||
|
@ -62,6 +63,7 @@ func UnmarshalServerFromDatabase(
|
|||
buildingInfo BuildingInfo,
|
||||
unitInfo UnitInfo,
|
||||
createdAt time.Time,
|
||||
snapshotCreatedAt time.Time,
|
||||
playerDataSyncedAt time.Time,
|
||||
playerSnapshotsCreatedAt time.Time,
|
||||
tribeDataSyncedAt time.Time,
|
||||
|
@ -114,6 +116,7 @@ func UnmarshalServerFromDatabase(
|
|||
buildingInfo: buildingInfo,
|
||||
unitInfo: unitInfo,
|
||||
createdAt: createdAt,
|
||||
snapshotCreatedAt: snapshotCreatedAt,
|
||||
playerDataSyncedAt: playerDataSyncedAt,
|
||||
playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
|
||||
tribeDataSyncedAt: tribeDataSyncedAt,
|
||||
|
@ -199,6 +202,10 @@ func (s Server) CreatedAt() time.Time {
|
|||
return s.createdAt
|
||||
}
|
||||
|
||||
func (s Server) SnapshotCreatedAt() time.Time {
|
||||
return s.snapshotCreatedAt
|
||||
}
|
||||
|
||||
func (s Server) PlayerDataSyncedAt() time.Time {
|
||||
return s.playerDataSyncedAt
|
||||
}
|
||||
|
@ -419,6 +426,7 @@ type UpdateServerParams struct {
|
|||
numBonusVillages NullInt
|
||||
villageDataSyncedAt NullTime
|
||||
ennoblementDataSyncedAt NullTime
|
||||
snapshotCreatedAt NullTime
|
||||
tribeSnapshotsCreatedAt NullTime
|
||||
playerSnapshotsCreatedAt NullTime
|
||||
}
|
||||
|
@ -688,6 +696,15 @@ func (params *UpdateServerParams) SetEnnoblementDataSyncedAt(ennoblementDataSync
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *UpdateServerParams) SnapshotCreatedAt() NullTime {
|
||||
return params.snapshotCreatedAt
|
||||
}
|
||||
|
||||
func (params *UpdateServerParams) SetSnapshotCreatedAt(snapshotCreatedAt NullTime) error {
|
||||
params.snapshotCreatedAt = snapshotCreatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *UpdateServerParams) TribeSnapshotsCreatedAt() NullTime {
|
||||
return params.tribeSnapshotsCreatedAt
|
||||
}
|
||||
|
@ -721,6 +738,7 @@ func (params *UpdateServerParams) IsZero() bool {
|
|||
!params.numBonusVillages.Valid &&
|
||||
!params.villageDataSyncedAt.Valid &&
|
||||
!params.ennoblementDataSyncedAt.Valid &&
|
||||
!params.snapshotCreatedAt.Valid &&
|
||||
!params.tribeSnapshotsCreatedAt.Valid &&
|
||||
!params.playerSnapshotsCreatedAt.Valid
|
||||
}
|
||||
|
@ -825,6 +843,7 @@ type ListServersParams struct {
|
|||
versionCodes []string
|
||||
open NullBool
|
||||
special NullBool
|
||||
snapshotCreatedAtLT NullTime
|
||||
tribeSnapshotsCreatedAtLT NullTime
|
||||
playerSnapshotsCreatedAtLT NullTime
|
||||
sort []ServerSort
|
||||
|
@ -908,6 +927,15 @@ func (params *ListServersParams) SetSpecial(special NullBool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SnapshotCreatedAtLT() NullTime {
|
||||
return params.snapshotCreatedAtLT
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetSnapshotCreatedAtLT(snapshotCreatedAtLT NullTime) error {
|
||||
params.snapshotCreatedAtLT = snapshotCreatedAtLT
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) TribeSnapshotsCreatedAtLT() NullTime {
|
||||
return params.tribeSnapshotsCreatedAtLT
|
||||
}
|
||||
|
|
578
internal/domain/server_snapshot.go
Normal file
578
internal/domain/server_snapshot.go
Normal file
|
@ -0,0 +1,578 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServerSnapshot struct {
|
||||
id int
|
||||
serverKey string
|
||||
numPlayers int
|
||||
numActivePlayers int
|
||||
numInactivePlayers int
|
||||
numTribes int
|
||||
numActiveTribes int
|
||||
numInactiveTribes int
|
||||
numVillages int
|
||||
numPlayerVillages int
|
||||
numBarbarianVillages int
|
||||
numBonusVillages int
|
||||
date time.Time
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
const serverSnapshotModelName = "ServerSnapshot"
|
||||
|
||||
// UnmarshalServerSnapshotFromDatabase unmarshals ServerSnapshot from the database.
|
||||
//
|
||||
// It should be used only for unmarshalling from the database!
|
||||
// You can't use UnmarshalServerSnapshotFromDatabase as constructor - It may put domain into the invalid state!
|
||||
func UnmarshalServerSnapshotFromDatabase(
|
||||
id int,
|
||||
serverKey string,
|
||||
numPlayers int,
|
||||
numActivePlayers int,
|
||||
numInactivePlayers int,
|
||||
numTribes int,
|
||||
numActiveTribes int,
|
||||
numInactiveTribes int,
|
||||
numVillages int,
|
||||
numPlayerVillages int,
|
||||
numBarbarianVillages int,
|
||||
numBonusVillages int,
|
||||
date time.Time,
|
||||
createdAt time.Time,
|
||||
) (ServerSnapshot, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return ServerSnapshot{}, ValidationError{
|
||||
Model: serverSnapshotModelName,
|
||||
Field: "id",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateServerKey(serverKey); err != nil {
|
||||
return ServerSnapshot{}, ValidationError{
|
||||
Model: serverSnapshotModelName,
|
||||
Field: "serverKey",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return ServerSnapshot{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
numPlayers: numPlayers,
|
||||
numActivePlayers: numActivePlayers,
|
||||
numInactivePlayers: numInactivePlayers,
|
||||
numTribes: numTribes,
|
||||
numActiveTribes: numActiveTribes,
|
||||
numInactiveTribes: numInactiveTribes,
|
||||
numVillages: numVillages,
|
||||
numPlayerVillages: numPlayerVillages,
|
||||
numBarbarianVillages: numBarbarianVillages,
|
||||
numBonusVillages: numBonusVillages,
|
||||
date: date,
|
||||
createdAt: createdAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ID() int {
|
||||
return ss.id
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ServerKey() string {
|
||||
return ss.serverKey
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumPlayers() int {
|
||||
return ss.numPlayers
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumActivePlayers() int {
|
||||
return ss.numActivePlayers
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumInactivePlayers() int {
|
||||
return ss.numInactivePlayers
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumTribes() int {
|
||||
return ss.numTribes
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumActiveTribes() int {
|
||||
return ss.numActiveTribes
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumInactiveTribes() int {
|
||||
return ss.numInactiveTribes
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumVillages() int {
|
||||
return ss.numVillages
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumPlayerVillages() int {
|
||||
return ss.numPlayerVillages
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumBarbarianVillages() int {
|
||||
return ss.numBarbarianVillages
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) NumBonusVillages() int {
|
||||
return ss.numBonusVillages
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) Date() time.Time {
|
||||
return ss.date
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) CreatedAt() time.Time {
|
||||
return ss.createdAt
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) ToCursor() (ServerSnapshotCursor, error) {
|
||||
return NewServerSnapshotCursor(ss.id, ss.serverKey, ss.date)
|
||||
}
|
||||
|
||||
func (ss ServerSnapshot) IsZero() bool {
|
||||
return ss == ServerSnapshot{}
|
||||
}
|
||||
|
||||
type ServerSnapshots []ServerSnapshot
|
||||
|
||||
type CreateServerSnapshotParams struct {
|
||||
serverKey string
|
||||
numPlayers int
|
||||
numActivePlayers int
|
||||
numInactivePlayers int
|
||||
numTribes int
|
||||
numActiveTribes int
|
||||
numInactiveTribes int
|
||||
numVillages int
|
||||
numPlayerVillages int
|
||||
numBarbarianVillages int
|
||||
numBonusVillages int
|
||||
date time.Time
|
||||
}
|
||||
|
||||
func NewCreateServerSnapshotParams(server Server, date time.Time) (CreateServerSnapshotParams, error) {
|
||||
if server.IsZero() {
|
||||
return CreateServerSnapshotParams{}, errors.New("given server is an empty struct")
|
||||
}
|
||||
|
||||
if !server.Open() {
|
||||
return CreateServerSnapshotParams{}, errors.New("given server is closed")
|
||||
}
|
||||
|
||||
return CreateServerSnapshotParams{
|
||||
serverKey: server.Key(),
|
||||
numPlayers: server.NumPlayers(),
|
||||
numActivePlayers: server.NumActivePlayers(),
|
||||
numInactivePlayers: server.NumInactivePlayers(),
|
||||
numTribes: server.NumTribes(),
|
||||
numActiveTribes: server.NumActiveTribes(),
|
||||
numInactiveTribes: server.NumInactivePlayers(),
|
||||
numVillages: server.NumVillages(),
|
||||
numPlayerVillages: server.NumPlayerVillages(),
|
||||
numBarbarianVillages: server.NumBarbarianVillages(),
|
||||
numBonusVillages: server.NumBonusVillages(),
|
||||
date: date,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) ServerKey() string {
|
||||
return params.serverKey
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumPlayers() int {
|
||||
return params.numPlayers
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumActivePlayers() int {
|
||||
return params.numActivePlayers
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumInactivePlayers() int {
|
||||
return params.numInactivePlayers
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumTribes() int {
|
||||
return params.numTribes
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumActiveTribes() int {
|
||||
return params.numActiveTribes
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumInactiveTribes() int {
|
||||
return params.numInactiveTribes
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumVillages() int {
|
||||
return params.numVillages
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumPlayerVillages() int {
|
||||
return params.numPlayerVillages
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumBarbarianVillages() int {
|
||||
return params.numBarbarianVillages
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) NumBonusVillages() int {
|
||||
return params.numBonusVillages
|
||||
}
|
||||
|
||||
func (params CreateServerSnapshotParams) Date() time.Time {
|
||||
return params.date
|
||||
}
|
||||
|
||||
type ServerSnapshotSort uint8
|
||||
|
||||
const (
|
||||
ServerSnapshotSortDateASC ServerSnapshotSort = iota + 1
|
||||
ServerSnapshotSortDateDESC
|
||||
ServerSnapshotSortIDASC
|
||||
ServerSnapshotSortIDDESC
|
||||
ServerSnapshotSortServerKeyASC
|
||||
ServerSnapshotSortServerKeyDESC
|
||||
)
|
||||
|
||||
// IsInConflict returns true if two sorts can't be used together
|
||||
// (e.g. ServerSnapshotSortIDASC and ServerSnapshotSortIDDESC).
|
||||
func (s ServerSnapshotSort) IsInConflict(s2 ServerSnapshotSort) bool {
|
||||
return isSortInConflict(s, s2)
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s ServerSnapshotSort) String() string {
|
||||
switch s {
|
||||
case ServerSnapshotSortDateASC:
|
||||
return "date:ASC"
|
||||
case ServerSnapshotSortDateDESC:
|
||||
return "date:DESC"
|
||||
case ServerSnapshotSortIDASC:
|
||||
return "id:ASC"
|
||||
case ServerSnapshotSortIDDESC:
|
||||
return "id:DESC"
|
||||
case ServerSnapshotSortServerKeyASC:
|
||||
return "serverKey:ASC"
|
||||
case ServerSnapshotSortServerKeyDESC:
|
||||
return "serverKey:DESC"
|
||||
default:
|
||||
return "unknown server snapshot sort"
|
||||
}
|
||||
}
|
||||
|
||||
type ServerSnapshotCursor struct {
|
||||
id int
|
||||
serverKey string
|
||||
date time.Time
|
||||
}
|
||||
|
||||
const serverSnapshotCursorModelName = "ServerSnapshotCursor"
|
||||
|
||||
func NewServerSnapshotCursor(id int, serverKey string, date time.Time) (ServerSnapshotCursor, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return ServerSnapshotCursor{}, ValidationError{
|
||||
Model: serverSnapshotCursorModelName,
|
||||
Field: "id",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateServerKey(serverKey); err != nil {
|
||||
return ServerSnapshotCursor{}, ValidationError{
|
||||
Model: serverSnapshotCursorModelName,
|
||||
Field: "serverKey",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return ServerSnapshotCursor{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
date: date,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeServerSnapshotCursor(encoded string) (ServerSnapshotCursor, error) {
|
||||
m, err := decodeCursor(encoded)
|
||||
if err != nil {
|
||||
return ServerSnapshotCursor{}, err
|
||||
}
|
||||
|
||||
id, err := m.int("id")
|
||||
if err != nil {
|
||||
return ServerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
serverKey, err := m.string("serverKey")
|
||||
if err != nil {
|
||||
return ServerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
date, err := m.time("date")
|
||||
if err != nil {
|
||||
return ServerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
tsc, err := NewServerSnapshotCursor(
|
||||
id,
|
||||
serverKey,
|
||||
date,
|
||||
)
|
||||
if err != nil {
|
||||
return ServerSnapshotCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
return tsc, nil
|
||||
}
|
||||
|
||||
func (ssc ServerSnapshotCursor) ID() int {
|
||||
return ssc.id
|
||||
}
|
||||
|
||||
func (ssc ServerSnapshotCursor) ServerKey() string {
|
||||
return ssc.serverKey
|
||||
}
|
||||
|
||||
func (ssc ServerSnapshotCursor) Date() time.Time {
|
||||
return ssc.date
|
||||
}
|
||||
|
||||
func (ssc ServerSnapshotCursor) IsZero() bool {
|
||||
return ssc == ServerSnapshotCursor{}
|
||||
}
|
||||
|
||||
func (ssc ServerSnapshotCursor) Encode() string {
|
||||
if ssc.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"id", ssc.id},
|
||||
{"serverKey", ssc.serverKey},
|
||||
{"date", ssc.date},
|
||||
})
|
||||
}
|
||||
|
||||
type ListServerSnapshotsParams struct {
|
||||
serverKeys []string
|
||||
sort []ServerSnapshotSort
|
||||
cursor ServerSnapshotCursor
|
||||
limit int
|
||||
}
|
||||
|
||||
const (
|
||||
ServerSnapshotListMaxLimit = 500
|
||||
listServerSnapshotsParamsModelName = "ListServerSnapshotsParams"
|
||||
)
|
||||
|
||||
func NewListServerSnapshotsParams() ListServerSnapshotsParams {
|
||||
return ListServerSnapshotsParams{
|
||||
sort: []ServerSnapshotSort{
|
||||
ServerSnapshotSortServerKeyASC,
|
||||
ServerSnapshotSortDateASC,
|
||||
ServerSnapshotSortIDASC,
|
||||
},
|
||||
limit: ServerSnapshotListMaxLimit,
|
||||
}
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) ServerKeys() []string {
|
||||
return params.serverKeys
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) SetServerKeys(serverKeys []string) error {
|
||||
for i, sk := range serverKeys {
|
||||
if err := validateServerKey(sk); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "serverKeys",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.serverKeys = serverKeys
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) Sort() []ServerSnapshotSort {
|
||||
return params.sort
|
||||
}
|
||||
|
||||
const (
|
||||
serverSnapshotSortMinLength = 0
|
||||
serverSnapshotSortMaxLength = 3
|
||||
)
|
||||
|
||||
func (params *ListServerSnapshotsParams) SetSort(sort []ServerSnapshotSort) error {
|
||||
if err := validateSort(sort, serverSnapshotSortMinLength, serverSnapshotSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.sort = sort
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) PrependSort(sort []ServerSnapshotSort) error {
|
||||
if len(sort) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := validateSliceLen(sort, 0, max(serverSnapshotSortMaxLength-len(params.sort), 0)); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return params.SetSort(append(sort, params.sort...))
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) PrependSortString(
|
||||
sort []string,
|
||||
allowed []ServerSnapshotSort,
|
||||
maxLength int,
|
||||
) error {
|
||||
if len(sort) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := validateSliceLen(sort, 0, max(min(serverSnapshotSortMaxLength-len(params.sort), maxLength), 0)); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
toPrepend := make([]ServerSnapshotSort, 0, len(sort))
|
||||
|
||||
for i, s := range sort {
|
||||
converted, err := newSortFromString(s, allowed...)
|
||||
if err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "sort",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
toPrepend = append(toPrepend, converted)
|
||||
}
|
||||
|
||||
return params.SetSort(append(toPrepend, params.sort...))
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) Cursor() ServerSnapshotCursor {
|
||||
return params.cursor
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) SetCursor(cursor ServerSnapshotCursor) error {
|
||||
params.cursor = cursor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) SetEncodedCursor(encoded string) error {
|
||||
decoded, err := decodeServerSnapshotCursor(encoded)
|
||||
if err != nil {
|
||||
return ValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "cursor",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.cursor = decoded
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
||||
func (params *ListServerSnapshotsParams) SetLimit(limit int) error {
|
||||
if err := validateIntInRange(limit, 1, ServerSnapshotListMaxLimit); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServerSnapshotsParamsModelName,
|
||||
Field: "limit",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.limit = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListServerSnapshotsResult struct {
|
||||
snapshots ServerSnapshots
|
||||
self ServerSnapshotCursor
|
||||
next ServerSnapshotCursor
|
||||
}
|
||||
|
||||
const listServerSnapshotsResultModelName = "ListServerSnapshotsResult"
|
||||
|
||||
func NewListServerSnapshotsResult(
|
||||
snapshots ServerSnapshots,
|
||||
next ServerSnapshot,
|
||||
) (ListServerSnapshotsResult, error) {
|
||||
var err error
|
||||
res := ListServerSnapshotsResult{
|
||||
snapshots: snapshots,
|
||||
}
|
||||
|
||||
if len(snapshots) > 0 {
|
||||
res.self, err = snapshots[0].ToCursor()
|
||||
if err != nil {
|
||||
return ListServerSnapshotsResult{}, ValidationError{
|
||||
Model: listServerSnapshotsResultModelName,
|
||||
Field: "self",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !next.IsZero() {
|
||||
res.next, err = next.ToCursor()
|
||||
if err != nil {
|
||||
return ListServerSnapshotsResult{}, ValidationError{
|
||||
Model: listServerSnapshotsResultModelName,
|
||||
Field: "next",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsResult) ServerSnapshots() ServerSnapshots {
|
||||
return res.snapshots
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsResult) Self() ServerSnapshotCursor {
|
||||
return res.self
|
||||
}
|
||||
|
||||
func (res ListServerSnapshotsResult) Next() ServerSnapshotCursor {
|
||||
return res.next
|
||||
}
|
854
internal/domain/server_snapshot_test.go
Normal file
854
internal/domain/server_snapshot_test.go
Normal file
|
@ -0,0 +1,854 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain/domaintest"
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewCreateServerSnapshotParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := domaintest.NewServer(t)
|
||||
date := time.Now()
|
||||
|
||||
params, err := domain.NewCreateServerSnapshotParams(server, date)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, server.Key(), params.ServerKey())
|
||||
assert.Equal(t, server.NumPlayers(), params.NumPlayers())
|
||||
assert.Equal(t, server.NumActivePlayers(), params.NumActivePlayers())
|
||||
assert.Equal(t, server.NumInactivePlayers(), params.NumInactivePlayers())
|
||||
assert.Equal(t, server.NumTribes(), params.NumTribes())
|
||||
assert.Equal(t, server.NumActiveTribes(), params.NumActiveTribes())
|
||||
assert.Equal(t, server.NumInactiveTribes(), params.NumInactiveTribes())
|
||||
assert.Equal(t, server.NumVillages(), params.NumVillages())
|
||||
assert.Equal(t, server.NumPlayerVillages(), params.NumPlayerVillages())
|
||||
assert.Equal(t, server.NumBarbarianVillages(), params.NumBarbarianVillages())
|
||||
assert.Equal(t, server.NumBonusVillages(), params.NumBonusVillages())
|
||||
assert.Equal(t, date, params.Date())
|
||||
}
|
||||
|
||||
func TestServerSnapshotSort_IsInConflict(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
sorts [2]domain.ServerSnapshotSort
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedRes bool
|
||||
}{
|
||||
{
|
||||
name: "OK: id:ASC serverKey:ASC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortServerKeyASC},
|
||||
},
|
||||
expectedRes: false,
|
||||
},
|
||||
{
|
||||
name: "OK: id:DESC serverKey:ASC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDDESC, domain.ServerSnapshotSortServerKeyASC},
|
||||
},
|
||||
expectedRes: false,
|
||||
},
|
||||
{
|
||||
name: "OK: id:ASC id:ASC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortIDASC},
|
||||
},
|
||||
expectedRes: true,
|
||||
},
|
||||
{
|
||||
name: "OK: id:ASC id:DESC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortIDASC, domain.ServerSnapshotSortIDDESC},
|
||||
},
|
||||
expectedRes: true,
|
||||
},
|
||||
{
|
||||
name: "OK: date:ASC date:DESC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortDateASC, domain.ServerSnapshotSortDateDESC},
|
||||
},
|
||||
expectedRes: true,
|
||||
},
|
||||
{
|
||||
name: "OK: serverKey:DESC serverKey:ASC",
|
||||
args: args{
|
||||
sorts: [2]domain.ServerSnapshotSort{domain.ServerSnapshotSortServerKeyDESC, domain.ServerSnapshotSortServerKeyASC},
|
||||
},
|
||||
expectedRes: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, tt.expectedRes, tt.args.sorts[0].IsInConflict(tt.args.sorts[1]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerSnapshotCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validServerSnapshotCursor := domaintest.NewServerSnapshotCursor(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: validServerSnapshotCursor.ID(),
|
||||
serverKey: validServerSnapshotCursor.ServerKey(),
|
||||
date: validServerSnapshotCursor.Date(),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "ERR: id < 1",
|
||||
args: args{
|
||||
id: 0,
|
||||
serverKey: validServerSnapshotCursor.ServerKey(),
|
||||
date: validServerSnapshotCursor.Date(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ServerSnapshotCursor",
|
||||
Field: "id",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
id: validServerSnapshotCursor.ID(),
|
||||
serverKey: serverKeyTest.key,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ServerSnapshotCursor",
|
||||
Field: "serverKey",
|
||||
Err: serverKeyTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ssc, err := domain.NewServerSnapshotCursor(
|
||||
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, ssc.ID())
|
||||
assert.Equal(t, tt.args.serverKey, ssc.ServerKey())
|
||||
assert.Equal(t, tt.args.date, ssc.Date())
|
||||
assert.NotEmpty(t, ssc.Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServerSnapshotsParams_SetServerKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
serverKeys []string
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
serverKeys: []string{
|
||||
domaintest.RandServerKey(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
serverKeys: []string{serverKeyTest.key},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "serverKeys",
|
||||
Index: 0,
|
||||
Err: serverKeyTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServerSnapshotsParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
sort []domain.ServerSnapshotSort
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: empty slice",
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 3",
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 3,
|
||||
Current: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: conflict",
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.SortConflictError{
|
||||
Sort: [2]string{domain.ServerSnapshotSortIDASC.String(), domain.ServerSnapshotSortIDDESC.String()},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.sort, params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServerSnapshotsParams_PrependSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultNewParams := func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
return domain.ListServerSnapshotsParams{}
|
||||
}
|
||||
|
||||
type args struct {
|
||||
sort []domain.ServerSnapshotSort
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
newParams func(t *testing.T) domain.ListServerSnapshotsParams
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: custom params",
|
||||
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: empty slice",
|
||||
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: custom params + len(sort) > sortMaxLength - len(sort)",
|
||||
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 1,
|
||||
Current: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 3",
|
||||
newParams: defaultNewParams,
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 3,
|
||||
Current: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: conflict",
|
||||
args: args{
|
||||
sort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.SortConflictError{
|
||||
Sort: [2]string{domain.ServerSnapshotSortDateASC.String(), domain.ServerSnapshotSortDateDESC.String()},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
newParams := defaultNewParams
|
||||
if tt.newParams != nil {
|
||||
newParams = tt.newParams
|
||||
}
|
||||
params := newParams(t)
|
||||
|
||||
expectedSort := params.Sort()
|
||||
|
||||
require.ErrorIs(t, params.PrependSort(tt.args.sort), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, append(tt.args.sort, expectedSort...), params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServerSnapshotsParams_PrependSortString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultNewParams := func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
return domain.ListServerSnapshotsParams{}
|
||||
}
|
||||
defaultAllowed := []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortDateDESC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortServerKeyDESC,
|
||||
}
|
||||
defaultMaxLength := 3
|
||||
|
||||
type args struct {
|
||||
sort []string
|
||||
allowed []domain.ServerSnapshotSort
|
||||
maxLength int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
newParams func(t *testing.T) domain.ListServerSnapshotsParams
|
||||
args args
|
||||
expectedSort []domain.ServerSnapshotSort
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK: [id:ASC, date:ASC, serverKey:ASC]",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"id:ASC",
|
||||
"date:ASC",
|
||||
"serverKey:ASC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedSort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: [id:DESC, date:DESC, serverKey:DESC]",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"id:DESC",
|
||||
"date:DESC",
|
||||
"serverKey:DESC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedSort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDDESC,
|
||||
domain.ServerSnapshotSortDateDESC,
|
||||
domain.ServerSnapshotSortServerKeyDESC,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: custom params",
|
||||
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: []string{
|
||||
"date:ASC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedSort: []domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: empty slice",
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: custom params + len(sort) > sortMaxLength - len(sort)",
|
||||
newParams: func(t *testing.T) domain.ListServerSnapshotsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
args: args{
|
||||
sort: []string{
|
||||
"date:ASC",
|
||||
"date:DESC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 1,
|
||||
Current: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > maxLength",
|
||||
newParams: defaultNewParams,
|
||||
args: args{
|
||||
sort: []string{
|
||||
"serverKey:ASC",
|
||||
"date:ASC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 0,
|
||||
Max: 1,
|
||||
Current: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: unsupported sort string",
|
||||
newParams: defaultNewParams,
|
||||
args: args{
|
||||
sort: []string{
|
||||
"date:",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Index: 0,
|
||||
Err: domain.UnsupportedSortStringError{
|
||||
Sort: "date:",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: conflict",
|
||||
args: args{
|
||||
sort: []string{
|
||||
"date:ASC",
|
||||
"date:DESC",
|
||||
},
|
||||
allowed: defaultAllowed,
|
||||
maxLength: defaultMaxLength,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "sort",
|
||||
Err: domain.SortConflictError{
|
||||
Sort: [2]string{domain.ServerSnapshotSortDateASC.String(), domain.ServerSnapshotSortDateDESC.String()},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
newParams := defaultNewParams
|
||||
if tt.newParams != nil {
|
||||
newParams = tt.newParams
|
||||
}
|
||||
params := newParams(t)
|
||||
|
||||
require.ErrorIs(t, params.PrependSortString(tt.args.sort, tt.args.allowed, tt.args.maxLength), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.expectedSort, params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServerSnapshotsParams_SetEncodedCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validCursor := domaintest.NewServerSnapshotCursor(t)
|
||||
|
||||
type args struct {
|
||||
cursor string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedCursor domain.ServerSnapshotCursor
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
cursor: validCursor.Encode(),
|
||||
},
|
||||
expectedCursor: validCursor,
|
||||
},
|
||||
{
|
||||
name: "ERR: len(cursor) < 1",
|
||||
args: args{
|
||||
cursor: "",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
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: "ListServerSnapshotsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1000,
|
||||
Current: 1001,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: malformed base64",
|
||||
args: args{
|
||||
cursor: "112345",
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "cursor",
|
||||
Err: domain.ErrInvalidCursor,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
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 TestListServerSnapshotsParams_SetLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
limit int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
limit: domain.ServerSnapshotListMaxLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit < 1",
|
||||
args: args{
|
||||
limit: 0,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "limit",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("ERR: limit > %d", domain.ServerSnapshotListMaxLimit),
|
||||
args: args{
|
||||
limit: domain.ServerSnapshotListMaxLimit + 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServerSnapshotsParams",
|
||||
Field: "limit",
|
||||
Err: domain.MaxLessEqualError{
|
||||
Max: domain.ServerSnapshotListMaxLimit,
|
||||
Current: domain.ServerSnapshotListMaxLimit + 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListServerSnapshotsParams()
|
||||
|
||||
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.limit, params.Limit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewListServerSnapshotsResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
snapshots := domain.ServerSnapshots{
|
||||
domaintest.NewServerSnapshot(t),
|
||||
domaintest.NewServerSnapshot(t),
|
||||
domaintest.NewServerSnapshot(t),
|
||||
}
|
||||
next := domaintest.NewServerSnapshot(t)
|
||||
|
||||
t.Run("OK: with next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListServerSnapshotsResult(snapshots, next)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.ServerSnapshots())
|
||||
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())
|
||||
})
|
||||
|
||||
t.Run("OK: without next", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewListServerSnapshotsResult(snapshots, domain.ServerSnapshot{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snapshots, res.ServerSnapshots())
|
||||
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.NewListServerSnapshotsResult(nil, domain.ServerSnapshot{})
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, res.ServerSnapshots())
|
||||
assert.True(t, res.Self().IsZero())
|
||||
assert.True(t, res.Next().IsZero())
|
||||
})
|
||||
}
|
|
@ -318,7 +318,6 @@ func NewTribeSnapshotCursor(id int, serverKey string, date time.Time) (TribeSnap
|
|||
}, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func decodeTribeSnapshotCursor(encoded string) (TribeSnapshotCursor, error) {
|
||||
m, err := decodeCursor(encoded)
|
||||
if err != nil {
|
||||
|
|
|
@ -176,7 +176,7 @@ func TestNewTribeSnapshotCursor(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
psc, err := domain.NewTribeSnapshotCursor(
|
||||
tsc, err := domain.NewTribeSnapshotCursor(
|
||||
tt.args.id,
|
||||
tt.args.serverKey,
|
||||
tt.args.date,
|
||||
|
@ -185,10 +185,10 @@ func TestNewTribeSnapshotCursor(t *testing.T) {
|
|||
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())
|
||||
assert.Equal(t, tt.args.id, tsc.ID())
|
||||
assert.Equal(t, tt.args.serverKey, tsc.ServerKey())
|
||||
assert.Equal(t, tt.args.date, tsc.Date())
|
||||
assert.NotEmpty(t, tsc.Encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,10 +158,12 @@ func TestDataSync(t *testing.T) {
|
|||
ctx,
|
||||
port.NewServerWatermillConsumer(
|
||||
serverSvc,
|
||||
nil,
|
||||
serverSub,
|
||||
nopLogger,
|
||||
marshaler,
|
||||
serverCmdSync,
|
||||
"",
|
||||
serverEventSynced,
|
||||
tribeEventSynced,
|
||||
playerEventSynced,
|
||||
|
|
|
@ -138,6 +138,7 @@ func TestEnnoblementSync(t *testing.T) {
|
|||
ctx,
|
||||
port.NewServerWatermillConsumer(
|
||||
serverSvc,
|
||||
nil,
|
||||
serverSub,
|
||||
nopLogger,
|
||||
marshaler,
|
||||
|
@ -146,6 +147,7 @@ func TestEnnoblementSync(t *testing.T) {
|
|||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
ennoblementEventSynced,
|
||||
"",
|
||||
"",
|
||||
|
|
|
@ -70,6 +70,7 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
)
|
||||
|
||||
// events/commands
|
||||
serverSnapshotCmdCreate := gofakeit.UUID()
|
||||
tribeSnapshotCmdCreate := gofakeit.UUID()
|
||||
tribeSnapshotEventCreated := gofakeit.UUID()
|
||||
playerSnapshotCmdCreate := gofakeit.UUID()
|
||||
|
@ -80,8 +81,15 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
serverRepo := adapter.NewServerBunRepository(db)
|
||||
tribeRepo := adapter.NewTribeBunRepository(db)
|
||||
playerRepo := adapter.NewPlayerBunRepository(db)
|
||||
serverSnapshotRepo := adapter.NewServerSnapshotBunRepository(db)
|
||||
tribeSnapshotRepo := adapter.NewTribeSnapshotBunRepository(db)
|
||||
playerSnapshotRepo := adapter.NewPlayerSnapshotBunRepository(db)
|
||||
serverSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
tribePub,
|
||||
marshaler,
|
||||
serverSnapshotCmdCreate,
|
||||
"",
|
||||
)
|
||||
tribeSnapshotPublisher := adapter.NewSnapshotWatermillPublisher(
|
||||
tribePub,
|
||||
marshaler,
|
||||
|
@ -100,19 +108,28 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
serverSvc := app.NewServerService(serverRepo, nil, nil)
|
||||
tribeSvc := app.NewTribeService(tribeRepo, nil, nil)
|
||||
playerSvc := app.NewPlayerService(playerRepo, nil, nil, nil)
|
||||
serverSnapshotSvc := app.NewServerSnapshotService(serverSnapshotRepo, serverSvc, serverSnapshotPublisher)
|
||||
tribeSnapshotSvc := app.NewTribeSnapshotService(tribeSnapshotRepo, tribeSvc, tribeSnapshotPublisher)
|
||||
playerSnapshotSvc := app.NewPlayerSnapshotService(playerSnapshotRepo, playerSvc, playerSnapshotPublisher)
|
||||
snapshotSvc := app.NewSnapshotService(versionSvc, serverSvc, tribeSnapshotPublisher, playerSnapshotPublisher)
|
||||
snapshotSvc := app.NewSnapshotService(
|
||||
versionSvc,
|
||||
serverSvc,
|
||||
serverSnapshotPublisher,
|
||||
tribeSnapshotPublisher,
|
||||
playerSnapshotPublisher,
|
||||
)
|
||||
|
||||
watermilltest.RunRouterWithContext(
|
||||
t,
|
||||
ctx,
|
||||
port.NewServerWatermillConsumer(
|
||||
serverSvc,
|
||||
serverSnapshotSvc,
|
||||
serverSub,
|
||||
nopLogger,
|
||||
marshaler,
|
||||
"",
|
||||
serverSnapshotCmdCreate,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
|
@ -155,31 +172,85 @@ func TestSnapshotCreation(t *testing.T) {
|
|||
assert.EventuallyWithTf(t, func(collect *assert.CollectT) {
|
||||
require.NoError(collect, ctx.Err())
|
||||
|
||||
listParams := domain.NewListServersParams()
|
||||
require.NoError(collect, listParams.SetSort([]domain.ServerSort{
|
||||
listServersParams := domain.NewListServersParams()
|
||||
require.NoError(collect, listServersParams.SetSort([]domain.ServerSort{
|
||||
domain.ServerSortKeyASC,
|
||||
}))
|
||||
require.NoError(collect, listParams.SetSpecial(domain.NullBool{
|
||||
require.NoError(collect, listServersParams.SetSpecial(domain.NullBool{
|
||||
V: false,
|
||||
Valid: true,
|
||||
}))
|
||||
require.NoError(collect, listParams.SetLimit(domain.ServerListMaxLimit))
|
||||
require.NoError(collect, listServersParams.SetLimit(domain.ServerListMaxLimit))
|
||||
|
||||
var allServers domain.Servers
|
||||
|
||||
for {
|
||||
res, err := serverRepo.List(ctx, listParams)
|
||||
res, err := serverRepo.List(ctx, listServersParams)
|
||||
require.NoError(collect, err)
|
||||
|
||||
for _, s := range res.Servers() {
|
||||
assert.WithinDuration(collect, time.Now(), s.SnapshotCreatedAt(), time.Minute, s.Key())
|
||||
assert.WithinDuration(collect, time.Now(), s.PlayerSnapshotsCreatedAt(), time.Minute, s.Key())
|
||||
assert.WithinDuration(collect, time.Now(), s.TribeSnapshotsCreatedAt(), time.Minute, s.Key())
|
||||
}
|
||||
|
||||
allServers = append(allServers, res.Servers()...)
|
||||
|
||||
if res.Next().IsZero() {
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
require.NoError(collect, listParams.SetCursor(res.Next()))
|
||||
require.NoError(collect, listServersParams.SetCursor(res.Next()))
|
||||
}
|
||||
|
||||
listSnapshotsParams := domain.NewListServerSnapshotsParams()
|
||||
require.NoError(collect, listSnapshotsParams.SetSort([]domain.ServerSnapshotSort{
|
||||
domain.ServerSnapshotSortServerKeyASC,
|
||||
domain.ServerSnapshotSortDateASC,
|
||||
domain.ServerSnapshotSortIDASC,
|
||||
}))
|
||||
require.NoError(collect, listSnapshotsParams.SetLimit(domain.ServerSnapshotListMaxLimit))
|
||||
|
||||
cnt := 0
|
||||
|
||||
for {
|
||||
res, err := serverSnapshotRepo.List(ctx, listSnapshotsParams)
|
||||
require.NoError(collect, err)
|
||||
|
||||
for _, ss := range res.ServerSnapshots() {
|
||||
cnt++
|
||||
msg := fmt.Sprintf("ServerKey=%s", ss.ServerKey())
|
||||
|
||||
idx := slices.IndexFunc(allServers, func(s domain.Server) bool {
|
||||
return s.Key() == ss.ServerKey()
|
||||
})
|
||||
if !assert.GreaterOrEqual(
|
||||
collect,
|
||||
idx,
|
||||
0,
|
||||
msg,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
server := allServers[idx]
|
||||
|
||||
assert.NotZero(collect, ss.ID(), msg)
|
||||
assert.Equal(collect, server.Key(), ss.ServerKey(), msg)
|
||||
assert.Equal(collect, server.NumVillages(), ss.NumVillages(), msg)
|
||||
assert.WithinDuration(collect, time.Now(), ss.CreatedAt(), time.Minute, msg)
|
||||
assert.WithinDuration(collect, time.Now(), ss.Date(), 24*time.Hour, msg)
|
||||
}
|
||||
|
||||
if res.Next().IsZero() {
|
||||
break
|
||||
}
|
||||
|
||||
require.NoError(collect, listSnapshotsParams.SetCursor(res.Next()))
|
||||
}
|
||||
|
||||
//nolint:testifylint
|
||||
assert.Equal(collect, len(allServers), cnt)
|
||||
}, 30*time.Second, 500*time.Millisecond, "servers")
|
||||
}()
|
||||
|
||||
|
|
|
@ -10,10 +10,12 @@ import (
|
|||
|
||||
type ServerWatermillConsumer struct {
|
||||
svc *app.ServerService
|
||||
snapshotSvc *app.ServerSnapshotService
|
||||
subscriber message.Subscriber
|
||||
logger watermill.LoggerAdapter
|
||||
marshaler watermillmsg.Marshaler
|
||||
cmdSyncTopic string
|
||||
cmdCreateSnapshotsTopic string
|
||||
eventServerSyncedTopic string
|
||||
eventTribesSyncedTopic string
|
||||
eventPlayersSyncedTopic string
|
||||
|
@ -25,10 +27,12 @@ type ServerWatermillConsumer struct {
|
|||
|
||||
func NewServerWatermillConsumer(
|
||||
svc *app.ServerService,
|
||||
snapshotSvc *app.ServerSnapshotService,
|
||||
subscriber message.Subscriber,
|
||||
logger watermill.LoggerAdapter,
|
||||
marshaler watermillmsg.Marshaler,
|
||||
cmdSyncTopic string,
|
||||
cmdCreateSnapshotsTopic string,
|
||||
eventServerSyncedTopic string,
|
||||
eventTribesSyncedTopic string,
|
||||
eventPlayersSyncedTopic string,
|
||||
|
@ -39,10 +43,12 @@ func NewServerWatermillConsumer(
|
|||
) *ServerWatermillConsumer {
|
||||
return &ServerWatermillConsumer{
|
||||
svc: svc,
|
||||
snapshotSvc: snapshotSvc,
|
||||
subscriber: subscriber,
|
||||
logger: logger,
|
||||
marshaler: marshaler,
|
||||
cmdSyncTopic: cmdSyncTopic,
|
||||
cmdCreateSnapshotsTopic: cmdCreateSnapshotsTopic,
|
||||
eventServerSyncedTopic: eventServerSyncedTopic,
|
||||
eventTribesSyncedTopic: eventTribesSyncedTopic,
|
||||
eventPlayersSyncedTopic: eventPlayersSyncedTopic,
|
||||
|
@ -55,6 +61,12 @@ func NewServerWatermillConsumer(
|
|||
|
||||
func (c *ServerWatermillConsumer) Register(router *message.Router) {
|
||||
router.AddNoPublisherHandler("ServerConsumer.sync", c.cmdSyncTopic, c.subscriber, c.sync)
|
||||
router.AddNoPublisherHandler(
|
||||
"ServerConsumer.createSnapshots",
|
||||
c.cmdCreateSnapshotsTopic,
|
||||
c.subscriber,
|
||||
c.createSnapshots,
|
||||
)
|
||||
router.AddNoPublisherHandler(
|
||||
"ServerConsumer.syncConfigAndInfo",
|
||||
c.eventServerSyncedTopic,
|
||||
|
@ -141,6 +153,32 @@ func (c *ServerWatermillConsumer) syncConfigAndInfo(msg *message.Message) error
|
|||
return c.svc.SyncConfigAndInfo(msg.Context(), payload)
|
||||
}
|
||||
|
||||
func (c *ServerWatermillConsumer) createSnapshots(msg *message.Message) error {
|
||||
var rawPayload watermillmsg.CreateSnapshotsCmdPayload
|
||||
|
||||
if err := c.marshaler.Unmarshal(msg, &rawPayload); err != nil {
|
||||
c.logger.Error("couldn't unmarshal payload", err, watermill.LogFields{
|
||||
"handler": message.HandlerNameFromCtx(msg.Context()),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := domain.NewCreateSnapshotsCmdPayload(
|
||||
rawPayload.ServerKey,
|
||||
rawPayload.VersionCode,
|
||||
rawPayload.VersionTimezone,
|
||||
rawPayload.Date,
|
||||
)
|
||||
if err != nil {
|
||||
c.logger.Error("couldn't construct domain.CreateSnapshotsCmdPayload", err, watermill.LogFields{
|
||||
"handler": message.HandlerNameFromCtx(msg.Context()),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.snapshotSvc.Create(msg.Context(), payload)
|
||||
}
|
||||
|
||||
func (c *ServerWatermillConsumer) updateNumTribes(msg *message.Message) error {
|
||||
var rawPayload watermillmsg.TribesSyncedEventPayload
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user