parent
206eed966e
commit
b5b699ca49
|
@ -64,6 +64,42 @@ var cmdConsumer = &cli.Command{
|
|||
)
|
||||
consumer.Register(router)
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "tribe",
|
||||
Usage: "Run the worker responsible for consuming tribe-related messages",
|
||||
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
|
||||
Action: func(c *cli.Context) error {
|
||||
return runConsumer(
|
||||
c,
|
||||
"TribeConsumer",
|
||||
func(
|
||||
c *cli.Context,
|
||||
router *message.Router,
|
||||
logger watermill.LoggerAdapter,
|
||||
publisher *amqp.Publisher,
|
||||
subscriber *amqp.Subscriber,
|
||||
marshaler watermillmsg.Marshaler,
|
||||
db *bun.DB,
|
||||
) error {
|
||||
twSvc, err := newTWServiceFromFlags(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
consumer := port.NewTribeWatermillConsumer(
|
||||
app.NewTribeService(twSvc),
|
||||
subscriber,
|
||||
logger,
|
||||
marshaler,
|
||||
c.String(rmqFlagTopicServerSyncedEvent.Name),
|
||||
)
|
||||
consumer.Register(router)
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package adapter
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/tw"
|
||||
|
@ -16,7 +17,7 @@ func NewTWHTTP(client *tw.Client) *TWHTTP {
|
|||
return &TWHTTP{client: client}
|
||||
}
|
||||
|
||||
func (t *TWHTTP) GetOpenServers(ctx context.Context, baseURL string) (domain.BaseServers, error) {
|
||||
func (t *TWHTTP) GetOpenServers(ctx context.Context, baseURL *url.URL) (domain.BaseServers, error) {
|
||||
servers, err := t.client.GetOpenServers(ctx, baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -40,7 +41,7 @@ func (t *TWHTTP) convertServersToDomain(servers []tw.Server) (domain.BaseServers
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL string) (domain.ServerConfig, error) {
|
||||
func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL *url.URL) (domain.ServerConfig, error) {
|
||||
cfg, err := t.client.GetServerConfig(ctx, baseURL)
|
||||
if err != nil {
|
||||
return domain.ServerConfig{}, err
|
||||
|
@ -92,7 +93,7 @@ func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL string) (domain.Se
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitInfo, error) {
|
||||
func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL *url.URL) (domain.UnitInfo, error) {
|
||||
info, err := t.client.GetUnitInfo(ctx, baseURL)
|
||||
if err != nil {
|
||||
return domain.UnitInfo{}, err
|
||||
|
@ -120,7 +121,7 @@ func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitIn
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL string) (domain.BuildingInfo, error) {
|
||||
func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (domain.BuildingInfo, error) {
|
||||
info, err := t.client.GetBuildingInfo(ctx, baseURL)
|
||||
if err != nil {
|
||||
return domain.BuildingInfo{}, err
|
||||
|
@ -151,3 +152,52 @@ func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL string) (domain.Bu
|
|||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (t *TWHTTP) GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error) {
|
||||
tribes, err := t.client.GetTribes(ctx, baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.convertTribesToDomain(tribes)
|
||||
}
|
||||
|
||||
func (t *TWHTTP) convertTribesToDomain(tribes []tw.Tribe) (domain.BaseTribes, error) {
|
||||
res := make(domain.BaseTribes, 0, len(tribes))
|
||||
|
||||
for _, tr := range tribes {
|
||||
od, err := domain.NewOpponentsDefeated(
|
||||
tr.OpponentsDefeated.RankAtt,
|
||||
tr.OpponentsDefeated.ScoreAtt,
|
||||
tr.OpponentsDefeated.RankDef,
|
||||
tr.OpponentsDefeated.ScoreDef,
|
||||
tr.OpponentsDefeated.RankSup,
|
||||
tr.OpponentsDefeated.ScoreSup,
|
||||
tr.OpponentsDefeated.RankTotal,
|
||||
tr.OpponentsDefeated.ScoreTotal,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't construct domain.OpponentsDefeated: %w", err)
|
||||
}
|
||||
|
||||
converted, err := domain.NewBaseTribe(
|
||||
tr.ID,
|
||||
tr.Name,
|
||||
tr.Tag,
|
||||
tr.NumMembers,
|
||||
tr.NumVillages,
|
||||
tr.Points,
|
||||
tr.AllPoints,
|
||||
tr.Rank,
|
||||
od,
|
||||
tr.ProfileURL,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't construct domain.BaseTribe: %w", err)
|
||||
}
|
||||
|
||||
res = append(res, converted)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func (pub *ServerWatermillPublisher) CmdSync(ctx context.Context, payloads ...do
|
|||
for _, p := range payloads {
|
||||
msg, err := pub.marshaler.Marshal(ctx, watermillmsg.SyncServersCmdPayload{
|
||||
VersionCode: p.VersionCode(),
|
||||
URL: p.URL().String(),
|
||||
URL: p.URL(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: couldn't marshal SyncServersCmdPayload: %w", p.VersionCode(), err)
|
||||
|
@ -62,7 +62,7 @@ func (pub *ServerWatermillPublisher) EventSynced(
|
|||
msg, err := pub.marshaler.Marshal(ctx, watermillmsg.ServerSyncedEventPayload{
|
||||
Key: p.Key(),
|
||||
VersionCode: p.VersionCode(),
|
||||
URL: p.URL().String(),
|
||||
URL: p.URL(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: couldn't marshal ServerSyncedEventPayload: %w", p.Key(), err)
|
||||
|
|
|
@ -26,7 +26,7 @@ func NewServerService(repo ServerRepository, twSvc TWService, publisher ServerPu
|
|||
func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCmdPayload) error {
|
||||
versionCode := payload.VersionCode()
|
||||
|
||||
openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL().String())
|
||||
openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get open servers for version code %s: %w", versionCode, err)
|
||||
}
|
||||
|
@ -138,19 +138,19 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
|
|||
}
|
||||
|
||||
func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
|
||||
url := payload.URL().String()
|
||||
u := payload.URL()
|
||||
|
||||
cfg, err := svc.twSvc.GetServerConfig(ctx, url)
|
||||
cfg, err := svc.twSvc.GetServerConfig(ctx, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get server config for server %s: %w", payload.Key(), err)
|
||||
}
|
||||
|
||||
buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, url)
|
||||
buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get building info for server %s: %w", payload.Key(), err)
|
||||
}
|
||||
|
||||
unitInfo, err := svc.twSvc.GetUnitInfo(ctx, url)
|
||||
unitInfo, err := svc.twSvc.GetUnitInfo(ctx, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get unit info for server %s: %w", payload.Key(), err)
|
||||
}
|
||||
|
|
27
internal/app/service_tribe.go
Normal file
27
internal/app/service_tribe.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type TribeService struct {
|
||||
twSvc TWService
|
||||
}
|
||||
|
||||
func NewTribeService(twSvc TWService) *TribeService {
|
||||
return &TribeService{twSvc: twSvc}
|
||||
}
|
||||
|
||||
func (svc *TribeService) Sync(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
|
||||
tribes, err := svc.twSvc.GetTribes(ctx, payload.URL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get tribes for server %s: %w", payload.Key(), err)
|
||||
}
|
||||
|
||||
fmt.Println(payload.URL(), len(tribes))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2,13 +2,15 @@ package app
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
)
|
||||
|
||||
type TWService interface {
|
||||
GetOpenServers(ctx context.Context, baseURL string) (domain.BaseServers, error)
|
||||
GetServerConfig(ctx context.Context, baseURL string) (domain.ServerConfig, error)
|
||||
GetUnitInfo(ctx context.Context, baseURL string) (domain.UnitInfo, error)
|
||||
GetBuildingInfo(ctx context.Context, baseURL string) (domain.BuildingInfo, error)
|
||||
GetOpenServers(ctx context.Context, baseURL *url.URL) (domain.BaseServers, error)
|
||||
GetServerConfig(ctx context.Context, baseURL *url.URL) (domain.ServerConfig, error)
|
||||
GetUnitInfo(ctx context.Context, baseURL *url.URL) (domain.UnitInfo, error)
|
||||
GetBuildingInfo(ctx context.Context, baseURL *url.URL) (domain.BuildingInfo, error)
|
||||
GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error)
|
||||
}
|
||||
|
|
|
@ -11,19 +11,22 @@ type BaseServer struct {
|
|||
open bool
|
||||
}
|
||||
|
||||
func NewBaseServer(key, rawURL string, open bool) (BaseServer, error) {
|
||||
const baseServerModelName = "BaseServer"
|
||||
|
||||
func NewBaseServer(key string, u *url.URL, open bool) (BaseServer, error) {
|
||||
if err := validateServerKey(key); err != nil {
|
||||
return BaseServer{}, ValidationError{
|
||||
Err: err,
|
||||
Model: baseServerModelName,
|
||||
Field: "key",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
u, err := parseURL(rawURL)
|
||||
if err != nil {
|
||||
if u == nil {
|
||||
return BaseServer{}, ValidationError{
|
||||
Err: err,
|
||||
Model: baseServerModelName,
|
||||
Field: "url",
|
||||
Err: ErrNotNil,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
|
@ -17,7 +18,7 @@ func TestNewBaseServer(t *testing.T) {
|
|||
|
||||
type args struct {
|
||||
key string
|
||||
url string
|
||||
url *url.URL
|
||||
open bool
|
||||
}
|
||||
|
||||
|
@ -32,20 +33,21 @@ func TestNewBaseServer(t *testing.T) {
|
|||
name: "OK",
|
||||
args: args{
|
||||
key: validBaseServer.Key(),
|
||||
url: validBaseServer.URL().String(),
|
||||
url: validBaseServer.URL(),
|
||||
open: validBaseServer.Open(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: invalid URL",
|
||||
name: "ERR: url can't be nil",
|
||||
args: args{
|
||||
key: validBaseServer.Key(),
|
||||
url: " ",
|
||||
url: nil,
|
||||
open: validBaseServer.Open(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Err: domain.InvalidURLError{URL: " "},
|
||||
Model: "BaseServer",
|
||||
Field: "url",
|
||||
Err: domain.ErrNotNil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -55,12 +57,13 @@ func TestNewBaseServer(t *testing.T) {
|
|||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
key: serverKeyTest.key,
|
||||
url: validBaseServer.URL().String(),
|
||||
url: validBaseServer.URL(),
|
||||
open: validBaseServer.Open(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Err: serverKeyTest.expectedErr,
|
||||
Model: "BaseServer",
|
||||
Field: "key",
|
||||
Err: serverKeyTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -77,7 +80,7 @@ func TestNewBaseServer(t *testing.T) {
|
|||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.key, res.Key())
|
||||
assert.Equal(t, tt.args.url, res.URL().String())
|
||||
assert.Equal(t, tt.args.url, res.URL())
|
||||
assert.Equal(t, tt.args.open, res.Open())
|
||||
})
|
||||
}
|
||||
|
|
188
internal/domain/base_tribe.go
Normal file
188
internal/domain/base_tribe.go
Normal file
|
@ -0,0 +1,188 @@
|
|||
package domain
|
||||
|
||||
import "net/url"
|
||||
|
||||
type BaseTribe struct {
|
||||
id int
|
||||
name string
|
||||
tag string
|
||||
numMembers int
|
||||
numVillages int
|
||||
points int
|
||||
allPoints int
|
||||
rank int
|
||||
od OpponentsDefeated
|
||||
profileURL *url.URL
|
||||
}
|
||||
|
||||
const baseTribeModelName = "BaseTribe"
|
||||
|
||||
//nolint:gocyclo
|
||||
func NewBaseTribe(
|
||||
id int,
|
||||
name string,
|
||||
tag string,
|
||||
numMembers int,
|
||||
numVillages int,
|
||||
points int,
|
||||
allPoints int,
|
||||
rank int,
|
||||
od OpponentsDefeated,
|
||||
profileURL *url.URL,
|
||||
) (BaseTribe, error) {
|
||||
if id < 1 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "id",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if l := len(name); l < tribeNameMinLength || l > tribeNameMaxLength {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "name",
|
||||
Err: LenOutOfRangeError{
|
||||
Min: tribeNameMinLength,
|
||||
Max: tribeNameMaxLength,
|
||||
Current: l,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "tag",
|
||||
Err: LenOutOfRangeError{
|
||||
Min: tribeTagMinLength,
|
||||
Max: tribeTagMaxLength,
|
||||
Current: l,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if numMembers < 0 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "numMembers",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: numMembers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if numVillages < 0 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "numVillages",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: numVillages,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if points < 0 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "points",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: points,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if allPoints < 0 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "allPoints",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: allPoints,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if rank < 0 {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "rank",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rank,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if profileURL == nil {
|
||||
return BaseTribe{}, ValidationError{
|
||||
Model: baseTribeModelName,
|
||||
Field: "profileURL",
|
||||
Err: ErrNotNil,
|
||||
}
|
||||
}
|
||||
|
||||
return BaseTribe{
|
||||
id: id,
|
||||
name: name,
|
||||
tag: tag,
|
||||
numMembers: numMembers,
|
||||
numVillages: numVillages,
|
||||
points: points,
|
||||
allPoints: allPoints,
|
||||
rank: rank,
|
||||
od: od,
|
||||
profileURL: profileURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b BaseTribe) ID() int {
|
||||
return b.id
|
||||
}
|
||||
|
||||
func (b BaseTribe) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
func (b BaseTribe) Tag() string {
|
||||
return b.tag
|
||||
}
|
||||
|
||||
func (b BaseTribe) NumMembers() int {
|
||||
return b.numMembers
|
||||
}
|
||||
|
||||
func (b BaseTribe) NumVillages() int {
|
||||
return b.numVillages
|
||||
}
|
||||
|
||||
func (b BaseTribe) Points() int {
|
||||
return b.points
|
||||
}
|
||||
|
||||
func (b BaseTribe) AllPoints() int {
|
||||
return b.allPoints
|
||||
}
|
||||
|
||||
func (b BaseTribe) Rank() int {
|
||||
return b.rank
|
||||
}
|
||||
|
||||
func (b BaseTribe) OD() OpponentsDefeated {
|
||||
return b.od
|
||||
}
|
||||
|
||||
func (b BaseTribe) ProfileURL() *url.URL {
|
||||
return b.profileURL
|
||||
}
|
||||
|
||||
type BaseTribes []BaseTribe
|
357
internal/domain/base_tribe_test.go
Normal file
357
internal/domain/base_tribe_test.go
Normal file
|
@ -0,0 +1,357 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"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 TestNewBaseTribe(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validBaseTribe := domaintest.NewBaseTribe(t)
|
||||
|
||||
type args struct {
|
||||
id int
|
||||
name string
|
||||
tag string
|
||||
numMembers int
|
||||
numVillages int
|
||||
points int
|
||||
allPoints int
|
||||
rank int
|
||||
od domain.OpponentsDefeated
|
||||
profileURL *url.URL
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: tag/name with non-ascii characters",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: "Шубутные",
|
||||
tag: "Орловы",
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: id < 1",
|
||||
args: args{
|
||||
id: 0,
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "id",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name) < 1",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: "",
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "name",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 255,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(name) > 255",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: gofakeit.LetterN(256),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "name",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 255,
|
||||
Current: 256,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(tag) < 1",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Tag(),
|
||||
tag: "",
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "tag",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 10,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(tag) > 10",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: gofakeit.LetterN(11),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "tag",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 10,
|
||||
Current: 11,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: numMembers < 0",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: -1,
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "numMembers",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: numVillages < 0",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: -1,
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "numVillages",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: points < 0",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: -1,
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "points",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: allPoints < 0",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: -1,
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "allPoints",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: rank < 0",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: -1,
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: validBaseTribe.ProfileURL(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "rank",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: profileURL can't be nil",
|
||||
args: args{
|
||||
id: validBaseTribe.ID(),
|
||||
name: validBaseTribe.Name(),
|
||||
tag: validBaseTribe.Tag(),
|
||||
numMembers: validBaseTribe.NumMembers(),
|
||||
numVillages: validBaseTribe.NumVillages(),
|
||||
points: validBaseTribe.Points(),
|
||||
allPoints: validBaseTribe.AllPoints(),
|
||||
rank: validBaseTribe.Rank(),
|
||||
od: validBaseTribe.OD(),
|
||||
profileURL: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "BaseTribe",
|
||||
Field: "profileURL",
|
||||
Err: domain.ErrNotNil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewBaseTribe(
|
||||
tt.args.id,
|
||||
tt.args.name,
|
||||
tt.args.tag,
|
||||
tt.args.numMembers,
|
||||
tt.args.numVillages,
|
||||
tt.args.points,
|
||||
tt.args.allPoints,
|
||||
tt.args.rank,
|
||||
tt.args.od,
|
||||
tt.args.profileURL,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.id, res.ID())
|
||||
assert.Equal(t, tt.args.name, res.Name())
|
||||
assert.Equal(t, tt.args.tag, res.Tag())
|
||||
assert.Equal(t, tt.args.numMembers, res.NumMembers())
|
||||
assert.Equal(t, tt.args.numVillages, res.NumVillages())
|
||||
assert.Equal(t, tt.args.points, res.Points())
|
||||
assert.Equal(t, tt.args.allPoints, res.AllPoints())
|
||||
assert.Equal(t, tt.args.rank, res.Rank())
|
||||
assert.Equal(t, tt.args.od, res.OD())
|
||||
assert.Equal(t, tt.args.profileURL, res.ProfileURL())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ func NewBaseServer(tb TestingTB, opts ...func(cfg *BaseServerConfig)) domain.Bas
|
|||
}
|
||||
}
|
||||
|
||||
s, err := domain.NewBaseServer(cfg.Key, cfg.URL.String(), cfg.Open)
|
||||
s, err := domain.NewBaseServer(cfg.Key, cfg.URL, cfg.Open)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return s
|
||||
|
|
48
internal/domain/domaintest/base_tribe.go
Normal file
48
internal/domain/domaintest/base_tribe.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type BaseTribeConfig struct {
|
||||
ID int
|
||||
Tag string
|
||||
OD domain.OpponentsDefeated
|
||||
}
|
||||
|
||||
func NewBaseTribe(tb TestingTB, opts ...func(cfg *BaseTribeConfig)) domain.BaseTribe {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &BaseTribeConfig{
|
||||
ID: RandID(),
|
||||
Tag: RandTribeTag(),
|
||||
OD: NewOpponentsDefeated(tb),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
u, err := url.ParseRequestURI(gofakeit.URL())
|
||||
require.NoError(tb, err)
|
||||
|
||||
t, err := domain.NewBaseTribe(
|
||||
cfg.ID,
|
||||
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,
|
||||
u,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return t
|
||||
}
|
23
internal/domain/domaintest/opponents_defeated.go
Normal file
23
internal/domain/domaintest/opponents_defeated.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func NewOpponentsDefeated(tb TestingTB) domain.OpponentsDefeated {
|
||||
tb.Helper()
|
||||
cfg, err := domain.NewOpponentsDefeated(
|
||||
gofakeit.IntRange(1, 100),
|
||||
gofakeit.IntRange(100000, 1000000),
|
||||
gofakeit.IntRange(1, 100),
|
||||
gofakeit.IntRange(100000, 1000000),
|
||||
gofakeit.IntRange(1, 100),
|
||||
gofakeit.IntRange(100000, 1000000),
|
||||
gofakeit.IntRange(1, 100),
|
||||
gofakeit.IntRange(100000, 1000000),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
return cfg
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func NewServerConfig(tb TestingTB) domain.ServerConfig {
|
||||
tb.Helper()
|
||||
cfg, err := domain.NewServerConfig(
|
||||
gofakeit.Float64Range(1, 1000),
|
||||
gofakeit.Float64Range(1, 1000),
|
||||
|
|
7
internal/domain/domaintest/tribe.go
Normal file
7
internal/domain/domaintest/tribe.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package domaintest
|
||||
|
||||
import "github.com/brianvoe/gofakeit/v6"
|
||||
|
||||
func RandTribeTag() string {
|
||||
return gofakeit.LetterN(5)
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func NewUnitInfo(tb TestingTB) domain.UnitInfo {
|
||||
tb.Helper()
|
||||
unitInfo, err := domain.NewUnitInfo(
|
||||
domain.Unit{Speed: gofakeit.Float64Range(1, 25)},
|
||||
domain.Unit{Pop: gofakeit.IntRange(1, 3000)},
|
||||
|
|
7
internal/domain/domaintest/utils.go
Normal file
7
internal/domain/domaintest/utils.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package domaintest
|
||||
|
||||
import "github.com/brianvoe/gofakeit/v6"
|
||||
|
||||
func RandID() int {
|
||||
return gofakeit.IntRange(1, 10000000)
|
||||
}
|
|
@ -4,14 +4,14 @@ type ErrorCode uint8
|
|||
|
||||
const (
|
||||
ErrorCodeUnknown ErrorCode = iota
|
||||
ErrorCodeEntityNotFound
|
||||
ErrorCodeNotFound
|
||||
ErrorCodeIncorrectInput
|
||||
)
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
switch e {
|
||||
case ErrorCodeEntityNotFound:
|
||||
return "entity-not-found"
|
||||
case ErrorCodeNotFound:
|
||||
return "not-found"
|
||||
case ErrorCodeIncorrectInput:
|
||||
return "incorrect-input"
|
||||
case ErrorCodeUnknown:
|
||||
|
@ -31,3 +31,23 @@ type ErrorWithParams interface {
|
|||
Error
|
||||
Params() map[string]any
|
||||
}
|
||||
|
||||
type simpleError struct {
|
||||
msg string
|
||||
code ErrorCode
|
||||
slug string
|
||||
}
|
||||
|
||||
var _ Error = simpleError{}
|
||||
|
||||
func (e simpleError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e simpleError) Code() ErrorCode {
|
||||
return e.code
|
||||
}
|
||||
|
||||
func (e simpleError) Slug() string {
|
||||
return e.slug
|
||||
}
|
||||
|
|
156
internal/domain/opponents_defeated.go
Normal file
156
internal/domain/opponents_defeated.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package domain
|
||||
|
||||
type OpponentsDefeated struct {
|
||||
rankAtt int
|
||||
scoreAtt int
|
||||
rankDef int
|
||||
scoreDef int
|
||||
rankSup int
|
||||
scoreSup int
|
||||
rankTotal int
|
||||
scoreTotal int
|
||||
}
|
||||
|
||||
const opponentsDefeatedModelName = "OpponentsDefeated"
|
||||
|
||||
func NewOpponentsDefeated(
|
||||
rankAtt int,
|
||||
scoreAtt int,
|
||||
rankDef int,
|
||||
scoreDef int,
|
||||
rankSup int,
|
||||
scoreSup int,
|
||||
rankTotal int,
|
||||
scoreTotal int,
|
||||
) (OpponentsDefeated, error) {
|
||||
if rankAtt < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankAtt",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankAtt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if scoreAtt < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreAtt",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreAtt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if rankDef < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankDef",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankDef,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if scoreDef < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreDef",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreDef,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if rankSup < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankSup",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankSup,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if scoreSup < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreSup",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreSup,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if rankTotal < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "rankTotal",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: rankTotal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if scoreTotal < 0 {
|
||||
return OpponentsDefeated{}, ValidationError{
|
||||
Model: opponentsDefeatedModelName,
|
||||
Field: "scoreTotal",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: scoreTotal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return OpponentsDefeated{
|
||||
rankAtt: rankAtt,
|
||||
scoreAtt: scoreAtt,
|
||||
rankDef: rankDef,
|
||||
scoreDef: scoreDef,
|
||||
rankSup: rankSup,
|
||||
scoreSup: scoreSup,
|
||||
rankTotal: rankTotal,
|
||||
scoreTotal: scoreTotal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) RankAtt() int {
|
||||
return o.rankAtt
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) ScoreAtt() int {
|
||||
return o.scoreAtt
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) RankDef() int {
|
||||
return o.rankDef
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) ScoreDef() int {
|
||||
return o.scoreDef
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) RankSup() int {
|
||||
return o.rankSup
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) ScoreSup() int {
|
||||
return o.scoreSup
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) RankTotal() int {
|
||||
return o.rankTotal
|
||||
}
|
||||
|
||||
func (o OpponentsDefeated) ScoreTotal() int {
|
||||
return o.scoreTotal
|
||||
}
|
246
internal/domain/opponents_defeated_test.go
Normal file
246
internal/domain/opponents_defeated_test.go
Normal file
|
@ -0,0 +1,246 @@
|
|||
package domain_test
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestNewOpponentsDefeated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validOD := domaintest.NewOpponentsDefeated(t)
|
||||
|
||||
type args struct {
|
||||
rankAtt int
|
||||
scoreAtt int
|
||||
rankDef int
|
||||
scoreDef int
|
||||
rankSup int
|
||||
scoreSup int
|
||||
rankTotal int
|
||||
scoreTotal int
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: rankAtt < 0",
|
||||
args: args{
|
||||
rankAtt: -1,
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "rankAtt",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: scoreAtt < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: -1,
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "scoreAtt",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: rankDef < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: -1,
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "rankDef",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: scoreDef < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: -1,
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "scoreDef",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: rankSup < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: -1,
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "rankSup",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: scoreSup < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: -1,
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "scoreSup",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: rankTotal < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: -1,
|
||||
scoreTotal: validOD.ScoreTotal(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "rankTotal",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: scoreTotal < 0",
|
||||
args: args{
|
||||
rankAtt: validOD.RankAtt(),
|
||||
scoreAtt: validOD.ScoreAtt(),
|
||||
rankDef: validOD.RankDef(),
|
||||
scoreDef: validOD.ScoreDef(),
|
||||
rankSup: validOD.RankSup(),
|
||||
scoreSup: validOD.ScoreSup(),
|
||||
rankTotal: validOD.RankTotal(),
|
||||
scoreTotal: -1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "OpponentsDefeated",
|
||||
Field: "scoreTotal",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := domain.NewOpponentsDefeated(
|
||||
tt.args.rankAtt,
|
||||
tt.args.scoreAtt,
|
||||
tt.args.rankDef,
|
||||
tt.args.scoreDef,
|
||||
tt.args.rankSup,
|
||||
tt.args.scoreSup,
|
||||
tt.args.rankTotal,
|
||||
tt.args.scoreTotal,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.rankAtt, res.RankAtt())
|
||||
assert.Equal(t, tt.args.scoreAtt, res.ScoreAtt())
|
||||
assert.Equal(t, tt.args.rankDef, res.RankDef())
|
||||
assert.Equal(t, tt.args.scoreDef, res.ScoreDef())
|
||||
assert.Equal(t, tt.args.rankSup, res.RankSup())
|
||||
assert.Equal(t, tt.args.scoreSup, res.ScoreSup())
|
||||
assert.Equal(t, tt.args.rankTotal, res.RankTotal())
|
||||
assert.Equal(t, tt.args.scoreTotal, res.ScoreTotal())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,11 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
serverKeyMinLength = 1
|
||||
serverKeyMaxLength = 10
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
key string
|
||||
versionCode string
|
||||
|
@ -195,7 +200,7 @@ func (ss Servers) Close(open BaseServers) (BaseServers, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
base, err := NewBaseServer(s.Key(), s.URL().String(), false)
|
||||
base, err := NewBaseServer(s.Key(), s.URL(), false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't construct BaseServer for server with key '%s': %w", s.Key(), err)
|
||||
}
|
||||
|
@ -211,11 +216,14 @@ type CreateServerParams struct {
|
|||
versionCode string
|
||||
}
|
||||
|
||||
const createServerParamsModelName = "CreateServerParams"
|
||||
|
||||
func NewCreateServerParams(servers BaseServers, versionCode string) ([]CreateServerParams, error) {
|
||||
if err := validateVersionCode(versionCode); err != nil {
|
||||
return nil, ValidationError{
|
||||
Err: err,
|
||||
Model: createServerParamsModelName,
|
||||
Field: "versionCode",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,6 +312,8 @@ type ListServersParams struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
const listServersParamsModelName = "ListServersParams"
|
||||
|
||||
func NewListServersParams() ListServersParams {
|
||||
return ListServersParams{
|
||||
sort: []ServerSort{ServerSortKeyASC},
|
||||
|
@ -372,6 +382,7 @@ const (
|
|||
func (params *ListServersParams) SetSort(sort []ServerSort) error {
|
||||
if err := validateSliceLen(sort, serverSortMinLength, serverSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
|
@ -389,6 +400,7 @@ func (params *ListServersParams) Limit() int {
|
|||
func (params *ListServersParams) SetLimit(limit int) error {
|
||||
if limit <= 0 {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "limit",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 1,
|
||||
|
@ -399,6 +411,7 @@ func (params *ListServersParams) SetLimit(limit int) error {
|
|||
|
||||
if limit > ServerListMaxLimit {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "limit",
|
||||
Err: MaxLessEqualError{
|
||||
Max: ServerListMaxLimit,
|
||||
|
@ -419,6 +432,7 @@ func (params *ListServersParams) Offset() int {
|
|||
func (params *ListServersParams) SetOffset(offset int) error {
|
||||
if offset < 0 {
|
||||
return ValidationError{
|
||||
Model: listServersParamsModelName,
|
||||
Field: "offset",
|
||||
Err: MinGreaterEqualError{
|
||||
Min: 0,
|
||||
|
@ -443,7 +457,7 @@ func (e ServerNotFoundError) Error() string {
|
|||
}
|
||||
|
||||
func (e ServerNotFoundError) Code() ErrorCode {
|
||||
return ErrorCodeEntityNotFound
|
||||
return ErrorCodeNotFound
|
||||
}
|
||||
|
||||
func (e ServerNotFoundError) Slug() string {
|
||||
|
|
|
@ -23,15 +23,6 @@ func NewSyncServersCmdPayload(versionCode string, u *url.URL) (SyncServersCmdPay
|
|||
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
|
||||
}
|
||||
|
@ -46,7 +37,7 @@ type ServerSyncedEventPayload struct {
|
|||
versionCode string
|
||||
}
|
||||
|
||||
func NewServerSyncedEventPayload(key string, rawURL string, versionCode string) (ServerSyncedEventPayload, error) {
|
||||
func NewServerSyncedEventPayload(key string, u *url.URL, versionCode string) (ServerSyncedEventPayload, error) {
|
||||
if key == "" {
|
||||
return ServerSyncedEventPayload{}, errors.New("key can't be blank")
|
||||
}
|
||||
|
@ -55,9 +46,8 @@ func NewServerSyncedEventPayload(key string, rawURL string, versionCode string)
|
|||
return ServerSyncedEventPayload{}, errors.New("version code can't be blank")
|
||||
}
|
||||
|
||||
u, err := parseURL(rawURL)
|
||||
if err != nil {
|
||||
return ServerSyncedEventPayload{}, err
|
||||
if u == nil {
|
||||
return ServerSyncedEventPayload{}, errors.New("url can't be nil")
|
||||
}
|
||||
|
||||
return ServerSyncedEventPayload{
|
||||
|
|
|
@ -53,61 +53,12 @@ func TestNewSyncServersCmdPayload(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewSyncServersCmdPayloadWithStringURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
versionCode string
|
||||
url string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
versionCode: "pl",
|
||||
url: "https://plemiona.pl",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: invalid url",
|
||||
args: args{
|
||||
versionCode: "pl",
|
||||
url: "plemiona.pl",
|
||||
},
|
||||
expectedErr: domain.InvalidURLError{
|
||||
URL: "plemiona.pl",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload, err := domain.NewSyncServersCmdPayloadWithStringURL(tt.args.versionCode, tt.args.url)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.args.versionCode, payload.VersionCode())
|
||||
assert.Equal(t, tt.args.url, payload.URL().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerSyncedEventPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := domaintest.NewServer(t)
|
||||
|
||||
payload, err := domain.NewServerSyncedEventPayload(server.Key(), server.URL().String(), server.VersionCode())
|
||||
payload, err := domain.NewServerSyncedEventPayload(server.Key(), server.URL(), server.VersionCode())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, server.Key(), payload.Key())
|
||||
assert.Equal(t, server.URL(), payload.URL())
|
||||
|
|
|
@ -90,8 +90,9 @@ func TestNewCreateServerParams(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Err: versionCodeTest.expectedErr,
|
||||
Model: "CreateServerParams",
|
||||
Field: "versionCode",
|
||||
Err: versionCodeTest.expectedErr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -142,6 +143,7 @@ func TestListServersParams_SetSort(t *testing.T) {
|
|||
sort: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServersParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
|
@ -160,6 +162,7 @@ func TestListServersParams_SetSort(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServersParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
|
@ -211,6 +214,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
|
|||
limit: 0,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServersParams",
|
||||
Field: "limit",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 1,
|
||||
|
@ -224,6 +228,7 @@ func TestListServersParams_SetLimit(t *testing.T) {
|
|||
limit: domain.ServerListMaxLimit + 1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServersParams",
|
||||
Field: "limit",
|
||||
Err: domain.MaxLessEqualError{
|
||||
Max: domain.ServerListMaxLimit,
|
||||
|
@ -274,6 +279,7 @@ func TestListServersParams_SetOffset(t *testing.T) {
|
|||
offset: -1,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListServersParams",
|
||||
Field: "offset",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
|
|
116
internal/domain/tribe.go
Normal file
116
internal/domain/tribe.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tribeNameMinLength = 1
|
||||
tribeNameMaxLength = 255
|
||||
tribeTagMinLength = 1
|
||||
tribeTagMaxLength = 10
|
||||
)
|
||||
|
||||
type Tribe struct {
|
||||
id int
|
||||
serverKey string
|
||||
name string
|
||||
tag string
|
||||
numMembers int
|
||||
numVillages int
|
||||
points int
|
||||
allPoints int
|
||||
rank int
|
||||
od OpponentsDefeated
|
||||
profileURL *url.URL
|
||||
dominance float64
|
||||
bestRank int
|
||||
bestRankAt time.Time
|
||||
mostPoints int
|
||||
mostPointsAt time.Time
|
||||
mostVillages int
|
||||
mostVillagesAt time.Time
|
||||
createdAt time.Time
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
func (t Tribe) ID() int {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t Tribe) ServerKey() string {
|
||||
return t.serverKey
|
||||
}
|
||||
|
||||
func (t Tribe) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t Tribe) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
func (t Tribe) NumMembers() int {
|
||||
return t.numMembers
|
||||
}
|
||||
|
||||
func (t Tribe) NumVillages() int {
|
||||
return t.numVillages
|
||||
}
|
||||
|
||||
func (t Tribe) Points() int {
|
||||
return t.points
|
||||
}
|
||||
|
||||
func (t Tribe) AllPoints() int {
|
||||
return t.allPoints
|
||||
}
|
||||
|
||||
func (t Tribe) Rank() int {
|
||||
return t.rank
|
||||
}
|
||||
|
||||
func (t Tribe) OD() OpponentsDefeated {
|
||||
return t.od
|
||||
}
|
||||
|
||||
func (t Tribe) ProfileURL() *url.URL {
|
||||
return t.profileURL
|
||||
}
|
||||
|
||||
func (t Tribe) Dominance() float64 {
|
||||
return t.dominance
|
||||
}
|
||||
|
||||
func (t Tribe) BestRank() int {
|
||||
return t.bestRank
|
||||
}
|
||||
|
||||
func (t Tribe) BestRankAt() time.Time {
|
||||
return t.bestRankAt
|
||||
}
|
||||
|
||||
func (t Tribe) MostPoints() int {
|
||||
return t.mostPoints
|
||||
}
|
||||
|
||||
func (t Tribe) MostPointsAt() time.Time {
|
||||
return t.mostPointsAt
|
||||
}
|
||||
|
||||
func (t Tribe) MostVillages() int {
|
||||
return t.mostVillages
|
||||
}
|
||||
|
||||
func (t Tribe) MostVillagesAt() time.Time {
|
||||
return t.mostVillagesAt
|
||||
}
|
||||
|
||||
func (t Tribe) CreatedAt() time.Time {
|
||||
return t.createdAt
|
||||
}
|
||||
|
||||
func (t Tribe) DeletedAt() time.Time {
|
||||
return t.deletedAt
|
||||
}
|
|
@ -10,7 +10,7 @@ type InvalidURLError struct {
|
|||
}
|
||||
|
||||
func (e InvalidURLError) Error() string {
|
||||
return fmt.Sprintf("'%s': invalid URL", e.URL)
|
||||
return fmt.Sprintf("%s: invalid URL", e.URL)
|
||||
}
|
||||
|
||||
func parseURL(rawURL string) (*url.URL, error) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
type ValidationError struct {
|
||||
Model string
|
||||
Field string
|
||||
Err error
|
||||
}
|
||||
|
@ -15,7 +16,20 @@ type ValidationError struct {
|
|||
var _ ErrorWithParams = ValidationError{}
|
||||
|
||||
func (e ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Err)
|
||||
prefix := e.Model
|
||||
|
||||
if e.Field != "" {
|
||||
if len(prefix) > 0 {
|
||||
prefix += "."
|
||||
}
|
||||
prefix += e.Field
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
prefix += ": "
|
||||
}
|
||||
|
||||
return prefix + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e ValidationError) Code() ErrorCode {
|
||||
|
@ -28,22 +42,35 @@ 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 fmt.Sprintf("%s-%s", slug.Make(e.Field), s)
|
||||
|
||||
if e.Field != "" {
|
||||
s = slug.Make(e.Field) + "-" + s
|
||||
}
|
||||
|
||||
if e.Model != "" {
|
||||
s = slug.Make(e.Model) + "-" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (e ValidationError) Params() map[string]any {
|
||||
var withParams ErrorWithParams
|
||||
ok := errors.As(e.Err, &withParams)
|
||||
if !ok {
|
||||
if ok := errors.As(e.Err, &withParams); !ok {
|
||||
return nil
|
||||
}
|
||||
return withParams.Params()
|
||||
}
|
||||
|
||||
func (e ValidationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type MinGreaterEqualError struct {
|
||||
Min int
|
||||
Current int
|
||||
|
@ -124,6 +151,12 @@ func (e LenOutOfRangeError) Params() map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
var ErrNotNil error = simpleError{
|
||||
msg: "must not be nil",
|
||||
code: ErrorCodeIncorrectInput,
|
||||
slug: "not-nil",
|
||||
}
|
||||
|
||||
func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
||||
if l := len(s); l > max || l < min {
|
||||
return LenOutOfRangeError{
|
||||
|
@ -136,11 +169,6 @@ func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serverKeyMinLength = 1
|
||||
serverKeyMaxLength = 10
|
||||
)
|
||||
|
||||
func validateServerKey(key string) error {
|
||||
if l := len(key); l < serverKeyMinLength || l > serverKeyMaxLength {
|
||||
return LenOutOfRangeError{
|
||||
|
@ -153,11 +181,6 @@ func validateServerKey(key string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
versionCodeMinLength = 2
|
||||
versionCodeMaxLength = 2
|
||||
)
|
||||
|
||||
func validateVersionCode(code string) error {
|
||||
if l := len(code); l < versionCodeMinLength || l > versionCodeMaxLength {
|
||||
return LenOutOfRangeError{
|
||||
|
|
|
@ -5,6 +5,11 @@ import (
|
|||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
versionCodeMinLength = 2
|
||||
versionCodeMaxLength = 2
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
code string
|
||||
name string
|
||||
|
@ -82,6 +87,8 @@ type ListVersionsParams struct {
|
|||
sort []VersionSort
|
||||
}
|
||||
|
||||
const listVersionsParamsModelName = "ListVersionsParams"
|
||||
|
||||
func NewListVersionsParams() ListVersionsParams {
|
||||
return ListVersionsParams{sort: []VersionSort{VersionSortCodeASC}}
|
||||
}
|
||||
|
@ -98,6 +105,7 @@ func (params *ListVersionsParams) Sort() []VersionSort {
|
|||
func (params *ListVersionsParams) SetSort(sort []VersionSort) error {
|
||||
if err := validateSliceLen(sort, versionSortMinLength, versionSortMaxLength); err != nil {
|
||||
return ValidationError{
|
||||
Model: listVersionsParamsModelName,
|
||||
Field: "sort",
|
||||
Err: err,
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
sort: nil,
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
|
@ -51,6 +52,7 @@ func TestListVersionsParams_SetSort(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "ListVersionsParams",
|
||||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
|
|
|
@ -55,7 +55,7 @@ func (c *ServerWatermillConsumer) sync(msg *message.Message) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
payload, err := domain.NewSyncServersCmdPayloadWithStringURL(rawPayload.VersionCode, rawPayload.URL)
|
||||
payload, err := domain.NewSyncServersCmdPayload(rawPayload.VersionCode, rawPayload.URL)
|
||||
if err != nil {
|
||||
c.logger.Error("couldn't construct domain.SyncServersCmdPayload", err, watermill.LogFields{
|
||||
"handler": message.HandlerNameFromCtx(msg.Context()),
|
||||
|
|
63
internal/port/consumer_watermill_tribe.go
Normal file
63
internal/port/consumer_watermill_tribe.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/watermillmsg"
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
)
|
||||
|
||||
type TribeWatermillConsumer struct {
|
||||
svc *app.TribeService
|
||||
subscriber message.Subscriber
|
||||
logger watermill.LoggerAdapter
|
||||
marshaler watermillmsg.Marshaler
|
||||
eventServerSyncedTopic string
|
||||
}
|
||||
|
||||
func NewTribeWatermillConsumer(
|
||||
svc *app.TribeService,
|
||||
subscriber message.Subscriber,
|
||||
logger watermill.LoggerAdapter,
|
||||
marshaler watermillmsg.Marshaler,
|
||||
eventServerSyncedTopic string,
|
||||
) *TribeWatermillConsumer {
|
||||
return &TribeWatermillConsumer{
|
||||
svc: svc,
|
||||
subscriber: subscriber,
|
||||
logger: logger,
|
||||
marshaler: marshaler,
|
||||
eventServerSyncedTopic: eventServerSyncedTopic,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TribeWatermillConsumer) Register(router *message.Router) {
|
||||
router.AddNoPublisherHandler(
|
||||
"TribeConsumer.sync",
|
||||
c.eventServerSyncedTopic,
|
||||
c.subscriber,
|
||||
c.sync,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TribeWatermillConsumer) sync(msg *message.Message) error {
|
||||
var rawPayload watermillmsg.ServerSyncedEventPayload
|
||||
|
||||
if err := c.marshaler.Unmarshal(msg, &rawPayload); err != nil {
|
||||
c.logger.Error("couldn't unmarshal payload", err, watermill.LogFields{
|
||||
"handler": message.HandlerNameFromCtx(msg.Context()),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := domain.NewServerSyncedEventPayload(rawPayload.Key, rawPayload.URL, rawPayload.VersionCode)
|
||||
if err != nil {
|
||||
c.logger.Error("couldn't construct domain.ServerSyncedEventPayload", err, watermill.LogFields{
|
||||
"handler": message.HandlerNameFromCtx(msg.Context()),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.svc.Sync(msg.Context(), payload)
|
||||
}
|
|
@ -59,19 +59,19 @@ func NewClient(opts ...ClientOption) *Client {
|
|||
}
|
||||
}
|
||||
|
||||
var ErrBaseURLCantBeNil = errors.New("baseURL can't be nil")
|
||||
|
||||
var (
|
||||
ErrInvalidServerKey = errors.New("invalid server key")
|
||||
ErrInvalidServerURL = errors.New("invalid server URL")
|
||||
)
|
||||
|
||||
func (c *Client) GetOpenServers(ctx context.Context, rawBaseURL string) ([]Server, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *Client) GetOpenServers(ctx context.Context, baseURL *url.URL) ([]Server, error) {
|
||||
if baseURL == nil {
|
||||
return nil, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
u := buildURL(baseURL, endpointGetServers)
|
||||
resp, err := c.get(ctx, u)
|
||||
resp, err := c.get(ctx, buildURL(baseURL, endpointGetServers))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -79,86 +79,54 @@ func (c *Client) GetOpenServers(ctx context.Context, rawBaseURL string) ([]Serve
|
|||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: couldn't read response body: %w", u, err)
|
||||
}
|
||||
|
||||
m, err := phpserialize.UnmarshalAssociativeArray(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: couldn't unmarshal response body: %w", u, err)
|
||||
}
|
||||
|
||||
servers := make([]Server, 0, len(m))
|
||||
for key, val := range m {
|
||||
keyStr, ok := key.(string)
|
||||
if !ok || keyStr == "" {
|
||||
return nil, fmt.Errorf("%s: parsing '%v': %w", u, key, ErrInvalidServerKey)
|
||||
}
|
||||
|
||||
urlStr, ok := val.(string)
|
||||
if !ok || urlStr == "" {
|
||||
return nil, fmt.Errorf("%s: parsing '%v': %w", u, val, ErrInvalidServerURL)
|
||||
}
|
||||
|
||||
servers = append(servers, Server{
|
||||
Key: keyStr,
|
||||
URL: urlStr,
|
||||
})
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
return serverParser{r: resp.Body}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetServerConfig(ctx context.Context, rawBaseURL string) (ServerConfig, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return ServerConfig{}, err
|
||||
func (c *Client) GetServerConfig(ctx context.Context, baseURL *url.URL) (ServerConfig, error) {
|
||||
if baseURL == nil {
|
||||
return ServerConfig{}, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
var cfg ServerConfig
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil {
|
||||
if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil {
|
||||
return ServerConfig{}, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBuildingInfo(ctx context.Context, rawBaseURL string) (BuildingInfo, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return BuildingInfo{}, err
|
||||
func (c *Client) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (BuildingInfo, error) {
|
||||
if baseURL == nil {
|
||||
return BuildingInfo{}, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
var info BuildingInfo
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil {
|
||||
if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil {
|
||||
return BuildingInfo{}, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetUnitInfo(ctx context.Context, rawBaseURL string) (UnitInfo, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return UnitInfo{}, err
|
||||
func (c *Client) GetUnitInfo(ctx context.Context, baseURL *url.URL) (UnitInfo, error) {
|
||||
if baseURL == nil {
|
||||
return UnitInfo{}, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
var info UnitInfo
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil {
|
||||
if err := c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil {
|
||||
return UnitInfo{}, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTribes(ctx context.Context, rawBaseURL string) ([]Tribe, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *Client) GetTribes(ctx context.Context, baseURL *url.URL) ([]Tribe, error) {
|
||||
if baseURL == nil {
|
||||
return nil, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
od, err := c.getOD(ctx, baseURL, true)
|
||||
|
@ -181,10 +149,9 @@ func (c *Client) GetTribes(ctx context.Context, rawBaseURL string) ([]Tribe, err
|
|||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetPlayers(ctx context.Context, rawBaseURL string) ([]Player, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *Client) GetPlayers(ctx context.Context, baseURL *url.URL) ([]Player, error) {
|
||||
if baseURL == nil {
|
||||
return nil, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
od, err := c.getOD(ctx, baseURL, false)
|
||||
|
@ -260,10 +227,9 @@ func (c *Client) getSingleODFile(ctx context.Context, u *url.URL) ([]odRecord, e
|
|||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetVillages(ctx context.Context, rawBaseURL string) ([]Village, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *Client) GetVillages(ctx context.Context, baseURL *url.URL) ([]Village, error) {
|
||||
if baseURL == nil {
|
||||
return nil, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
resp, err := c.get(ctx, buildURL(baseURL, endpointVillages))
|
||||
|
@ -280,10 +246,9 @@ func (c *Client) GetVillages(ctx context.Context, rawBaseURL string) ([]Village,
|
|||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetEnnoblements(ctx context.Context, rawBaseURL string, since time.Time) ([]Ennoblement, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *Client) GetEnnoblements(ctx context.Context, baseURL *url.URL, since time.Time) ([]Ennoblement, error) {
|
||||
if baseURL == nil {
|
||||
return nil, ErrBaseURLCantBeNil
|
||||
}
|
||||
|
||||
u := buildURL(baseURL, endpointEnnoblements)
|
||||
|
@ -311,7 +276,6 @@ func (c *Client) getXML(ctx context.Context, u *url.URL, v any) error {
|
|||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
|
@ -327,7 +291,7 @@ func (c *Client) get(ctx context.Context, u *url.URL) (*http.Response, error) {
|
|||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", urlStr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// headers
|
||||
|
@ -347,6 +311,48 @@ func (c *Client) get(ctx context.Context, u *url.URL) (*http.Response, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
type serverParser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (p serverParser) parse() ([]Server, error) {
|
||||
b, err := io.ReadAll(p.r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't read data: %w", err)
|
||||
}
|
||||
|
||||
m, err := phpserialize.UnmarshalAssociativeArray(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't unmarshal response body: %w", err)
|
||||
}
|
||||
|
||||
servers := make([]Server, 0, len(m))
|
||||
|
||||
for key, val := range m {
|
||||
keyStr, ok := key.(string)
|
||||
if !ok || keyStr == "" {
|
||||
return nil, fmt.Errorf("parsing '%v': %w", key, ErrInvalidServerKey)
|
||||
}
|
||||
|
||||
urlStr, ok := val.(string)
|
||||
if !ok || urlStr == "" {
|
||||
return nil, fmt.Errorf("parsing '%v': %w", val, ErrInvalidServerURL)
|
||||
}
|
||||
|
||||
serverURL, parseErr := url.ParseRequestURI(urlStr)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("parsing '%v': %w", val, parseErr)
|
||||
}
|
||||
|
||||
servers = append(servers, Server{
|
||||
Key: keyStr,
|
||||
URL: serverURL,
|
||||
})
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
type tribeCSVParser struct {
|
||||
r io.Reader
|
||||
baseURL *url.URL
|
||||
|
@ -355,8 +361,8 @@ type tribeCSVParser struct {
|
|||
|
||||
const fieldsPerRecordTribe = 8
|
||||
|
||||
func (t tribeCSVParser) parse() ([]Tribe, error) {
|
||||
csvR := newCSVReader(t.r, fieldsPerRecordTribe)
|
||||
func (p tribeCSVParser) parse() ([]Tribe, error) {
|
||||
csvR := newCSVReader(p.r, fieldsPerRecordTribe)
|
||||
|
||||
var tribes []Tribe
|
||||
for {
|
||||
|
@ -368,7 +374,7 @@ func (t tribeCSVParser) parse() ([]Tribe, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tribe, err := t.parseRecord(rec)
|
||||
tribe, err := p.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -383,7 +389,7 @@ func (t tribeCSVParser) parse() ([]Tribe, error) {
|
|||
return tribes, nil
|
||||
}
|
||||
|
||||
func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) {
|
||||
func (p tribeCSVParser) parseRecord(record []string) (Tribe, error) {
|
||||
var err error
|
||||
var tribe Tribe
|
||||
|
||||
|
@ -429,9 +435,9 @@ func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) {
|
|||
return Tribe{}, ParseError{Err: err, Str: record[7], Field: "Tribe.Rank"}
|
||||
}
|
||||
|
||||
tribe.OpponentsDefeated = t.od[tribe.ID]
|
||||
tribe.OpponentsDefeated = p.od[tribe.ID]
|
||||
|
||||
tribe.ProfileURL = buildURLWithQuery(t.baseURL, endpointGame, fmt.Sprintf(queryTribeProfile, tribe.ID)).String()
|
||||
tribe.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryTribeProfile, tribe.ID))
|
||||
|
||||
return tribe, nil
|
||||
}
|
||||
|
@ -510,7 +516,7 @@ func (p playerCSVParser) parseRecord(record []string) (Player, error) {
|
|||
|
||||
player.OpponentsDefeated = p.od[player.ID]
|
||||
|
||||
player.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryPlayerProfile, player.ID)).String()
|
||||
player.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryPlayerProfile, player.ID))
|
||||
|
||||
return player, nil
|
||||
}
|
||||
|
@ -522,8 +528,8 @@ type villageCSVParser struct {
|
|||
|
||||
const fieldsPerRecordVillage = 7
|
||||
|
||||
func (v villageCSVParser) parse() ([]Village, error) {
|
||||
csvR := newCSVReader(v.r, fieldsPerRecordVillage)
|
||||
func (p villageCSVParser) parse() ([]Village, error) {
|
||||
csvR := newCSVReader(p.r, fieldsPerRecordVillage)
|
||||
|
||||
var villages []Village
|
||||
for {
|
||||
|
@ -534,7 +540,7 @@ func (v villageCSVParser) parse() ([]Village, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
village, err := v.parseRecord(rec)
|
||||
village, err := p.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -548,7 +554,7 @@ func (v villageCSVParser) parse() ([]Village, error) {
|
|||
return villages, nil
|
||||
}
|
||||
|
||||
func (v villageCSVParser) parseRecord(record []string) (Village, error) {
|
||||
func (p villageCSVParser) parseRecord(record []string) (Village, error) {
|
||||
var err error
|
||||
var village Village
|
||||
|
||||
|
@ -589,14 +595,14 @@ func (v villageCSVParser) parseRecord(record []string) (Village, error) {
|
|||
return Village{}, ParseError{Err: err, Str: record[6], Field: "Village.Bonus"}
|
||||
}
|
||||
|
||||
village.Continent = v.buildContinent(record, village.X, village.Y)
|
||||
village.Continent = p.buildContinent(record, village.X, village.Y)
|
||||
|
||||
village.ProfileURL = buildURLWithQuery(v.baseURL, endpointGame, fmt.Sprintf(queryVillageProfile, village.ID)).String()
|
||||
village.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryVillageProfile, village.ID))
|
||||
|
||||
return village, nil
|
||||
}
|
||||
|
||||
func (v villageCSVParser) buildContinent(record []string, x, y int) string {
|
||||
func (p villageCSVParser) buildContinent(record []string, x, y int) string {
|
||||
continent := "K"
|
||||
|
||||
switch {
|
||||
|
@ -625,8 +631,8 @@ type odRecord struct {
|
|||
|
||||
const fieldsPerRecordOD = 3
|
||||
|
||||
func (o odCSVParser) parse() ([]odRecord, error) {
|
||||
csvR := newCSVReader(o.r, fieldsPerRecordOD)
|
||||
func (p odCSVParser) parse() ([]odRecord, error) {
|
||||
csvR := newCSVReader(p.r, fieldsPerRecordOD)
|
||||
|
||||
var odRecords []odRecord
|
||||
for {
|
||||
|
@ -638,7 +644,7 @@ func (o odCSVParser) parse() ([]odRecord, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
od, err := o.parseRecord(rec)
|
||||
od, err := p.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -649,7 +655,7 @@ func (o odCSVParser) parse() ([]odRecord, error) {
|
|||
return odRecords, nil
|
||||
}
|
||||
|
||||
func (o odCSVParser) parseRecord(record []string) (odRecord, error) {
|
||||
func (p odCSVParser) parseRecord(record []string) (odRecord, error) {
|
||||
var err error
|
||||
var rec odRecord
|
||||
|
||||
|
@ -680,8 +686,8 @@ type ennoblementCSVParser struct {
|
|||
|
||||
const fieldsPerRecordEnnoblement = 7
|
||||
|
||||
func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
|
||||
csvR := newCSVReader(e.r, fieldsPerRecordEnnoblement)
|
||||
func (p ennoblementCSVParser) parse() ([]Ennoblement, error) {
|
||||
csvR := newCSVReader(p.r, fieldsPerRecordEnnoblement)
|
||||
|
||||
var ennoblements []Ennoblement
|
||||
for {
|
||||
|
@ -692,12 +698,12 @@ func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ennoblement, err := e.parseRecord(rec)
|
||||
ennoblement, err := p.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ennoblement.CreatedAt.Before(e.since) {
|
||||
if ennoblement.CreatedAt.Before(p.since) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -711,7 +717,7 @@ func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
|
|||
return ennoblements, nil
|
||||
}
|
||||
|
||||
func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) {
|
||||
func (p ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) {
|
||||
var err error
|
||||
var ennoblement Ennoblement
|
||||
|
||||
|
@ -722,7 +728,7 @@ func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error)
|
|||
return Ennoblement{}, ParseError{Err: err, Str: record[0], Field: "Ennoblement.VillageID"}
|
||||
}
|
||||
|
||||
ennoblement.CreatedAt, err = e.parseTimestamp(record[1])
|
||||
ennoblement.CreatedAt, err = p.parseTimestamp(record[1])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[1], Field: "Ennoblement.CreatedAt"}
|
||||
}
|
||||
|
@ -755,7 +761,7 @@ func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error)
|
|||
return ennoblement, nil
|
||||
}
|
||||
|
||||
func (e ennoblementCSVParser) parseTimestamp(s string) (time.Time, error) {
|
||||
func (p ennoblementCSVParser) parseTimestamp(s string) (time.Time, error) {
|
||||
timestamp, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
|
@ -795,10 +801,8 @@ func buildURL(base *url.URL, path string) *url.URL {
|
|||
}
|
||||
|
||||
func buildURLWithQuery(base *url.URL, path, query string) *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: base.Scheme,
|
||||
Host: base.Host,
|
||||
Path: path,
|
||||
RawQuery: query,
|
||||
}
|
||||
newURL := *base
|
||||
newURL.Path = path
|
||||
newURL.RawQuery = query
|
||||
return &newURL
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -52,21 +54,17 @@ func TestClient_GetOpenServers(t *testing.T) {
|
|||
"pl164",
|
||||
}
|
||||
|
||||
servers, err := newClient(srv.Client()).GetOpenServers(context.Background(), srv.URL)
|
||||
servers, err := newClient(srv.Client()).GetOpenServers(context.Background(), parseURL(t, srv.URL))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, servers, len(expectedServerKeys))
|
||||
|
||||
for _, key := range expectedServerKeys {
|
||||
found := false
|
||||
|
||||
for _, server := range servers {
|
||||
if server.Key == key && server.URL == fmt.Sprintf("https://%s.plemiona.pl", key) {
|
||||
found = true
|
||||
break
|
||||
assert.True(t, slices.ContainsFunc(servers, func(server tw.Server) bool {
|
||||
return server.Key == key && *server.URL == url.URL{
|
||||
Scheme: "https",
|
||||
Host: key + ".plemiona.pl",
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found, "key '%s' not found", key)
|
||||
}), "key '%s' not found", key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +80,7 @@ func TestClient_GetServerConfig(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
cfg, err := newClient(srv.Client()).GetServerConfig(context.Background(), srv.URL)
|
||||
cfg, err := newClient(srv.Client()).GetServerConfig(context.Background(), parseURL(t, srv.URL))
|
||||
require.NoError(t, err)
|
||||
assert.InEpsilon(t, 4.5, cfg.Speed, 0.01)
|
||||
assert.InEpsilon(t, 0.5, cfg.UnitSpeed, 0.01)
|
||||
|
@ -206,7 +204,7 @@ func TestClient_GetUnitInfo(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
unitInfo, err := newClient(srv.Client()).GetUnitInfo(context.Background(), srv.URL)
|
||||
unitInfo, err := newClient(srv.Client()).GetUnitInfo(context.Background(), parseURL(t, srv.URL))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.InEpsilon(t, 226.66666666667, unitInfo.Spear.BuildTime, 0.01)
|
||||
|
@ -321,7 +319,7 @@ func TestClient_GetBuildingInfo(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
buildingInfo, err := newClient(srv.Client()).GetBuildingInfo(context.Background(), srv.URL)
|
||||
buildingInfo, err := newClient(srv.Client()).GetBuildingInfo(context.Background(), parseURL(t, srv.URL))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 30, buildingInfo.Main.MaxLevel)
|
||||
|
@ -556,7 +554,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
respODA string
|
||||
respODD string
|
||||
checkErr func(err error) bool
|
||||
expectedTribes func(url string) []tw.Tribe
|
||||
expectedTribes func(t *testing.T, url string) []tw.Tribe
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
|
@ -564,13 +562,14 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
respOD: "1,1,1\n2,2,2\n3,3,3",
|
||||
respODA: "1,1,1\n2,2,2\n3,3,3",
|
||||
respODD: "1,1,1\n2,2,2\n3,3,3",
|
||||
expectedTribes: func(url string) []tw.Tribe {
|
||||
expectedTribes: func(t *testing.T, baseURL string) []tw.Tribe {
|
||||
t.Helper()
|
||||
return []tw.Tribe{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "name 1",
|
||||
Tag: "tag 1",
|
||||
ProfileURL: url + "/game.php?screen=info_ally&id=1",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=1"),
|
||||
NumMembers: 100,
|
||||
NumVillages: 101,
|
||||
Points: 102,
|
||||
|
@ -591,7 +590,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
ID: 2,
|
||||
Name: "name2",
|
||||
Tag: "tag2",
|
||||
ProfileURL: url + "/game.php?screen=info_ally&id=2",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=2"),
|
||||
NumMembers: 200,
|
||||
NumVillages: 202,
|
||||
Points: 202,
|
||||
|
@ -612,7 +611,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
ID: 3,
|
||||
Name: "name3",
|
||||
Tag: "tag3",
|
||||
ProfileURL: url + "/game.php?screen=info_ally&id=3",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=3"),
|
||||
NumMembers: 300,
|
||||
NumVillages: 303,
|
||||
Points: 302,
|
||||
|
@ -633,7 +632,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
ID: 4,
|
||||
Name: "name4",
|
||||
Tag: "tag4",
|
||||
ProfileURL: url + "/game.php?screen=info_ally&id=4",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_ally&id=4"),
|
||||
NumMembers: 400,
|
||||
NumVillages: 404,
|
||||
Points: 402,
|
||||
|
@ -795,7 +794,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
tribes, err := newClient(srv.Client()).GetTribes(context.Background(), srv.URL)
|
||||
tribes, err := newClient(srv.Client()).GetTribes(context.Background(), parseURL(t, srv.URL))
|
||||
|
||||
checkErr := func(err error) bool {
|
||||
return errors.Is(err, nil)
|
||||
|
@ -806,7 +805,7 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
|
||||
var expectedTribes []tw.Tribe
|
||||
if tt.expectedTribes != nil {
|
||||
expectedTribes = tt.expectedTribes(srv.URL)
|
||||
expectedTribes = tt.expectedTribes(t, srv.URL)
|
||||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
|
@ -829,7 +828,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
respODD string
|
||||
respODS string
|
||||
checkErr func(err error) bool
|
||||
expectedPlayers func(url string) []tw.Player
|
||||
expectedPlayers func(t *testing.T, url string) []tw.Player
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
|
@ -838,12 +837,13 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
respODA: "1,1,1000\n2,2,253\n3,3,100",
|
||||
respODD: "1,1,1002\n2,2,251\n3,3,155",
|
||||
respODS: "1,1,1003\n2,2,250\n3,3,166",
|
||||
expectedPlayers: func(url string) []tw.Player {
|
||||
expectedPlayers: func(t *testing.T, baseURL string) []tw.Player {
|
||||
t.Helper()
|
||||
return []tw.Player{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "name 1",
|
||||
ProfileURL: url + "/game.php?screen=info_player&id=1",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=1"),
|
||||
TribeID: 123,
|
||||
NumVillages: 124,
|
||||
Points: 125,
|
||||
|
@ -862,7 +862,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
{
|
||||
ID: 2,
|
||||
Name: "name2",
|
||||
ProfileURL: url + "/game.php?screen=info_player&id=2",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=2"),
|
||||
TribeID: 256,
|
||||
NumVillages: 257,
|
||||
Points: 258,
|
||||
|
@ -881,7 +881,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
{
|
||||
ID: 3,
|
||||
Name: "name3",
|
||||
ProfileURL: url + "/game.php?screen=info_player&id=3",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=3"),
|
||||
TribeID: 356,
|
||||
NumVillages: 357,
|
||||
Points: 358,
|
||||
|
@ -900,7 +900,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
{
|
||||
ID: 4,
|
||||
Name: "name4",
|
||||
ProfileURL: url + "/game.php?screen=info_player&id=4",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_player&id=4"),
|
||||
TribeID: 456,
|
||||
NumVillages: 457,
|
||||
Points: 458,
|
||||
|
@ -1078,7 +1078,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
players, err := newClient(srv.Client()).GetPlayers(context.Background(), srv.URL)
|
||||
players, err := newClient(srv.Client()).GetPlayers(context.Background(), parseURL(t, srv.URL))
|
||||
|
||||
checkErr := func(err error) bool {
|
||||
return errors.Is(err, nil)
|
||||
|
@ -1089,7 +1089,7 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
|
||||
var expectedPlayers []tw.Player
|
||||
if tt.expectedPlayers != nil {
|
||||
expectedPlayers = tt.expectedPlayers(srv.URL)
|
||||
expectedPlayers = tt.expectedPlayers(t, srv.URL)
|
||||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
|
@ -1108,18 +1108,19 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
name string
|
||||
resp string
|
||||
checkErr func(err error) bool
|
||||
expectedVillages func(url string) []tw.Village
|
||||
expectedVillages func(t *testing.T, baseURL string) []tw.Village
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
//nolint:lll
|
||||
resp: "123,village%201,500,501,502,503,504\n124,village 2,100,201,102,103,104\n125,village%203,1,501,502,503,504\n126,village%204,1,1,502,503,504\n127,village%205,103,1,502,503,504",
|
||||
expectedVillages: func(url string) []tw.Village {
|
||||
expectedVillages: func(t *testing.T, baseURL string) []tw.Village {
|
||||
t.Helper()
|
||||
return []tw.Village{
|
||||
{
|
||||
ID: 123,
|
||||
Name: "village 1",
|
||||
ProfileURL: url + "/game.php?screen=info_village&id=123",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=123"),
|
||||
X: 500,
|
||||
Y: 501,
|
||||
Continent: "K55",
|
||||
|
@ -1130,7 +1131,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
{
|
||||
ID: 124,
|
||||
Name: "village 2",
|
||||
ProfileURL: url + "/game.php?screen=info_village&id=124",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=124"),
|
||||
X: 100,
|
||||
Y: 201,
|
||||
Continent: "K21",
|
||||
|
@ -1141,7 +1142,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
{
|
||||
ID: 125,
|
||||
Name: "village 3",
|
||||
ProfileURL: url + "/game.php?screen=info_village&id=125",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=125"),
|
||||
X: 1,
|
||||
Y: 501,
|
||||
Continent: "K50",
|
||||
|
@ -1152,7 +1153,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
{
|
||||
ID: 126,
|
||||
Name: "village 4",
|
||||
ProfileURL: url + "/game.php?screen=info_village&id=126",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=126"),
|
||||
X: 1,
|
||||
Y: 1,
|
||||
Continent: "K0",
|
||||
|
@ -1163,7 +1164,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
{
|
||||
ID: 127,
|
||||
Name: "village 5",
|
||||
ProfileURL: url + "/game.php?screen=info_village&id=127",
|
||||
ProfileURL: parseURL(t, baseURL+"/game.php?screen=info_village&id=127"),
|
||||
X: 103,
|
||||
Y: 1,
|
||||
Continent: "K1",
|
||||
|
@ -1250,7 +1251,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
villages, err := newClient(srv.Client()).GetVillages(context.Background(), srv.URL)
|
||||
villages, err := newClient(srv.Client()).GetVillages(context.Background(), parseURL(t, srv.URL))
|
||||
|
||||
checkErr := func(err error) bool {
|
||||
return errors.Is(err, nil)
|
||||
|
@ -1261,7 +1262,7 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
|
||||
var expectedVillages []tw.Village
|
||||
if tt.expectedVillages != nil {
|
||||
expectedVillages = tt.expectedVillages(srv.URL)
|
||||
expectedVillages = tt.expectedVillages(t, srv.URL)
|
||||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
|
@ -1420,7 +1421,7 @@ func TestClient_GetEnnoblements(t *testing.T) {
|
|||
srv := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
ennoblements, err := newClient(srv.Client()).GetEnnoblements(context.Background(), srv.URL, tt.since)
|
||||
ennoblements, err := newClient(srv.Client()).GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
|
||||
|
||||
checkErr := func(err error) bool {
|
||||
return errors.Is(err, nil)
|
||||
|
@ -1644,7 +1645,7 @@ func TestClient_GetEnnoblements(t *testing.T) {
|
|||
|
||||
client := newClient(srv.Client(), tt.clientOpts...)
|
||||
|
||||
ennoblements, err := client.GetEnnoblements(context.Background(), srv.URL, tt.since)
|
||||
ennoblements, err := client.GetEnnoblements(context.Background(), parseURL(t, srv.URL), tt.since)
|
||||
|
||||
checkErr := func(err error) bool {
|
||||
return errors.Is(err, nil)
|
||||
|
@ -1663,6 +1664,13 @@ func TestClient_GetEnnoblements(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func parseURL(tb testing.TB, rawURL string) *url.URL {
|
||||
tb.Helper()
|
||||
u, err := url.ParseRequestURI(rawURL)
|
||||
require.NoError(tb, err)
|
||||
return u
|
||||
}
|
||||
|
||||
// user agent and http client can't be overridden via opts
|
||||
func newClient(hc *http.Client, opts ...tw.ClientOption) *tw.Client {
|
||||
return tw.NewClient(append(opts, tw.WithHTTPClient(hc), tw.WithUserAgent(testUserAgent))...)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Key string
|
||||
URL string
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
type OpponentsDefeated struct {
|
||||
|
@ -31,7 +32,7 @@ type Tribe struct {
|
|||
Points int
|
||||
AllPoints int
|
||||
Rank int
|
||||
ProfileURL string
|
||||
ProfileURL *url.URL
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
|
@ -43,7 +44,7 @@ type Player struct {
|
|||
Points int
|
||||
Rank int
|
||||
TribeID int
|
||||
ProfileURL string
|
||||
ProfileURL *url.URL
|
||||
}
|
||||
|
||||
type Village struct {
|
||||
|
@ -55,7 +56,7 @@ type Village struct {
|
|||
Continent string
|
||||
Bonus int
|
||||
PlayerID int
|
||||
ProfileURL string
|
||||
ProfileURL *url.URL
|
||||
}
|
||||
|
||||
type Ennoblement struct {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package watermillmsg
|
||||
|
||||
import "net/url"
|
||||
|
||||
type SyncServersCmdPayload struct {
|
||||
VersionCode string `json:"versionCode"`
|
||||
URL string `json:"url"`
|
||||
URL *url.URL `json:"url"`
|
||||
}
|
||||
|
||||
type ServerSyncedEventPayload struct {
|
||||
Key string `json:"key"`
|
||||
URL string `json:"url"`
|
||||
URL *url.URL `json:"url"`
|
||||
VersionCode string `json:"versionCode"`
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ kind: Kustomization
|
|||
resources:
|
||||
- jobs.yml
|
||||
- server-consumer.yml
|
||||
- tribe-consumer.yml
|
||||
images:
|
||||
- name: twhelp
|
||||
newName: twhelp
|
||||
|
|
44
k8s/base/tribe-consumer.yml
Normal file
44
k8s/base/tribe-consumer.yml
Normal file
|
@ -0,0 +1,44 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: twhelp-tribe-consumer-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: twhelp-tribe-consumer
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: twhelp-tribe-consumer
|
||||
spec:
|
||||
containers:
|
||||
- name: twhelp-tribe-consumer
|
||||
image: twhelp
|
||||
args: [consumer, tribe]
|
||||
env:
|
||||
- name: APP_MODE
|
||||
value: development
|
||||
- name: LOG_LEVEL
|
||||
value: debug
|
||||
- name: DB_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: twhelp-secret
|
||||
key: db-connection-string
|
||||
- name: RABBITMQ_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: twhelp-secret
|
||||
key: rabbitmq-connection-string
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: [cat, /tmp/live]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 300Mi
|
Loading…
Reference in New Issue
Block a user