feat: add DeletedAt field to Player and Tribe (#33)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: twhelp/core#33
This commit is contained in:
Dawid Wysokiński 2022-08-16 03:56:12 +00:00
parent f246e205b4
commit 631c080376
22 changed files with 633 additions and 73 deletions

View File

@ -7,7 +7,6 @@ linters:
- bodyclose
- deadcode
- depguard
- dupl
- errcheck
- gocritic
- gofmt
@ -32,6 +31,10 @@ linters:
- gomnd
- tenv
- testpackage
- noctx
- tparallel
- usestdlibvars
- unconvert
linters-settings:
tagliatelle:

View File

@ -6,6 +6,6 @@ repos:
stages: [commit-msg]
additional_dependencies: ['@commitlint/config-conventional']
- repo: https://github.com/golangci/golangci-lint
rev: v1.47.2
rev: v1.48.0
hooks:
- id: golangci-lint

View File

@ -2,6 +2,7 @@ package serve
import (
"context"
"errors"
"fmt"
"net/http"
"os"
@ -131,7 +132,7 @@ func newCORSMiddlewareForDevMode() func(next http.Handler) http.Handler {
}
func runServer(srv *http.Server, logger *zap.Logger) {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Fatal("srv.ListenAndServe:" + err.Error())
}
}

View File

@ -18,6 +18,7 @@ type Player struct {
Rank int64 `bun:"rank,default:0"`
TribeID int64 `bun:"tribe_id,nullzero"`
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
DeletedAt time.Time `bun:"deleted_at,nullzero"`
OpponentsDefeated
}
@ -49,5 +50,6 @@ func (p Player) ToDomain() domain.Player {
},
ServerKey: p.ServerKey,
CreatedAt: p.CreatedAt,
DeletedAt: p.DeletedAt,
}
}

View File

@ -22,6 +22,7 @@ type Tribe struct {
Rank int64 `bun:"rank,default:0"`
Dominance float64 `bun:"dominance,default:0"`
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
DeletedAt time.Time `bun:"deleted_at,nullzero"`
OpponentsDefeated
}
@ -58,5 +59,6 @@ func (t Tribe) ToDomain() domain.Tribe {
ServerKey: t.ServerKey,
Dominance: t.Dominance,
CreatedAt: t.CreatedAt,
DeletedAt: t.DeletedAt,
}
}

View File

@ -0,0 +1,48 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
"github.com/uptrace/bun"
)
func init() {
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
models := []interface{}{
&model.Player{},
&model.Tribe{},
}
for _, m := range models {
if _, err := tx.NewAddColumn().
Model(m).
ColumnExpr("deleted_at timestamp with time zone").
IfNotExists().
Exec(ctx); err != nil {
return fmt.Errorf("couldn't add the deleted_at column (model=%T): %w", m, err)
}
}
return nil
})
}, func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
models := []interface{}{
&model.Player{},
&model.Tribe{},
}
for _, m := range models {
if _, err := tx.NewDropColumn().
Model(m).
Column("deleted_at").
Exec(ctx); err != nil {
return fmt.Errorf("couldn't drop the deleted_at column (model=%T): %w", m, err)
}
}
return nil
})
})
}

View File

@ -3,7 +3,9 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -19,7 +21,7 @@ func NewPlayer(db *bun.DB) *Player {
return &Player{db: db}
}
func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error {
func (p *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error {
if len(params) == 0 {
return nil
}
@ -28,7 +30,7 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
for _, p := range params {
players = append(players, model.NewPlayer(p))
}
if _, err := t.db.NewInsert().
if _, err := p.db.NewInsert().
Model(&players).
On("CONFLICT ON CONSTRAINT players_pkey DO UPDATE").
Set("name = EXCLUDED.name").
@ -36,6 +38,7 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
Set("points = EXCLUDED.points").
Set("rank = EXCLUDED.rank").
Set("tribe_id = EXCLUDED.tribe_id").
Set("deleted_at = null").
Apply(appendODSetClauses).
Returning("NULL").
Exec(ctx); err != nil {
@ -45,12 +48,31 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
return nil
}
func (t *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error) {
func (p *Player) DeleteByID(ctx context.Context, ids ...int64) error {
if len(ids) == 0 {
return nil
}
if _, err := p.db.NewUpdate().
Model(&model.Player{}).
Where("deleted_at IS NULL").
Where("id IN (?)", bun.In(ids)).
Set("deleted_at = ?", time.Now()).
Set("tribe_id = NULL").
Returning("NULL").
Exec(ctx); err != nil {
return fmt.Errorf("couldn't delete players: %w", err)
}
return nil
}
func (p *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error) {
var players []model.Player
var count int
var err error
q := t.db.NewSelect().
q := p.db.NewSelect().
Model(&players).
Order("server_key ASC", "id ASC").
Apply((listPlayersParamsApplier{params}).Apply)
@ -59,7 +81,7 @@ func (t *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]d
} else {
err = q.Scan(ctx)
}
if err != nil && err != sql.ErrNoRows {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, 0, fmt.Errorf("couldn't select players from the db: %w", err)
}
@ -83,5 +105,13 @@ func (l listPlayersParamsApplier) Apply(q *bun.SelectQuery) *bun.SelectQuery {
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
}
if l.params.Deleted.Valid {
if l.params.Deleted.Bool {
q = q.Where("deleted_at IS NOT NULL")
} else {
q = q.Where("deleted_at IS NULL")
}
}
return q
}

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
@ -68,7 +69,7 @@ func (s *Server) List(ctx context.Context, params domain.ListServersParams) ([]d
} else {
err = q.Scan(ctx)
}
if err != nil && err != sql.ErrNoRows {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, 0, fmt.Errorf("couldn't select servers from the db: %w", err)
}

View File

@ -214,6 +214,7 @@
rank: 30
dominance: 0
created_at: 2020-06-22T13:45:46.000Z
deleted_at: 2020-12-03T18:01:07Z
- rank_att: 3
score_att: 231146512
rank_def: 5
@ -254,6 +255,7 @@
rank: 40
dominance: 0
created_at: 2020-06-22T13:45:46.000Z
deleted_at: 2020-12-07T04:00:56Z
- rank_att: 1
score_att: 132897
rank_def: 3
@ -1085,6 +1087,24 @@
rank: 3
tribe_id: 122
created_at: 2020-04-08T02:10:08.000Z
- rank_att: 16
score_att: 43305549
rank_def: 5
score_def: 88199345
rank_sup: 1
score_sup: 64715292
rank_total: 4
score_total: 196220186
_id: en113-deleted-1
id: 1111
server_key: en113
name: Deleted 1
num_villages: 1421
points: 17185328
rank: 3
tribe_id: 0
created_at: 2020-04-08T02:10:08.000Z
deleted_at: 2020-12-03T18:01:07Z
- rank_att: 8
score_att: 78782855
rank_def: 27
@ -1102,6 +1122,24 @@
rank: 4
tribe_id: 122
created_at: 2020-04-13T06:15:34.000Z
- rank_att: 8
score_att: 78782855
rank_def: 27
score_def: 32539992
rank_sup: 28
score_sup: 9291867
rank_total: 17
score_total: 120614714
_id: en113-deleted-2
id: 1112
server_key: en113
name: Deleted 2
num_villages: 1358
points: 16287830
rank: 4
tribe_id: 0
created_at: 2020-04-13T06:15:34.000Z
deleted_at: 2020-06-22T13:45:46Z
- rank_att: 6
score_att: 95156312
rank_def: 23

View File

@ -3,7 +3,9 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -45,6 +47,7 @@ func (t *Tribe) CreateOrUpdate(ctx context.Context, params ...domain.CreateTribe
Set("points = EXCLUDED.points").
Set("all_points = EXCLUDED.all_points").
Set("rank = EXCLUDED.rank").
Set("deleted_at = null").
Apply(appendODSetClauses).
Returning("NULL").
Exec(ctx); err != nil {
@ -54,6 +57,46 @@ func (t *Tribe) CreateOrUpdate(ctx context.Context, params ...domain.CreateTribe
return nil
}
func (t *Tribe) DeleteByID(ctx context.Context, ids ...int64) error {
if len(ids) == 0 {
return nil
}
if _, err := t.db.NewUpdate().
Model(&model.Tribe{}).
Where("deleted_at IS NULL").
Where("id IN (?)", bun.In(ids)).
Set("deleted_at = ?", time.Now()).
Returning("NULL").
Exec(ctx); err != nil {
return fmt.Errorf("couldn't delete tribes: %w", err)
}
return nil
}
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
if numPlayerVillages < 0 {
return ErrNumPlayerVillagesGTE
}
q := t.db.NewUpdate().
Model(&model.Tribe{}).
Returning("NULL").
Where("server_key = ?", serverKey).
Where("deleted_at IS NULL")
if numPlayerVillages > 0 {
q = q.Set("dominance = num_villages::double precision / ? * 100", numPlayerVillages)
} else {
q = q.Set("dominance = 0")
}
if _, err := q.Exec(ctx); err != nil {
return fmt.Errorf("couldn't update dominance (server=%s): %w", serverKey, err)
}
return nil
}
func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error) {
var tribes []model.Tribe
var count int
@ -68,7 +111,7 @@ func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]dom
} else {
err = q.Scan(ctx)
}
if err != nil && err != sql.ErrNoRows {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, 0, fmt.Errorf("couldn't select tribes from the db: %w", err)
}
@ -79,27 +122,6 @@ func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]dom
return result, int64(count), nil
}
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
if numPlayerVillages < 0 {
return ErrNumPlayerVillagesGTE
}
q := t.db.NewUpdate().
Model(&model.Tribe{}).
Returning("NULL").
Where("server_key = ?", serverKey)
if numPlayerVillages > 0 {
q = q.Set("dominance = num_villages::double precision / ? * 100", numPlayerVillages)
} else {
q = q.Set("dominance = 0")
}
if _, err := q.Exec(ctx); err != nil {
return fmt.Errorf("couldn't update dominance (server=%s): %w", serverKey, err)
}
return nil
}
type listTribesParamsApplier struct {
params domain.ListTribesParams
}
@ -113,5 +135,13 @@ func (l listTribesParamsApplier) Apply(q *bun.SelectQuery) *bun.SelectQuery {
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
}
if l.params.Deleted.Valid {
if l.params.Deleted.Bool {
q = q.Where("deleted_at IS NOT NULL")
} else {
q = q.Where("deleted_at IS NULL")
}
}
return q
}

View File

@ -164,6 +164,40 @@ func TestTribe_CreateOrUpdate(t *testing.T) {
})
}
func TestTribe_DeleteByID(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewTribe(db)
t.Run("OK", func(t *testing.T) {
t.Parallel()
tribeCSA := getTribeFromFixture(t, fixture, "pl169-csa")
require.Zero(t, tribeCSA.DeletedAt)
tribeCSAJR := getTribeFromFixture(t, fixture, "pl169-csa-jr")
require.Zero(t, tribeCSAJR.DeletedAt)
assert.NoError(t, repo.DeleteByID(context.Background(), tribeCSA.ID, tribeCSAJR.ID))
tribes, _, err := repo.List(context.Background(), domain.ListTribesParams{
IDs: []int64{tribeCSA.ID, tribeCSAJR.ID},
ServerKeys: []string{tribeCSA.ServerKey},
})
assert.NoError(t, err)
assert.Len(t, tribes, 2)
for _, tribe := range tribes {
assert.WithinDuration(t, time.Now(), tribe.DeletedAt, 1*time.Second)
}
})
t.Run("OK: len(ids) == 0", func(t *testing.T) {
t.Parallel()
assert.NoError(t, repo.DeleteByID(context.Background()))
})
}
func TestTribe_List(t *testing.T) {
t.Parallel()
@ -244,6 +278,25 @@ func TestTribe_List(t *testing.T) {
},
expectedCount: 1,
},
{
name: "Deleted=true,ServerKeys=[en113],Count=true",
params: domain.ListTribesParams{
Deleted: domain.NullBool{Bool: true, Valid: true},
ServerKeys: []string{"en113"},
Count: true,
},
expectedTribes: []expectedTribe{
{
id: 1337,
serverKey: "en113",
},
{
id: 1115,
serverKey: "en113",
},
},
expectedCount: 2,
},
}
for _, tt := range tests {

View File

@ -3,6 +3,7 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -32,7 +33,7 @@ func (v *Version) List(ctx context.Context, params domain.ListVersionsParams) ([
} else {
err = q.Scan(ctx)
}
if err != nil && err != sql.ErrNoRows {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, 0, fmt.Errorf("couldn't select versions from the db: %w", err)
}

View File

@ -3,6 +3,7 @@ package bundb
import (
"context"
"database/sql"
"errors"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -60,7 +61,7 @@ func (v *Village) List(ctx context.Context, params domain.ListVillagesParams) ([
} else {
err = q.Scan(ctx)
}
if err != nil && err != sql.ErrNoRows {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, 0, fmt.Errorf("couldn't select villages from the db: %w", err)
}

View File

@ -18,6 +18,7 @@ type Player struct {
ServerKey string
CreatedAt time.Time
DeletedAt time.Time
}
type CreatePlayerParams struct {
@ -29,6 +30,7 @@ type CreatePlayerParams struct {
type ListPlayersParams struct {
IDs []int64
ServerKeys []string
Deleted NullBool
Count bool
}

View File

@ -21,6 +21,7 @@ type Tribe struct {
ServerKey string
Dominance float64
CreatedAt time.Time
DeletedAt time.Time
}
type CreateTribeParams struct {
@ -32,6 +33,7 @@ type CreateTribeParams struct {
type ListTribesParams struct {
IDs []int64
ServerKeys []string
Deleted NullBool
Count bool
}

View File

@ -3,14 +3,19 @@ package service
import (
"context"
"fmt"
"sort"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
)
//counterfeiter:generate -o internal/mock/player_repository.gen.go . PlayerRepository
type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
DeleteByID(ctx context.Context, ids ...int64) error
List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error)
}
//counterfeiter:generate -o internal/mock/players_getter.gen.go . PlayersGetter
type PlayersGetter interface {
GetPlayers(ctx context.Context, baseURL string) ([]domain.BasePlayer, error)
}
@ -30,6 +35,17 @@ func (p *Player) Refresh(ctx context.Context, key, url string) (int64, error) {
return 0, fmt.Errorf("TWClient.GetPlayers: %w", err)
}
existingPlayers, _, err := p.repo.List(ctx, domain.ListPlayersParams{
ServerKeys: []string{key},
Deleted: domain.NullBool{
Valid: true,
Bool: false,
},
})
if err != nil {
return 0, fmt.Errorf("PlayerRepository.List: %w", err)
}
params := make([]domain.CreatePlayerParams, 0, len(players))
for _, player := range players {
params = append(params, domain.CreatePlayerParams{
@ -42,5 +58,21 @@ func (p *Player) Refresh(ctx context.Context, key, url string) (int64, error) {
return 0, fmt.Errorf("PlayerRepository.CreateOrUpdate: %w", err)
}
//nolint:prealloc
var playersToDelete []int64
for _, existing := range existingPlayers {
i := sort.Search(len(players), func(i int) bool {
return players[i].ID >= existing.ID
})
if i < len(players) && players[i].ID == existing.ID {
continue
}
playersToDelete = append(playersToDelete, existing.ID)
}
err = p.repo.DeleteByID(ctx, playersToDelete...)
if err != nil {
return 0, fmt.Errorf("PlayerRepository.DeleteByID: %w", err)
}
return int64(len(players)), nil
}

View File

@ -0,0 +1,115 @@
package service_test
import (
"context"
"testing"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"gitea.dwysokinski.me/twhelp/core/internal/service"
"gitea.dwysokinski.me/twhelp/core/internal/service/internal/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPlayer_Refresh(t *testing.T) {
t.Parallel()
serverKey, serverUrl := "pl169", "https://pl169.plemiona.pl"
players := []domain.BasePlayer{
{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 8,
ScoreAtt: 7,
RankDef: 6,
ScoreDef: 5,
RankSup: 4,
ScoreSup: 3,
RankTotal: 2,
ScoreTotal: 1,
},
ID: 998,
Name: "name 998",
NumVillages: 5,
Points: 4,
Rank: 2,
},
{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 1,
ScoreAtt: 2,
RankDef: 3,
ScoreDef: 4,
RankSup: 5,
ScoreSup: 6,
RankTotal: 7,
ScoreTotal: 8,
},
ID: 999,
Name: "name 999",
NumVillages: 3,
Points: 4,
Rank: 6,
TribeID: 1234,
},
}
client := &mock.FakePlayersGetter{}
client.GetPlayersReturns(players, nil)
existingPlayers := []domain.Player{
{
BasePlayer: domain.BasePlayer{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 8,
ScoreAtt: 7,
RankDef: 6,
ScoreDef: 5,
RankSup: 4,
ScoreSup: 3,
RankTotal: 2,
ScoreTotal: 1,
},
ID: 997,
Name: "name 997",
NumVillages: 5,
Points: 4,
Rank: 2,
},
ServerKey: serverKey,
CreatedAt: time.Now(),
},
{
BasePlayer: players[0],
ServerKey: serverKey,
CreatedAt: time.Now(),
},
}
repo := &mock.FakePlayerRepository{}
repo.ListReturnsOnCall(0, existingPlayers, 0, nil)
repo.CreateOrUpdateReturns(nil)
repo.DeleteByIDReturns(nil)
numPlayers, err := service.NewPlayer(repo, client).Refresh(context.Background(), serverKey, serverUrl)
assert.NoError(t, err)
assert.EqualValues(t, len(players), numPlayers)
require.Equal(t, 1, client.GetPlayersCallCount())
_, passedServerUrl := client.GetPlayersArgsForCall(0)
assert.Equal(t, passedServerUrl, serverUrl)
require.Equal(t, 1, repo.CreateOrUpdateCallCount())
_, params := repo.CreateOrUpdateArgsForCall(0)
assert.Len(t, params, len(players))
for i, p := range params {
assert.Equal(t, players[i], p.BasePlayer)
assert.Equal(t, serverKey, p.ServerKey)
}
require.Equal(t, 1, repo.DeleteByIDCallCount())
_, playersToDelete := repo.DeleteByIDArgsForCall(0)
require.Len(t, playersToDelete, 1)
assert.Equal(t, existingPlayers[0].ID, playersToDelete[0])
require.Equal(t, 1, repo.ListCallCount())
}

View File

@ -3,15 +3,20 @@ package service
import (
"context"
"fmt"
"sort"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
)
//counterfeiter:generate -o internal/mock/tribe_repository.gen.go . TribeRepository
type TribeRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
UpdateDominance(ctx context.Context, serverKey string, numVillages int64) error
DeleteByID(ctx context.Context, ids ...int64) error
List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error)
}
//counterfeiter:generate -o internal/mock/tribes_getter.gen.go . TribesGetter
type TribesGetter interface {
GetTribes(ctx context.Context, baseURL string) ([]domain.BaseTribe, error)
}
@ -31,6 +36,17 @@ func (t *Tribe) Refresh(ctx context.Context, key, url string) (int64, error) {
return 0, fmt.Errorf("TWClient.GetTribes: %w", err)
}
existingTribes, _, err := t.repo.List(ctx, domain.ListTribesParams{
ServerKeys: []string{key},
Deleted: domain.NullBool{
Valid: true,
Bool: false,
},
})
if err != nil {
return 0, fmt.Errorf("TribeRepository.List: %w", err)
}
params := make([]domain.CreateTribeParams, 0, len(tribes))
for _, tribe := range tribes {
params = append(params, domain.CreateTribeParams{
@ -43,6 +59,22 @@ func (t *Tribe) Refresh(ctx context.Context, key, url string) (int64, error) {
return 0, fmt.Errorf("TribeRepository.CreateOrUpdate: %w", err)
}
//nolint:prealloc
var tribesToDelete []int64
for _, existing := range existingTribes {
i := sort.Search(len(tribes), func(i int) bool {
return tribes[i].ID >= existing.ID
})
if i < len(tribes) && tribes[i].ID == existing.ID {
continue
}
tribesToDelete = append(tribesToDelete, existing.ID)
}
err = t.repo.DeleteByID(ctx, tribesToDelete...)
if err != nil {
return 0, fmt.Errorf("TribeRepository.DeleteByID: %w", err)
}
return int64(len(tribes)), nil
}

View File

@ -0,0 +1,128 @@
package service_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"gitea.dwysokinski.me/twhelp/core/internal/service"
"github.com/stretchr/testify/assert"
"gitea.dwysokinski.me/twhelp/core/internal/service/internal/mock"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
)
func TestTribe_Refresh(t *testing.T) {
t.Parallel()
serverKey, serverUrl := "pl169", "https://pl169.plemiona.pl"
tribes := []domain.BaseTribe{
{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 8,
ScoreAtt: 7,
RankDef: 6,
ScoreDef: 5,
RankSup: 4,
ScoreSup: 3,
RankTotal: 2,
ScoreTotal: 1,
},
ID: 998,
Name: "name 998",
Tag: "tag 998",
NumMembers: 6,
NumVillages: 5,
Points: 4,
AllPoints: 3,
Rank: 2,
},
{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 1,
ScoreAtt: 2,
RankDef: 3,
ScoreDef: 4,
RankSup: 5,
ScoreSup: 6,
RankTotal: 7,
ScoreTotal: 8,
},
ID: 999,
Name: "name 999",
Tag: "tag 999",
NumMembers: 2,
NumVillages: 3,
Points: 4,
AllPoints: 5,
Rank: 6,
},
}
client := &mock.FakeTribesGetter{}
client.GetTribesReturns(tribes, nil)
existingTribes := []domain.Tribe{
{
BaseTribe: domain.BaseTribe{
OpponentsDefeated: domain.OpponentsDefeated{
RankAtt: 8,
ScoreAtt: 7,
RankDef: 6,
ScoreDef: 5,
RankSup: 4,
ScoreSup: 3,
RankTotal: 2,
ScoreTotal: 1,
},
ID: 997,
Name: "name 997",
Tag: "tag 997",
NumMembers: 6,
NumVillages: 5,
Points: 4,
AllPoints: 3,
Rank: 2,
},
ServerKey: serverKey,
Dominance: 12.5,
CreatedAt: time.Now(),
},
{
BaseTribe: tribes[0],
ServerKey: serverKey,
Dominance: 11.5,
CreatedAt: time.Now(),
},
}
repo := &mock.FakeTribeRepository{}
repo.ListReturnsOnCall(0, existingTribes, 0, nil)
repo.CreateOrUpdateReturns(nil)
repo.DeleteByIDReturns(nil)
numTribes, err := service.NewTribe(repo, client).Refresh(context.Background(), serverKey, serverUrl)
assert.NoError(t, err)
assert.EqualValues(t, len(tribes), numTribes)
require.Equal(t, 1, client.GetTribesCallCount())
_, passedServerUrl := client.GetTribesArgsForCall(0)
assert.Equal(t, passedServerUrl, serverUrl)
require.Equal(t, 1, repo.CreateOrUpdateCallCount())
_, params := repo.CreateOrUpdateArgsForCall(0)
assert.Len(t, params, len(tribes))
for i, p := range params {
assert.Equal(t, tribes[i], p.BaseTribe)
assert.Equal(t, serverKey, p.ServerKey)
}
require.Equal(t, 1, repo.DeleteByIDCallCount())
_, tribesToDelete := repo.DeleteByIDArgsForCall(0)
require.Len(t, tribesToDelete, 1)
assert.Equal(t, existingTribes[0].ID, tribesToDelete[0])
require.Equal(t, 1, repo.ListCallCount())
}

View File

@ -9,6 +9,7 @@ import (
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
@ -177,6 +178,10 @@ func (c *Client) GetTribes(ctx context.Context, baseURL string) ([]domain.BaseTr
tribes[i] = tribe
}
sort.SliceStable(tribes, func(i, j int) bool {
return tribes[i].ID < tribes[j].ID
})
return tribes, nil
}
@ -250,6 +255,10 @@ func (c *Client) GetPlayers(ctx context.Context, baseURL string) ([]domain.BaseP
players[i] = player
}
sort.SliceStable(players, func(i, j int) bool {
return players[i].ID < players[j].ID
})
return players, nil
}
@ -380,6 +389,10 @@ func (c *Client) GetVillages(ctx context.Context, baseURL string) ([]domain.Base
villages[i] = village
}
sort.SliceStable(villages, func(i, j int) bool {
return villages[i].ID < villages[j].ID
})
return villages, nil
}

View File

@ -10,6 +10,8 @@ import (
"strconv"
"testing"
"github.com/stretchr/testify/require"
"gitea.dwysokinski.me/twhelp/core/internal/domain"
"gitea.dwysokinski.me/twhelp/core/internal/tw"
@ -814,19 +816,9 @@ func TestClient_GetTribes(t *testing.T) {
}
assert.True(t, checkErr(err))
assert.Len(t, tribes, len(tt.expectedTribes))
for _, tribe := range tt.expectedTribes {
var expectedTribe domain.BaseTribe
for _, tribe2 := range tribes {
if tribe.ID == tribe2.ID {
expectedTribe = tribe2
break
}
}
assert.Equal(t, expectedTribe, tribe)
require.Len(t, tribes, len(tt.expectedTribes))
for i, tribe := range tt.expectedTribes {
assert.Equal(t, tribe, tt.expectedTribes[i])
}
})
}
@ -1096,19 +1088,9 @@ func TestClient_GetPlayers(t *testing.T) {
}
assert.True(t, checkErr(err))
assert.Len(t, players, len(tt.expectedPlayers))
for _, player1 := range tt.expectedPlayers {
var expectedPlayer domain.BasePlayer
for _, player2 := range players {
if player1.ID == player2.ID {
expectedPlayer = player2
break
}
}
assert.Equal(t, expectedPlayer, player1)
require.Len(t, players, len(tt.expectedPlayers))
for i, player := range tt.expectedPlayers {
assert.Equal(t, player, tt.expectedPlayers[i])
}
})
}
@ -1235,19 +1217,9 @@ func TestClient_GetVillages(t *testing.T) {
}
assert.True(t, checkErr(err))
assert.Len(t, villages, len(tt.expectedVillages))
for _, village := range tt.expectedVillages {
var expectedVillage domain.BaseVillage
for _, village2 := range villages {
if village.ID == village2.ID {
expectedVillage = village2
break
}
}
assert.Equal(t, expectedVillage, village)
require.Len(t, villages, len(tt.expectedVillages))
for i, village := range tt.expectedVillages {
assert.Equal(t, village, tt.expectedVillages[i])
}
})
}