refactor: executors and routers
* refactor: executors and routers
This commit is contained in:
@@ -134,11 +134,11 @@ func TestProduceBattles(t *testing.T) {
|
||||
race_C_idx, _ := c.AddRace(race_C_name)
|
||||
race_D_idx, _ := c.AddRace(race_D_name)
|
||||
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
|
||||
|
||||
assert.NoError(t, g.UpdateRelation(race_C_name, race_D_name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(race_D_name, race_C_name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(race_C_name, race_D_name, game.RelationWar.String()))
|
||||
assert.NoError(t, g.UpdateRelation(race_D_name, race_C_name, game.RelationWar.String()))
|
||||
|
||||
assert.Equal(t, game.RelationPeace, c.Relation(Race_0_idx, race_C_idx))
|
||||
assert.Equal(t, game.RelationPeace, c.Relation(Race_1_idx, race_C_idx))
|
||||
|
||||
@@ -37,8 +37,8 @@ func TestBombPlanet(t *testing.T) {
|
||||
func TestCollectBombingGroups(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
|
||||
|
||||
// 1: idx = 0 / Ready to bomb: Race_1/Planet_1
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 2)) // bombs
|
||||
@@ -82,7 +82,7 @@ func TestCollectBombingGroups(t *testing.T) {
|
||||
assert.Equal(t, 1, bg[R0_Planet_2_num][Race_1_idx][0])
|
||||
|
||||
// remove bombings from Race_1
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace.String()))
|
||||
bg = c.CollectBombingGroups()
|
||||
assert.Len(t, bg, 1)
|
||||
assert.Contains(t, bg, R1_Planet_1_num)
|
||||
@@ -95,8 +95,8 @@ func TestCollectBombingGroups(t *testing.T) {
|
||||
func TestProduceBombings(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar.String()))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationWar.String()))
|
||||
|
||||
// 1: idx = 0 / Bombs on: Race_1/Planet_1
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3))
|
||||
|
||||
@@ -38,7 +38,11 @@ func (c Controller) GiveVotes(actor, acceptor string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Controller) UpdateRelation(actor, acceptor string, rel game.Relation) error {
|
||||
func (c Controller) UpdateRelation(actor, acceptor string, v string) error {
|
||||
rel, ok := game.ParseRelation(v)
|
||||
if !ok {
|
||||
return e.NewUnknownRelationError(v)
|
||||
}
|
||||
ri, err := c.Cache.validActor(actor)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"github.com/iliadenisov/galaxy/internal/repo"
|
||||
)
|
||||
|
||||
type Configurer func(*Param)
|
||||
|
||||
type Repo interface {
|
||||
// Lock must be called before any repository operations
|
||||
Lock() error
|
||||
@@ -42,18 +43,109 @@ type Repo interface {
|
||||
LoadReport(uint, uuid.UUID) (*report.Report, error)
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
Repo Repo
|
||||
Cache *Cache
|
||||
type Ctrl interface {
|
||||
RaceID(actor string) (uuid.UUID, error)
|
||||
QuitGame(actor string) error
|
||||
GiveVotes(actor, acceptor string) error
|
||||
UpdateRelation(actor, acceptor string, rel string) error
|
||||
JoinShipGroupToFleet(actor, fleetName string, group, count uint) error
|
||||
JoinFleets(actor, fleetSourceName, fleetTargetName string) error
|
||||
SendFleet(actor, fleetName string, planetNumber uint) error
|
||||
RenamePlanet(actor string, planetNumber int, typeName string) error
|
||||
PlanetProduction(actor string, planetNumber int, prodType, subject string) error
|
||||
SetRoute(actor, loadType string, origin, destination uint) error
|
||||
RemoveRoute(actor, loadType string, origin uint) error
|
||||
CreateScience(actor, typeName string, drive, weapons, shields, cargo float64) error
|
||||
DeleteScience(actor, typeName string) error
|
||||
CreateShipType(actor, typeName string, drive float64, ammo int, weapons, shileds, cargo float64) error
|
||||
MergeShipType(actor, name, targetName string) error
|
||||
DeleteShipType(actor, typeName string) error
|
||||
SendGroup(actor string, groupIndex, planetNumber, quantity uint) error
|
||||
UpgradeGroup(actor string, groupIndex uint, techInput string, limitShips uint, limitLevel float64) error
|
||||
JoinEqualGroups(actor string) error
|
||||
BreakGroup(actor string, groupIndex, quantity uint) error
|
||||
DisassembleGroup(actor string, groupIndex, quantity uint) error
|
||||
LoadCargo(actor string, groupIndex uint, cargoType string, ships uint, quantity float64) error
|
||||
UnloadCargo(actor string, groupIndex uint, ships uint, quantity float64) error
|
||||
TransferGroup(actor, acceptor string, groupIndex, quantity uint) error
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
StoragePath string
|
||||
func GenerateGame(configure func(*Param), races []string) (ID uuid.UUID, err error) {
|
||||
ec, err := NewRepoController(configure)
|
||||
if err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
if err = ec.Repo.Lock(); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err = errors.Join(err, ec.Repo.Release())
|
||||
}()
|
||||
|
||||
ID, err = NewGame(ec.Repo, races)
|
||||
return
|
||||
}
|
||||
|
||||
type Configurer func(*Param)
|
||||
func ExecuteCommand(configure func(*Param), consumer func(c Ctrl) error) (err error) {
|
||||
ec, err := NewRepoController(configure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ec.ExecuteCommand(func(c *Controller) error { return consumer(c) })
|
||||
}
|
||||
|
||||
func NewController(config Configurer) (*Controller, error) {
|
||||
func GameState(configure func(*Param)) (s game.State, err error) {
|
||||
ec, err := NewRepoController(configure)
|
||||
|
||||
g, err := ec.Repo.LoadStateSafe()
|
||||
if err != nil {
|
||||
return game.State{}, err
|
||||
}
|
||||
|
||||
result := &game.State{
|
||||
ID: g.ID,
|
||||
Turn: g.Turn,
|
||||
Stage: g.Stage,
|
||||
Players: make([]game.PlayerState, len(g.Race)),
|
||||
}
|
||||
|
||||
for i := range g.Race {
|
||||
r := &g.Race[i]
|
||||
result.Players[i].ID = r.ID
|
||||
result.Players[i].Name = r.Name
|
||||
result.Players[i].Extinct = r.Extinct
|
||||
}
|
||||
|
||||
return *result, nil
|
||||
}
|
||||
|
||||
type RepoController struct {
|
||||
Repo Repo
|
||||
}
|
||||
|
||||
func (ec *RepoController) ExecuteCommand(consumer func(c *Controller) error) (err error) {
|
||||
if err := ec.Repo.Lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = errors.Join(err, ec.Repo.Release())
|
||||
}()
|
||||
|
||||
g, err := ec.Repo.LoadState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = consumer(NewGameController(g))
|
||||
|
||||
if err == nil {
|
||||
g.Stage += 1
|
||||
ec.Repo.SaveLastState(g)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewRepoController(config Configurer) (*RepoController, error) {
|
||||
c := &Param{
|
||||
StoragePath: ".",
|
||||
}
|
||||
@@ -64,44 +156,22 @@ func NewController(config Configurer) (*Controller, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Controller{
|
||||
return &RepoController{
|
||||
Repo: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRepoController(r Repo) *Controller {
|
||||
func NewGameController(g *game.Game) *Controller {
|
||||
return &Controller{
|
||||
Repo: r,
|
||||
Cache: NewCache(g),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) ExecuteState(consumer func(Repo) error) (err error) {
|
||||
if err := c.Repo.Lock(); err != nil {
|
||||
return fmt.Errorf("execute: lock failed: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
err = errors.Join(err, c.Repo.Release())
|
||||
}()
|
||||
err = consumer(c.Repo)
|
||||
return
|
||||
type Controller struct {
|
||||
Repo Repo
|
||||
Cache *Cache
|
||||
}
|
||||
|
||||
func (c *Controller) ExecuteCommand(consumer func(Repo, *game.Game) error) (err error) {
|
||||
if err := c.Repo.Lock(); err != nil {
|
||||
return fmt.Errorf("execute: lock failed: %s", err)
|
||||
}
|
||||
g, err := c.Repo.LoadState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = errors.Join(err, c.Repo.Release())
|
||||
}()
|
||||
c.Cache = NewCache(g)
|
||||
err = consumer(c.Repo, g)
|
||||
if err == nil {
|
||||
g.Stage += 1
|
||||
c.Repo.SaveLastState(g)
|
||||
}
|
||||
return
|
||||
type Param struct {
|
||||
StoragePath string
|
||||
}
|
||||
|
||||
@@ -129,8 +129,10 @@ func newGame() *game.Game {
|
||||
}
|
||||
|
||||
func newCache() (*controller.Cache, *controller.Controller) {
|
||||
g := newGame()
|
||||
c := controller.NewCache(g)
|
||||
ctl := controller.NewGameController(newGame())
|
||||
// g := newGame()
|
||||
// c := controller.NewCache(g)
|
||||
c := ctl.Cache
|
||||
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Gunship, 60, 3, 30, 100, 0))
|
||||
assertNoError(c.CreateShipType(Race_0_idx, Race_0_Freighter, 8, 0, 0, 2, 10))
|
||||
assertNoError(c.CreateShipType(Race_0_idx, ShipType_Cruiser, Cruiser.Drive.F(), int(Cruiser.Armament), Cruiser.Weapons.F(), Cruiser.Shields.F(), Cruiser.Cargo.F()))
|
||||
@@ -139,8 +141,8 @@ func newCache() (*controller.Cache, *controller.Controller) {
|
||||
assertNoError(c.CreateShipType(Race_1_idx, Race_1_Freighter, 8, 0, 0, 2, 10))
|
||||
assertNoError(c.CreateShipType(Race_1_idx, ShipType_Cruiser, 15, 2, 15, 15, 0)) // same name - different type (why.)
|
||||
|
||||
ctl := controller.NewRepoController(nil)
|
||||
ctl.Cache = c
|
||||
// ctl := controller.NewRepoController(nil)
|
||||
// ctl.Cache = c
|
||||
|
||||
return c, ctl
|
||||
}
|
||||
|
||||
@@ -35,23 +35,26 @@ func TestGiveVotes(t *testing.T) {
|
||||
func TestRelation(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, game.RelationWar))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, game.RelationPeace))
|
||||
assert.NoError(t, g.UpdateRelation(Race_0.Name, Race_1.Name, "war"))
|
||||
assert.NoError(t, g.UpdateRelation(Race_1.Name, Race_0.Name, "PEACE"))
|
||||
|
||||
assert.Equal(t, game.RelationWar, c.Relation(Race_0_idx, Race_1_idx))
|
||||
assert.Equal(t, game.RelationPeace, c.Relation(Race_1_idx, Race_0_idx))
|
||||
|
||||
assert.ErrorContains(t,
|
||||
g.UpdateRelation(Race_0.Name, UnknownRace, game.RelationWar),
|
||||
g.UpdateRelation(Race_0.Name, Race_1.Name, "Wojna"),
|
||||
e.GenericErrorText(e.ErrInputUnknownRelation))
|
||||
assert.ErrorContains(t,
|
||||
g.UpdateRelation(Race_0.Name, UnknownRace, "War"),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.UpdateRelation(UnknownRace, Race_0.Name, game.RelationWar),
|
||||
g.UpdateRelation(UnknownRace, Race_0.Name, "Peace"),
|
||||
e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
assert.ErrorContains(t,
|
||||
g.UpdateRelation(Race_0.Name, Race_Extinct.Name, game.RelationWar),
|
||||
g.UpdateRelation(Race_0.Name, Race_Extinct.Name, "War"),
|
||||
e.GenericErrorText(e.ErrRaceExinct))
|
||||
assert.ErrorContains(t,
|
||||
g.UpdateRelation(Race_Extinct.Name, Race_0.Name, game.RelationWar),
|
||||
g.UpdateRelation(Race_Extinct.Name, Race_0.Name, "War"),
|
||||
e.GenericErrorText(e.ErrRaceExinct))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
@@ -35,6 +36,58 @@ func TestCreateShipClass(t *testing.T) {
|
||||
e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
|
||||
}
|
||||
|
||||
func TestCreateShipTypeValidation(t *testing.T) {
|
||||
race := Race_0.Name
|
||||
typeName := "Drone"
|
||||
type tc struct {
|
||||
name string
|
||||
d, w, s, c float64
|
||||
a int
|
||||
err string
|
||||
}
|
||||
table := []tc{
|
||||
// correct values
|
||||
{typeName, 1, 0, 0, 0, 0, ""},
|
||||
{typeName, 1.1, 0, 0, 0, 0, ""},
|
||||
{typeName, 1, 1.2, 0, 0, 1, ""},
|
||||
{typeName, 1, 1.2, 2.5, 0, 1, ""},
|
||||
{typeName, 1, 0, 2.5, 7.7, 0, ""},
|
||||
// incorrect values...
|
||||
{"", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{" ", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{typeName, 0, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeZeroValues)},
|
||||
// drive
|
||||
{typeName, -1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
{typeName, 0.5, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
// weapons
|
||||
{typeName, 0, -1, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
{typeName, 0, 0.5, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
// shields
|
||||
{typeName, 0, 0, -1, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
{typeName, 0, 0, 0.5, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
// cargo
|
||||
{typeName, 0, 0, 0, -1, 0, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
{typeName, 0, 0, 0, 0.5, 0, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
// armament (and weapons)
|
||||
{typeName, 0, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputShipTypeArmamentValue)},
|
||||
{typeName, 0, 1, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
|
||||
{typeName, 0, 0, 0, 0, 1, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
|
||||
}
|
||||
for i, tc := range table {
|
||||
_, g := newCache()
|
||||
|
||||
if tc.err == "" {
|
||||
err := g.CreateShipType(race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.NoError(t, err)
|
||||
err = g.CreateShipType(race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate))
|
||||
} else {
|
||||
err := g.CreateShipType(race, tc.name, tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeShipClass(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
|
||||
const (
|
||||
ErrInputUnknownRace int = 3000 + iota
|
||||
ErrInputUnknownRelation
|
||||
ErrInputSameRace
|
||||
ErrInputEntityTypeNameInvalid
|
||||
ErrInputEntityTypeNameDuplicate
|
||||
@@ -78,6 +79,8 @@ func GenericErrorText(code int) string {
|
||||
return "Invalid game state"
|
||||
case ErrInputUnknownRace:
|
||||
return "Race name is unknown to this game"
|
||||
case ErrInputUnknownRelation:
|
||||
return "Unknown relation"
|
||||
case ErrInputSameRace:
|
||||
return "Race name must be different from your own"
|
||||
case ErrInputEntityTypeNameInvalid:
|
||||
|
||||
@@ -3,6 +3,11 @@ package error
|
||||
func NewRaceUnknownError(arg ...any) error {
|
||||
return newGenericError(ErrInputUnknownRace, arg...)
|
||||
}
|
||||
|
||||
func NewUnknownRelationError(arg ...any) error {
|
||||
return newGenericError(ErrInputUnknownRelation, arg...)
|
||||
}
|
||||
|
||||
func NewSameRaceError(arg ...any) error {
|
||||
return newGenericError(ErrInputSameRace, arg...)
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func JoinEqualGroups(configure func(*controller.Param), race string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return joinEqualGroups(c, race)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func joinEqualGroups(c *controller.Controller, race string) error {
|
||||
return c.JoinEqualGroups(race)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJoinEqualGroups(t *testing.T) {
|
||||
c(t, func(p func(*controller.Param), g func() *controller.Controller) {
|
||||
err := game.JoinEqualGroups(p, "race_01")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return renamePlanet(c, race, number, name)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func renamePlanet(c *controller.Controller, race string, number int, name string) error {
|
||||
return c.RenamePlanet(race, number, name)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
mg "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenamePlanet(t *testing.T) {
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
cg := g()
|
||||
var number int
|
||||
var owner uuid.UUID
|
||||
for pl := range cg.Map.Planet {
|
||||
if cg.Map.Planet[pl].Owned() {
|
||||
number = int(cg.Map.Planet[pl].Number)
|
||||
owner = *cg.Map.Planet[pl].Owner
|
||||
break
|
||||
}
|
||||
}
|
||||
var race string
|
||||
for r := range cg.Race {
|
||||
if cg.Race[r].ID == owner {
|
||||
race = cg.Race[r].Name
|
||||
break
|
||||
}
|
||||
}
|
||||
newName := "Some-New-Name"
|
||||
err := game.RenamePlanet(p, race, number, newName)
|
||||
assert.NoError(t, err)
|
||||
cg = g()
|
||||
pi := slices.IndexFunc(cg.Map.Planet, func(pl mg.Planet) bool { return pl.OwnedBy(owner) && pl.Number == uint(number) })
|
||||
assert.GreaterOrEqual(t, pi, 0)
|
||||
assert.Equal(t, "Some-New-Name", cg.Map.Planet[pi].Name)
|
||||
|
||||
ri := slices.IndexFunc(cg.Race, func(r mg.Race) bool { return r.Name != race })
|
||||
assert.GreaterOrEqual(t, ri, 0)
|
||||
otherRace := cg.Race[ri].Name
|
||||
|
||||
err = game.RenamePlanet(p, unknownRaceName, number, newName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = game.RenamePlanet(p, race, number, "")
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid))
|
||||
err = game.RenamePlanet(p, race, -1, newName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputPlanetNumber))
|
||||
err = game.RenamePlanet(p, race, 100500, newName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.RenamePlanet(p, otherRace, number, newName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotOwned))
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return planetProduction(c, race, planetNumber, prodType, subject)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func planetProduction(c *controller.Controller, race string, planetNumber int, prodType, subject string) error {
|
||||
return c.PlanetProduction(race, planetNumber, prodType, subject)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return createScience(c, race, typeName, drive, weapons, shields, cargo)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createScience(c *controller.Controller, race, typeName string, drive, weapons, shields, cargo float64) error {
|
||||
return c.CreateScience(race, typeName, drive, weapons, shields, cargo)
|
||||
}
|
||||
|
||||
func DeleteScience(configure func(*controller.Param), race, typeName string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return deleteScience(c, race, typeName)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func deleteScience(c *controller.Controller, race, typeName string) error {
|
||||
return c.DeleteScience(race, typeName)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateScience(t *testing.T) {
|
||||
typeName := "First Step"
|
||||
c(t, func(p func(*controller.Param), g func() *controller.Controller) {
|
||||
err := g().CreateScience(unknownRaceName, " "+typeName+" ", 1, 0, 0, 0)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = g().DeleteScience(unknownRaceName, typeName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateScienceValidation(t *testing.T) {
|
||||
race := "race_01"
|
||||
typeName := "First_Step"
|
||||
type tc struct {
|
||||
name string
|
||||
d, w, s, c float64
|
||||
err string
|
||||
}
|
||||
table := []tc{
|
||||
// correct values
|
||||
{typeName, 1, 0, 0, 0, ""},
|
||||
{typeName, 0.5, 0.5, 0, 0, ""},
|
||||
{typeName, 0.25, 0.25, 0.25, 0.25, ""},
|
||||
{typeName, 0.33, 0.33, 0.34, 0, ""},
|
||||
{typeName, 0, 0, 0.99, 0.01, ""},
|
||||
// incorrect values...
|
||||
{"", 1, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{" ", 1, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{typeName, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputScienceSumValues)},
|
||||
// drive
|
||||
{typeName, -1, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
{typeName, -1, 2, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
// weapons
|
||||
{typeName, 0, -1, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
{typeName, 2, -1, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
// shields
|
||||
{typeName, 0, 0, -1, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
{typeName, 0.5, 0.5, -1, 0.5, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
// cargo
|
||||
{typeName, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
{typeName, 0, 1, 1, -1, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
}
|
||||
c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
for i, tc := range table {
|
||||
if tc.err == "" {
|
||||
n := tc.name + strconv.Itoa(i)
|
||||
err := game.CreateScience(p, race, n, tc.d, tc.w, tc.s, tc.c)
|
||||
assert.NoError(t, err, "for name=%q", n)
|
||||
err = game.CreateScience(p, race, n, tc.d, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate), "for name=%q", n)
|
||||
} else {
|
||||
err := game.CreateScience(p, race, tc.name, tc.d, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func CreateShipType(configure func(*controller.Param), race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return createShipType(c, race, typeName, drive, ammo, weapons, shields, cargo)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createShipType(c *controller.Controller, race, typeName string, drive float64, ammo int, weapons, shields, cargo float64) error {
|
||||
if err := c.CreateShipType(race, typeName, drive, ammo, weapons, shields, cargo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil // r.SaveLastState(g)
|
||||
}
|
||||
|
||||
func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return mergeShipType(c, race, source, target)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func mergeShipType(c *controller.Controller, race, source, target string) error {
|
||||
if err := c.MergeShipType(race, source, target); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil // r.SaveLastState(g)
|
||||
}
|
||||
|
||||
func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return deleteShipType(c, race, typeName)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func deleteShipType(c *controller.Controller, race, typeName string) error {
|
||||
if err := c.DeleteShipType(race, typeName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil // r.SaveLastState(g)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
mg "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateShipType(t *testing.T) {
|
||||
race := "race_01"
|
||||
typeName := "Drone"
|
||||
c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
err := game.DeleteShipType(p, race, typeName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.CreateShipType(p, unknownRaceName, " "+typeName+" ", 1, 0, 0, 0, 0)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = game.CreateShipType(p, race, " "+typeName+" ", 1, 0, 0, 0, 0)
|
||||
assert.NoError(t, err)
|
||||
st := ctrl().Cache.ShipTypes(1)
|
||||
assert.Len(t, st, 1)
|
||||
assert.Equal(t, st[0].Name, typeName)
|
||||
assert.Equal(t, st[0].Drive.F(), 1.)
|
||||
assert.Equal(t, st[0].Weapons.F(), 0.)
|
||||
assert.Equal(t, st[0].Shields.F(), 0.)
|
||||
assert.Equal(t, st[0].Cargo.F(), 0.)
|
||||
assert.Equal(t, st[0].Armament, uint(0))
|
||||
err = game.DeleteShipType(p, unknownRaceName, typeName)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputUnknownRace))
|
||||
err = game.DeleteShipType(p, race, typeName)
|
||||
assert.NoError(t, err)
|
||||
st = ctrl().Cache.ShipTypes(1)
|
||||
assert.Len(t, st, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateShipTypeValidation(t *testing.T) {
|
||||
race := "race_01"
|
||||
typeName := "Drone"
|
||||
type tc struct {
|
||||
name string
|
||||
d, w, s, c float64
|
||||
a int
|
||||
err string
|
||||
}
|
||||
table := []tc{
|
||||
// correct values
|
||||
{typeName, 1, 0, 0, 0, 0, ""},
|
||||
{typeName, 1.1, 0, 0, 0, 0, ""},
|
||||
{typeName, 1, 1.2, 0, 0, 1, ""},
|
||||
{typeName, 1, 1.2, 2.5, 0, 1, ""},
|
||||
{typeName, 1, 0, 2.5, 7.7, 0, ""},
|
||||
// incorrect values...
|
||||
{"", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{" ", 1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputEntityTypeNameInvalid)},
|
||||
{typeName, 0, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeZeroValues)},
|
||||
// drive
|
||||
{typeName, -1, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
{typeName, 0.5, 0, 0, 0, 0, e.GenericErrorText(e.ErrInputDriveValue)},
|
||||
// weapons
|
||||
{typeName, 0, -1, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
{typeName, 0, 0.5, 0, 0, 0, e.GenericErrorText(e.ErrInputWeaponsValue)},
|
||||
// shields
|
||||
{typeName, 0, 0, -1, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
{typeName, 0, 0, 0.5, 0, 0, e.GenericErrorText(e.ErrInputShieldsValue)},
|
||||
// cargo
|
||||
{typeName, 0, 0, 0, -1, 0, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
{typeName, 0, 0, 0, 0.5, 0, e.GenericErrorText(e.ErrInputCargoValue)},
|
||||
// armament (and weapons)
|
||||
{typeName, 0, 0, 0, 0, -1, e.GenericErrorText(e.ErrInputShipTypeArmamentValue)},
|
||||
{typeName, 0, 1, 0, 0, 0, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
|
||||
{typeName, 0, 0, 0, 0, 1, e.GenericErrorText(e.ErrInputShipTypeWeaponsAndArmamentValue)},
|
||||
}
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
for i, tc := range table {
|
||||
if tc.err == "" {
|
||||
err := game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.NoError(t, err)
|
||||
err = game.CreateShipType(p, race, tc.name+strconv.Itoa(i), tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityTypeNameDuplicate))
|
||||
} else {
|
||||
err := game.CreateShipType(p, race, tc.name, tc.d, tc.a, tc.w, tc.s, tc.c)
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMergeShipType(t *testing.T) {
|
||||
race := "race_01"
|
||||
c(t, func(p func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
err := game.CreateShipType(p, race, "Drone", 1, 0, 0, 0, 0)
|
||||
assert.NoError(t, err)
|
||||
err = game.CreateShipType(p, race, "Spy", 1, 0, 0, 0, 0)
|
||||
assert.NoError(t, err)
|
||||
err = game.CreateShipType(p, race, "Cruiser", 15, 15, 15, 0, 1)
|
||||
assert.NoError(t, err)
|
||||
err = game.MergeShipType(p, race, "Sky", "Drone")
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.MergeShipType(p, race, "Spy", "Freighter")
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrInputEntityNotExists))
|
||||
err = game.MergeShipType(p, race, "Spy", "Drone")
|
||||
assert.NoError(t, err)
|
||||
st := ctrl().Cache.ShipTypes(1)
|
||||
assert.Len(t, st, 2)
|
||||
err = game.MergeShipType(p, race, "Drone", "Cruiser")
|
||||
assert.ErrorContains(t, err, e.GenericErrorText(e.ErrMergeShipTypeNotEqual))
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
)
|
||||
|
||||
func GenerateTurn(configure func(*controller.Param)) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteState(func(r controller.Repo) error {
|
||||
g, err := r.LoadState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Cache = controller.NewCache(g)
|
||||
return controller.MakeTurn(c, r)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
func DeclareWar(configure func(*controller.Param), from, to string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return updateRelation(c, from, to, game.RelationWar)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func DeclarePeace(configure func(*controller.Param), from, to string) (err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteCommand(func(r controller.Repo, g *game.Game) error {
|
||||
return updateRelation(c, from, to, game.RelationPeace)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func updateRelation(c *controller.Controller, hostRace, opponentRace string, rel game.Relation) error {
|
||||
if err := c.UpdateRelation(hostRace, opponentRace, rel); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil // r.SaveLastState(g)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
mg "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeclarePeaceAndWarSingle(t *testing.T) {
|
||||
c(t, func(f func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
hostRace := 5
|
||||
opponentRace := 1
|
||||
|
||||
assert.Equal(t, mg.RelationWar, ctrl().Cache.Relation(5, 1))
|
||||
|
||||
assert.NoError(t, game.DeclarePeace(f, raceNum(hostRace), raceNum(opponentRace)))
|
||||
assert.Equal(t, mg.RelationPeace, ctrl().Cache.Relation(hostRace, opponentRace))
|
||||
|
||||
assert.NoError(t, game.DeclareWar(f, raceNum(hostRace), raceNum(opponentRace)))
|
||||
assert.Equal(t, mg.RelationWar, ctrl().Cache.Relation(hostRace, opponentRace))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeclarePeaceAndWarAll(t *testing.T) {
|
||||
c(t, func(f func(*controller.Param), ctrl func() *controller.Controller) {
|
||||
hostRace := 7
|
||||
|
||||
for i := range testRaceCount {
|
||||
if i == hostRace {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, mg.RelationWar, ctrl().Cache.Relation(hostRace, i))
|
||||
}
|
||||
|
||||
assert.NoError(t, game.DeclarePeace(f, raceNum(hostRace), raceNum(hostRace)))
|
||||
assert.Equal(t, 1, int(ctrl().Cache.Stage()))
|
||||
|
||||
for i := range testRaceCount {
|
||||
if i == hostRace {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, mg.RelationPeace, ctrl().Cache.Relation(hostRace, i))
|
||||
}
|
||||
|
||||
assert.NoError(t, game.DeclareWar(f, raceNum(hostRace), raceNum(hostRace)))
|
||||
assert.Equal(t, 2, int(ctrl().Cache.Stage()))
|
||||
|
||||
for i := range testRaceCount {
|
||||
if i == hostRace {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, mg.RelationWar, ctrl().Cache.Relation(hostRace, i))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||
)
|
||||
|
||||
func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
return c.ExecuteState(func(r controller.Repo) error {
|
||||
gameID, err = controller.NewGame(r, races)
|
||||
return err
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// LoadState used for lock-safe loading game state and may be called concurrently.
|
||||
func LoadState(configure func(*controller.Param)) (g *game.Game, err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
g, err = c.Repo.LoadStateSafe()
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func LoadReport(configure func(*controller.Param), t uint, actor string) (g *report.Report, err error) {
|
||||
err = control(configure, func(c *controller.Controller) error {
|
||||
game, err := c.Repo.LoadStateSafe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Cache = controller.NewCache(game)
|
||||
id, err := c.RaceID(actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g, err = c.Repo.LoadReport(t, id)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func control(configure func(*controller.Param), consumer func(*controller.Controller) error) (err error) {
|
||||
c, err := controller.NewController(configure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return consumer(c)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
mg "github.com/iliadenisov/galaxy/internal/model/game"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testRaceCount = 20
|
||||
unknownRaceName = "Race_RIP"
|
||||
)
|
||||
|
||||
func raceNum(i int) string {
|
||||
return fmt.Sprintf("race_%02d", i)
|
||||
}
|
||||
|
||||
func TestComposeGame(t *testing.T) {
|
||||
g(t, func(p func(*controller.Param), g func() *mg.Game) {
|
||||
_, err := game.GenerateGame(p, []string{"r1", "r2"})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "turn 0 already saved at 0000/state.json")
|
||||
})
|
||||
}
|
||||
|
||||
func g(t *testing.T, f func(func(*controller.Param), func() *mg.Game)) {
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
races := make([]string, testRaceCount)
|
||||
for i := range testRaceCount {
|
||||
races[i] = raceNum(i)
|
||||
}
|
||||
p := func(p *controller.Param) { p.StoragePath = root }
|
||||
_, err := game.GenerateGame(p, races)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "g: ComposeGame", err)
|
||||
return
|
||||
}
|
||||
g := func() *mg.Game {
|
||||
g, err := game.LoadState(p)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "g: LoadState", err)
|
||||
return nil
|
||||
}
|
||||
return g
|
||||
}
|
||||
f(p, g)
|
||||
}
|
||||
|
||||
func c(t *testing.T, f func(func(*controller.Param), func() *controller.Controller)) {
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
races := make([]string, testRaceCount)
|
||||
for i := range testRaceCount {
|
||||
races[i] = raceNum(i)
|
||||
}
|
||||
p := func(p *controller.Param) { p.StoragePath = root }
|
||||
_, err := game.GenerateGame(p, races)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: GenerateGame", err)
|
||||
return
|
||||
}
|
||||
ctrl := func() *controller.Controller {
|
||||
c, err := controller.NewController(p)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: NewController", err)
|
||||
return nil
|
||||
}
|
||||
g, err := game.LoadState(p)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "c: LoadState", err)
|
||||
return nil
|
||||
}
|
||||
c.Cache = controller.NewCache(g)
|
||||
return c
|
||||
}
|
||||
f(p, ctrl)
|
||||
}
|
||||
@@ -1,6 +1,24 @@
|
||||
package game
|
||||
|
||||
import "github.com/google/uuid"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Relation string
|
||||
|
||||
const (
|
||||
RelationWar Relation = "War"
|
||||
RelationPeace Relation = "Peace"
|
||||
)
|
||||
|
||||
var (
|
||||
relationSet = map[string]Relation{
|
||||
strings.ToLower(RelationWar.String()): RelationWar,
|
||||
strings.ToLower(RelationPeace.String()): RelationPeace,
|
||||
}
|
||||
)
|
||||
|
||||
type Race struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
@@ -15,17 +33,17 @@ type Race struct {
|
||||
ShipTypes []ShipType `json:"shipType,omitempty"`
|
||||
}
|
||||
|
||||
type Relation string
|
||||
func ParseRelation(v string) (Relation, bool) {
|
||||
if v, ok := relationSet[strings.ToLower(v)]; ok {
|
||||
return v, ok
|
||||
}
|
||||
return Relation(""), false
|
||||
}
|
||||
|
||||
func (r Relation) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
const (
|
||||
RelationWar Relation = "War"
|
||||
RelationPeace Relation = "Peace"
|
||||
)
|
||||
|
||||
type RaceRelation struct {
|
||||
RaceID uuid.UUID `json:"raceId"`
|
||||
Relation Relation `json:"relation"`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package game
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type State struct {
|
||||
ID uuid.UUID
|
||||
Turn uint
|
||||
Stage uint
|
||||
Players []PlayerState
|
||||
}
|
||||
|
||||
type PlayerState struct {
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
Extinct bool
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
package rest
|
||||
|
||||
/*
|
||||
Full list of requirements must be updated when adding new command:
|
||||
import "encoding/json"
|
||||
|
||||
required_without_all=Vote DeclarePeace DeclareWar
|
||||
| excluded_with=Vote DeclarePeace DeclareWar
|
||||
*/
|
||||
type Command struct {
|
||||
Race string `json:"race" binding:"required,notblank"`
|
||||
Vote *CommandVote `json:"vote" binding:"required_without_all=DeclarePeace DeclareWar,excluded_with=DeclarePeace DeclareWar"`
|
||||
DeclarePeace *CommandDeclarePeace `json:"declarePeace" binding:"required_without_all=Vote DeclareWar,excluded_with=Vote DeclareWar"`
|
||||
DeclareWar *CommandDeclareWar `json:"declareWar" binding:"required_without_all=Vote DeclarePeace,excluded_with=Vote DeclarePeace"`
|
||||
Actor string `json:"actor" binding:"required,notblank"`
|
||||
Commands []json.RawMessage `json:"cmd" binding:"min=1"`
|
||||
}
|
||||
|
||||
type CommandType string
|
||||
|
||||
const (
|
||||
CommandTypeVote CommandType = "vote"
|
||||
CommandTypeRelation CommandType = "declarePeace"
|
||||
)
|
||||
|
||||
type CommandMeta struct {
|
||||
Type CommandType `json:"@type"`
|
||||
}
|
||||
|
||||
type CommandVote struct {
|
||||
CommandMeta
|
||||
Recipient string `json:"recipient" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
type CommandDeclarePeace struct {
|
||||
Opponent string `json:"recipient" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
type CommandDeclareWar struct {
|
||||
type CommandUpdateRelation struct {
|
||||
CommandMeta
|
||||
Opponent string `json:"recipient" binding:"required,notblank"`
|
||||
Relation string `json:"relation" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
package rest
|
||||
|
||||
type Status struct {
|
||||
Turn uint `json:"turn"`
|
||||
Players int `json:"players"`
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type StateResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Turn uint `json:"turn"`
|
||||
Stage uint `json:"stage"`
|
||||
Players []PlayerState `json:"player"`
|
||||
}
|
||||
|
||||
type PlayerState struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Extinct bool `json:"extinct"`
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
r := setupRouter()
|
||||
|
||||
payload := rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
Actor: "SomeRace",
|
||||
Commands: []json.RawMessage{
|
||||
encodeCommand(&rest.CommandVote{
|
||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeVote},
|
||||
Recipient: "AnotherRace",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,17 +27,17 @@ func TestCommand(t *testing.T) {
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code, w.Body)
|
||||
|
||||
// error: notblank validator
|
||||
payload.Race = ""
|
||||
payload.Actor = ""
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
payload.Race = " "
|
||||
payload.Actor = " "
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
@@ -43,24 +46,7 @@ func TestCommand(t *testing.T) {
|
||||
|
||||
// error: no commands
|
||||
payload = rest.Command{
|
||||
Race: "SomeRace",
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
|
||||
// error: more than one command
|
||||
payload = rest.Command{
|
||||
Race: "SomeRace",
|
||||
Vote: &rest.CommandVote{
|
||||
Recipient: "AnotherRace",
|
||||
},
|
||||
DeclarePeace: &rest.CommandDeclarePeace{
|
||||
Opponent: "OpponentRace",
|
||||
},
|
||||
Actor: "SomeRace",
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
@@ -69,3 +55,11 @@ func TestCommand(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
}
|
||||
|
||||
func encodeCommand(cmd any) json.RawMessage {
|
||||
v, err := json.Marshal(cmd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -1,47 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
type CommandExecutor func(controller.Configurer, rest.Command) error
|
||||
|
||||
var (
|
||||
ErrCommandNotProcessed = errors.New("command was not processed by executor")
|
||||
)
|
||||
|
||||
func CommandHandler(c *gin.Context, configurer controller.Configurer, executor CommandExecutor) {
|
||||
func CommandHandler(c *gin.Context, executor CommandExecutor) {
|
||||
var cmd rest.Command
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
if errorResponded(c, c.ShouldBindJSON(&cmd)) {
|
||||
return
|
||||
}
|
||||
err := executor(configurer, cmd)
|
||||
switch {
|
||||
case err == nil:
|
||||
c.Status(http.StatusOK)
|
||||
case errors.Is(err, ErrCommandNotProcessed):
|
||||
c.Status(http.StatusInternalServerError) // TODO: add error text?
|
||||
|
||||
commands := make([]Command, 0)
|
||||
for i := range cmd.Commands {
|
||||
command, err := parseCommand(cmd.Actor, cmd.Commands[i])
|
||||
if errorResponded(c, err) {
|
||||
return
|
||||
}
|
||||
commands = append(commands, command)
|
||||
}
|
||||
if len(commands) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no commands given"})
|
||||
return
|
||||
}
|
||||
|
||||
if errorResponded(c, executor.Execute(commands...)) {
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func parseCommand(actor string, c json.RawMessage) (Command, error) {
|
||||
meta := new(rest.CommandMeta)
|
||||
if err := json.Unmarshal(c, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := meta.Type; t {
|
||||
case rest.CommandTypeVote:
|
||||
return giveVotes(actor, c)
|
||||
case rest.CommandTypeRelation:
|
||||
return updateRelation(actor, c)
|
||||
default:
|
||||
// TODO: separate invalid input and game state errors
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return nil, fmt.Errorf("unknown comman type: %s", t)
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteCommand(config controller.Configurer, cmd rest.Command) error {
|
||||
switch {
|
||||
case cmd.DeclareWar != nil:
|
||||
return game.DeclareWar(config, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
case cmd.DeclarePeace != nil:
|
||||
return game.DeclarePeace(config, cmd.Race, cmd.DeclareWar.Opponent)
|
||||
default:
|
||||
return ErrCommandNotProcessed
|
||||
func giveVotes(actor string, c json.RawMessage) (Command, error) {
|
||||
var v rest.CommandVote
|
||||
if err := json.Unmarshal(c, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c controller.Ctrl) error { return c.GiveVotes(actor, v.Recipient) }, nil
|
||||
}
|
||||
|
||||
func updateRelation(actor string, c json.RawMessage) (Command, error) {
|
||||
var v rest.CommandUpdateRelation
|
||||
if err := json.Unmarshal(c, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c controller.Ctrl) error { return c.UpdateRelation(actor, v.Opponent, v.Relation) }, nil
|
||||
}
|
||||
|
||||
@@ -3,18 +3,88 @@ package handler
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
e "github.com/iliadenisov/galaxy/internal/error"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func transformError(c *gin.Context, err error) bool {
|
||||
type CommandExecutor interface {
|
||||
GenerateGame([]string) (uuid.UUID, error)
|
||||
Execute(cmd ...Command) error
|
||||
GameState() (rest.StateResponse, error)
|
||||
}
|
||||
|
||||
type Command func(controller.Ctrl) error
|
||||
|
||||
type executor struct {
|
||||
cfg controller.Configurer
|
||||
}
|
||||
|
||||
func initConfig() controller.Configurer {
|
||||
return func(p *controller.Param) {
|
||||
p.StoragePath = os.Getenv("STORAGE_PATH")
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultExecutor() CommandExecutor {
|
||||
return NewDefaultConfigExecutor(initConfig())
|
||||
}
|
||||
|
||||
func NewDefaultConfigExecutor(configurer controller.Configurer) CommandExecutor {
|
||||
return &executor{cfg: configurer}
|
||||
}
|
||||
|
||||
func (e *executor) Execute(cmd ...Command) error {
|
||||
return controller.ExecuteCommand(e.cfg, func(c controller.Ctrl) error {
|
||||
for i := range cmd {
|
||||
if err := cmd[i](c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (e *executor) GenerateGame(races []string) (uuid.UUID, error) {
|
||||
return controller.GenerateGame(e.cfg, races)
|
||||
}
|
||||
|
||||
func (e *executor) GameState() (rest.StateResponse, error) {
|
||||
s, err := controller.GameState(e.cfg)
|
||||
if err != nil {
|
||||
return rest.StateResponse{}, err
|
||||
}
|
||||
result := &rest.StateResponse{
|
||||
ID: s.ID,
|
||||
Turn: s.Turn,
|
||||
Stage: s.Stage,
|
||||
Players: make([]rest.PlayerState, len(s.Players)),
|
||||
}
|
||||
for i := range s.Players {
|
||||
result.Players[i].ID = s.Players[i].ID
|
||||
result.Players[i].Name = s.Players[i].Name
|
||||
result.Players[i].Extinct = s.Players[i].Extinct
|
||||
}
|
||||
return *result, nil
|
||||
}
|
||||
|
||||
func errorResponded(c *gin.Context, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var ge = new(e.GenericError)
|
||||
|
||||
if v, ok := err.(validator.ValidationErrors); ok {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": v.Error()})
|
||||
return true
|
||||
}
|
||||
|
||||
if errors.As(err, ge) {
|
||||
switch ge.Code {
|
||||
case e.ErrGameNotInitialized:
|
||||
@@ -24,7 +94,6 @@ func transformError(c *gin.Context, err error) bool {
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -4,15 +4,12 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func InitHandler(c *gin.Context, config controller.Configurer) {
|
||||
func InitHandler(c *gin.Context, executor CommandExecutor) {
|
||||
var init rest.Init
|
||||
if err := c.ShouldBindJSON(&init); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
if errorResponded(c, c.ShouldBindJSON(&init)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,10 +18,9 @@ func InitHandler(c *gin.Context, config controller.Configurer) {
|
||||
races[i] = init.Races[i].Name
|
||||
}
|
||||
|
||||
uuid, err := game.GenerateGame(config, races)
|
||||
if transformError(c, err) {
|
||||
uuid, err := executor.GenerateGame(races)
|
||||
if errorResponded(c, err) {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, rest.InitResponse{
|
||||
|
||||
@@ -4,20 +4,14 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/game"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
)
|
||||
|
||||
func StatusHandler(c *gin.Context, config controller.Configurer) {
|
||||
g, err := game.LoadState(config)
|
||||
func StatusHandler(c *gin.Context, executor CommandExecutor) {
|
||||
state, err := executor.GameState()
|
||||
|
||||
if transformError(c, err) {
|
||||
if errorResponded(c, err) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, rest.Status{
|
||||
Turn: g.Turn,
|
||||
Players: len(g.Race),
|
||||
})
|
||||
c.JSON(http.StatusOK, state)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -19,7 +19,7 @@ func TestInit(t *testing.T) {
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
|
||||
r := router.SetupRouterConfig(func(p *controller.Param) { p.StoragePath = root })
|
||||
r := router.SetupRouter(handler.NewDefaultConfigExecutor(func(p *controller.Param) { p.StoragePath = root }))
|
||||
|
||||
payload := generateInitRequest(10)
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitValidators(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
r := setupRouter()
|
||||
payload := generateInitRequest(9)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -43,13 +43,3 @@ func TestInitValidators(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||
}
|
||||
|
||||
func generateInitRequest(races int) rest.Init {
|
||||
request := rest.Init{
|
||||
Races: make([]rest.Race, races),
|
||||
}
|
||||
for i := range request.Races {
|
||||
request.Races[i] = rest.Race{Name: fmt.Sprintf("Race_%02d", i)}
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
)
|
||||
|
||||
@@ -16,12 +15,6 @@ const (
|
||||
ISO8601 = "2006-01-02 15:04:05.0 -07:00"
|
||||
)
|
||||
|
||||
func initConfig() func(*controller.Param) {
|
||||
return func(p *controller.Param) {
|
||||
p.StoragePath = os.Getenv("STORAGE_PATH")
|
||||
}
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
r *gin.Engine
|
||||
executor handler.CommandExecutor
|
||||
@@ -32,14 +25,14 @@ func (r Router) Run() error {
|
||||
}
|
||||
|
||||
func NewRouter() Router {
|
||||
return NewRouterExecutor(handler.ExecuteCommand)
|
||||
return NewRouterExecutor(handler.NewDefaultExecutor())
|
||||
}
|
||||
|
||||
func NewRouterExecutor(executor handler.CommandExecutor) Router {
|
||||
return Router{r: setupRouter(initConfig(), executor)}
|
||||
return Router{r: setupRouter(executor)}
|
||||
}
|
||||
|
||||
func setupRouter(config controller.Configurer, executor handler.CommandExecutor) *gin.Engine {
|
||||
func setupRouter(executor handler.CommandExecutor) *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
|
||||
@@ -55,9 +48,9 @@ func setupRouter(config controller.Configurer, executor handler.CommandExecutor)
|
||||
|
||||
groupV1 := r.Group("/api/v1")
|
||||
|
||||
groupV1.GET("/status", func(ctx *gin.Context) { handler.StatusHandler(ctx, config) })
|
||||
groupV1.POST("/init", func(ctx *gin.Context) { handler.InitHandler(ctx, config) })
|
||||
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, config, executor) })
|
||||
groupV1.GET("/status", func(ctx *gin.Context) { handler.StatusHandler(ctx, executor) })
|
||||
groupV1.POST("/init", func(ctx *gin.Context) { handler.InitHandler(ctx, executor) })
|
||||
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, executor) })
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -2,14 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
)
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
return SetupRouterConfig(nil)
|
||||
}
|
||||
|
||||
func SetupRouterConfig(config controller.Configurer) *gin.Engine {
|
||||
return setupRouter(config, func(controller.Configurer, rest.Command) error { return nil })
|
||||
func SetupRouter(e handler.CommandExecutor) *gin.Engine {
|
||||
return setupRouter(e)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
)
|
||||
|
||||
type dummyExecutor struct {
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) Execute(cmd ...handler.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) GenerateGame(races []string) (uuid.UUID, error) {
|
||||
return uuid.New(), nil
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) GameState() (rest.StateResponse, error) {
|
||||
return rest.StateResponse{}, nil
|
||||
}
|
||||
|
||||
func setupRouter() *gin.Engine {
|
||||
return router.SetupRouter(&dummyExecutor{})
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -63,3 +65,17 @@ func limitTestingRouter() *gin.Engine {
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func generateInitRequest(races int) rest.Init {
|
||||
request := rest.Init{
|
||||
Races: make([]rest.Race, races),
|
||||
}
|
||||
for i := range request.Races {
|
||||
request.Races[i] = rest.Race{Name: raceName(i)}
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func raceName(i int) string {
|
||||
return fmt.Sprintf("Race_%02d", i)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/controller"
|
||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||
"github.com/iliadenisov/galaxy/internal/router"
|
||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetStatus(t *testing.T) {
|
||||
r := router.SetupRouter()
|
||||
root, cleanup := util.CreateWorkDir(t)
|
||||
defer cleanup()
|
||||
|
||||
r := router.SetupRouter(handler.NewDefaultConfigExecutor(func(p *controller.Param) { p.StoragePath = root }))
|
||||
|
||||
payload := generateInitRequest(10)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/api/v1/status", nil)
|
||||
req, _ := http.NewRequest("POST", "/api/v1/init", asBody(payload))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotImplemented, w.Code, w.Body)
|
||||
assert.Equal(t, http.StatusCreated, w.Code, w.Body)
|
||||
var initResponse rest.InitResponse
|
||||
assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &initResponse))
|
||||
assert.NoError(t, uuid.Validate(initResponse.UUID.String()))
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/api/v1/status", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||
var stateResponse rest.StateResponse
|
||||
assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &stateResponse))
|
||||
assert.NoError(t, uuid.Validate(stateResponse.ID.String()))
|
||||
assert.Equal(t, uint(0), stateResponse.Turn)
|
||||
assert.Equal(t, uint(0), stateResponse.Stage)
|
||||
assert.Len(t, stateResponse.Players, 10)
|
||||
for i := range stateResponse.Players {
|
||||
assert.NoError(t, uuid.Validate(stateResponse.Players[i].ID.String()))
|
||||
assert.Equal(t, raceName(i), stateResponse.Players[i].Name)
|
||||
assert.False(t, stateResponse.Players[i].Extinct)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user