parent
d787edde00
commit
b4e95f3267
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/uptrace/bun v1.1.16
|
||||
github.com/uptrace/bun/dbfixture v1.1.16
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.16
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.16
|
||||
|
|
2
go.sum
2
go.sum
|
@ -149,6 +149,8 @@ github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYm
|
|||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A=
|
||||
github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8=
|
||||
github.com/uptrace/bun/dbfixture v1.1.16 h1:CEdQaeptGRRvwVRhn9PMrawo+yaK+HzAU8/O0h463hA=
|
||||
github.com/uptrace/bun/dbfixture v1.1.16/go.mod h1:kw11JRwFD3eAHs3Lyq7sHJ6i2KWngUlZQsrvyRy+dqY=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 h1:gbc9BP/e4sNOB9VBj+Si46dpOz2oktmZPidkda92GYY=
|
||||
|
|
47
internal/adapter/adaptertest/fixture.go
Normal file
47
internal/adapter/adaptertest/fixture.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package adaptertest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/internal/bunmodel"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dbfixture"
|
||||
)
|
||||
|
||||
type Fixture struct {
|
||||
f *dbfixture.Fixture
|
||||
}
|
||||
|
||||
func NewFixture(bunDB *bun.DB) *Fixture {
|
||||
bunDB.RegisterModel(
|
||||
(*bunmodel.Version)(nil),
|
||||
(*bunmodel.Server)(nil),
|
||||
)
|
||||
return &Fixture{
|
||||
f: dbfixture.New(bunDB),
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func (f *Fixture) Load(tb TestingTB, ctx context.Context, fsys fs.FS, names ...string) {
|
||||
tb.Helper()
|
||||
require.NoError(tb, f.f.Load(ctx, fsys, names...))
|
||||
}
|
||||
|
||||
func (f *Fixture) Server(tb TestingTB, id string) domain.Server {
|
||||
tb.Helper()
|
||||
|
||||
row, err := f.f.Row("Server." + id)
|
||||
require.NoError(tb, err)
|
||||
|
||||
s, ok := row.(*bunmodel.Server)
|
||||
require.True(tb, ok)
|
||||
|
||||
converted, err := s.ToDomain()
|
||||
require.NoError(tb, err)
|
||||
|
||||
return converted
|
||||
}
|
|
@ -9,14 +9,13 @@ import (
|
|||
"github.com/uptrace/bun/driver/sqliteshim"
|
||||
)
|
||||
|
||||
const sqliteMaxIdleConns = 1000
|
||||
|
||||
// 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:?cache=shared")
|
||||
sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:")
|
||||
require.NoError(tb, err)
|
||||
sqldb.SetMaxIdleConns(sqliteMaxIdleConns)
|
||||
sqldb.SetMaxOpenConns(1)
|
||||
sqldb.SetMaxIdleConns(1)
|
||||
sqldb.SetConnMaxLifetime(0)
|
||||
|
||||
db := bun.NewDB(sqldb, sqlitedialect.New())
|
||||
|
|
|
@ -3,7 +3,6 @@ package adaptertest
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -26,8 +25,8 @@ func runMigrations(tb TestingTB, db *bun.DB) {
|
|||
defer cancel()
|
||||
|
||||
require.NoError(tb, migrator.Init(ctx), "couldn't init migrator")
|
||||
|
||||
_, err := migrator.Migrate(ctx)
|
||||
fmt.Println(err)
|
||||
require.NoError(tb, err, "couldn't apply migrations")
|
||||
}
|
||||
|
||||
|
|
91
internal/adapter/internal/bunmodel/building_info.go
Normal file
91
internal/adapter/internal/bunmodel/building_info.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type Building struct {
|
||||
MaxLevel int
|
||||
MinLevel int
|
||||
Wood int
|
||||
Stone int
|
||||
Iron int
|
||||
Pop int
|
||||
WoodFactor float64
|
||||
StoneFactor float64
|
||||
IronFactor float64
|
||||
PopFactor float64
|
||||
BuildTime float64
|
||||
BuildTimeFactor float64
|
||||
}
|
||||
|
||||
type BuildingInfo struct {
|
||||
Main Building
|
||||
Barracks Building
|
||||
Stable Building
|
||||
Garage Building
|
||||
Watchtower Building
|
||||
Snob Building
|
||||
Smith Building
|
||||
Place Building
|
||||
Statue Building
|
||||
Market Building
|
||||
Wood Building
|
||||
Stone Building
|
||||
Iron Building
|
||||
Farm Building
|
||||
Storage Building
|
||||
Hide Building
|
||||
Wall Building
|
||||
}
|
||||
|
||||
func NewBuildingInfo(info domain.BuildingInfo) BuildingInfo {
|
||||
return BuildingInfo{
|
||||
Main: Building(info.Main()),
|
||||
Barracks: Building(info.Barracks()),
|
||||
Stable: Building(info.Stable()),
|
||||
Garage: Building(info.Garage()),
|
||||
Watchtower: Building(info.Watchtower()),
|
||||
Snob: Building(info.Snob()),
|
||||
Smith: Building(info.Smith()),
|
||||
Place: Building(info.Place()),
|
||||
Statue: Building(info.Statue()),
|
||||
Market: Building(info.Market()),
|
||||
Wood: Building(info.Wood()),
|
||||
Stone: Building(info.Stone()),
|
||||
Iron: Building(info.Iron()),
|
||||
Farm: Building(info.Farm()),
|
||||
Storage: Building(info.Storage()),
|
||||
Hide: Building(info.Hide()),
|
||||
Wall: Building(info.Wall()),
|
||||
}
|
||||
}
|
||||
|
||||
func (b BuildingInfo) ToDomain() (domain.BuildingInfo, error) {
|
||||
info, err := domain.NewBuildingInfo(
|
||||
domain.Building(b.Main),
|
||||
domain.Building(b.Barracks),
|
||||
domain.Building(b.Stable),
|
||||
domain.Building(b.Garage),
|
||||
domain.Building(b.Watchtower),
|
||||
domain.Building(b.Snob),
|
||||
domain.Building(b.Smith),
|
||||
domain.Building(b.Place),
|
||||
domain.Building(b.Statue),
|
||||
domain.Building(b.Market),
|
||||
domain.Building(b.Wood),
|
||||
domain.Building(b.Stone),
|
||||
domain.Building(b.Iron),
|
||||
domain.Building(b.Farm),
|
||||
domain.Building(b.Storage),
|
||||
domain.Building(b.Hide),
|
||||
domain.Building(b.Wall),
|
||||
)
|
||||
if err != nil {
|
||||
return domain.BuildingInfo{}, fmt.Errorf("couldn't construct domain.BuildingInfo: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
97
internal/adapter/internal/bunmodel/server.go
Normal file
97
internal/adapter/internal/bunmodel/server.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
bun.BaseModel `bun:"table:servers,alias:server"`
|
||||
Key string `bun:"key,nullzero,pk"`
|
||||
URL string `bun:"url,nullzero"`
|
||||
Open bool `bun:"open"`
|
||||
Special bool `bun:"special"`
|
||||
NumPlayers int `bun:"num_players"`
|
||||
NumTribes int `bun:"num_tribes"`
|
||||
NumVillages int `bun:"num_villages"`
|
||||
NumPlayerVillages int `bun:"num_player_villages"`
|
||||
NumBarbarianVillages int `bun:"num_barbarian_villages"`
|
||||
NumBonusVillages int `bun:"num_bonus_villages"`
|
||||
Config ServerConfig `bun:"config"`
|
||||
BuildingInfo BuildingInfo `bun:"building_info"`
|
||||
UnitInfo UnitInfo `bun:"unit_info"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero"`
|
||||
PlayerDataUpdatedAt time.Time `bun:"player_data_updated_at,nullzero"`
|
||||
PlayerSnapshotsCreatedAt time.Time `bun:"player_snapshots_created_at,nullzero"`
|
||||
TribeDataUpdatedAt time.Time `bun:"tribe_data_updated_at,nullzero"`
|
||||
TribeSnapshotsCreatedAt time.Time `bun:"tribe_snapshots_created_at,nullzero"`
|
||||
VillageDataUpdatedAt time.Time `bun:"village_data_updated_at,nullzero"`
|
||||
EnnoblementDataUpdatedAt time.Time `bun:"ennoblement_data_updated_at,nullzero"`
|
||||
VersionCode string `bun:"version_code,nullzero"`
|
||||
}
|
||||
|
||||
func (s Server) ToDomain() (domain.Server, error) {
|
||||
serverCfg, err := s.Config.ToDomain()
|
||||
if err != nil {
|
||||
return domain.Server{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
|
||||
}
|
||||
|
||||
buildingInfo, err := s.BuildingInfo.ToDomain()
|
||||
if err != nil {
|
||||
return domain.Server{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
|
||||
}
|
||||
|
||||
unitInfo, err := s.UnitInfo.ToDomain()
|
||||
if err != nil {
|
||||
return domain.Server{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
|
||||
}
|
||||
|
||||
converted, err := domain.UnmarshalServerFromDatabase(
|
||||
s.Key,
|
||||
s.VersionCode,
|
||||
s.URL,
|
||||
s.Open,
|
||||
s.Special,
|
||||
s.NumPlayers,
|
||||
s.NumTribes,
|
||||
s.NumVillages,
|
||||
s.NumPlayerVillages,
|
||||
s.NumBarbarianVillages,
|
||||
s.NumBonusVillages,
|
||||
serverCfg,
|
||||
buildingInfo,
|
||||
unitInfo,
|
||||
s.CreatedAt,
|
||||
s.PlayerDataUpdatedAt,
|
||||
s.PlayerSnapshotsCreatedAt,
|
||||
s.TribeDataUpdatedAt,
|
||||
s.TribeSnapshotsCreatedAt,
|
||||
s.VillageDataUpdatedAt,
|
||||
s.EnnoblementDataUpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.Server{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
|
||||
}
|
||||
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
type Servers []Server
|
||||
|
||||
func (ss Servers) ToDomain() (domain.Servers, error) {
|
||||
res := make(domain.Servers, 0, len(ss))
|
||||
|
||||
for _, s := range ss {
|
||||
converted, err := s.ToDomain()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, converted)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
207
internal/adapter/internal/bunmodel/server_config.go
Normal file
207
internal/adapter/internal/bunmodel/server_config.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type ServerConfigBuild struct {
|
||||
Destroy int
|
||||
}
|
||||
|
||||
type ServerConfigMisc struct {
|
||||
KillRanking int
|
||||
Tutorial int
|
||||
TradeCancelTime int
|
||||
}
|
||||
|
||||
type ServerConfigCommands struct {
|
||||
MillisArrival int
|
||||
CommandCancelTime int
|
||||
}
|
||||
|
||||
type ServerConfigNewbie struct {
|
||||
Days int
|
||||
RatioDays int
|
||||
Ratio int
|
||||
RemoveNewbieVillages int
|
||||
}
|
||||
|
||||
type ServerConfigGame struct {
|
||||
BuildtimeFormula int
|
||||
Knight int
|
||||
KnightNewItems int
|
||||
Archer int
|
||||
Tech int
|
||||
FarmLimit int
|
||||
Church int
|
||||
Watchtower int
|
||||
Stronghold int
|
||||
FakeLimit float64
|
||||
BarbarianRise float64
|
||||
BarbarianShrink int
|
||||
BarbarianMaxPoints int
|
||||
Scavenging int
|
||||
Hauls int
|
||||
HaulsBase int
|
||||
HaulsMax int
|
||||
BaseProduction int
|
||||
Event int
|
||||
SuppressEvents int
|
||||
}
|
||||
|
||||
type ServerConfigBuildings struct {
|
||||
CustomMain int
|
||||
CustomFarm int
|
||||
CustomStorage int
|
||||
CustomPlace int
|
||||
CustomBarracks int
|
||||
CustomChurch int
|
||||
CustomSmith int
|
||||
CustomWood int
|
||||
CustomStone int
|
||||
CustomIron int
|
||||
CustomMarket int
|
||||
CustomStable int
|
||||
CustomWall int
|
||||
CustomGarage int
|
||||
CustomHide int
|
||||
CustomSnob int
|
||||
CustomStatue int
|
||||
CustomWatchtower int
|
||||
}
|
||||
|
||||
type ServerConfigSnob struct {
|
||||
Gold int
|
||||
CheapRebuild int
|
||||
Rise int
|
||||
MaxDist int
|
||||
Factor float64
|
||||
CoinWood int
|
||||
CoinStone int
|
||||
CoinIron int
|
||||
NoBarbConquer int
|
||||
}
|
||||
|
||||
type ServerConfigAlly struct {
|
||||
NoHarm int
|
||||
NoOtherSupport int
|
||||
NoOtherSupportType int
|
||||
AllytimeSupport int
|
||||
NoLeave int
|
||||
NoJoin int
|
||||
Limit int
|
||||
FixedAllies int
|
||||
PointsMemberCount int
|
||||
WarsMemberRequirement int
|
||||
WarsPointsRequirement int
|
||||
WarsAutoacceptDays int
|
||||
Levels int
|
||||
XpRequirements string
|
||||
}
|
||||
|
||||
type ServerConfigCoord struct {
|
||||
MapSize int
|
||||
Func int
|
||||
EmptyVillages int
|
||||
BonusVillages int
|
||||
BonusNew int
|
||||
Inner int
|
||||
SelectStart int
|
||||
VillageMoveWait int
|
||||
NobleRestart int
|
||||
StartVillages int
|
||||
}
|
||||
|
||||
type ServerConfigSitter struct {
|
||||
Allow int
|
||||
}
|
||||
|
||||
type ServerConfigSleep struct {
|
||||
Active int
|
||||
Delay int
|
||||
Min int
|
||||
Max int
|
||||
MinAwake int
|
||||
MaxAwake int
|
||||
WarnTime int
|
||||
}
|
||||
|
||||
type ServerConfigNight struct {
|
||||
Active int
|
||||
StartHour int
|
||||
EndHour int
|
||||
DefFactor float64
|
||||
Duration int
|
||||
}
|
||||
|
||||
type ServerConfigWin struct {
|
||||
Check int
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Speed float64
|
||||
UnitSpeed float64
|
||||
Moral int
|
||||
Build ServerConfigBuild
|
||||
Misc ServerConfigMisc
|
||||
Commands ServerConfigCommands
|
||||
Newbie ServerConfigNewbie
|
||||
Game ServerConfigGame
|
||||
Buildings ServerConfigBuildings
|
||||
Snob ServerConfigSnob
|
||||
Ally ServerConfigAlly
|
||||
Coord ServerConfigCoord
|
||||
Sitter ServerConfigSitter
|
||||
Sleep ServerConfigSleep
|
||||
Night ServerConfigNight
|
||||
Win ServerConfigWin
|
||||
}
|
||||
|
||||
func NewServerConfig(cfg domain.ServerConfig) ServerConfig {
|
||||
return ServerConfig{
|
||||
Speed: cfg.Speed(),
|
||||
UnitSpeed: cfg.UnitSpeed(),
|
||||
Moral: cfg.Moral(),
|
||||
Build: ServerConfigBuild(cfg.Build()),
|
||||
Misc: ServerConfigMisc(cfg.Misc()),
|
||||
Commands: ServerConfigCommands(cfg.Commands()),
|
||||
Newbie: ServerConfigNewbie(cfg.Newbie()),
|
||||
Game: ServerConfigGame(cfg.Game()),
|
||||
Buildings: ServerConfigBuildings(cfg.Buildings()),
|
||||
Snob: ServerConfigSnob(cfg.Snob()),
|
||||
Ally: ServerConfigAlly(cfg.Ally()),
|
||||
Coord: ServerConfigCoord(cfg.Coord()),
|
||||
Sitter: ServerConfigSitter(cfg.Sitter()),
|
||||
Sleep: ServerConfigSleep(cfg.Sleep()),
|
||||
Night: ServerConfigNight(cfg.Night()),
|
||||
Win: ServerConfigWin(cfg.Win()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s ServerConfig) ToDomain() (domain.ServerConfig, error) {
|
||||
cfg, err := domain.NewServerConfig(
|
||||
s.Speed,
|
||||
s.UnitSpeed,
|
||||
s.Moral,
|
||||
domain.ServerConfigBuild(s.Build),
|
||||
domain.ServerConfigMisc(s.Misc),
|
||||
domain.ServerConfigCommands(s.Commands),
|
||||
domain.ServerConfigNewbie(s.Newbie),
|
||||
domain.ServerConfigGame(s.Game),
|
||||
domain.ServerConfigBuildings(s.Buildings),
|
||||
domain.ServerConfigSnob(s.Snob),
|
||||
domain.ServerConfigAlly(s.Ally),
|
||||
domain.ServerConfigCoord(s.Coord),
|
||||
domain.ServerConfigSitter(s.Sitter),
|
||||
domain.ServerConfigSleep(s.Sleep),
|
||||
domain.ServerConfigNight(s.Night),
|
||||
domain.ServerConfigWin(s.Win),
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ServerConfig{}, fmt.Errorf("couldn't construct domain.ServerConfig: %w", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
75
internal/adapter/internal/bunmodel/unit_info.go
Normal file
75
internal/adapter/internal/bunmodel/unit_info.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package bunmodel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type Unit struct {
|
||||
BuildTime float64
|
||||
Pop int
|
||||
Speed float64
|
||||
Attack int
|
||||
Defense int
|
||||
DefenseCavalry int
|
||||
DefenseArcher int
|
||||
Carry int
|
||||
}
|
||||
|
||||
type UnitInfo struct {
|
||||
Spear Unit
|
||||
Sword Unit
|
||||
Axe Unit
|
||||
Archer Unit
|
||||
Spy Unit
|
||||
Light Unit
|
||||
Marcher Unit
|
||||
Heavy Unit
|
||||
Ram Unit
|
||||
Catapult Unit
|
||||
Knight Unit
|
||||
Snob Unit
|
||||
Militia Unit
|
||||
}
|
||||
|
||||
func NewUnitInfo(info domain.UnitInfo) UnitInfo {
|
||||
return UnitInfo{
|
||||
Spear: Unit(info.Spear()),
|
||||
Sword: Unit(info.Sword()),
|
||||
Axe: Unit(info.Axe()),
|
||||
Archer: Unit(info.Archer()),
|
||||
Spy: Unit(info.Spy()),
|
||||
Light: Unit(info.Light()),
|
||||
Marcher: Unit(info.Marcher()),
|
||||
Heavy: Unit(info.Heavy()),
|
||||
Ram: Unit(info.Ram()),
|
||||
Catapult: Unit(info.Catapult()),
|
||||
Knight: Unit(info.Knight()),
|
||||
Snob: Unit(info.Snob()),
|
||||
Militia: Unit(info.Militia()),
|
||||
}
|
||||
}
|
||||
|
||||
func (u UnitInfo) ToDomain() (domain.UnitInfo, error) {
|
||||
info, err := domain.NewUnitInfo(
|
||||
domain.Unit(u.Spear),
|
||||
domain.Unit(u.Sword),
|
||||
domain.Unit(u.Axe),
|
||||
domain.Unit(u.Archer),
|
||||
domain.Unit(u.Spy),
|
||||
domain.Unit(u.Light),
|
||||
domain.Unit(u.Marcher),
|
||||
domain.Unit(u.Heavy),
|
||||
domain.Unit(u.Ram),
|
||||
domain.Unit(u.Catapult),
|
||||
domain.Unit(u.Knight),
|
||||
domain.Unit(u.Snob),
|
||||
domain.Unit(u.Militia),
|
||||
)
|
||||
if err != nil {
|
||||
return domain.UnitInfo{}, fmt.Errorf("couldn't construct domain.UnitInfo: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
119
internal/adapter/repository_bun_server.go
Normal file
119
internal/adapter/repository_bun_server.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
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 ServerBunRepository struct {
|
||||
db bun.IDB
|
||||
}
|
||||
|
||||
func NewServerBunRepository(db bun.IDB) *ServerBunRepository {
|
||||
return &ServerBunRepository{db: db}
|
||||
}
|
||||
|
||||
func (repo *ServerBunRepository) CreateOrUpdate(ctx context.Context, params ...domain.CreateServerParams) error {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
servers := make(bunmodel.Servers, 0, len(params))
|
||||
|
||||
for _, p := range params {
|
||||
base := p.Base()
|
||||
servers = append(servers, bunmodel.Server{
|
||||
Key: base.Key(),
|
||||
URL: base.URL().String(),
|
||||
Open: base.Open(),
|
||||
VersionCode: p.VersionCode(),
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
q := repo.db.NewInsert().
|
||||
Model(&servers)
|
||||
|
||||
//nolint:exhaustive
|
||||
switch q.Dialect().Name() {
|
||||
case dialect.PG:
|
||||
q = q.On("CONFLICT ON CONSTRAINT servers_pkey DO UPDATE")
|
||||
case dialect.SQLite:
|
||||
q = q.On("CONFLICT(key) DO UPDATE")
|
||||
default:
|
||||
q = q.Err(errors.New("unsupported dialect"))
|
||||
}
|
||||
|
||||
if _, err := q.
|
||||
Set("url = EXCLUDED.url").
|
||||
Set("open = EXCLUDED.open").
|
||||
Returning("").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("something went wrong while inserting servers into the db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *ServerBunRepository) List(ctx context.Context, params domain.ListServersParams) (domain.Servers, error) {
|
||||
var servers bunmodel.Servers
|
||||
|
||||
if err := repo.baseListQuery(params).Model(&servers).Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("couldn't select servers from the db: %w", err)
|
||||
}
|
||||
|
||||
return servers.ToDomain()
|
||||
}
|
||||
|
||||
func (repo *ServerBunRepository) baseListQuery(params domain.ListServersParams) *bun.SelectQuery {
|
||||
return repo.db.NewSelect().Apply(listServersParamsApplier{params: params}.apply)
|
||||
}
|
||||
|
||||
type listServersParamsApplier struct {
|
||||
params domain.ListServersParams
|
||||
}
|
||||
|
||||
func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
if keys := a.params.Keys(); len(keys) > 0 {
|
||||
q = q.Where("server.key IN (?)", bun.In(keys))
|
||||
}
|
||||
|
||||
if keyGT := a.params.KeyGT(); keyGT.Valid {
|
||||
q = q.Where("server.key > ?", keyGT.Value)
|
||||
}
|
||||
|
||||
if open := a.params.Open(); open.Valid {
|
||||
q = q.Where("server.open = ?", open.Value)
|
||||
}
|
||||
|
||||
if special := a.params.Special(); special.Valid {
|
||||
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:
|
||||
q = q.Order("server.key ASC")
|
||||
case domain.ServerSortKeyDESC:
|
||||
q = q.Order("server.key DESC")
|
||||
case domain.ServerSortOpenASC:
|
||||
q = q.Order("server.open ASC")
|
||||
case domain.ServerSortOpenDESC:
|
||||
q = q.Order("server.open DESC")
|
||||
default:
|
||||
return q.Err(errors.New("unsupported sort value"))
|
||||
}
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
29
internal/adapter/repository_bun_server_test.go
Normal file
29
internal/adapter/repository_bun_server_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
|
||||
)
|
||||
|
||||
func TestServerBunRepository_Postgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping long-running test")
|
||||
}
|
||||
|
||||
testServerRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, postgres.NewBunDB(t))
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerBunRepository_SQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testServerRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return newBunDBRepositories(t, adaptertest.NewBunDBSQLite(t))
|
||||
})
|
||||
}
|
|
@ -3,28 +3,27 @@ package adapter_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
|
||||
)
|
||||
|
||||
func TestVersionBunRepo_Postgres(t *testing.T) {
|
||||
func TestVersionBunRepository_Postgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skipping long-running test")
|
||||
}
|
||||
|
||||
testVersionRepo(t, func(t *testing.T) versionRepository {
|
||||
testVersionRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return adapter.NewVersionBunRepository(postgres.NewBunDB(t))
|
||||
return newBunDBRepositories(t, postgres.NewBunDB(t))
|
||||
})
|
||||
}
|
||||
|
||||
func TestVersionBunRepo_SQLite(t *testing.T) {
|
||||
func TestVersionBunRepository_SQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testVersionRepo(t, func(t *testing.T) versionRepository {
|
||||
testVersionRepository(t, func(t *testing.T) repositories {
|
||||
t.Helper()
|
||||
return adapter.NewVersionBunRepository(adaptertest.NewBunDBSQLite(t))
|
||||
return newBunDBRepositories(t, adaptertest.NewBunDBSQLite(t))
|
||||
})
|
||||
}
|
||||
|
|
354
internal/adapter/repository_server_test.go
Normal file
354
internal/adapter/repository_server_test.go
Normal file
|
@ -0,0 +1,354 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||
t.Helper()
|
||||
|
||||
t.Run("CreateOrUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
versions, err := repos.version.List(ctx, domain.NewListVersionsParams())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, versions)
|
||||
version := versions[0]
|
||||
|
||||
serversToCreate := domain.BaseServers{
|
||||
domaintest.NewBaseServer(t, domaintest.BaseServerConfig{Open: true}),
|
||||
domaintest.NewBaseServer(t, domaintest.BaseServerConfig{Open: true}),
|
||||
}
|
||||
|
||||
createParams, err := domain.NewCreateServerParams(serversToCreate, version.Code())
|
||||
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()
|
||||
}))
|
||||
}
|
||||
|
||||
serversToUpdate := domain.BaseServers{
|
||||
domaintest.NewBaseServer(t, domaintest.BaseServerConfig{
|
||||
Key: serversToCreate[0].Key(),
|
||||
URL: randURL(t),
|
||||
Open: !serversToCreate[0].Open(),
|
||||
}),
|
||||
}
|
||||
|
||||
updateParams, err := domain.NewCreateServerParams(serversToUpdate, version.Code())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, repos.server.CreateOrUpdate(ctx, updateParams...))
|
||||
|
||||
keys = make([]string, 0, len(serversToUpdate))
|
||||
for _, s := range serversToUpdate {
|
||||
keys = append(keys, s.Key())
|
||||
}
|
||||
|
||||
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()
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("List & ListCount", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repos := newRepos(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params func(t *testing.T) domain.ListServersParams
|
||||
assertServers func(t *testing.T, params domain.ListServersParams, servers domain.Servers)
|
||||
assertError func(t *testing.T, err error)
|
||||
assertTotal func(t *testing.T, params domain.ListServersParams, total int)
|
||||
}{
|
||||
{
|
||||
name: "OK: default params",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
return domain.NewListServersParams()
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
assert.True(t, slices.IsSortedFunc(servers, func(a, b domain.Server) int {
|
||||
return cmp.Compare(a.Key(), b.Key())
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[open ASC, key ASC]",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSort{domain.ServerSortOpenASC, domain.ServerSortKeyASC}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
assert.True(t, slices.IsSortedFunc(servers, func(a, b domain.Server) int {
|
||||
if a.Open() && !b.Open() {
|
||||
return 1
|
||||
}
|
||||
|
||||
if !a.Open() && b.Open() {
|
||||
return -1
|
||||
}
|
||||
|
||||
return cmp.Compare(a.Key(), b.Key())
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[open DESC, key DESC]",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetSort([]domain.ServerSort{domain.ServerSortOpenDESC, domain.ServerSortKeyDESC}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
assert.True(t, slices.IsSortedFunc(servers, func(a, b domain.Server) int {
|
||||
if a.Open() && !b.Open() {
|
||||
return -1
|
||||
}
|
||||
|
||||
if !a.Open() && b.Open() {
|
||||
return 1
|
||||
}
|
||||
|
||||
return cmp.Compare(a.Key(), b.Key()) * -1
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: keys=[de188, en113]",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetKeys([]string{"de188", "en113"}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
|
||||
keys := params.Keys()
|
||||
|
||||
assert.Len(t, servers, len(keys))
|
||||
for _, k := range keys {
|
||||
assert.True(t, slices.ContainsFunc(servers, func(server domain.Server) bool {
|
||||
return server.Key() == k
|
||||
}), k)
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.Equal(t, len(params.Keys()), total) //nolint:testifylint
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: keyGT=de188",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetKeyGT(domain.NullString{
|
||||
Value: "de188",
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
for _, s := range servers {
|
||||
assert.Greater(t, s.Key(), params.KeyGT().Value, s.Key())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: special=true",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetSpecial(domain.NullBool{
|
||||
Value: true,
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
for _, s := range servers {
|
||||
assert.True(t, s.Special(), s.Key())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: open=false",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetOpen(domain.NullBool{
|
||||
Value: false,
|
||||
Valid: true,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, len(servers))
|
||||
for _, s := range servers {
|
||||
assert.False(t, s.Open(), s.Key())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: offset=1 limit=2",
|
||||
params: func(t *testing.T) domain.ListServersParams {
|
||||
t.Helper()
|
||||
params := domain.NewListServersParams()
|
||||
require.NoError(t, params.SetOffset(1))
|
||||
require.NoError(t, params.SetLimit(2))
|
||||
return params
|
||||
},
|
||||
assertServers: func(t *testing.T, params domain.ListServersParams, servers domain.Servers) {
|
||||
t.Helper()
|
||||
assert.Len(t, servers, params.Limit())
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertTotal: func(t *testing.T, params domain.ListServersParams, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
params := tt.params(t)
|
||||
|
||||
res, err := repos.server.List(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertServers(t, params, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func randURL(tb testing.TB) *url.URL {
|
||||
tb.Helper()
|
||||
u, err := url.Parse("https://" + gofakeit.DomainName())
|
||||
require.NoError(tb, err)
|
||||
return u
|
||||
}
|
41
internal/adapter/repository_test.go
Normal file
41
internal/adapter/repository_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package adapter_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type versionRepository interface {
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error)
|
||||
ListCount(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.Versions, int, error)
|
||||
}
|
||||
|
||||
type serverRepository interface {
|
||||
CreateOrUpdate(ctx context.Context, params ...domain.CreateServerParams) error
|
||||
List(ctx context.Context, params domain.ListServersParams) (domain.Servers, error)
|
||||
}
|
||||
|
||||
type repositories struct {
|
||||
version versionRepository
|
||||
server serverRepository
|
||||
}
|
||||
|
||||
func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
||||
tb.Helper()
|
||||
|
||||
adaptertest.NewFixture(bunDB).Load(tb, context.Background(), os.DirFS("testdata"), "fixture.yml")
|
||||
|
||||
return repositories{
|
||||
version: adapter.NewVersionBunRepository(bunDB),
|
||||
server: adapter.NewServerBunRepository(bunDB),
|
||||
}
|
||||
}
|
|
@ -11,21 +11,13 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type versionRepository interface {
|
||||
List(ctx context.Context, params domain.ListVersionsParams) (domain.Versions, error)
|
||||
ListCount(
|
||||
ctx context.Context,
|
||||
params domain.ListVersionsParams,
|
||||
) (domain.Versions, int, error)
|
||||
}
|
||||
|
||||
func testVersionRepo(t *testing.T, newRepo func(t *testing.T) versionRepository) {
|
||||
func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||
t.Helper()
|
||||
|
||||
t.Run("List & ListCount", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := newRepo(t)
|
||||
repos := newRepos(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -53,11 +45,11 @@ func testVersionRepo(t *testing.T, newRepo func(t *testing.T) versionRepository)
|
|||
},
|
||||
assertTotal: func(t *testing.T, total int) {
|
||||
t.Helper()
|
||||
assert.NotEmpty(t, total, 0)
|
||||
assert.NotEmpty(t, total)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: Sort=[Code DESC]",
|
||||
name: "OK: sort=[code DESC]",
|
||||
params: func(t *testing.T) domain.ListVersionsParams {
|
||||
t.Helper()
|
||||
params := domain.NewListVersionsParams()
|
||||
|
@ -91,11 +83,11 @@ func testVersionRepo(t *testing.T, newRepo func(t *testing.T) versionRepository)
|
|||
ctx := context.Background()
|
||||
params := tt.params(t)
|
||||
|
||||
res, err := repo.List(ctx, params)
|
||||
res, err := repos.version.List(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertVersions(t, res)
|
||||
|
||||
res, count, err := repo.ListCount(ctx, params)
|
||||
res, count, err := repos.version.ListCount(ctx, params)
|
||||
tt.assertError(t, err)
|
||||
tt.assertVersions(t, res)
|
||||
tt.assertTotal(t, count)
|
||||
|
|
78
internal/adapter/testdata/fixture.yml
vendored
Normal file
78
internal/adapter/testdata/fixture.yml
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
- model: Server
|
||||
rows:
|
||||
- _id: de188
|
||||
key: de188
|
||||
url: https://de188.die-staemme.de
|
||||
open: true
|
||||
special: false
|
||||
num_players: 180
|
||||
num_tribes: 76
|
||||
num_villages: 16180
|
||||
num_player_villages: 15000
|
||||
num_barbarian_villages: 1180
|
||||
num_bonus_villages: 512
|
||||
created_at: 2022-03-19T12:00:54.000Z
|
||||
player_data_updated_at: 2022-03-19T12:00:54.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:00:54.000Z
|
||||
tribe_data_updated_at: 2022-03-19T12:00:54.000Z
|
||||
tribe_snapshots_created_at: 2022-03-19T12:00:54.000Z
|
||||
village_data_updated_at: 2022-03-19T12:00:54.000Z
|
||||
ennoblement_data_updated_at: 2022-03-19T12:00:54.000Z
|
||||
version_code: de
|
||||
- _id: en113
|
||||
key: en113
|
||||
url: https://en113.tribalwars.net
|
||||
open: false
|
||||
special: false
|
||||
num_players: 251
|
||||
num_tribes: 57
|
||||
num_villages: 41700
|
||||
num_player_villages: 41000
|
||||
num_barbarian_villages: 700
|
||||
num_bonus_villages: 1024
|
||||
created_at: 2021-04-02T16:01:25.000Z
|
||||
player_data_updated_at: 2021-04-02T16:01:25.000Z
|
||||
player_snapshots_created_at: 2021-04-02T16:01:25.000Z
|
||||
tribe_data_updated_at: 2021-04-02T16:01:25.000Z
|
||||
tribe_snapshots_created_at: 2021-04-02T16:01:25.000Z
|
||||
village_data_updated_at: 2021-04-02T16:01:25.000Z
|
||||
ennoblement_data_updated_at: 2021-04-02T16:01:25.000Z
|
||||
version_code: en
|
||||
- _id: it70
|
||||
key: it70
|
||||
url: https://it70.tribals.it
|
||||
open: true
|
||||
special: false
|
||||
num_players: 1883
|
||||
num_tribes: 101
|
||||
num_villages: 4882
|
||||
num_player_villages: 3200
|
||||
num_barbarian_villages: 1682
|
||||
num_bonus_villages: 256
|
||||
created_at: 2022-03-19T12:00:04.000Z
|
||||
player_data_updated_at: 2022-03-19T12:00:04.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:00:04.000Z
|
||||
tribe_data_updated_at: 2022-03-19T12:00:04.000Z
|
||||
tribe_snapshots_created_at: 2022-03-19T12:00:04.000Z
|
||||
village_data_updated_at: 2022-03-19T12:00:04.000Z
|
||||
ennoblement_data_updated_at: 2022-03-19T12:00:04.000Z
|
||||
version_code: it
|
||||
- _id: pl169
|
||||
key: pl169
|
||||
url: https://pl169.plemiona.pl
|
||||
open: true
|
||||
special: false
|
||||
num_players: 2001
|
||||
num_tribes: 214
|
||||
num_villages: 49074
|
||||
num_player_villages: 48500
|
||||
num_barbarian_villages: 1574
|
||||
num_bonus_villages: 2048
|
||||
created_at: 2022-03-19T12:01:39.000Z
|
||||
player_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
player_snapshots_created_at: 2022-03-19T12:01:39.000Z
|
||||
tribe_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
tribe_snapshots_created_at: 2022-03-19T12:01:39.000Z
|
||||
village_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
ennoblement_data_updated_at: 2022-03-19T12:01:39.000Z
|
||||
version_code: pl
|
|
@ -9,26 +9,27 @@ import (
|
|||
)
|
||||
|
||||
type BaseServerConfig struct {
|
||||
Key string
|
||||
URL *url.URL
|
||||
Key string
|
||||
URL *url.URL
|
||||
Open bool
|
||||
}
|
||||
|
||||
func (cfg *BaseServerConfig) init() {
|
||||
if cfg.Key == "" {
|
||||
cfg.Key = gofakeit.LetterN(5)
|
||||
cfg.Key = RandServerKey()
|
||||
}
|
||||
|
||||
if cfg.URL == nil {
|
||||
cfg.URL = &url.URL{
|
||||
Scheme: "https",
|
||||
Host: cfg.Key + ".plemiona.pl",
|
||||
Host: cfg.Key + "." + gofakeit.DomainName(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewBaseServer(tb TestingTB, cfg BaseServerConfig) domain.BaseServer {
|
||||
cfg.init()
|
||||
s, err := domain.NewBaseServer(cfg.Key, cfg.URL.String(), true)
|
||||
s, err := domain.NewBaseServer(cfg.Key, cfg.URL.String(), cfg.Open)
|
||||
require.NoError(tb, err)
|
||||
return s
|
||||
}
|
||||
|
|
7
internal/domain/domaintest/server.go
Normal file
7
internal/domain/domaintest/server.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package domaintest
|
||||
|
||||
import "github.com/brianvoe/gofakeit/v6"
|
||||
|
||||
func RandServerKey() string {
|
||||
return gofakeit.LetterN(5)
|
||||
}
|
33
internal/domain/domaintest/version.go
Normal file
33
internal/domain/domaintest/version.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type VersionConfig struct {
|
||||
Code string
|
||||
}
|
||||
|
||||
func (cfg *VersionConfig) init() {
|
||||
if cfg.Code == "" {
|
||||
cfg.Code = RandVersionCode()
|
||||
}
|
||||
}
|
||||
|
||||
func NewVersion(tb TestingTB, cfg VersionConfig) domain.Version {
|
||||
cfg.init()
|
||||
s, err := domain.UnmarshalVersionFromDatabase(
|
||||
cfg.Code,
|
||||
gofakeit.LetterN(10),
|
||||
gofakeit.DomainName(),
|
||||
gofakeit.TimeZoneRegion(),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
return s
|
||||
}
|
||||
|
||||
func RandVersionCode() string {
|
||||
return gofakeit.LetterN(2)
|
||||
}
|
|
@ -1,12 +1,5 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
)
|
||||
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
|
@ -38,40 +31,3 @@ type ErrorWithParams interface {
|
|||
Error
|
||||
Params() map[string]any
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = ValidationError{}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Err)
|
||||
}
|
||||
|
||||
func (e ValidationError) Code() ErrorCode {
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
return domainErr.Code()
|
||||
}
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e ValidationError) Slug() string {
|
||||
s := "validation"
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
s = domainErr.Slug()
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", slug.Make(e.Field), s)
|
||||
}
|
||||
|
||||
func (e ValidationError) Params() map[string]any {
|
||||
var withParams ErrorWithParams
|
||||
ok := errors.As(e.Err, &withParams)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return withParams.Params()
|
||||
}
|
||||
|
|
12
internal/domain/null.go
Normal file
12
internal/domain/null.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package domain
|
||||
|
||||
type NullValue[T any] struct {
|
||||
Value T
|
||||
Valid bool // Valid is true if Value is not NULL
|
||||
}
|
||||
|
||||
type NullInt = NullValue[int]
|
||||
|
||||
type NullString = NullValue[string]
|
||||
|
||||
type NullBool = NullValue[bool]
|
|
@ -2,39 +2,359 @@ package domain
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SyncServersCmdPayload struct {
|
||||
versionCode string
|
||||
url *url.URL
|
||||
type Server struct {
|
||||
key string
|
||||
versionCode string
|
||||
url *url.URL
|
||||
open bool
|
||||
special bool
|
||||
numPlayers int
|
||||
numTribes int
|
||||
numVillages int
|
||||
numPlayerVillages int
|
||||
numBarbarianVillages int
|
||||
numBonusVillages int
|
||||
config ServerConfig
|
||||
buildingInfo BuildingInfo
|
||||
unitInfo UnitInfo
|
||||
createdAt time.Time
|
||||
playerDataSyncedAt time.Time
|
||||
playerSnapshotsCreatedAt time.Time
|
||||
tribeDataSyncedAt time.Time
|
||||
tribeSnapshotsCreatedAt time.Time
|
||||
villageDataSyncedAt time.Time
|
||||
ennoblementDataSyncedAt time.Time
|
||||
}
|
||||
|
||||
func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPayload, error) {
|
||||
// UnmarshalServerFromDatabase unmarshals Server from the database.
|
||||
//
|
||||
// It should be used only for unmarshalling from the database!
|
||||
// You can't use UnmarshalServerFromDatabase as constructor - It may put domain into the invalid state!
|
||||
func UnmarshalServerFromDatabase(
|
||||
key string,
|
||||
versionCode string,
|
||||
rawURL string,
|
||||
open bool,
|
||||
special bool,
|
||||
numPlayers int,
|
||||
numTribes int,
|
||||
numVillages int,
|
||||
numPlayerVillages int,
|
||||
numBarbarianVillages int,
|
||||
numBonusVillages int,
|
||||
config ServerConfig,
|
||||
buildingInfo BuildingInfo,
|
||||
unitInfo UnitInfo,
|
||||
createdAt time.Time,
|
||||
playerDataSyncedAt time.Time,
|
||||
playerSnapshotsCreatedAt time.Time,
|
||||
tribeDataSyncedAt time.Time,
|
||||
tribeSnapshotsCreatedAt time.Time,
|
||||
villageDataSyncedAt time.Time,
|
||||
ennoblementDataSyncedAt time.Time,
|
||||
) (Server, error) {
|
||||
if key == "" {
|
||||
return Server{}, errors.New("key can't be blank")
|
||||
}
|
||||
|
||||
if versionCode == "" {
|
||||
return SyncServersCmdPayload{}, errors.New("version code can't be blank")
|
||||
return Server{}, errors.New("version code can't be blank")
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
return SyncServersCmdPayload{}, errors.New("url can't be nil")
|
||||
}
|
||||
|
||||
return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil
|
||||
}
|
||||
|
||||
func NewSyncServersCmdPayloadWithStringURL(versionCode string, rawURL string) (SyncServersCmdPayload, error) {
|
||||
u, err := parseURL(rawURL)
|
||||
if err != nil {
|
||||
return SyncServersCmdPayload{}, err
|
||||
return Server{}, err
|
||||
}
|
||||
|
||||
return NewSyncServersCmdPayload(versionCode, u)
|
||||
return Server{
|
||||
key: key,
|
||||
url: u,
|
||||
open: open,
|
||||
special: special,
|
||||
numPlayers: numPlayers,
|
||||
numTribes: numTribes,
|
||||
numVillages: numVillages,
|
||||
numPlayerVillages: numPlayerVillages,
|
||||
numBarbarianVillages: numBarbarianVillages,
|
||||
numBonusVillages: numBonusVillages,
|
||||
config: config,
|
||||
buildingInfo: buildingInfo,
|
||||
unitInfo: unitInfo,
|
||||
createdAt: createdAt,
|
||||
playerDataSyncedAt: playerDataSyncedAt,
|
||||
playerSnapshotsCreatedAt: playerSnapshotsCreatedAt,
|
||||
tribeDataSyncedAt: tribeDataSyncedAt,
|
||||
tribeSnapshotsCreatedAt: tribeSnapshotsCreatedAt,
|
||||
villageDataSyncedAt: villageDataSyncedAt,
|
||||
ennoblementDataSyncedAt: ennoblementDataSyncedAt,
|
||||
versionCode: versionCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p SyncServersCmdPayload) VersionCode() string {
|
||||
return p.versionCode
|
||||
func (s Server) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
func (p SyncServersCmdPayload) URL() *url.URL {
|
||||
return p.url
|
||||
func (s Server) VersionCode() string {
|
||||
return s.versionCode
|
||||
}
|
||||
|
||||
func (s Server) URL() *url.URL {
|
||||
return s.url
|
||||
}
|
||||
|
||||
func (s Server) Open() bool {
|
||||
return s.open
|
||||
}
|
||||
|
||||
func (s Server) Special() bool {
|
||||
return s.special
|
||||
}
|
||||
|
||||
func (s Server) NumPlayers() int {
|
||||
return s.numPlayers
|
||||
}
|
||||
|
||||
func (s Server) NumTribes() int {
|
||||
return s.numTribes
|
||||
}
|
||||
|
||||
func (s Server) NumVillages() int {
|
||||
return s.numVillages
|
||||
}
|
||||
|
||||
func (s Server) NumPlayerVillages() int {
|
||||
return s.numPlayerVillages
|
||||
}
|
||||
|
||||
func (s Server) NumBarbarianVillages() int {
|
||||
return s.numBarbarianVillages
|
||||
}
|
||||
|
||||
func (s Server) NumBonusVillages() int {
|
||||
return s.numBonusVillages
|
||||
}
|
||||
|
||||
func (s Server) Config() ServerConfig {
|
||||
return s.config
|
||||
}
|
||||
|
||||
func (s Server) BuildingInfo() BuildingInfo {
|
||||
return s.buildingInfo
|
||||
}
|
||||
|
||||
func (s Server) UnitInfo() UnitInfo {
|
||||
return s.unitInfo
|
||||
}
|
||||
|
||||
func (s Server) CreatedAt() time.Time {
|
||||
return s.createdAt
|
||||
}
|
||||
|
||||
func (s Server) PlayerDataSyncedAt() time.Time {
|
||||
return s.playerDataSyncedAt
|
||||
}
|
||||
|
||||
func (s Server) PlayerSnapshotsCreatedAt() time.Time {
|
||||
return s.playerSnapshotsCreatedAt
|
||||
}
|
||||
|
||||
func (s Server) TribeDataSyncedAt() time.Time {
|
||||
return s.tribeDataSyncedAt
|
||||
}
|
||||
|
||||
func (s Server) TribeSnapshotsCreatedAt() time.Time {
|
||||
return s.tribeSnapshotsCreatedAt
|
||||
}
|
||||
|
||||
func (s Server) VillageDataSyncedAt() time.Time {
|
||||
return s.villageDataSyncedAt
|
||||
}
|
||||
|
||||
func (s Server) EnnoblementDataSyncedAt() time.Time {
|
||||
return s.ennoblementDataSyncedAt
|
||||
}
|
||||
|
||||
type Servers []Server
|
||||
|
||||
type CreateServerParams struct {
|
||||
base BaseServer
|
||||
versionCode string
|
||||
}
|
||||
|
||||
func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) {
|
||||
if err := validateVersionCode(versionCode); err != nil {
|
||||
return nil, ValidationError{
|
||||
Err: err,
|
||||
Field: "versionCode",
|
||||
}
|
||||
}
|
||||
|
||||
res := make([]CreateServerParams, 0, len(servers))
|
||||
|
||||
for i, s := range servers {
|
||||
if s.IsZero() {
|
||||
return nil, fmt.Errorf("servers[%d] is an empty struct", i)
|
||||
}
|
||||
|
||||
res = append(res, CreateServerParams{
|
||||
base: s,
|
||||
versionCode: versionCode,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (params CreateServerParams) Base() BaseServer {
|
||||
return params.base
|
||||
}
|
||||
|
||||
func (params CreateServerParams) VersionCode() string {
|
||||
return params.versionCode
|
||||
}
|
||||
|
||||
type ServerSort uint8
|
||||
|
||||
const (
|
||||
ServerSortKeyASC ServerSort = iota + 1
|
||||
ServerSortKeyDESC
|
||||
ServerSortOpenASC
|
||||
ServerSortOpenDESC
|
||||
)
|
||||
|
||||
const ServerListMaxLimit = 500
|
||||
|
||||
type ListServersParams struct {
|
||||
keys []string
|
||||
keyGT NullString
|
||||
open NullBool
|
||||
special NullBool
|
||||
sort []ServerSort
|
||||
limit int
|
||||
offset int
|
||||
}
|
||||
|
||||
func NewListServersParams() ListServersParams {
|
||||
return ListServersParams{
|
||||
sort: []ServerSort{ServerSortKeyASC},
|
||||
limit: ServerListMaxLimit,
|
||||
special: NullBool{
|
||||
Value: false,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Keys() []string {
|
||||
return params.keys
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetKeys(keys []string) error {
|
||||
params.keys = keys
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) KeyGT() NullString {
|
||||
return params.keyGT
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetKeyGT(keyGT NullString) error {
|
||||
params.keyGT = keyGT
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Open() NullBool {
|
||||
return params.open
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetOpen(open NullBool) error {
|
||||
params.open = open
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Special() NullBool {
|
||||
return params.special
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetSpecial(special NullBool) error {
|
||||
params.special = special
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Sort() []ServerSort {
|
||||
return params.sort
|
||||
}
|
||||
|
||||
const (
|
||||
serverSortMinLength = 1
|
||||
serverSortMaxLength = 2
|
||||
)
|
||||
|
||||
func (params *ListServersParams) SetSort(sort []ServerSort) error {
|
||||
if err := validateSliceLen(sort, serverSortMinLength, serverSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
params.sort = sort
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Limit() int {
|
||||
return params.limit
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetLimit(limit int) error {
|
||||
if limit <= 0 {
|
||||
return ValidationError{
|
||||
Field: "limit",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: limit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if limit > ServerListMaxLimit {
|
||||
return ValidationError{
|
||||
Field: "limit",
|
||||
Err: MaxLessEqualError{
|
||||
Max: ServerListMaxLimit,
|
||||
Current: limit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
params.limit = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListServersParams) Offset() int {
|
||||
return params.offset
|
||||
}
|
||||
|
||||
func (params *ListServersParams) SetOffset(offset int) error {
|
||||
if offset < 0 {
|
||||
return ValidationError{
|
||||
Field: "offset",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: offset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
params.offset = offset
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
40
internal/domain/server_message_payloads.go
Normal file
40
internal/domain/server_message_payloads.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type SyncServersCmdPayload struct {
|
||||
versionCode string
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPayload, error) {
|
||||
if versionCode == "" {
|
||||
return SyncServersCmdPayload{}, errors.New("version code can't be blank")
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
return SyncServersCmdPayload{}, errors.New("url can't be nil")
|
||||
}
|
||||
|
||||
return SyncServersCmdPayload{versionCode: versionCode, url: u}, nil
|
||||
}
|
||||
|
||||
func NewSyncServersCmdPayloadWithStringURL(versionCode string, rawURL string) (SyncServersCmdPayload, error) {
|
||||
u, err := parseURL(rawURL)
|
||||
if err != nil {
|
||||
return SyncServersCmdPayload{}, err
|
||||
}
|
||||
|
||||
return NewSyncServersCmdPayload(versionCode, u)
|
||||
}
|
||||
|
||||
func (p SyncServersCmdPayload) VersionCode() string {
|
||||
return p.versionCode
|
||||
}
|
||||
|
||||
func (p SyncServersCmdPayload) URL() *url.URL {
|
||||
return p.url
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
@ -100,6 +102,255 @@ func TestNewSyncServersCmdPayloadWithStringURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewCreateServerParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validVersion := domaintest.NewVersion(t, domaintest.VersionConfig{})
|
||||
validBaseServer := domaintest.NewBaseServer(t, domaintest.BaseServerConfig{})
|
||||
|
||||
type args struct {
|
||||
servers domain.BaseServers
|
||||
versionCode string
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
versionCode: validVersion.Code(),
|
||||
servers: domain.BaseServers{
|
||||
validBaseServer,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, versionCodeTest := range newVersionCodeValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: versionCodeTest.name,
|
||||
args: args{
|
||||
versionCode: versionCodeTest.code,
|
||||
servers: domain.BaseServers{
|
||||
validBaseServer,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Err: versionCodeTest.expectedErr,
|
||||
Field: "versionCode",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewCreateServerParams(tt.args.servers, tt.args.versionCode)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
require.Len(t, res, len(tt.args.servers))
|
||||
for i, b := range tt.args.servers {
|
||||
assert.Equal(t, b, res[i].Base())
|
||||
assert.Equal(t, tt.args.versionCode, res[i].VersionCode())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServersParams_SetSort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
sort []domain.ServerSort
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.ServerSort{
|
||||
domain.ServerSortKeyASC,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) < 1",
|
||||
args: args{
|
||||
sort: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 2",
|
||||
args: args{
|
||||
sort: []domain.ServerSort{
|
||||
domain.ServerSortKeyASC,
|
||||
domain.ServerSortKeyDESC,
|
||||
domain.ServerSortOpenASC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
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.NewListServersParams()
|
||||
|
||||
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.sort, params.Sort())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServersParams_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.ServerListMaxLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: limit < 1",
|
||||
args: args{
|
||||
limit: 0,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Field: "limit",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: fmt.Sprintf("ERR: limit > %d", domain.ServerListMaxLimit),
|
||||
args: args{
|
||||
limit: domain.ServerListMaxLimit + 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Field: "limit",
|
||||
Err: domain.MaxLessEqualError{
|
||||
Max: domain.ServerListMaxLimit,
|
||||
Current: domain.ServerListMaxLimit + 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := domain.NewListServersParams()
|
||||
|
||||
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.limit, params.Limit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListServersParams_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{
|
||||
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.NewListServersParams()
|
||||
|
||||
require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.offset, params.Offset())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type serverKeyValidationTest struct {
|
||||
name string
|
||||
key string
|
||||
|
@ -110,7 +361,7 @@ func newServerKeyValidationTests() []serverKeyValidationTest {
|
|||
return []serverKeyValidationTest{
|
||||
{
|
||||
name: "ERR: server key length < 1",
|
||||
expectedErr: domain.LenError{
|
||||
expectedErr: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 10,
|
||||
},
|
||||
|
@ -118,7 +369,7 @@ func newServerKeyValidationTests() []serverKeyValidationTest {
|
|||
{
|
||||
name: "ERR: server key length > 10",
|
||||
key: "keykeykeyke",
|
||||
expectedErr: domain.LenError{
|
||||
expectedErr: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 10,
|
||||
Current: len("keykeykeyke"),
|
||||
|
|
171
internal/domain/validation.go
Normal file
171
internal/domain/validation.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
)
|
||||
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = ValidationError{}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Err)
|
||||
}
|
||||
|
||||
func (e ValidationError) Code() ErrorCode {
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
return domainErr.Code()
|
||||
}
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e ValidationError) Slug() string {
|
||||
s := "validation"
|
||||
var domainErr Error
|
||||
if errors.As(e.Err, &domainErr) {
|
||||
s = domainErr.Slug()
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", slug.Make(e.Field), s)
|
||||
}
|
||||
|
||||
func (e ValidationError) Params() map[string]any {
|
||||
var withParams ErrorWithParams
|
||||
ok := errors.As(e.Err, &withParams)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return withParams.Params()
|
||||
}
|
||||
|
||||
type MinGreaterEqualError struct {
|
||||
Min int
|
||||
Current int
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = MinGreaterEqualError{}
|
||||
|
||||
func (e MinGreaterEqualError) Error() string {
|
||||
return fmt.Sprintf("must be no less than %d (current: %d)", e.Min, e.Current)
|
||||
}
|
||||
|
||||
func (e MinGreaterEqualError) Code() ErrorCode {
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e MinGreaterEqualError) Slug() string {
|
||||
return "min-greater-equal"
|
||||
}
|
||||
|
||||
func (e MinGreaterEqualError) Params() map[string]any {
|
||||
return map[string]any{
|
||||
"Min": e.Min,
|
||||
"Current": e.Current,
|
||||
}
|
||||
}
|
||||
|
||||
type MaxLessEqualError struct {
|
||||
Max int
|
||||
Current int
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = MaxLessEqualError{}
|
||||
|
||||
func (e MaxLessEqualError) Error() string {
|
||||
return fmt.Sprintf("must be no greater than %d (current: %d)", e.Max, e.Current)
|
||||
}
|
||||
|
||||
func (e MaxLessEqualError) Code() ErrorCode {
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e MaxLessEqualError) Slug() string {
|
||||
return "max-less-equal"
|
||||
}
|
||||
|
||||
func (e MaxLessEqualError) Params() map[string]any {
|
||||
return map[string]any{
|
||||
"Max": e.Max,
|
||||
"Current": e.Current,
|
||||
}
|
||||
}
|
||||
|
||||
type LenOutOfRangeError struct {
|
||||
Min int
|
||||
Max int
|
||||
Current int
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = LenOutOfRangeError{}
|
||||
|
||||
func (e LenOutOfRangeError) Error() string {
|
||||
return fmt.Sprintf("length must be between %d and %d (current length: %d)", e.Min, e.Max, e.Current)
|
||||
}
|
||||
|
||||
func (e LenOutOfRangeError) Code() ErrorCode {
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e LenOutOfRangeError) Slug() string {
|
||||
return "length-out-of-range"
|
||||
}
|
||||
|
||||
func (e LenOutOfRangeError) Params() map[string]any {
|
||||
return map[string]any{
|
||||
"Min": e.Min,
|
||||
"Max": e.Max,
|
||||
"Current": e.Current,
|
||||
}
|
||||
}
|
||||
|
||||
func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
||||
if l := len(s); l > max || l < min {
|
||||
return LenOutOfRangeError{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serverKeyMinLength = 1
|
||||
serverKeyMaxLength = 10
|
||||
)
|
||||
|
||||
func validateServerKey(key string) error {
|
||||
if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength {
|
||||
return LenOutOfRangeError{
|
||||
Min: serverKeyMinLength,
|
||||
Max: serverKeyMaxLength,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
versionCodeMinLength = 2
|
||||
versionCodeMaxLength = 2
|
||||
)
|
||||
|
||||
func validateVersionCode(code string) error {
|
||||
if l := len(code); l < versionCodeMinLength || l > versionCodeMaxLength {
|
||||
return LenOutOfRangeError{
|
||||
Min: versionCodeMinLength,
|
||||
Max: versionCodeMaxLength,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type LenError struct {
|
||||
Min int
|
||||
Max int
|
||||
Current int
|
||||
}
|
||||
|
||||
var _ ErrorWithParams = LenError{}
|
||||
|
||||
func (e LenError) Error() string {
|
||||
return fmt.Sprintf("length must be between %d and %d (current length: %d)", e.Min, e.Max, e.Current)
|
||||
}
|
||||
|
||||
func (e LenError) Code() ErrorCode {
|
||||
return ErrorCodeIncorrectInput
|
||||
}
|
||||
|
||||
func (e LenError) Slug() string {
|
||||
return "length"
|
||||
}
|
||||
|
||||
func (e LenError) Params() map[string]any {
|
||||
return map[string]any{
|
||||
"Min": e.Min,
|
||||
"Max": e.Max,
|
||||
"Current": e.Current,
|
||||
}
|
||||
}
|
||||
|
||||
func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
||||
if l := len(s); l > max || l < min {
|
||||
return LenError{
|
||||
Min: min,
|
||||
Max: max,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serverKeyMinLength = 1
|
||||
serverKeyMaxLength = 10
|
||||
)
|
||||
|
||||
func validateServerKey(key string) error {
|
||||
if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength {
|
||||
return LenError{
|
||||
Min: serverKeyMinLength,
|
||||
Max: serverKeyMaxLength,
|
||||
Current: l,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -91,6 +91,10 @@ const (
|
|||
versionSortMaxLength = 1
|
||||
)
|
||||
|
||||
func (params *ListVersionsParams) Sort() []VersionSort {
|
||||
return params.sort
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
|
||||
if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
|
@ -103,7 +107,3 @@ func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (params *ListVersionsParams) Sort() []VersionSort {
|
||||
return params.sort
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Field: "sort",
|
||||
Err: domain.LenError{
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1,
|
||||
Current: 0,
|
||||
|
@ -52,7 +52,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Field: "sort",
|
||||
Err: domain.LenError{
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 1,
|
||||
Current: 2,
|
||||
|
@ -77,3 +77,32 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
type versionCodeValidationTest struct {
|
||||
name string
|
||||
code string
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
func newVersionCodeValidationTests() []versionCodeValidationTest {
|
||||
return []versionCodeValidationTest{
|
||||
{
|
||||
name: "ERR: version code length < 2",
|
||||
code: "p",
|
||||
expectedErr: domain.LenOutOfRangeError{
|
||||
Min: 2,
|
||||
Max: 2,
|
||||
Current: len("p"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: version code length > 2",
|
||||
code: "pll",
|
||||
expectedErr: domain.LenOutOfRangeError{
|
||||
Min: 2,
|
||||
Max: 2,
|
||||
Current: len("pll"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ func testObserver(t *testing.T, newObserver func(t *testing.T) *healthfile.Obser
|
|||
case <-observerStopped:
|
||||
// OK
|
||||
default:
|
||||
t.Error("Run goroutine not stopped")
|
||||
assert.Fail(t, "Run goroutine not stopped")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user