parent
b5b699ca49
commit
9668c23cc8
|
@ -92,7 +92,7 @@ var cmdConsumer = &cli.Command{
|
|||
}
|
||||
|
||||
consumer := port.NewTribeWatermillConsumer(
|
||||
app.NewTribeService(twSvc),
|
||||
app.NewTribeService(adapter.NewTribeBunRepository(db), twSvc),
|
||||
subscriber,
|
||||
logger,
|
||||
marshaler,
|
||||
|
|
9
go.mod
9
go.mod
|
@ -11,7 +11,6 @@ require (
|
|||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gosimple/slug v1.13.1
|
||||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/uptrace/bun v1.1.16
|
||||
|
@ -20,6 +19,7 @@ require (
|
|||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.16
|
||||
github.com/uptrace/bun/driver/sqliteshim v1.1.16
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.16
|
||||
github.com/urfave/cli/v2 v2.26.0
|
||||
)
|
||||
|
||||
|
@ -36,15 +36,16 @@ require (
|
|||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
|
@ -68,9 +69,9 @@ require (
|
|||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
|
|
24
go.sum
24
go.sum
|
@ -42,6 +42,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotchance/phpserialize v1.3.3 h1:hV4QVmGdCiYgoBbw+ADt6fNgyZ2mYX0OgpnON1adTCM=
|
||||
github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
|
@ -65,10 +67,6 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3
|
|||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
|
||||
github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -92,6 +90,9 @@ github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW
|
|||
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
|
@ -159,6 +160,8 @@ github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7Q
|
|||
github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU=
|
||||
github.com/uptrace/bun/driver/sqliteshim v1.1.16 h1:dhXrdXQegGSM+Jk07s9p1Y575DMhCKl7wBkss8kgM+8=
|
||||
github.com/uptrace/bun/driver/sqliteshim v1.1.16/go.mod h1:7fJaPqaiSvnWytQcMwVv1ZcjUJh7tGDonO/zq3O6RRI=
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM=
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
||||
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
|
@ -187,8 +190,8 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
|||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -198,8 +201,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -212,6 +215,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -223,8 +227,8 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -19,6 +19,7 @@ func NewFixture(bunDB *bun.DB) *Fixture {
|
|||
bunDB.RegisterModel(
|
||||
(*bunmodel.Version)(nil),
|
||||
(*bunmodel.Server)(nil),
|
||||
(*bunmodel.Tribe)(nil),
|
||||
)
|
||||
return &Fixture{
|
||||
f: dbfixture.New(bunDB),
|
||||
|
|
|
@ -141,7 +141,7 @@ func (p *Postgres) NewBunDB(tb TestingTB) *bun.DB {
|
|||
|
||||
schema := generatePostgresSchema()
|
||||
|
||||
sqldb := sql.OpenDB(
|
||||
sqlDB := sql.OpenDB(
|
||||
pgdriver.NewConnector(
|
||||
pgdriver.WithDSN(p.connectionString.String()),
|
||||
pgdriver.WithConnParams(map[string]any{
|
||||
|
@ -150,11 +150,13 @@ func (p *Postgres) NewBunDB(tb TestingTB) *bun.DB {
|
|||
),
|
||||
)
|
||||
|
||||
bunDB := bun.NewDB(sqldb, pgdialect.New())
|
||||
bunDB := bun.NewDB(sqlDB, pgdialect.New())
|
||||
tb.Cleanup(func() {
|
||||
_ = bunDB.Close()
|
||||
})
|
||||
|
||||
bunDB.AddQueryHook(newBunDebugHook())
|
||||
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.MaxInterval = postgresPingBackOffMaxInterval
|
||||
bo.MaxElapsedTime = postgresPingBackOffMaxElapsedTime
|
||||
|
|
|
@ -12,15 +12,17 @@ import (
|
|||
// NewBunDBSQLite initializes a new instance of *bun.DB, which is ready for use (all required migrations are applied).
|
||||
// Data is stored in memory (https://www.sqlite.org/inmemorydb.html).
|
||||
func NewBunDBSQLite(tb TestingTB) *bun.DB {
|
||||
sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:")
|
||||
sqlDB, err := sql.Open(sqliteshim.ShimName, "file::memory:")
|
||||
require.NoError(tb, err)
|
||||
sqldb.SetMaxOpenConns(1)
|
||||
sqldb.SetMaxIdleConns(1)
|
||||
sqldb.SetConnMaxLifetime(0)
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
sqlDB.SetMaxIdleConns(1)
|
||||
sqlDB.SetConnMaxLifetime(0)
|
||||
|
||||
db := bun.NewDB(sqldb, sqlitedialect.New())
|
||||
bunDB := bun.NewDB(sqlDB, sqlitedialect.New())
|
||||
|
||||
runMigrations(tb, db)
|
||||
bunDB.AddQueryHook(newBunDebugHook())
|
||||
|
||||
return db
|
||||
runMigrations(tb, bunDB)
|
||||
|
||||
return bunDB
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/ory/dockertest/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
)
|
||||
|
||||
const migrationTimeout = 10 * time.Second
|
||||
|
@ -55,3 +56,7 @@ func retry(bo backoff.BackOff, op backoff.Operation) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBunDebugHook() *bundebug.QueryHook {
|
||||
return bundebug.NewQueryHook(bundebug.FromEnv("BUNDEBUG"))
|
||||
}
|
||||
|
|
14
internal/adapter/bun_utils.go
Normal file
14
internal/adapter/bun_utils.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package adapter
|
||||
|
||||
import "github.com/uptrace/bun"
|
||||
|
||||
func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
|
||||
return q.Set("rank_att = EXCLUDED.rank_att").
|
||||
Set("score_att = EXCLUDED.score_att").
|
||||
Set("rank_def = EXCLUDED.rank_def").
|
||||
Set("score_def = EXCLUDED.score_def").
|
||||
Set("rank_sup = EXCLUDED.rank_sup").
|
||||
Set("score_sup = EXCLUDED.score_sup").
|
||||
Set("rank_total = EXCLUDED.rank_total").
|
||||
Set("score_total = EXCLUDED.score_total")
|
||||
}
|
48
internal/adapter/internal/bunmodel/opponents_defeated.go
Normal file
48
internal/adapter/internal/bunmodel/opponents_defeated.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type OpponentsDefeated struct {
|
||||
RankAtt int `bun:"rank_att"`
|
||||
ScoreAtt int `bun:"score_att"`
|
||||
RankDef int `bun:"rank_def"`
|
||||
ScoreDef int `bun:"score_def"`
|
||||
RankSup int `bun:"rank_sup"`
|
||||
ScoreSup int `bun:"score_sup"`
|
||||
RankTotal int `bun:"rank_total"`
|
||||
ScoreTotal int `bun:"score_total"`
|
||||
}
|
||||
|
||||
func NewOpponentsDefeated(od domain.OpponentsDefeated) OpponentsDefeated {
|
||||
return OpponentsDefeated{
|
||||
RankAtt: od.RankAtt(),
|
||||
ScoreAtt: od.ScoreAtt(),
|
||||
RankDef: od.RankDef(),
|
||||
ScoreDef: od.ScoreDef(),
|
||||
RankSup: od.RankSup(),
|
||||
ScoreSup: od.ScoreSup(),
|
||||
RankTotal: od.RankTotal(),
|
||||
ScoreTotal: od.ScoreTotal(),
|
||||
}
|
||||
}
|
||||
|
||||
func (od OpponentsDefeated) ToDomain() (domain.OpponentsDefeated, error) {
|
||||
converted, err := domain.NewOpponentsDefeated(
|
||||
od.RankAtt,
|
||||
od.ScoreAtt,
|
||||
od.RankDef,
|
||||
od.ScoreDef,
|
||||
od.RankSup,
|
||||
od.ScoreSup,
|
||||
od.RankTotal,
|
||||
od.ScoreTotal,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.OpponentsDefeated{}, fmt.Errorf("couldn't construct domain.OpponentsDefeated: %w", err)
|
||||
}
|
||||
return converted, nil
|
||||
}
|
87
internal/adapter/internal/bunmodel/tribe.go
Normal file
87
internal/adapter/internal/bunmodel/tribe.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Tribe struct {
|
||||
bun.BaseModel `bun:"table:tribes,alias:tribe"`
|
||||
|
||||
ID int `bun:"id,nullzero,pk"`
|
||||
ServerKey string `bun:"server_key,nullzero,pk"`
|
||||
Name string `bun:"name,nullzero"`
|
||||
Tag string `bun:"tag,nullzero"`
|
||||
NumMembers int `bun:"num_members"`
|
||||
NumVillages int `bun:"num_villages"`
|
||||
Points int `bun:"points"`
|
||||
AllPoints int `bun:"all_points"`
|
||||
Rank int `bun:"rank"`
|
||||
Dominance float64 `bun:"dominance"`
|
||||
ProfileURL string `bun:"profile_url,nullzero"`
|
||||
BestRank int `bun:"best_rank,nullzero,default:999999"`
|
||||
BestRankAt time.Time `bun:"best_rank_at,nullzero,default:CURRENT_TIMESTAMP"`
|
||||
MostPoints int `bun:"most_points"`
|
||||
MostPointsAt time.Time `bun:"most_points_at,nullzero,default:CURRENT_TIMESTAMP"`
|
||||
MostVillages int `bun:"most_villages"`
|
||||
MostVillagesAt time.Time `bun:"most_villages_at,nullzero,default:CURRENT_TIMESTAMP"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero,default:CURRENT_TIMESTAMP"`
|
||||
DeletedAt time.Time `bun:"deleted_at,nullzero"`
|
||||
|
||||
OpponentsDefeated
|
||||
}
|
||||
|
||||
func (t Tribe) ToDomain() (domain.Tribe, error) {
|
||||
od, err := t.OpponentsDefeated.ToDomain()
|
||||
if err != nil {
|
||||
return domain.Tribe{}, fmt.Errorf("couldn't construct domain.Tribe (id=%d,serverKey=%s): %w", t.ID, t.ServerKey, err)
|
||||
}
|
||||
|
||||
converted, err := domain.UnmarshalTribeFromDatabase(
|
||||
t.ID,
|
||||
t.ServerKey,
|
||||
t.Name,
|
||||
t.Tag,
|
||||
t.NumMembers,
|
||||
t.NumVillages,
|
||||
t.Points,
|
||||
t.AllPoints,
|
||||
t.Rank,
|
||||
od,
|
||||
t.ProfileURL,
|
||||
t.Dominance,
|
||||
t.BestRank,
|
||||
t.BestRankAt,
|
||||
t.MostPoints,
|
||||
t.MostPointsAt,
|
||||
t.MostVillages,
|
||||
t.MostVillagesAt,
|
||||
t.CreatedAt,
|
||||
t.DeletedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.Tribe{}, fmt.Errorf("couldn't construct domain.Tribe (id=%d,serverKey=%s): %w", t.ID, t.ServerKey, err)
|
||||
}
|
||||
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
type Tribes []Tribe
|
||||
|
||||
func (ts Tribes) ToDomain() (domain.Tribes, error) {
|
||||
res := make(domain.Tribes, 0, len(ts))
|
||||
|
||||
for _, t := range ts {
|
||||
converted, err := t.ToDomain()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, converted)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -146,8 +146,6 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
q = q.Where("server.special = ?", special.Value)
|
||||
}
|
||||
|
||||
q = q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
switch s {
|
||||
case domain.ServerSortKeyASC:
|
||||
|
@ -163,5 +161,5 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
}
|
||||
}
|
||||
|
||||
return q
|
||||
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||
}
|
||||
|
|
167
internal/adapter/repository_bun_tribe.go
Normal file
167
internal/adapter/repository_bun_tribe.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/internal/bunmodel"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
type TribeBunRepository struct {
|
||||
db bun.IDB
|
||||
}
|
||||
|
||||
func NewTribeBunRepository(db bun.IDB) *TribeBunRepository {
|
||||
return &TribeBunRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *TribeBunRepository) CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tribes := make(bunmodel.Tribes, 0, len(params))
|
||||
|
||||
for _, p := range params {
|
||||
base := p.Base()
|
||||
tribes = append(tribes, bunmodel.Tribe{
|
||||
ID: base.ID(),
|
||||
ServerKey: p.ServerKey(),
|
||||
Name: base.Name(),
|
||||
Tag: base.Tag(),
|
||||
NumMembers: base.NumMembers(),
|
||||
NumVillages: base.NumVillages(),
|
||||
Points: base.Points(),
|
||||
AllPoints: base.AllPoints(),
|
||||
Rank: base.Rank(),
|
||||
ProfileURL: base.ProfileURL().String(),
|
||||
BestRank: p.BestRank(),
|
||||
BestRankAt: p.BestRankAt(),
|
||||
MostPoints: p.MostPoints(),
|
||||
MostPointsAt: p.MostPointsAt(),
|
||||
MostVillages: p.MostVillages(),
|
||||
MostVillagesAt: p.MostVillagesAt(),
|
||||
CreatedAt: time.Now(),
|
||||
OpponentsDefeated: bunmodel.NewOpponentsDefeated(base.OD()),
|
||||
})
|
||||
}
|
||||
|
||||
q := repo.db.NewInsert().
|
||||
Model(&tribes)
|
||||
|
||||
//nolint:exhaustive
|
||||
switch q.Dialect().Name() {
|
||||
case dialect.PG:
|
||||
q = q.On("CONFLICT ON CONSTRAINT tribes_pkey DO UPDATE")
|
||||
case dialect.SQLite:
|
||||
q = q.On("CONFLICT(id, server_key) DO UPDATE")
|
||||
default:
|
||||
q = q.Err(errors.New("unsupported dialect"))
|
||||
}
|
||||
|
||||
if _, err := q.
|
||||
Set("name = EXCLUDED.name").
|
||||
Set("tag = EXCLUDED.tag").
|
||||
Set("num_members = EXCLUDED.num_members").
|
||||
Set("num_villages = EXCLUDED.num_villages").
|
||||
Set("points = EXCLUDED.points").
|
||||
Set("all_points = EXCLUDED.all_points").
|
||||
Set("rank = EXCLUDED.rank").
|
||||
Set("profile_url = EXCLUDED.profile_url").
|
||||
Set("best_rank = EXCLUDED.best_rank").
|
||||
Set("best_rank_at = EXCLUDED.best_rank_at").
|
||||
Set("most_villages = EXCLUDED.most_villages").
|
||||
Set("most_villages_at = EXCLUDED.most_villages_at").
|
||||
Set("most_points = EXCLUDED.most_points").
|
||||
Set("most_points_at = EXCLUDED.most_points_at").
|
||||
Set("deleted_at = EXCLUDED.deleted_at").
|
||||
Apply(appendODSetClauses).
|
||||
Returning("").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("something went wrong while inserting tribes into the db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *TribeBunRepository) List(ctx context.Context, params domain.ListTribesParams) (domain.Tribes, error) {
|
||||
var servers bunmodel.Tribes
|
||||
|
||||
if err := repo.db.NewSelect().
|
||||
Model(&servers).
|
||||
Apply(listTribesParamsApplier{params: params}.apply).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("couldn't select tribes from the db: %w", err)
|
||||
}
|
||||
|
||||
return servers.ToDomain()
|
||||
}
|
||||
|
||||
func (repo *TribeBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := repo.db.NewUpdate().
|
||||
Model((*bunmodel.Tribe)(nil)).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("id IN (?)", bun.In(ids)).
|
||||
Where("server_key = ?", serverKey).
|
||||
Set("deleted_at = ?", time.Now()).
|
||||
Returning("").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't delete tribes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listTribesParamsApplier struct {
|
||||
params domain.ListTribesParams
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if ids := a.params.IDs(); len(ids) > 0 {
|
||||
q = q.Where("tribe.id IN (?)", bun.In(ids))
|
||||
}
|
||||
|
||||
if idGT := a.params.IDGT(); idGT.Valid {
|
||||
q = q.Where("tribe.id > ?", idGT.Value)
|
||||
}
|
||||
|
||||
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
|
||||
q = q.Where("tribe.server_key IN (?)", bun.In(serverKeys))
|
||||
}
|
||||
|
||||
if deleted := a.params.Deleted(); deleted.Valid {
|
||||
if deleted.Value {
|
||||
q = q.Where("tribe.deleted_at IS NOT NULL")
|
||||
} else {
|
||||
q = q.Where("tribe.deleted_at IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range a.params.Sort() {
|
||||
switch s {
|
||||
case domain.TribeSortIDASC:
|
||||
q = q.Order("tribe.id ASC")
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Order("tribe.id DESC")
|
||||
case domain.TribeSortServerKeyASC:
|
||||
q = q.Order("tribe.server_key ASC")
|
||||
case domain.TribeSortServerKeyDESC:
|
||||
q = q.Order("tribe.server_key DESC")
|
||||
default:
|
||||
return q.Err(errors.New("unsupported sort value"))
|
||||
}
|
||||
}
|
||||
|
||||
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||
}
|
29
internal/adapter/repository_bun_tribe_test.go
Normal file
29
internal/adapter/repository_bun_tribe_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
|
||||
)
|
||||
|
||||
func TestTribeBunRepository_Postgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping long-running test")
|
||||
}
|
||||
|
||||
testTribeRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, postgres.NewBunDB(t))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTribeBunRepository_SQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testTribeRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, adaptertest.NewBunDBSQLite(t))
|
||||
})
|
||||
}
|
|
@ -4,13 +4,11 @@ import (
|
|||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -23,11 +21,39 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
t.Run("CreateOrUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
assertCreatedUpdated := func(t *testing.T, params []domain.CreateServerParams) {
|
||||
t.Helper()
|
||||
|
||||
require.NotEmpty(t, params)
|
||||
|
||||
keys := make([]string, 0, len(params))
|
||||
for _, p := range params {
|
||||
keys = append(keys, p.Base().Key())
|
||||
}
|
||||
|
||||
listParams := domain.NewListServersParams()
|
||||
require.NoError(t, listParams.SetKeys(keys))
|
||||
|
||||
servers, err := repos.server.List(ctx, listParams)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, servers, len(params))
|
||||
for i, p := range params {
|
||||
idx := slices.IndexFunc(servers, func(server domain.Server) bool {
|
||||
return server.Key() == p.Base().Key()
|
||||
})
|
||||
require.GreaterOrEqualf(t, idx, 0, "params[%d]", i)
|
||||
server := servers[idx]
|
||||
|
||||
assert.Equalf(t, p.Base(), server.Base(), "params[%d]", i)
|
||||
assert.Equalf(t, p.VersionCode(), server.VersionCode(), "params[%d]", i)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
versions, err := repos.version.List(ctx, domain.NewListVersionsParams())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, versions)
|
||||
|
@ -42,31 +68,11 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.server.CreateOrUpdate(ctx, createParams...))
|
||||
|
||||
keys := make([]string, 0, len(serversToCreate))
|
||||
for _, s := range serversToCreate {
|
||||
keys = append(keys, s.Key())
|
||||
}
|
||||
|
||||
listCreatedServersParams := domain.NewListServersParams()
|
||||
require.NoError(t, listCreatedServersParams.SetKeys(keys))
|
||||
|
||||
createdServers, err := repos.server.List(ctx, listCreatedServersParams)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, createdServers, len(serversToCreate))
|
||||
for _, base := range serversToCreate {
|
||||
assert.True(t, slices.ContainsFunc(createdServers, func(server domain.Server) bool {
|
||||
return server.Key() == base.Key() &&
|
||||
server.Open() == base.Open() &&
|
||||
server.URL().String() == base.URL().String() &&
|
||||
server.VersionCode() == version.Code()
|
||||
}))
|
||||
}
|
||||
assertCreatedUpdated(t, createParams)
|
||||
|
||||
serversToUpdate := domain.BaseServers{
|
||||
domaintest.NewBaseServer(t, func(cfg *domaintest.BaseServerConfig) {
|
||||
cfg.Key = serversToCreate[0].Key()
|
||||
cfg.URL = randURL(t)
|
||||
cfg.Open = !serversToCreate[0].Open()
|
||||
}),
|
||||
}
|
||||
|
@ -75,26 +81,13 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.server.CreateOrUpdate(ctx, updateParams...))
|
||||
assertCreatedUpdated(t, updateParams)
|
||||
})
|
||||
|
||||
keys = make([]string, 0, len(serversToUpdate))
|
||||
for _, s := range serversToUpdate {
|
||||
keys = append(keys, s.Key())
|
||||
}
|
||||
t.Run("OK: len(params) == 0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
listUpdatedServersParams := domain.NewListServersParams()
|
||||
require.NoError(t, listUpdatedServersParams.SetKeys(keys))
|
||||
|
||||
updatedServers, err := repos.server.List(ctx, listUpdatedServersParams)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, updatedServers, len(serversToUpdate))
|
||||
for _, base := range serversToUpdate {
|
||||
assert.True(t, slices.ContainsFunc(updatedServers, func(server domain.Server) bool {
|
||||
return server.Key() == base.Key() &&
|
||||
server.Open() == base.Open() &&
|
||||
server.URL().String() == base.URL().String() &&
|
||||
server.VersionCode() == version.Code()
|
||||
}))
|
||||
}
|
||||
require.NoError(t, repos.server.CreateOrUpdate(ctx))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -447,10 +440,3 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
func randURL(tb testing.TB) *url.URL {
|
||||
tb.Helper()
|
||||
u, err := url.Parse("https://" + gofakeit.DomainName())
|
||||
require.NoError(tb, err)
|
||||
return u
|
||||
}
|
||||
|
|
|
@ -25,9 +25,16 @@ type serverRepository interface {
|
|||
Update(ctx context.Context, key string, params domain.UpdateServerParams) error
|
||||
}
|
||||
|
||||
type tribeRepository interface {
|
||||
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
|
||||
List(ctx context.Context, params domain.ListTribesParams) (domain.Tribes, error)
|
||||
Delete(ctx context.Context, serverKey string, ids ...int) error
|
||||
}
|
||||
|
||||
type repositories struct {
|
||||
version versionRepository
|
||||
server serverRepository
|
||||
tribe tribeRepository
|
||||
}
|
||||
|
||||
func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
||||
|
@ -38,5 +45,6 @@ func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
|||
return repositories{
|
||||
version: adapter.NewVersionBunRepository(bunDB),
|
||||
server: adapter.NewServerBunRepository(bunDB),
|
||||
tribe: adapter.NewTribeBunRepository(bunDB),
|
||||
}
|
||||
}
|
||||
|
|
390
internal/adapter/repository_tribe_test.go
Normal file
390
internal/adapter/repository_tribe_test.go
Normal file
|
@ -0,0 +1,390 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("CreateOrUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
assertCreatedUpdated := func(t *testing.T, params []domain.CreateTribeParams) {
|
||||
t.Helper()
|
||||
|
||||
require.NotEmpty(t, params)
|
||||
|
||||
ids := make([]int, 0, len(params))
|
||||
for _, p := range params {
|
||||
ids = append(ids, p.Base().ID())
|
||||
}
|
||||
|
||||
listParams := domain.NewListTribesParams()
|
||||
require.NoError(t, listParams.SetIDs(ids))
|
||||
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
|
||||
|
||||
tribes, err := repos.tribe.List(ctx, listParams)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tribes, len(params))
|
||||
for i, p := range params {
|
||||
idx := slices.IndexFunc(tribes, func(tribe domain.Tribe) bool {
|
||||
return tribe.ID() == p.Base().ID() && tribe.ServerKey() == p.ServerKey()
|
||||
})
|
||||
require.GreaterOrEqualf(t, idx, 0, "params[%d]", i)
|
||||
tribe := tribes[idx]
|
||||
|
||||
assert.Equalf(t, p.Base(), tribe.Base(), "params[%d]", i)
|
||||
assert.Equalf(t, p.ServerKey(), tribe.ServerKey(), "params[%d]", i)
|
||||
assert.Equalf(t, p.BestRank(), tribe.BestRank(), "params[%d]", i)
|
||||
assert.WithinDurationf(t, p.BestRankAt(), tribe.BestRankAt(), time.Minute, "params[%d]", i)
|
||||
assert.Equalf(t, p.MostVillages(), tribe.MostVillages(), "params[%d]", i)
|
||||
assert.WithinDurationf(t, p.MostVillagesAt(), tribe.MostVillagesAt(), time.Minute, "params[%d]", i)
|
||||
assert.Equalf(t, p.MostPoints(), tribe.MostPoints(), "params[%d]", i)
|
||||
assert.WithinDurationf(t, p.MostPointsAt(), tribe.MostPointsAt(), time.Minute, "params[%d]", i)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
servers, err := repos.server.List(ctx, domain.NewListServersParams())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, servers)
|
||||
server := servers[0]
|
||||
|
||||
tribesToCreate := domain.BaseTribes{
|
||||
domaintest.NewBaseTribe(t),
|
||||
domaintest.NewBaseTribe(t),
|
||||
}
|
||||
|
||||
createParams, err := domain.NewCreateTribeParams(server.Key(), tribesToCreate, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.tribe.CreateOrUpdate(ctx, createParams...))
|
||||
assertCreatedUpdated(t, createParams)
|
||||
|
||||
tribesToUpdate := domain.BaseTribes{
|
||||
domaintest.NewBaseTribe(t, func(cfg *domaintest.BaseTribeConfig) {
|
||||
cfg.ID = tribesToCreate[0].ID()
|
||||
}),
|
||||
}
|
||||
|
||||
updateParams, err := domain.NewCreateTribeParams(server.Key(), tribesToUpdate, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.tribe.CreateOrUpdate(ctx, updateParams...))
|
||||
assertCreatedUpdated(t, updateParams)
|
||||
})
|
||||
|
||||
t.Run("OK: len(params) == 0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.NoError(t, repos.tribe.CreateOrUpdate(ctx))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("List & ListCount", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
tribes, listTribesErr := repos.tribe.List(ctx, domain.NewListTribesParams())
|
||||
require.NoError(t, listTribesErr)
|
||||
require.NotEmpty(t, tribes)
|
||||
randTribe := tribes[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListTribesParams
|
||||
assertTribes func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, params domain.ListTribesParams, total int)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
return domain.NewListTribesParams()
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
if x := cmp.Compare(a.ServerKey(), b.ServerKey()); x != 0 {
|
||||
return x
|
||||
}
|
||||
return cmp.Compare(a.ID(), b.ID())
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[serverKey DESC, id DESC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{domain.TribeSortServerKeyDESC, domain.TribeSortIDDESC}))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
if x := cmp.Compare(a.ServerKey(), b.ServerKey()) * -1; x != 0 {
|
||||
return x
|
||||
}
|
||||
return cmp.Compare(a.ID(), b.ID()) * -1
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("OK: ids=[%d] serverKeys=[%s]", randTribe.ID(), randTribe.ServerKey()),
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetIDs([]int{randTribe.ID()}))
|
||||
require.NoError(t, params.SetServerKeys([]string{randTribe.ServerKey()}))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
|
||||
ids := params.IDs()
|
||||
serverKeys := params.ServerKeys()
|
||||
|
||||
for _, tr := range tribes {
|
||||
assert.True(t, slices.Contains(ids, tr.ID()))
|
||||
assert.True(t, slices.Contains(serverKeys, tr.ServerKey()))
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("OK: idGT=%d", randTribe.ID()),
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetIDGT(domain.NullInt{
|
||||
Value: randTribe.ID(),
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, tribes)
|
||||
for _, tr := range tribes {
|
||||
assert.Greater(t, tr.ID(), params.IDGT().Value, tr.ID())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: deleted=true",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetDeleted(domain.NullBool{
|
||||
Value: true,
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, tribes)
|
||||
for _, s := range tribes {
|
||||
assert.True(t, s.IsDeleted())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: deleted=false",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetDeleted(domain.NullBool{
|
||||
Value: false,
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, tribes)
|
||||
for _, s := range tribes {
|
||||
assert.False(t, s.IsDeleted())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: offset=1 limit=2",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetOffset(1))
|
||||
require.NoError(t, params.SetLimit(2))
|
||||
return params
|
||||
},
|
||||
assertTribes: func(t *testing.T, params domain.ListTribesParams, tribes domain.Tribes) {
|
||||
t.Helper()
|
||||
assert.Len(t, tribes, params.Limit())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListTribesParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := tt.params(t)
|
||||
|
||||
res, err := repos.tribe.List(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertTribes(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
listServersParams := domain.NewListServersParams()
|
||||
require.NoError(t, listServersParams.SetSpecial(domain.NullBool{Value: false, Valid: true}))
|
||||
servers, listServersErr := repos.server.List(ctx, listServersParams)
|
||||
require.NoError(t, listServersErr)
|
||||
require.NotEmpty(t, servers)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serverKeys := make([]string, 0, len(servers))
|
||||
for _, s := range servers {
|
||||
serverKeys = append(serverKeys, s.Key())
|
||||
}
|
||||
|
||||
listTribesParams := domain.NewListTribesParams()
|
||||
require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{Value: false, Valid: true}))
|
||||
require.NoError(t, listTribesParams.SetServerKeys(serverKeys))
|
||||
|
||||
tribes, err := repos.tribe.List(ctx, listTribesParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
var serverKey string
|
||||
var ids []int
|
||||
|
||||
for _, tr := range tribes {
|
||||
if serverKey == "" {
|
||||
serverKey = tr.ServerKey()
|
||||
}
|
||||
|
||||
if tr.ServerKey() == serverKey {
|
||||
ids = append(ids, tr.ID())
|
||||
}
|
||||
}
|
||||
|
||||
idsToDelete := ids[:int(math.Ceil(float64(len(ids))/2))]
|
||||
|
||||
require.NoError(t, repos.tribe.Delete(ctx, serverKey, idsToDelete...))
|
||||
|
||||
listTribesParams = domain.NewListTribesParams()
|
||||
require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{Valid: false}))
|
||||
require.NoError(t, listTribesParams.SetServerKeys(serverKeys))
|
||||
|
||||
tribes, err = repos.tribe.List(ctx, listTribesParams)
|
||||
require.NoError(t, err)
|
||||
for _, tr := range tribes {
|
||||
if tr.ServerKey() == serverKey && slices.Contains(ids, tr.ID()) {
|
||||
if slices.Contains(idsToDelete, tr.ID()) {
|
||||
assert.WithinDuration(t, time.Now(), tr.DeletedAt(), time.Minute)
|
||||
} else {
|
||||
assert.Zero(t, tr.DeletedAt())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure that no other tribe is removed
|
||||
assert.WithinRange(t, tr.DeletedAt(), time.Time{}, time.Now().Add(-time.Minute))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OK: len(ids) == 0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.NoError(t, repos.tribe.Delete(ctx, servers[0].Key()))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -14,6 +14,8 @@ import (
|
|||
func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("List & ListCount", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -80,7 +82,6 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
params := tt.params(t)
|
||||
|
||||
res, err := repos.version.List(ctx, params)
|
||||
|
|
424
internal/adapter/testdata/fixture.yml
vendored
424
internal/adapter/testdata/fixture.yml
vendored
|
@ -76,3 +76,427 @@
|
|||
village_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
ennoblement_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
version_code: pl
|
||||
- model: Tribe
|
||||
rows:
|
||||
- rank_att: 1
|
||||
score_att: 730488339
|
||||
rank_def: 1
|
||||
score_def: 303300128
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 1
|
||||
score_total: 1033788467
|
||||
_id: de188-obott
|
||||
id: 772
|
||||
server_key: de188
|
||||
name: Obdachlose Otter
|
||||
tag: ObOtt
|
||||
num_members: 20
|
||||
num_villages: 8419
|
||||
points: 86151161
|
||||
all_points: 86151161
|
||||
rank: 1
|
||||
dominance: 58.367997781475324
|
||||
created_at: 2021-03-07T11:00:51.000Z
|
||||
profile_url: https://de188.die-staemme.de/game.php?screen=info_ally&id=772
|
||||
- rank_att: 4
|
||||
score_att: 84537872
|
||||
rank_def: 2
|
||||
score_def: 233565453
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 2
|
||||
score_total: 318103325
|
||||
_id: de188-clap
|
||||
id: 950
|
||||
server_key: de188
|
||||
name: Klatschn muss et
|
||||
tag: Clap!
|
||||
num_members: 4
|
||||
num_villages: 333
|
||||
points: 3274015
|
||||
all_points: 3274015
|
||||
rank: 5
|
||||
dominance: 2.3086522462562398
|
||||
created_at: 2021-03-16T01:00:54.000Z
|
||||
profile_url: https://de188.die-staemme.de/game.php?screen=info_ally&id=950
|
||||
- rank_att: 2
|
||||
score_att: 91976316
|
||||
rank_def: 21
|
||||
score_def: 15324882
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 6
|
||||
score_total: 107301198
|
||||
_id: de188-rb
|
||||
id: 1307
|
||||
server_key: de188
|
||||
name: Rasselbande
|
||||
tag: -RB-
|
||||
num_members: 6
|
||||
num_villages: 1509
|
||||
points: 14729501
|
||||
all_points: 14729501
|
||||
rank: 2
|
||||
dominance: 10.461730449251249
|
||||
created_at: 2021-04-17T09:01:01.000Z
|
||||
profile_url: https://de188.die-staemme.de/game.php?screen=info_ally&id=1307
|
||||
- rank_att: 3
|
||||
score_att: 87034216
|
||||
rank_def: 3
|
||||
score_def: 228160403
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 3
|
||||
score_total: 315194619
|
||||
_id: de188-lost
|
||||
id: 97
|
||||
server_key: de188
|
||||
name: Fühl ich nicht.
|
||||
tag: LOST
|
||||
num_members: 2
|
||||
num_villages: 274
|
||||
points: 2541853
|
||||
all_points: 2541853
|
||||
rank: 7
|
||||
dominance: 1.8996117581808099
|
||||
created_at: 2021-02-26T15:00:43.000Z
|
||||
profile_url: https://de188.die-staemme.de/game.php?screen=info_ally&id=97
|
||||
- rank_att: 5
|
||||
score_att: 81487046
|
||||
rank_def: 5
|
||||
score_def: 67353196
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 4
|
||||
score_total: 148840242
|
||||
_id: de188-shnauz
|
||||
id: 64
|
||||
server_key: de188
|
||||
name: aufshnauz
|
||||
tag: shnauz
|
||||
num_members: 2
|
||||
num_villages: 351
|
||||
points: 3431912
|
||||
all_points: 3431912
|
||||
rank: 4
|
||||
dominance: 2.4334442595673877
|
||||
created_at: 2021-02-24T22:00:55.000Z
|
||||
profile_url: https://de188.die-staemme.de/game.php?screen=info_ally&id=64
|
||||
- rank_att: 1
|
||||
score_att: 1449031872
|
||||
rank_def: 1
|
||||
score_def: 993299748
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 1
|
||||
score_total: 2442331620
|
||||
_id: en113-kekw
|
||||
id: 122
|
||||
server_key: en113
|
||||
name: Identity Crisis
|
||||
tag: KEKW
|
||||
num_members: 20
|
||||
num_villages: 22568
|
||||
points: 266237628
|
||||
all_points: 266237628
|
||||
rank: 1
|
||||
dominance: 54.27478896611433
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
profile_url: https://en113.tribalwars.net/game.php?screen=info_ally&id=122
|
||||
- rank_att: 2
|
||||
score_att: 508057735
|
||||
rank_def: 2
|
||||
score_def: 742858106
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 2
|
||||
score_total: 1250915841
|
||||
_id: en113-pear
|
||||
id: 112
|
||||
server_key: en113
|
||||
name: The Pear Bears
|
||||
tag: Pear
|
||||
num_members: 6
|
||||
num_villages: 804
|
||||
points: 7407640
|
||||
all_points: 7407640
|
||||
rank: 7
|
||||
dominance: 1.9335754310863134
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
profile_url: https://en113.tribalwars.net/game.php?screen=info_ally&id=112
|
||||
- rank_att: 3
|
||||
score_att: 321486919
|
||||
rank_def: 2
|
||||
score_def: 531735322
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 3
|
||||
score_total: 853222241
|
||||
_id: en113-virus
|
||||
id: 1337
|
||||
server_key: en113
|
||||
name: RIM VIRUS
|
||||
tag: VIRUS
|
||||
num_members: 4
|
||||
num_villages: 34
|
||||
points: 268858
|
||||
all_points: 268858
|
||||
rank: 30
|
||||
dominance: 0
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
deleted_at: 2020-12-03T18:01:07Z
|
||||
profile_url: https://en113.tribalwars.net/game.php?screen=info_ally&id=1337
|
||||
- rank_att: 3
|
||||
score_att: 231146512
|
||||
rank_def: 5
|
||||
score_def: 107383183
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 3
|
||||
score_total: 338529695
|
||||
_id: en113-rule
|
||||
id: 2544
|
||||
server_key: en113
|
||||
name: One Rule to Rule Them All
|
||||
tag: RULE
|
||||
num_members: 11
|
||||
num_villages: 4529
|
||||
points: 47701113
|
||||
all_points: 47701113
|
||||
rank: 2
|
||||
dominance: 10.891993939539693
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
profile_url: https://en113.tribalwars.net/game.php?screen=info_ally&id=2544
|
||||
- rank_att: 5
|
||||
score_att: 63878040
|
||||
rank_def: 3
|
||||
score_def: 132316341
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 5
|
||||
score_total: 196194381
|
||||
_id: en113-pq
|
||||
id: 1115
|
||||
server_key: en113
|
||||
name: Promising Quest in your area
|
||||
tag: ~PQ~
|
||||
num_members: 1
|
||||
num_villages: 23
|
||||
points: 72446
|
||||
all_points: 72446
|
||||
rank: 40
|
||||
dominance: 0
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
deleted_at: 2020-12-07T04:00:56Z
|
||||
profile_url: https://en113.tribalwars.net/game.php?screen=info_ally&id=1115
|
||||
- rank_att: 1
|
||||
score_att: 132897
|
||||
rank_def: 3
|
||||
score_def: 60776
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 1
|
||||
score_total: 193673
|
||||
_id: it70-tww
|
||||
id: 5
|
||||
server_key: it70
|
||||
name: The White Wolves
|
||||
tag: TWW
|
||||
num_members: 25
|
||||
num_villages: 65
|
||||
points: 182130
|
||||
all_points: 182130
|
||||
rank: 1
|
||||
dominance: 3.026070763500931
|
||||
created_at: 2022-02-21T20:00:07.000Z
|
||||
profile_url: https://it70.tribals.it/game.php?screen=info_ally&id=5
|
||||
- rank_att: 2
|
||||
score_att: 73109
|
||||
rank_def: 6
|
||||
score_def: 29374
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 4
|
||||
score_total: 102483
|
||||
_id: it70-tbw
|
||||
id: 30
|
||||
server_key: it70
|
||||
name: The Black Wolves
|
||||
tag: TBW
|
||||
num_members: 25
|
||||
num_villages: 57
|
||||
points: 155593
|
||||
all_points: 155593
|
||||
rank: 2
|
||||
dominance: 2.653631284916201
|
||||
created_at: 2022-02-24T12:00:05.000Z
|
||||
profile_url: https://it70.tribals.it/game.php?screen=info_ally&id=30
|
||||
- rank_att: 4
|
||||
score_att: 48518
|
||||
rank_def: 16
|
||||
score_def: 7674
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 7
|
||||
score_total: 56192
|
||||
_id: it70-tprm
|
||||
id: 1
|
||||
server_key: it70
|
||||
name: The Pokémon Red Machine
|
||||
tag: TPRM
|
||||
num_members: 23
|
||||
num_villages: 72
|
||||
points: 147717
|
||||
all_points: 147717
|
||||
rank: 3
|
||||
dominance: 3.35195530726257
|
||||
created_at: 2022-02-21T19:00:08.000Z
|
||||
profile_url: https://it70.tribals.it/game.php?screen=info_ally&id=1
|
||||
- rank_att: 5
|
||||
score_att: 47159
|
||||
rank_def: 1
|
||||
score_def: 88401
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 2
|
||||
score_total: 135560
|
||||
_id: it70-tbrm
|
||||
id: 3
|
||||
server_key: it70
|
||||
name: The Big Red Machine
|
||||
tag: TBRM
|
||||
num_members: 23
|
||||
num_villages: 56
|
||||
points: 142377
|
||||
all_points: 142377
|
||||
rank: 4
|
||||
dominance: 2.60707635009311
|
||||
created_at: 2022-02-21T20:00:07.000Z
|
||||
profile_url: https://it70.tribals.it/game.php?screen=info_ally&id=3
|
||||
- rank_att: 6
|
||||
score_att: 46008
|
||||
rank_def: 2
|
||||
score_def: 74025
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 3
|
||||
score_total: 120033
|
||||
_id: it70-tsrm
|
||||
id: 31
|
||||
server_key: it70
|
||||
name: The Small Red Machine
|
||||
tag: TSRM
|
||||
num_members: 25
|
||||
num_villages: 46
|
||||
points: 105512
|
||||
all_points: 105512
|
||||
rank: 5
|
||||
dominance: 2.1415270018621975
|
||||
created_at: 2022-02-24T12:00:05.000Z
|
||||
profile_url: https://it70.tribals.it/game.php?screen=info_ally&id=31
|
||||
- rank_att: 1
|
||||
score_att: 669292167
|
||||
rank_def: 5
|
||||
score_def: 199124128
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 2
|
||||
score_total: 868416295
|
||||
_id: pl169-csa
|
||||
id: 28
|
||||
server_key: pl169
|
||||
name: Konfederacja
|
||||
tag: CSA.
|
||||
num_members: 66
|
||||
num_villages: 14480
|
||||
points: 114277979
|
||||
all_points: 143350058
|
||||
rank: 1
|
||||
dominance: 29.76912481240106
|
||||
created_at: 2021-09-02T23:01:13.000Z
|
||||
profile_url: https://pl169.plemiona.pl/game.php?screen=info_ally&id=28
|
||||
- rank_att: 3
|
||||
score_att: 358150686
|
||||
rank_def: 3
|
||||
score_def: 331975367
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 4
|
||||
score_total: 690126053
|
||||
_id: pl169-csa-jr
|
||||
id: 2
|
||||
server_key: pl169
|
||||
name: KONFEDERACJA JUNIOR
|
||||
tag: CSA.JR
|
||||
num_members: 53
|
||||
num_villages: 10752
|
||||
points: 97684589
|
||||
all_points: 105576587
|
||||
rank: 2
|
||||
dominance: 22.10480870047902
|
||||
created_at: 2021-09-02T18:01:08.000Z
|
||||
profile_url: https://pl169.plemiona.pl/game.php?screen=info_ally&id=2
|
||||
- rank_att: 2
|
||||
score_att: 513902873
|
||||
rank_def: 6
|
||||
score_def: 177660231
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 3
|
||||
score_total: 691563104
|
||||
_id: pl169-kuzyni
|
||||
id: 27
|
||||
server_key: pl169
|
||||
name: Spoceni Kuzyni
|
||||
tag: KUZYNI
|
||||
num_members: 52
|
||||
num_villages: 10152
|
||||
points: 91951132
|
||||
all_points: 97812820
|
||||
rank: 3
|
||||
dominance: 20.87128142924693
|
||||
created_at: 2021-09-02T22:00:37.000Z
|
||||
profile_url: https://pl169.plemiona.pl/game.php?screen=info_ally&id=27
|
||||
- rank_att: 4
|
||||
score_att: 228636414
|
||||
rank_def: 1
|
||||
score_def: 865423891
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 1
|
||||
score_total: 1094060305
|
||||
_id: pl169-lpk
|
||||
id: 1
|
||||
server_key: pl169
|
||||
name: Łapanka
|
||||
tag: ŁPK
|
||||
num_members: 61
|
||||
num_villages: 3471
|
||||
points: 28417793
|
||||
all_points: 30608034
|
||||
rank: 4
|
||||
dominance: 7.13595526407763
|
||||
created_at: 2021-09-02T18:01:08.000Z
|
||||
profile_url: https://pl169.plemiona.pl/game.php?screen=info_ally&id=1
|
||||
- rank_att: 5
|
||||
score_att: 163168564
|
||||
rank_def: 13
|
||||
score_def: 60229489
|
||||
rank_sup: 0
|
||||
score_sup: 0
|
||||
rank_total: 7
|
||||
score_total: 223398053
|
||||
_id: pl169-tt
|
||||
id: 122
|
||||
server_key: pl169
|
||||
name: TIP TOP
|
||||
tag: TT.
|
||||
num_members: 23
|
||||
num_villages: 908
|
||||
points: 8188040
|
||||
all_points: 8188040
|
||||
rank: 6
|
||||
dominance: 1.8667379371312267
|
||||
created_at: 2021-09-08T02:00:58.000Z
|
||||
profile_url: https://pl169.plemiona.pl/game.php?screen=info_ally&id=122
|
||||
|
|
|
@ -7,12 +7,19 @@ import (
|
|||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type TribeRepository interface {
|
||||
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
|
||||
List(ctx context.Context, params domain.ListTribesParams) (domain.Tribes, error)
|
||||
Delete(ctx context.Context, serverKey string, ids ...int) error
|
||||
}
|
||||
|
||||
type TribeService struct {
|
||||
repo TribeRepository
|
||||
twSvc TWService
|
||||
}
|
||||
|
||||
func NewTribeService(twSvc TWService) *TribeService {
|
||||
return &TribeService{twSvc: twSvc}
|
||||
func NewTribeService(repo TribeRepository, twSvc TWService) *TribeService {
|
||||
return &TribeService{repo: repo, twSvc: twSvc}
|
||||
}
|
||||
|
||||
func (svc *TribeService) Sync(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
|
||||
|
|
|
@ -26,7 +26,7 @@ func NewBaseServer(key string, u *url.URL, open bool) (BaseServer, error) {
|
|||
return BaseServer{}, ValidationError{
|
||||
Model: baseServerModelName,
|
||||
Field: "url",
|
||||
Err: ErrNotNil,
|
||||
Err: ErrNil,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,20 +37,20 @@ func NewBaseServer(key string, u *url.URL, open bool) (BaseServer, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b BaseServer) Key() string {
|
||||
return b.key
|
||||
func (s BaseServer) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
func (b BaseServer) URL() *url.URL {
|
||||
return b.url
|
||||
func (s BaseServer) URL() *url.URL {
|
||||
return s.url
|
||||
}
|
||||
|
||||
func (b BaseServer) Open() bool {
|
||||
return b.open
|
||||
func (s BaseServer) Open() bool {
|
||||
return s.open
|
||||
}
|
||||
|
||||
func (b BaseServer) IsZero() bool {
|
||||
return b == BaseServer{}
|
||||
func (s BaseServer) IsZero() bool {
|
||||
return s == BaseServer{}
|
||||
}
|
||||
|
||||
type BaseServers []BaseServer
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestNewBaseServer(t *testing.T) {
|
|||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseServer",
|
||||
Field: "url",
|
||||
Err: domain.ErrNotNil,
|
||||
Err: domain.ErrNil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -108,12 +108,12 @@ func TestBaseServers_FilterOutSpecial(t *testing.T) {
|
|||
}
|
||||
|
||||
res := servers.FilterOutSpecial(special)
|
||||
assert.Len(t, res, len(servers)-len(special))
|
||||
for _, s := range servers {
|
||||
if slices.ContainsFunc(special, func(server domain.Server) bool {
|
||||
return server.Key() == s.Key()
|
||||
}) {
|
||||
return
|
||||
assert.NotContains(t, res, s)
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Contains(t, res, s)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package domain
|
||||
|
||||
import "net/url"
|
||||
import (
|
||||
"math"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type BaseTribe struct {
|
||||
id int
|
||||
|
@ -30,96 +33,70 @@ func NewBaseTribe(
|
|||
od OpponentsDefeated,
|
||||
profileURL *url.URL,
|
||||
) (BaseTribe, error) {
|
||||
if id < 1 {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "id",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: id,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if l := len(name); l < tribeNameMinLength || l > tribeNameMaxLength {
|
||||
if err := validateStringLen(name, tribeNameMinLength, tribeNameMaxLength); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "name",
|
||||
Err: LenOutOfRangeError{
|
||||
Min: tribeNameMinLength,
|
||||
Max: tribeNameMaxLength,
|
||||
Current: l,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// we convert tag to []rune to get the correct length
|
||||
// e.g. for tags such as Орловы, which takes 12 bytes rather than 6
|
||||
// explanation: https://golangbyexample.com/number-characters-string-golang/
|
||||
if l := len([]rune(tag)); l < tribeTagMinLength || l > tribeTagMaxLength {
|
||||
if err := validateSliceLen([]rune(tag), tribeTagMinLength, tribeTagMaxLength); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "tag",
|
||||
Err: LenOutOfRangeError{
|
||||
Min: tribeTagMinLength,
|
||||
Max: tribeTagMaxLength,
|
||||
Current: l,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if numMembers < 0 {
|
||||
if err := validateIntInRange(numMembers, 0, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "numMembers",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: numMembers,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if numVillages < 0 {
|
||||
if err := validateIntInRange(numVillages, 0, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "numVillages",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: numVillages,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if points < 0 {
|
||||
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "points",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: points,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if allPoints < 0 {
|
||||
if err := validateIntInRange(allPoints, 0, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "allPoints",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: allPoints,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if rank < 0 {
|
||||
if err := validateIntInRange(rank, 0, math.MaxInt); err != nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "rank",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rank,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +104,7 @@ func NewBaseTribe(
|
|||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "profileURL",
|
||||
Err: ErrNotNil,
|
||||
Err: ErrNil,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,44 +122,48 @@ func NewBaseTribe(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b BaseTribe) ID() int {
|
||||
return b.id
|
||||
func (t BaseTribe) ID() int {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (b BaseTribe) Name() string {
|
||||
return b.name
|
||||
func (t BaseTribe) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (b BaseTribe) Tag() string {
|
||||
return b.tag
|
||||
func (t BaseTribe) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
func (b BaseTribe) NumMembers() int {
|
||||
return b.numMembers
|
||||
func (t BaseTribe) NumMembers() int {
|
||||
return t.numMembers
|
||||
}
|
||||
|
||||
func (b BaseTribe) NumVillages() int {
|
||||
return b.numVillages
|
||||
func (t BaseTribe) NumVillages() int {
|
||||
return t.numVillages
|
||||
}
|
||||
|
||||
func (b BaseTribe) Points() int {
|
||||
return b.points
|
||||
func (t BaseTribe) Points() int {
|
||||
return t.points
|
||||
}
|
||||
|
||||
func (b BaseTribe) AllPoints() int {
|
||||
return b.allPoints
|
||||
func (t BaseTribe) AllPoints() int {
|
||||
return t.allPoints
|
||||
}
|
||||
|
||||
func (b BaseTribe) Rank() int {
|
||||
return b.rank
|
||||
func (t BaseTribe) Rank() int {
|
||||
return t.rank
|
||||
}
|
||||
|
||||
func (b BaseTribe) OD() OpponentsDefeated {
|
||||
return b.od
|
||||
func (t BaseTribe) OD() OpponentsDefeated {
|
||||
return t.od
|
||||
}
|
||||
|
||||
func (b BaseTribe) ProfileURL() *url.URL {
|
||||
return b.profileURL
|
||||
func (t BaseTribe) ProfileURL() *url.URL {
|
||||
return t.profileURL
|
||||
}
|
||||
|
||||
func (t BaseTribe) IsZero() bool {
|
||||
return t == BaseTribe{}
|
||||
}
|
||||
|
||||
type BaseTribes []BaseTribe
|
||||
|
|
|
@ -315,7 +315,7 @@ func TestNewBaseTribe(t *testing.T) {
|
|||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "profileURL",
|
||||
Err: domain.ErrNotNil,
|
||||
Err: domain.ErrNil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ type BaseTribeConfig struct {
|
|||
ID int
|
||||
Tag string
|
||||
OD domain.OpponentsDefeated
|
||||
NumVillages int
|
||||
AllPoints int
|
||||
Rank int
|
||||
}
|
||||
|
||||
func NewBaseTribe(tb TestingTB, opts ...func(cfg *BaseTribeConfig)) domain.BaseTribe {
|
||||
|
@ -21,6 +24,9 @@ func NewBaseTribe(tb TestingTB, opts ...func(cfg *BaseTribeConfig)) domain.BaseT
|
|||
ID: RandID(),
|
||||
Tag: RandTribeTag(),
|
||||
OD: NewOpponentsDefeated(tb),
|
||||
NumVillages: gofakeit.IntRange(1, 10000),
|
||||
AllPoints: gofakeit.IntRange(1, 10000),
|
||||
Rank: gofakeit.IntRange(1, 10000),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
@ -35,10 +41,10 @@ func NewBaseTribe(tb TestingTB, opts ...func(cfg *BaseTribeConfig)) domain.BaseT
|
|||
gofakeit.LetterN(50),
|
||||
cfg.Tag,
|
||||
gofakeit.IntRange(1, 10000),
|
||||
cfg.NumVillages,
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
cfg.AllPoints,
|
||||
cfg.Rank,
|
||||
cfg.OD,
|
||||
u,
|
||||
)
|
||||
|
|
|
@ -1,7 +1,73 @@
|
|||
package domaintest
|
||||
|
||||
import "github.com/brianvoe/gofakeit/v6"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func RandTribeTag() string {
|
||||
return gofakeit.LetterN(5)
|
||||
}
|
||||
|
||||
type TribeConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
Tag string
|
||||
OD domain.OpponentsDefeated
|
||||
BestRank int
|
||||
BestRankAt time.Time
|
||||
MostPoints int
|
||||
MostPointsAt time.Time
|
||||
MostVillages int
|
||||
MostVillagesAt time.Time
|
||||
}
|
||||
|
||||
func NewTribe(tb TestingTB, opts ...func(cfg *TribeConfig)) domain.Tribe {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &TribeConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
Tag: RandTribeTag(),
|
||||
OD: NewOpponentsDefeated(tb),
|
||||
BestRank: gofakeit.IntRange(1, 10000),
|
||||
BestRankAt: time.Now(),
|
||||
MostPoints: gofakeit.IntRange(1, 10000),
|
||||
MostPointsAt: time.Now(),
|
||||
MostVillages: gofakeit.IntRange(1, 10000),
|
||||
MostVillagesAt: time.Now(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
t, err := domain.UnmarshalTribeFromDatabase(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
gofakeit.LetterN(50),
|
||||
cfg.Tag,
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
gofakeit.IntRange(1, 10000),
|
||||
cfg.OD,
|
||||
gofakeit.URL(),
|
||||
gofakeit.Float64Range(0.1, 99.9),
|
||||
cfg.BestRank,
|
||||
cfg.BestRankAt,
|
||||
cfg.MostPoints,
|
||||
cfg.MostPointsAt,
|
||||
cfg.MostVillages,
|
||||
cfg.MostVillagesAt,
|
||||
time.Now(),
|
||||
time.Time{},
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return t
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package domain
|
||||
|
||||
import "math"
|
||||
|
||||
type OpponentsDefeated struct {
|
||||
rankAtt int
|
||||
scoreAtt int
|
||||
|
@ -23,91 +25,67 @@ func NewOpponentsDefeated(
|
|||
rankTotal int,
|
||||
scoreTotal int,
|
||||
) (OpponentsDefeated, error) {
|
||||
if rankAtt < 0 {
|
||||
if err := validateIntInRange(rankAtt, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankAtt",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankAtt,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if scoreAtt < 0 {
|
||||
if err := validateIntInRange(scoreAtt, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreAtt",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreAtt,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if rankDef < 0 {
|
||||
if err := validateIntInRange(rankDef, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankDef",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankDef,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if scoreDef < 0 {
|
||||
if err := validateIntInRange(scoreDef, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreDef",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreDef,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if rankSup < 0 {
|
||||
if err := validateIntInRange(rankSup, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankSup",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankSup,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if scoreSup < 0 {
|
||||
if err := validateIntInRange(scoreSup, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreSup",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreSup,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if rankTotal < 0 {
|
||||
if err := validateIntInRange(rankTotal, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankTotal",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankTotal,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if scoreTotal < 0 {
|
||||
if err := validateIntInRange(scoreTotal, 0, math.MaxInt); err != nil {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreTotal",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreTotal,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
@ -37,6 +37,8 @@ type Server struct {
|
|||
ennoblementDataSyncedAt time.Time
|
||||
}
|
||||
|
||||
const serverModelName = "Server"
|
||||
|
||||
// UnmarshalServerFromDatabase unmarshals Server from the database.
|
||||
//
|
||||
// It should be used only for unmarshalling from the database!
|
||||
|
@ -65,16 +67,28 @@ func UnmarshalServerFromDatabase(
|
|||
ennoblementDataSyncedAt time.Time,
|
||||
) (Server, error) {
|
||||
if key == "" {
|
||||
return Server{}, errors.New("key can't be blank")
|
||||
return Server{}, ValidationError{
|
||||
Model: serverModelName,
|
||||
Field: "key",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if versionCode == "" {
|
||||
return Server{}, errors.New("version code can't be blank")
|
||||
return Server{}, ValidationError{
|
||||
Model: serverModelName,
|
||||
Field: "versionCode",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
u, err := parseURL(rawURL)
|
||||
if err != nil {
|
||||
return Server{}, err
|
||||
return Server{}, ValidationError{
|
||||
Model: serverModelName,
|
||||
Field: "url",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return Server{
|
||||
|
@ -186,6 +200,14 @@ func (s Server) EnnoblementDataSyncedAt() time.Time {
|
|||
return s.ennoblementDataSyncedAt
|
||||
}
|
||||
|
||||
func (s Server) Base() BaseServer {
|
||||
return BaseServer{
|
||||
key: s.key,
|
||||
url: s.url,
|
||||
open: s.open,
|
||||
}
|
||||
}
|
||||
|
||||
type Servers []Server
|
||||
|
||||
// Close finds all servers with Server.Open returning true that are not in the given slice with open servers
|
||||
|
@ -398,25 +420,11 @@ func (params *ListServersParams) Limit() int {
|
|||
}
|
||||
|
||||
func (params *ListServersParams) SetLimit(limit int) error {
|
||||
if limit <= 0 {
|
||||
if err := validateIntInRange(limit, 1, ServerListMaxLimit); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "limit",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: limit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if limit > ServerListMaxLimit {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "limit",
|
||||
Err: MaxLessEqualError{
|
||||
Max: ServerListMaxLimit,
|
||||
Current: limit,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,14 +438,11 @@ func (params *ListServersParams) Offset() int {
|
|||
}
|
||||
|
||||
func (params *ListServersParams) SetOffset(offset int) error {
|
||||
if offset < 0 {
|
||||
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "offset",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: offset,
|
||||
},
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
|
@ -11,13 +9,23 @@ type SyncServersCmdPayload struct {
|
|||
url *url.URL
|
||||
}
|
||||
|
||||
const syncServersCmdPayloadModelName = "SyncServersCmdPayload"
|
||||
|
||||
func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPayload, error) {
|
||||
if versionCode == "" {
|
||||
return SyncServersCmdPayload{}, errors.New("version code can't be blank")
|
||||
return SyncServersCmdPayload{}, ValidationError{
|
||||
Model: syncServersCmdPayloadModelName,
|
||||
Field: "versionCode",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
return SyncServersCmdPayload{}, errors.New("url can't be nil")
|
||||
return SyncServersCmdPayload{}, ValidationError{
|
||||
Model: syncServersCmdPayloadModelName,
|
||||
Field: "url",
|
||||
Err: ErrNil,
|
||||
}
|
||||
}
|
||||
|
||||
return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil
|
||||
|
@ -37,17 +45,31 @@ type ServerSyncedEventPayload struct {
|
|||
versionCode string
|
||||
}
|
||||
|
||||
const serverSyncedEventPayloadModelName = "ServerSyncedEventPayload"
|
||||
|
||||
func NewServerSyncedEventPayload(key string, u *url.URL, versionCode string) (ServerSyncedEventPayload, error) {
|
||||
if key == "" {
|
||||
return ServerSyncedEventPayload{}, errors.New("key can't be blank")
|
||||
return ServerSyncedEventPayload{}, ValidationError{
|
||||
Model: serverSyncedEventPayloadModelName,
|
||||
Field: "key",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if versionCode == "" {
|
||||
return ServerSyncedEventPayload{}, errors.New("version code can't be blank")
|
||||
return ServerSyncedEventPayload{}, ValidationError{
|
||||
Model: serverSyncedEventPayloadModelName,
|
||||
Field: "versionCode",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
return ServerSyncedEventPayload{}, errors.New("url can't be nil")
|
||||
return ServerSyncedEventPayload{}, ValidationError{
|
||||
Model: serverSyncedEventPayloadModelName,
|
||||
Field: "url",
|
||||
Err: ErrNil,
|
||||
}
|
||||
}
|
||||
|
||||
return ServerSyncedEventPayload{
|
||||
|
@ -58,22 +80,15 @@ func NewServerSyncedEventPayload(key string, u *url.URL, versionCode string) (Se
|
|||
}
|
||||
|
||||
func NewServerSyncedEventPayloads(servers BaseServers, versionCode string) ([]ServerSyncedEventPayload, error) {
|
||||
if versionCode == "" {
|
||||
return nil, errors.New("version code can't be blank")
|
||||
}
|
||||
|
||||
res := make([]ServerSyncedEventPayload, 0, len(servers))
|
||||
|
||||
for i, s := range servers {
|
||||
if s.IsZero() {
|
||||
return nil, fmt.Errorf("servers[%d] is an empty struct", i)
|
||||
for _, s := range servers {
|
||||
payload, err := NewServerSyncedEventPayload(s.Key(), s.URL(), versionCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, ServerSyncedEventPayload{
|
||||
key: s.Key(),
|
||||
url: s.URL(),
|
||||
versionCode: versionCode,
|
||||
})
|
||||
res = append(res, payload)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
|
|
@ -37,17 +37,20 @@ func TestServers_Close(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, res)
|
||||
for _, s := range servers {
|
||||
if !s.Open() || slices.ContainsFunc(open, func(server domain.BaseServer) bool {
|
||||
return server.Key() == s.Key()
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Contains(t, res, domaintest.NewBaseServer(t, func(cfg *domaintest.BaseServerConfig) {
|
||||
expected := domaintest.NewBaseServer(t, func(cfg *domaintest.BaseServerConfig) {
|
||||
cfg.Key = s.Key()
|
||||
cfg.URL = s.URL()
|
||||
cfg.Open = false
|
||||
}))
|
||||
})
|
||||
|
||||
if !s.Open() || slices.ContainsFunc(open, func(server domain.BaseServer) bool {
|
||||
return server.Key() == s.Key()
|
||||
}) {
|
||||
assert.NotContains(t, res, expected)
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Contains(t, res, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -35,6 +38,75 @@ type Tribe struct {
|
|||
deletedAt time.Time
|
||||
}
|
||||
|
||||
const tribeModelName = "Tribe"
|
||||
|
||||
// UnmarshalTribeFromDatabase unmarshals Tribe from the database.
|
||||
//
|
||||
// It should be used only for unmarshalling from the database!
|
||||
// You can't use UnmarshalTribeFromDatabase as constructor - It may put domain into the invalid state!
|
||||
func UnmarshalTribeFromDatabase(
|
||||
id int,
|
||||
serverKey string,
|
||||
name string,
|
||||
tag string,
|
||||
numMembers int,
|
||||
numVillages int,
|
||||
points int,
|
||||
allPoints int,
|
||||
rank int,
|
||||
od OpponentsDefeated,
|
||||
rawProfileURL string,
|
||||
dominance float64,
|
||||
bestRank int,
|
||||
bestRankAt time.Time,
|
||||
mostPoints int,
|
||||
mostPointsAt time.Time,
|
||||
mostVillages int,
|
||||
mostVillagesAt time.Time,
|
||||
createdAt time.Time,
|
||||
deletedAt time.Time,
|
||||
) (Tribe, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return Tribe{}, ValidationError{
|
||||
Model: tribeModelName,
|
||||
Field: "id",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
profileURL, err := parseURL(rawProfileURL)
|
||||
if err != nil {
|
||||
return Tribe{}, ValidationError{
|
||||
Model: tribeModelName,
|
||||
Field: "profileURL",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return Tribe{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
name: name,
|
||||
tag: tag,
|
||||
numMembers: numMembers,
|
||||
numVillages: numVillages,
|
||||
points: points,
|
||||
allPoints: allPoints,
|
||||
rank: rank,
|
||||
od: od,
|
||||
profileURL: profileURL,
|
||||
dominance: dominance,
|
||||
bestRank: bestRank,
|
||||
bestRankAt: bestRankAt,
|
||||
mostPoints: mostPoints,
|
||||
mostPointsAt: mostPointsAt,
|
||||
mostVillages: mostVillages,
|
||||
mostVillagesAt: mostVillagesAt,
|
||||
createdAt: createdAt,
|
||||
deletedAt: deletedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t Tribe) ID() int {
|
||||
return t.id
|
||||
}
|
||||
|
@ -114,3 +186,276 @@ func (t Tribe) CreatedAt() time.Time {
|
|||
func (t Tribe) DeletedAt() time.Time {
|
||||
return t.deletedAt
|
||||
}
|
||||
|
||||
func (t Tribe) IsDeleted() bool {
|
||||
return !t.deletedAt.IsZero()
|
||||
}
|
||||
|
||||
func (t Tribe) Base() BaseTribe {
|
||||
return BaseTribe{
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
tag: t.tag,
|
||||
numMembers: t.numMembers,
|
||||
numVillages: t.numVillages,
|
||||
points: t.points,
|
||||
allPoints: t.allPoints,
|
||||
rank: t.rank,
|
||||
od: t.od,
|
||||
profileURL: t.profileURL,
|
||||
}
|
||||
}
|
||||
|
||||
type Tribes []Tribe
|
||||
|
||||
type CreateTribeParams struct {
|
||||
base BaseTribe
|
||||
serverKey string
|
||||
bestRank int
|
||||
bestRankAt time.Time
|
||||
mostPoints int
|
||||
mostPointsAt time.Time
|
||||
mostVillages int
|
||||
mostVillagesAt time.Time
|
||||
}
|
||||
|
||||
const createTribeParamsModelName = "CreateTribeParams"
|
||||
|
||||
//nolint:gocyclo
|
||||
func NewCreateTribeParams(serverKey string, tribes BaseTribes, storedTribes Tribes) ([]CreateTribeParams, error) {
|
||||
if err := validateServerKey(serverKey); err != nil {
|
||||
return nil, ValidationError{
|
||||
Model: createTribeParamsModelName,
|
||||
Field: "serverKey",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params := make([]CreateTribeParams, 0, len(tribes))
|
||||
|
||||
for i, t := range tribes {
|
||||
if t.IsZero() {
|
||||
return nil, fmt.Errorf("tribes[%d] is an empty struct", i)
|
||||
}
|
||||
|
||||
var old Tribe
|
||||
if oldIdx := slices.IndexFunc(storedTribes, func(old Tribe) bool {
|
||||
return old.ID() == t.ID() && old.ServerKey() == serverKey
|
||||
}); oldIdx >= 0 {
|
||||
old = storedTribes[oldIdx]
|
||||
}
|
||||
|
||||
p := CreateTribeParams{
|
||||
base: t,
|
||||
serverKey: serverKey,
|
||||
bestRank: old.bestRank,
|
||||
bestRankAt: old.bestRankAt,
|
||||
mostPoints: old.mostPoints,
|
||||
mostPointsAt: old.mostPointsAt,
|
||||
mostVillages: old.mostVillages,
|
||||
mostVillagesAt: old.mostVillagesAt,
|
||||
}
|
||||
|
||||
if t.AllPoints() > p.MostPoints() || old.ID() <= 0 {
|
||||
p.mostPoints = t.AllPoints()
|
||||
p.mostPointsAt = time.Now()
|
||||
}
|
||||
|
||||
if t.NumVillages() > p.MostVillages() || old.ID() <= 0 {
|
||||
p.mostVillages = t.NumVillages()
|
||||
p.mostVillagesAt = time.Now()
|
||||
}
|
||||
|
||||
if t.Rank() < p.BestRank() || old.ID() <= 0 {
|
||||
p.bestRank = t.Rank()
|
||||
p.bestRankAt = time.Now()
|
||||
}
|
||||
|
||||
params = append(params, p)
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) Base() BaseTribe {
|
||||
return params.base
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) ServerKey() string {
|
||||
return params.serverKey
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) BestRank() int {
|
||||
return params.bestRank
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) BestRankAt() time.Time {
|
||||
return params.bestRankAt
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) MostPoints() int {
|
||||
return params.mostPoints
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) MostPointsAt() time.Time {
|
||||
return params.mostPointsAt
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) MostVillages() int {
|
||||
return params.mostVillages
|
||||
}
|
||||
|
||||
func (params CreateTribeParams) MostVillagesAt() time.Time {
|
||||
return params.mostVillagesAt
|
||||
}
|
||||
|
||||
type TribeSort uint8
|
||||
|
||||
const (
|
||||
TribeSortIDASC TribeSort = iota + 1
|
||||
TribeSortIDDESC
|
||||
TribeSortServerKeyASC
|
||||
TribeSortServerKeyDESC
|
||||
)
|
||||
|
||||
const TribeListMaxLimit = 200
|
||||
|
||||
type ListTribesParams struct {
|
||||
ids []int
|
||||
idGT NullInt
|
||||
serverKeys []string
|
||||
deleted NullBool
|
||||
sort []TribeSort
|
||||
limit int
|
||||
offset int
|
||||
}
|
||||
|
||||
const listTribesParamsModelName = "ListTribesParams"
|
||||
|
||||
func NewListTribesParams() ListTribesParams {
|
||||
return ListTribesParams{
|
||||
sort: []TribeSort{
|
||||
TribeSortServerKeyASC,
|
||||
TribeSortIDASC,
|
||||
},
|
||||
limit: TribeListMaxLimit,
|
||||
}
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) IDs() []int {
|
||||
return params.ids
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetIDs(ids []int) error {
|
||||
for i, id := range ids {
|
||||
if err := validateIntInRange(id, 0, math.MaxInt); err != nil {
|
||||
return SliceElementValidationError{
|
||||
Model: listTribesParamsModelName,
|
||||
Field: "ids",
|
||||
Index: i,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.ids = ids
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) IDGT() NullInt {
|
||||
return params.idGT
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetIDGT(idGT NullInt) error {
|
||||
if idGT.Valid {
|
||||
if err := validateIntInRange(idGT.Value, 0, math.MaxInt); err != nil {
|
||||
return ValidationError{
|
||||
Model: listTribesParamsModelName,
|
||||
Field: "idGT",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.idGT = idGT
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) ServerKeys() []string {
|
||||
return params.serverKeys
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetServerKeys(serverKeys []string) error {
|
||||
params.serverKeys = serverKeys
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) Deleted() NullBool {
|
||||
return params.deleted
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetDeleted(deleted NullBool) error {
|
||||
params.deleted = deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) Sort() []TribeSort {
|
||||
return params.sort
|
||||
}
|
||||
|
||||
const (
|
||||
tribeSortMinLength = 1
|
||||
tribeSortMaxLength = 2
|
||||
)
|
||||
|
||||
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
||||
if err := validateSliceLen(sort, tribeSortMinLength, tribeSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Model: listTribesParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.sort = sort
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetLimit(limit int) error {
|
||||
if err := validateIntInRange(limit, 1, TribeListMaxLimit); err != nil {
|
||||
return ValidationError{
|
||||
Model: listTribesParamsModelName,
|
||||
Field: "limit",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.limit = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) Offset() int {
|
||||
return params.offset
|
||||
}
|
||||
|
||||
func (params *ListTribesParams) SetOffset(offset int) error {
|
||||
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
||||
return ValidationError{
|
||||
Model: listTribesParamsModelName,
|
||||
Field: "offset",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.offset = offset
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
419
internal/domain/tribe_test.go
Normal file
419
internal/domain/tribe_test.go
Normal file
|
@ -0,0 +1,419 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewCreateTribeParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
server := domaintest.NewServer(t)
|
||||
|
||||
tribes := domain.BaseTribes{
|
||||
domaintest.NewBaseTribe(t),
|
||||
domaintest.NewBaseTribe(t),
|
||||
domaintest.NewBaseTribe(t),
|
||||
}
|
||||
|
||||
storedTribes := domain.Tribes{
|
||||
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
|
||||
cfg.ID = tribes[0].ID()
|
||||
cfg.ServerKey = server.Key()
|
||||
cfg.BestRank = tribes[0].Rank() + 1
|
||||
cfg.BestRankAt = now.Add(-time.Hour)
|
||||
cfg.MostPoints = tribes[0].AllPoints() - 1
|
||||
cfg.MostPointsAt = now.Add(-time.Hour)
|
||||
cfg.MostVillages = tribes[0].NumVillages() - 1
|
||||
cfg.MostVillagesAt = now.Add(-time.Hour)
|
||||
}),
|
||||
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
|
||||
cfg.ID = tribes[1].ID()
|
||||
cfg.ServerKey = server.Key()
|
||||
cfg.BestRank = tribes[1].Rank() - 1
|
||||
cfg.BestRankAt = now.Add(-time.Hour)
|
||||
cfg.MostPoints = tribes[1].AllPoints() + 1
|
||||
cfg.MostPointsAt = now.Add(-time.Hour)
|
||||
cfg.MostVillages = tribes[1].NumVillages() + 1
|
||||
cfg.MostVillagesAt = now.Add(-time.Hour)
|
||||
}),
|
||||
}
|
||||
|
||||
expectedParams := []struct {
|
||||
base domain.BaseTribe
|
||||
bestRank int
|
||||
bestRankAt time.Time
|
||||
mostPoints int
|
||||
mostPointsAt time.Time
|
||||
mostVillages int
|
||||
mostVillagesAt time.Time
|
||||
}{
|
||||
{
|
||||
base: tribes[0],
|
||||
bestRank: tribes[0].Rank(),
|
||||
bestRankAt: now,
|
||||
mostPoints: tribes[0].AllPoints(),
|
||||
mostPointsAt: now,
|
||||
mostVillages: tribes[0].NumVillages(),
|
||||
mostVillagesAt: now,
|
||||
},
|
||||
{
|
||||
base: tribes[1],
|
||||
bestRank: storedTribes[1].BestRank(),
|
||||
bestRankAt: storedTribes[1].BestRankAt(),
|
||||
mostPoints: storedTribes[1].MostPoints(),
|
||||
mostPointsAt: storedTribes[1].MostPointsAt(),
|
||||
mostVillages: storedTribes[1].MostVillages(),
|
||||
mostVillagesAt: storedTribes[1].MostVillagesAt(),
|
||||
},
|
||||
{
|
||||
base: tribes[2],
|
||||
bestRank: tribes[2].Rank(),
|
||||
bestRankAt: now,
|
||||
mostPoints: tribes[2].AllPoints(),
|
||||
mostPointsAt: now,
|
||||
mostVillages: tribes[2].NumVillages(),
|
||||
mostVillagesAt: now,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := domain.NewCreateTribeParams(server.Key(), tribes, storedTribes)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res, len(expectedParams))
|
||||
for i, expected := range expectedParams {
|
||||
idx := slices.IndexFunc(res, func(params domain.CreateTribeParams) bool {
|
||||
return params.Base().ID() == expected.base.ID()
|
||||
})
|
||||
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
||||
|
||||
params := res[idx]
|
||||
|
||||
assert.Equalf(t, expected.base, params.Base(), "expectedParams[%d]", i)
|
||||
assert.Equalf(t, expected.bestRank, params.BestRank(), "expectedParams[%d]", i)
|
||||
assert.WithinDurationf(t, expected.bestRankAt, params.BestRankAt(), time.Minute, "expectedParams[%d]", i)
|
||||
assert.Equalf(t, expected.mostPoints, params.MostPoints(), "expectedParams[%d]", i)
|
||||
assert.WithinDurationf(t, expected.mostPointsAt, params.MostPointsAt(), time.Minute, "expectedParams[%d]", i)
|
||||
assert.Equalf(t, expected.mostVillages, params.MostVillages(), "expectedParams[%d]", i)
|
||||
assert.WithinDurationf(t, expected.mostVillagesAt, params.MostVillagesAt(), time.Minute, "expectedParams[%d]", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTribesParams_SetIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
ids []int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
ids: []int{
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: value < 0",
|
||||
args: args{
|
||||
ids: []int{
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
-1,
|
||||
gofakeit.IntRange(0, math.MaxInt),
|
||||
},
|
||||
},
|
||||
expectedErr: domain.SliceElementValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "ids",
|
||||
Index: 3,
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
|
||||
require.ErrorIs(t, params.SetIDs(tt.args.ids), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.ids, params.IDs())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTribesParams_SetIDGT(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
idGT domain.NullInt
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
idGT: domain.NullInt{
|
||||
Value: gofakeit.IntRange(0, math.MaxInt),
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: value < 0",
|
||||
args: args{
|
||||
idGT: domain.NullInt{
|
||||
Value: -1,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "idGT",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
|
||||
require.ErrorIs(t, params.SetIDGT(tt.args.idGT), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.idGT, params.IDGT())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTribesParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
sort []domain.TribeSort
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.TribeSort{
|
||||
domain.TribeSortIDASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) < 1",
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 2",
|
||||
args: args{
|
||||
sort: []domain.TribeSort{
|
||||
domain.TribeSortIDASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Current: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
|
||||
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.sort, params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTribesParams_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.TribeListMaxLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit < 1",
|
||||
args: args{
|
||||
limit: 0,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "limit",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("ERR: limit > %d", domain.TribeListMaxLimit),
|
||||
args: args{
|
||||
limit: domain.TribeListMaxLimit + 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "limit",
|
||||
Err: domain.MaxLessEqualError{
|
||||
Max: domain.TribeListMaxLimit,
|
||||
Current: domain.TribeListMaxLimit + 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
|
||||
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.limit, params.Limit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTribesParams_SetOffset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
offset int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
offset: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: offset < 0",
|
||||
args: args{
|
||||
offset: -1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListTribesParams",
|
||||
Field: "offset",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
|
||||
require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.offset, params.Offset())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,8 +3,7 @@ package domain
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ValidationError struct {
|
||||
|
@ -41,22 +40,11 @@ func (e ValidationError) Code() ErrorCode {
|
|||
}
|
||||
|
||||
func (e ValidationError) Slug() string {
|
||||
s := "validation"
|
||||
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
s = domainErr.Slug()
|
||||
return domainErr.Slug()
|
||||
}
|
||||
|
||||
if e.Field != "" {
|
||||
s = slug.Make(e.Field) + "-" + s
|
||||
}
|
||||
|
||||
if e.Model != "" {
|
||||
s = slug.Make(e.Model) + "-" + s
|
||||
}
|
||||
|
||||
return s
|
||||
return "validation-failed"
|
||||
}
|
||||
|
||||
func (e ValidationError) Params() map[string]any {
|
||||
|
@ -71,6 +59,60 @@ func (e ValidationError) Unwrap() error {
|
|||
return e.Err
|
||||
}
|
||||
|
||||
type SliceElementValidationError struct {
|
||||
Model string
|
||||
Field string
|
||||
Index int
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = SliceElementValidationError{}
|
||||
|
||||
func (e SliceElementValidationError) Error() string {
|
||||
prefix := e.Model
|
||||
|
||||
if e.Field != "" {
|
||||
if len(prefix) > 0 {
|
||||
prefix += "."
|
||||
}
|
||||
prefix += e.Field + "[" + strconv.Itoa(e.Index) + "]"
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
prefix += ": "
|
||||
}
|
||||
|
||||
return prefix + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e SliceElementValidationError) Code() ErrorCode {
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
return domainErr.Code()
|
||||
}
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e SliceElementValidationError) Slug() string {
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
return domainErr.Slug()
|
||||
}
|
||||
return "slice-element-validation-failed"
|
||||
}
|
||||
|
||||
func (e SliceElementValidationError) Params() map[string]any {
|
||||
var withParams ErrorWithParams
|
||||
if ok := errors.As(e.Err, &withParams); !ok {
|
||||
return nil
|
||||
}
|
||||
return withParams.Params()
|
||||
}
|
||||
|
||||
func (e SliceElementValidationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type MinGreaterEqualError struct {
|
||||
Min int
|
||||
Current int
|
||||
|
@ -151,10 +193,16 @@ func (e LenOutOfRangeError) Params() map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
var ErrNotNil error = simpleError{
|
||||
var ErrRequired error = simpleError{
|
||||
msg: "can't be blank",
|
||||
code: ErrorCodeIncorrectInput,
|
||||
slug: "required",
|
||||
}
|
||||
|
||||
var ErrNil error = simpleError{
|
||||
msg: "must not be nil",
|
||||
code: ErrorCodeIncorrectInput,
|
||||
slug: "not-nil",
|
||||
slug: "nil",
|
||||
}
|
||||
|
||||
func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
||||
|
@ -169,6 +217,18 @@ func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateStringLen(s string, min, max int) error {
|
||||
if l := len(s); l > max || l < min {
|
||||
return LenOutOfRangeError{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateServerKey(key string) error {
|
||||
if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength {
|
||||
return LenOutOfRangeError{
|
||||
|
@ -192,3 +252,21 @@ func validateVersionCode(code string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateIntInRange(current, min, max int) error {
|
||||
if current < min {
|
||||
return MinGreaterEqualError{
|
||||
Min: min,
|
||||
Current: current,
|
||||
}
|
||||
}
|
||||
|
||||
if current > max {
|
||||
return MaxLessEqualError{
|
||||
Max: max,
|
||||
Current: current,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
|
@ -17,6 +16,8 @@ type Version struct {
|
|||
timezone string
|
||||
}
|
||||
|
||||
var versionModelName = "Version"
|
||||
|
||||
// UnmarshalVersionFromDatabase unmarshals Version from the database.
|
||||
//
|
||||
// It should be used only for unmarshalling from the database!
|
||||
|
@ -28,19 +29,35 @@ func UnmarshalVersionFromDatabase(
|
|||
timezone string,
|
||||
) (Version, error) {
|
||||
if code == "" {
|
||||
return Version{}, errors.New("code can't be blank")
|
||||
return Version{}, ValidationError{
|
||||
Model: versionModelName,
|
||||
Field: "code",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return Version{}, errors.New("name can't be blank")
|
||||
return Version{}, ValidationError{
|
||||
Model: versionModelName,
|
||||
Field: "name",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return Version{}, errors.New("host can't be blank")
|
||||
return Version{}, ValidationError{
|
||||
Model: versionModelName,
|
||||
Field: "host",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
if timezone == "" {
|
||||
return Version{}, errors.New("timezone can't be blank")
|
||||
return Version{}, ValidationError{
|
||||
Model: versionModelName,
|
||||
Field: "timezone",
|
||||
Err: ErrRequired,
|
||||
}
|
||||
}
|
||||
|
||||
return Version{
|
||||
|
|
Loading…
Reference in New Issue
Block a user