feat: order processing
feat: order commands result save/load
This commit is contained in:
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RaceID returns ID of race with given actor's name or error when race not found or extinct
|
||||||
func (c Controller) RaceID(actor string) (uuid.UUID, error) {
|
func (c Controller) RaceID(actor string) (uuid.UUID, error) {
|
||||||
ri, err := c.Cache.validRace(actor)
|
ri, err := c.Cache.validRace(actor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||||
"github.com/iliadenisov/galaxy/internal/repo"
|
"github.com/iliadenisov/galaxy/internal/repo"
|
||||||
)
|
)
|
||||||
@@ -41,9 +42,17 @@ type Repo interface {
|
|||||||
|
|
||||||
// LoadReport loads report for specific turn and player id
|
// LoadReport loads report for specific turn and player id
|
||||||
LoadReport(uint, uuid.UUID) (*report.Report, error)
|
LoadReport(uint, uuid.UUID) (*report.Report, error)
|
||||||
|
|
||||||
|
// SaveOrder stores order for given turn
|
||||||
|
SaveOrder(uint, uuid.UUID, *order.Order) error
|
||||||
|
|
||||||
|
// LoadOrder loads order for specific turn and player id
|
||||||
|
LoadOrder(uint, uuid.UUID) (*order.Order, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ctrl interface {
|
type Ctrl interface {
|
||||||
|
ValidateOrder(actor string, cmd ...order.DecodableCommand) error
|
||||||
|
// remove below funcs if /command api will be deleted
|
||||||
RaceID(actor string) (uuid.UUID, error)
|
RaceID(actor string) (uuid.UUID, error)
|
||||||
RaceQuit(actor string) error
|
RaceQuit(actor string) error
|
||||||
RaceVote(actor, acceptor string) error
|
RaceVote(actor, acceptor string) error
|
||||||
@@ -94,7 +103,7 @@ func GenerateTurn(configure func(*Param)) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ec.ExecuteLocked(func(c *Controller) error { return c.MakeTurn() })
|
err = ec.executeLocked(func(c *Controller) error { return c.MakeTurn() })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +112,15 @@ func ExecuteCommand(configure func(*Param), consumer func(c Ctrl) error) (err er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ec.ExecuteCommand(func(c *Controller) error { return consumer(c) })
|
return ec.executeCommand(func(c *Controller) error { return consumer(c) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateOrder(configure func(*Param), actor string, cmd ...order.DecodableCommand) (err error) {
|
||||||
|
ec, err := NewRepoController(configure)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ec.validateOrder(actor, cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GameState(configure func(*Param)) (s game.State, err error) {
|
func GameState(configure func(*Param)) (s game.State, err error) {
|
||||||
@@ -138,34 +155,6 @@ type RepoController struct {
|
|||||||
Repo Repo
|
Repo Repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *RepoController) ExecuteLocked(consumer func(*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(ec.NewGameController(g))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *RepoController) ExecuteCommand(consumer func(*Controller) error) (err error) {
|
|
||||||
return ec.ExecuteLocked(func(c *Controller) error {
|
|
||||||
err = consumer(c)
|
|
||||||
if err == nil {
|
|
||||||
c.Cache.StageCommand()
|
|
||||||
err = c.saveState()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRepoController(config Configurer) (*RepoController, error) {
|
func NewRepoController(config Configurer) (*RepoController, error) {
|
||||||
c := &Param{
|
c := &Param{
|
||||||
StoragePath: ".",
|
StoragePath: ".",
|
||||||
@@ -189,6 +178,60 @@ func (ec *RepoController) NewGameController(g *game.Game) *Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *RepoController) validateOrder(actor string, cmd ...order.DecodableCommand) (err error) {
|
||||||
|
return ec.executeSafe(func(t uint, c *Controller) error {
|
||||||
|
id, err := c.RaceID(actor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = c.ValidateOrder(actor, cmd...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o := &order.Order{Commands: make([]order.DecodableCommand, len(cmd))}
|
||||||
|
copy(o.Commands, cmd)
|
||||||
|
return ec.Repo.SaveOrder(t, id, o)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *RepoController) executeCommand(consumer func(*Controller) error) (err error) {
|
||||||
|
return ec.executeLocked(func(c *Controller) error {
|
||||||
|
err = consumer(c)
|
||||||
|
if err == nil {
|
||||||
|
c.Cache.StageCommand()
|
||||||
|
err = c.saveState()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *RepoController) executeSafe(consumer func(uint, *Controller) error) (err error) {
|
||||||
|
g, err := ec.Repo.LoadStateSafe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = consumer(g.Turn, ec.NewGameController(g))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *RepoController) executeLocked(consumer func(*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(ec.NewGameController(g))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) saveState() error {
|
func (c *Controller) saveState() error {
|
||||||
return c.Repo.SaveLastState(c.Cache.g)
|
return c.Repo.SaveLastState(c.Cache.g)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) MakeTurn() error {
|
func (c *Controller) MakeTurn() error {
|
||||||
|
|
||||||
|
if err := c.applyOrders(c.Cache.g.Turn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Next turn
|
// Next turn
|
||||||
c.Cache.g.Turn += 1
|
c.Cache.g.Turn += 1
|
||||||
c.Cache.g.Stage = 0
|
c.Cache.g.Stage = 0
|
||||||
|
|
||||||
// TODO: Выполнение приказов
|
|
||||||
|
|
||||||
// 01. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода
|
// 01. Вышедшие расы удаляются из списка участвующих рас перед началом просчета очередного хода
|
||||||
c.Cache.TurnWipeExtinctRaces()
|
c.Cache.TurnWipeExtinctRaces()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Controller) ValidateOrder(actor string, commands ...order.DecodableCommand) (err error) {
|
||||||
|
for i := range commands {
|
||||||
|
if _, ok := commands[i].(order.CommandRaceQuit); ok && i != len(commands)-1 {
|
||||||
|
err = e.NewQuitCommandFollowedByCommandError()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = errors.Join(err, c.applyCommand(actor, commands[i]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) applyCommand(actor string, cmd order.DecodableCommand) (err error) {
|
||||||
|
var m *order.CommandMeta
|
||||||
|
if v, ok := order.AsCommand[*order.CommandRaceQuit](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.RaceQuit(actor)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandRaceVote](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.RaceVote(actor, v.Acceptor)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandRaceRelation](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.RaceRelation(actor, v.Acceptor, v.Relation)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipClassCreate](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipClassCreate(actor, v.Name, v.Drive, int(v.Armament), v.Weapons, v.Shields, v.Cargo)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipClassMerge](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipClassMerge(actor, v.Name, v.Target)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipClassRemove](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipClassRemove(actor, v.Name)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupLoad](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupLoad(actor, uuid.MustParse(v.ID), v.Cargo, v.Quantity)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupUnload](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupUnload(actor, uuid.MustParse(v.ID), v.Quantity)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupSend](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupSend(actor, uuid.MustParse(v.ID), uint(v.Destination))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupUpgrade](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupUpgrade(actor, uuid.MustParse(v.ID), v.Tech, v.Level)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupMerge(actor)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupBreak](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupBreak(actor, uuid.MustParse(v.ID), uuid.MustParse(v.NewID), uint(v.Quantity))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupDismantle](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupDismantle(actor, uuid.MustParse(v.ID))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupTransfer](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupTransfer(actor, v.Acceptor, uuid.MustParse(v.ID))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupJoinFleet](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ShipGroupJoinFleet(actor, v.Name, uuid.MustParse(v.ID))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandFleetMerge](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.FleetMerge(actor, v.Name, v.Target)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandFleetSend](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.FleetSend(actor, v.Name, uint(v.Destination))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandScienceCreate](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ScienceCreate(actor, v.Name, v.Drive, v.Weapons, v.Shields, v.Cargo)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandScienceRemove](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.ScienceRemove(actor, v.Name)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandPlanetRename](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.PlanetRename(actor, v.Number, v.Name)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandPlanetProduce](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.PlanetProduce(actor, v.Number, v.Production, v.Subject)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandPlanetRouteSet](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.PlanetRouteSet(actor, v.LoadType, uint(v.Origin), uint(v.Destination))
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandPlanetRouteRemove](cmd); ok {
|
||||||
|
m = &v.CommandMeta
|
||||||
|
err = c.PlanetRouteRemove(actor, v.LoadType, uint(v.Origin))
|
||||||
|
} else {
|
||||||
|
return e.NewUnrecognizedCommandError(cmd.CommandType().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ge, ok := errors.AsType[*e.GenericError](err); ok {
|
||||||
|
m.Result(ge.Code)
|
||||||
|
} else if err != nil {
|
||||||
|
panic(fmt.Errorf("error applying command has unknown origin: %w", err))
|
||||||
|
} else {
|
||||||
|
m.Result(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test commands ordering
|
||||||
|
func (c *Controller) applyOrders(t uint) error {
|
||||||
|
raceOrder := make(map[int][]order.DecodableCommand)
|
||||||
|
unloadGroups := make([]uuid.UUID, 0)
|
||||||
|
unloadQuantities := make([]float64, 0)
|
||||||
|
for ri := range c.Cache.listRaceActingIdx() {
|
||||||
|
o, ok, err := c.Repo.LoadOrder(t, c.Cache.g.Race[ri].ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raceOrder[ri] = o.Commands
|
||||||
|
for i := range o.Commands {
|
||||||
|
if o.Commands[i].CommandType() == order.CommandTypeShipGroupUnload {
|
||||||
|
unloadCommand := o.Commands[i].(order.CommandShipGroupUnload)
|
||||||
|
unloadGroups = append(unloadGroups, uuid.MustParse(unloadCommand.ID))
|
||||||
|
unloadQuantities = append(unloadQuantities, unloadCommand.Quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Cache.shipGroupUnloadColonistChallenge(unloadGroups, unloadQuantities); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for ri := range raceOrder {
|
||||||
|
for _, cmd := range raceOrder[ri] {
|
||||||
|
if cmd.CommandType() == order.CommandTypeShipGroupUnload {
|
||||||
|
// group unload commands execution should be failed at this point,
|
||||||
|
// otherwise produce no-errors
|
||||||
|
if v, ok := order.AsCommand[*order.CommandShipGroupUnload](cmd); ok {
|
||||||
|
v.Result(0)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// any command might fail due to challenged planets colonization
|
||||||
|
_ = c.applyCommand(c.Cache.g.Race[ri].Name, cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ri := range c.Cache.listRaceActingIdx() {
|
||||||
|
if err := c.Repo.SaveOrder(t, c.Cache.g.Race[ri].ID, &order.Order{Commands: raceOrder[ri]}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ func (c *Cache) validActor(name string) (int, error) {
|
|||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validRace returns index of race with given name or error when race not found or extinct
|
||||||
func (c *Cache) validRace(name string) (int, error) {
|
func (c *Cache) validRace(name string) (int, error) {
|
||||||
i, err := c.raceIndex(name)
|
i, err := c.raceIndex(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -277,17 +277,21 @@ func (c *Cache) listRoutedUnloadShipGroupIds(pn uint, routeType game.RouteType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxOrRandomLoadId(IDtoLoad map[int]float64) int {
|
func MaxOrRandomLoadId(loadByRace map[int]float64) int {
|
||||||
if len(IDtoLoad) < 2 {
|
if len(loadByRace) < 2 {
|
||||||
panic("IDtoLoad must contain at least 2 keys")
|
panic("loadByRace must contain at least 2 keys")
|
||||||
}
|
}
|
||||||
IDs := slices.Collect(maps.Keys(IDtoLoad))
|
IDs := slices.Collect(maps.Keys(loadByRace))
|
||||||
slices.SortFunc(IDs, func(id1, id2 int) int { return cmp.Compare(IDtoLoad[id2], IDtoLoad[id1]) })
|
slices.SortFunc(IDs, func(id1, id2 int) int {
|
||||||
|
return cmp.Or(
|
||||||
|
cmp.Compare(loadByRace[id2], loadByRace[id1]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// no single winner with highest load
|
// no single winner with highest load
|
||||||
if IDtoLoad[IDs[0]] == IDtoLoad[IDs[1]] {
|
if loadByRace[IDs[0]] == loadByRace[IDs[1]] {
|
||||||
// remove IDs which load less than maximum
|
// remove IDs which load less than maximum
|
||||||
IDs = slices.DeleteFunc(IDs, func(v int) bool { return IDtoLoad[v] < IDtoLoad[IDs[0]] })
|
IDs = slices.DeleteFunc(IDs, func(v int) bool { return loadByRace[v] < loadByRace[IDs[0]] })
|
||||||
// IDs[0] will have random index
|
// IDs[0] will have random index
|
||||||
rand.Shuffle(len(IDs), func(i, j int) { IDs[i], IDs[j] = IDs[j], IDs[i] })
|
rand.Shuffle(len(IDs), func(i, j int) { IDs[i], IDs[j] = IDs[j], IDs[i] })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"maps"
|
"maps"
|
||||||
|
"math/rand/v2"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -295,6 +296,95 @@ func (c *Cache) shipGroupUnload(ri int, groupID uuid.UUID, quantity float64) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) shipGroupUnloadColonistChallenge(groupIDs []uuid.UUID, quantites []float64) error {
|
||||||
|
if len(groupIDs) != len(quantites) {
|
||||||
|
e.NewGameStateError("challenge group unload: groups=%d quantities=%d", len(groupIDs), len(quantites))
|
||||||
|
}
|
||||||
|
if len(groupIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
type challenger struct {
|
||||||
|
ri int
|
||||||
|
sgi int
|
||||||
|
quantity float64
|
||||||
|
}
|
||||||
|
challenge := make(map[uint]map[int][]challenger, 0)
|
||||||
|
for i, gid := range groupIDs {
|
||||||
|
sgi, ok := c.shipGroupIndexByID(gid)
|
||||||
|
if !ok {
|
||||||
|
return e.NewGameStateError("challenge group unload: group not found: %v", gid)
|
||||||
|
}
|
||||||
|
sg := c.ShipGroup(sgi)
|
||||||
|
pn, ok := sg.AtPlanet()
|
||||||
|
if !ok || sg.CargoType == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := c.MustPlanet(pn)
|
||||||
|
ri := c.ShipGroupOwnerRaceIndex(sgi)
|
||||||
|
if p.Owned() || *sg.CargoType != game.CargoColonist {
|
||||||
|
if err := c.shipGroupUnload(ri, gid, quantites[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := challenge[pn]; !ok {
|
||||||
|
challenge[pn] = make(map[int][]challenger)
|
||||||
|
}
|
||||||
|
challenge[pn][ri] = append(challenge[pn][ri], challenger{ri: ri, sgi: sgi, quantity: quantites[i]})
|
||||||
|
}
|
||||||
|
for pn := range challenge {
|
||||||
|
if len(challenge[pn]) < 2 {
|
||||||
|
for _, v := range challenge[pn] {
|
||||||
|
for _, ch := range v {
|
||||||
|
if err := c.shipGroupUnload(ch.ri, c.ShipGroup(ch.sgi).ID, ch.quantity); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum := make(map[int]float64)
|
||||||
|
races := slices.Collect(maps.Keys(challenge[pn]))
|
||||||
|
for ri, groups := range challenge[pn] {
|
||||||
|
for i := range groups {
|
||||||
|
sum[ri] = sum[ri] + groups[i].quantity
|
||||||
|
}
|
||||||
|
c.listProducingPlanets()
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(races, func(ria, rib int) int {
|
||||||
|
return cmp.Or(
|
||||||
|
// Наибольшее количество выгружаемых колонистов
|
||||||
|
cmp.Compare(sum[rib], sum[ria]),
|
||||||
|
|
||||||
|
// Наибольшее количество населения расы (они же голоса)
|
||||||
|
cmp.Compare(c.g.Race[rib].Votes, c.g.Race[ria].Votes),
|
||||||
|
|
||||||
|
// Случайный выбор претендента
|
||||||
|
cmp.Compare(rand.Float64(), rand.Float64()),
|
||||||
|
|
||||||
|
// in theoty, unreacheable option, but let's randomize again
|
||||||
|
cmp.Compare(rand.Float64(), rand.Float64()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, ch := range challenge[pn][races[0]] {
|
||||||
|
if err := c.shipGroupUnload(ch.ri, c.ShipGroup(ch.sgi).ID, ch.quantity); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) shipGroupIndexByID(id uuid.UUID) (int, bool) {
|
||||||
|
for sgi := range c.g.ShipGroups {
|
||||||
|
if c.g.ShipGroups[sgi].ID == id {
|
||||||
|
return sgi, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cache) unsafeUnloadCargo(sgi int, q float64) {
|
func (c *Cache) unsafeUnloadCargo(sgi int, q float64) {
|
||||||
if q <= 0 {
|
if q <= 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ const (
|
|||||||
ErrInputUpgradeParameterNotAllowed
|
ErrInputUpgradeParameterNotAllowed
|
||||||
ErrInputUpgradeShipsAlreadyUpToDate
|
ErrInputUpgradeShipsAlreadyUpToDate
|
||||||
ErrInputUpgradeTechLevelInsufficient
|
ErrInputUpgradeTechLevelInsufficient
|
||||||
|
ErrInputQuitCommandFollowedByCommand
|
||||||
|
ErrInputUnrecognizedCommand
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenericErrorText(code int) string {
|
func GenericErrorText(code int) string {
|
||||||
@@ -159,6 +161,10 @@ func GenericErrorText(code int) string {
|
|||||||
return "Insufficient planet production capacity"
|
return "Insufficient planet production capacity"
|
||||||
case ErrInputUpgradeTechLevelInsufficient:
|
case ErrInputUpgradeTechLevelInsufficient:
|
||||||
return "Insifficient Tech level for requested upgrade"
|
return "Insifficient Tech level for requested upgrade"
|
||||||
|
case ErrInputQuitCommandFollowedByCommand:
|
||||||
|
return "'Quit' must be the last order's command"
|
||||||
|
case ErrInputUnrecognizedCommand:
|
||||||
|
return "Unrecognized command"
|
||||||
case ErrSendShipHasNoDrives:
|
case ErrSendShipHasNoDrives:
|
||||||
return "One or more ships are not equipped with hyperdrive and cannot be moved"
|
return "One or more ships are not equipped with hyperdrive and cannot be moved"
|
||||||
case ErrSendUnreachableDestination:
|
case ErrSendUnreachableDestination:
|
||||||
|
|||||||
@@ -167,3 +167,11 @@ func NewSendUnreachableDestinationError(arg ...any) error {
|
|||||||
func NewSendShipOwnerHasNoPlanetsError(arg ...any) error {
|
func NewSendShipOwnerHasNoPlanetsError(arg ...any) error {
|
||||||
return newGenericError(ErrSendShipOwnerHasNoPlanets, arg...)
|
return newGenericError(ErrSendShipOwnerHasNoPlanets, arg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewQuitCommandFollowedByCommandError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputQuitCommandFollowedByCommand, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnrecognizedCommandError(arg ...any) error {
|
||||||
|
return newGenericError(ErrInputUnrecognizedCommand, arg...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
Commands []DecodableCommand `json:"cmd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Order) MarshalBinary() (data []byte, err error) {
|
||||||
|
return json.Marshal(&o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) UnmarshalBinary(data []byte) error {
|
||||||
|
return json.Unmarshal(data, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AsCommand[E DecodableCommand](c DecodableCommand) (E, bool) {
|
||||||
|
if v, ok := c.(E); ok {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
return *new(E), false
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandTypeRaceQuit CommandType = "raceQuit"
|
||||||
|
CommandTypeRaceVote CommandType = "raceVote"
|
||||||
|
CommandTypeRaceRelation CommandType = "raceRelation"
|
||||||
|
CommandTypeShipClassCreate CommandType = "shipClassCreate"
|
||||||
|
CommandTypeShipClassMerge CommandType = "shipClassMerge"
|
||||||
|
CommandTypeShipClassRemove CommandType = "shipClassRemove"
|
||||||
|
CommandTypeShipGroupBreak CommandType = "shipGroupBreak"
|
||||||
|
CommandTypeShipGroupLoad CommandType = "shipGroupLoad"
|
||||||
|
CommandTypeShipGroupUnload CommandType = "shipGroupUnload"
|
||||||
|
CommandTypeShipGroupSend CommandType = "shipGroupSend"
|
||||||
|
CommandTypeShipGroupUpgrade CommandType = "shipGroupUpgrade"
|
||||||
|
CommandTypeShipGroupMerge CommandType = "shipGroupMerge"
|
||||||
|
CommandTypeShipGroupDismantle CommandType = "shipGroupDismantle"
|
||||||
|
CommandTypeShipGroupTransfer CommandType = "shipGroupTransfer"
|
||||||
|
CommandTypeShipGroupJoinFleet CommandType = "shipGroupJoinFleet"
|
||||||
|
CommandTypeFleetMerge CommandType = "fleetMerge"
|
||||||
|
CommandTypeFleetSend CommandType = "fleetSend"
|
||||||
|
CommandTypeScienceCreate CommandType = "scienceCreate"
|
||||||
|
CommandTypeScienceRemove CommandType = "scienceRemove"
|
||||||
|
CommandTypePlanetRename CommandType = "planetRename"
|
||||||
|
CommandTypePlanetProduce CommandType = "planetProduce"
|
||||||
|
CommandTypePlanetRouteSet CommandType = "planetRouteSet"
|
||||||
|
CommandTypePlanetRouteRemove CommandType = "planetRouteRemove"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ct CommandType) String() string {
|
||||||
|
return string(ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecodableCommand interface {
|
||||||
|
CommandType() CommandType
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandMeta struct {
|
||||||
|
CmdType CommandType `json:"@type" binding:"notblank"`
|
||||||
|
CmdID string `json:"cmdId" binding:"required,uuid_rfc4122"`
|
||||||
|
CmdApplied *bool `json:"cmdApplied,omitempty"`
|
||||||
|
CmdErrCode *int `json:"cmdErrorCode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm CommandMeta) CommandType() CommandType {
|
||||||
|
return cm.CmdType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *CommandMeta) Result(errCode int) {
|
||||||
|
cm.CmdErrCode = &errCode
|
||||||
|
cm.CmdApplied = new(bool(errCode == 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandRaceQuit struct {
|
||||||
|
CommandMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandRaceVote struct {
|
||||||
|
CommandMeta
|
||||||
|
Acceptor string `json:"acceptor" binding:"notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandRaceRelation struct {
|
||||||
|
CommandMeta
|
||||||
|
Acceptor string `json:"acceptor" binding:"notblank,entity"`
|
||||||
|
Relation string `json:"relation" binding:"oneof=WAR PEACE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipClassCreate struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"notblank,entity"`
|
||||||
|
Drive float64 `json:"drive" binding:"eq=0|gte=1"`
|
||||||
|
Armament int `json:"armament" binding:"ammoWeapons=Weapons"`
|
||||||
|
Weapons float64 `json:"weapons" binding:"ammoWeapons=Armament"`
|
||||||
|
Shields float64 `json:"shields" binding:"eq=0|gte=1"`
|
||||||
|
Cargo float64 `json:"cargo" binding:"eq=0|gte=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipClassMerge struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"notblank,entity,nefield=Target"`
|
||||||
|
Target string `json:"target" binding:"notblank,entity,nefield=Name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipClassRemove struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupLoad struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Cargo string `json:"cargo" binding:"oneof=COL MAT CAP"`
|
||||||
|
Quantity float64 `json:"quantity" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupUnload struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Quantity float64 `json:"quantity" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupSend struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Destination int `json:"planetNumber" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupUpgrade struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Tech string `json:"tech" binding:"oneof=ALL DRIVE WEAPONS SHIELDS CARGO"`
|
||||||
|
Level float64 `json:"level" binding:"eq=0|gt=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupMerge struct {
|
||||||
|
CommandMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupBreak struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"uuid_rfc4122,nefield=NewID"`
|
||||||
|
NewID string `json:"newId" binding:"uuid_rfc4122,nefield=ID"`
|
||||||
|
Quantity int `json:"quantity" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupDismantle struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupTransfer struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Acceptor string `json:"acceptor" binding:"required,notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandShipGroupJoinFleet struct {
|
||||||
|
CommandMeta
|
||||||
|
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandFleetMerge struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity,nefield=Target"`
|
||||||
|
Target string `json:"target" binding:"required,notblank,entity,nefield=Name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandFleetSend struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
Destination int `json:"planetNumber" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandScienceCreate struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
Drive float64 `json:"drive" binding:"gte=0,lte=1"`
|
||||||
|
Weapons float64 `json:"weapons" binding:"gte=0,lte=1"`
|
||||||
|
Shields float64 `json:"shields" binding:"gte=0,lte=1"`
|
||||||
|
Cargo float64 `json:"cargo" binding:"gte=0,lte=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandScienceRemove struct {
|
||||||
|
CommandMeta
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandPlanetRename struct {
|
||||||
|
CommandMeta
|
||||||
|
Number int `json:"planetNumber" binding:"gte=0"`
|
||||||
|
Name string `json:"name" binding:"required,notblank,entity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandPlanetProduce struct {
|
||||||
|
CommandMeta
|
||||||
|
Number int `json:"planetNumber" binding:"gte=0"`
|
||||||
|
Production string `json:"production" binding:"oneof=MAT CAP DRIVE WEAPONS SHIELDS CARGO SCIENCE SHIP"`
|
||||||
|
Subject string `json:"subject" binding:"subject=Production"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandPlanetRouteSet struct {
|
||||||
|
CommandMeta
|
||||||
|
Origin int `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
|
||||||
|
Destination int `json:"toPlanetNumber" binding:"gte=0,nefield=Origin"`
|
||||||
|
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandPlanetRouteRemove struct {
|
||||||
|
CommandMeta
|
||||||
|
Origin int `json:"fromPlanetNumber" binding:"gte=0"`
|
||||||
|
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
||||||
|
}
|
||||||
@@ -7,184 +7,10 @@ type Command struct {
|
|||||||
Commands []json.RawMessage `json:"cmd" binding:"min=1"`
|
Commands []json.RawMessage `json:"cmd" binding:"min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandType string
|
func (o Command) MarshalBinary() (data []byte, err error) {
|
||||||
|
return json.Marshal(&o)
|
||||||
const (
|
|
||||||
CommandTypeRaceQuit CommandType = "raceQuit"
|
|
||||||
CommandTypeRaceVote CommandType = "raceVote"
|
|
||||||
CommandTypeRaceRelation CommandType = "raceRelation"
|
|
||||||
CommandTypeShipClassCreate CommandType = "shipClassCreate"
|
|
||||||
CommandTypeShipClassMerge CommandType = "shipClassMerge"
|
|
||||||
CommandTypeShipClassRemove CommandType = "shipClassRemove"
|
|
||||||
CommandTypeShipGroupBreak CommandType = "shipGroupBreak"
|
|
||||||
CommandTypeShipGroupLoad CommandType = "shipGroupLoad"
|
|
||||||
CommandTypeShipGroupUnload CommandType = "shipGroupUnload"
|
|
||||||
CommandTypeShipGroupSend CommandType = "shipGroupSend"
|
|
||||||
CommandTypeShipGroupUpgrade CommandType = "shipGroupUpgrade"
|
|
||||||
CommandTypeShipGroupMerge CommandType = "shipGroupMerge"
|
|
||||||
CommandTypeShipGroupDismantle CommandType = "shipGroupDismantle"
|
|
||||||
CommandTypeShipGroupTransfer CommandType = "shipGroupTransfer"
|
|
||||||
CommandTypeShipGroupJoinFleet CommandType = "shipGroupJoinFleet"
|
|
||||||
CommandTypeFleetMerge CommandType = "fleetMerge"
|
|
||||||
CommandTypeFleetSend CommandType = "fleetSend"
|
|
||||||
CommandTypeScienceCreate CommandType = "scienceCreate"
|
|
||||||
CommandTypeScienceRemove CommandType = "scienceRemove"
|
|
||||||
CommandTypePlanetRename CommandType = "planetRename"
|
|
||||||
CommandTypePlanetProduce CommandType = "planetProduce"
|
|
||||||
CommandTypePlanetRouteSet CommandType = "planetRouteSet"
|
|
||||||
CommandTypePlanetRouteRemove CommandType = "planetRouteRemove"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DecodableCommand interface {
|
|
||||||
CommandType() CommandType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandMeta struct {
|
func (o *Command) UnmarshalBinary(data []byte) error {
|
||||||
Type CommandType `json:"@type" binding:"notblank"`
|
return json.Unmarshal(data, o)
|
||||||
}
|
|
||||||
|
|
||||||
func (cm CommandMeta) CommandType() CommandType {
|
|
||||||
return cm.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandRaceQuit struct {
|
|
||||||
CommandMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandRaceVote struct {
|
|
||||||
CommandMeta
|
|
||||||
Acceptor string `json:"acceptor" binding:"notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandRaceRelation struct {
|
|
||||||
CommandMeta
|
|
||||||
Acceptor string `json:"acceptor" binding:"notblank,entity"`
|
|
||||||
Relation string `json:"relation" binding:"oneof=WAR PEACE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipClassCreate struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"notblank,entity"`
|
|
||||||
Drive float64 `json:"drive" binding:"eq=0|gte=1"`
|
|
||||||
Armament int `json:"armament" binding:"ammoWeapons=Weapons"`
|
|
||||||
Weapons float64 `json:"weapons" binding:"ammoWeapons=Armament"`
|
|
||||||
Shields float64 `json:"shields" binding:"eq=0|gte=1"`
|
|
||||||
Cargo float64 `json:"cargo" binding:"eq=0|gte=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipClassMerge struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"notblank,entity,nefield=Target"`
|
|
||||||
Target string `json:"target" binding:"notblank,entity,nefield=Name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipClassRemove struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupLoad struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Cargo string `json:"cargo" binding:"oneof=COL MAT CAP"`
|
|
||||||
Quantity float64 `json:"quantity" binding:"gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupUnload struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Quantity float64 `json:"quantity" binding:"gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupSend struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Destination int `json:"planetNumber" binding:"gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupUpgrade struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Tech string `json:"tech" binding:"oneof=ALL DRIVE WEAPONS SHIELDS CARGO"`
|
|
||||||
Level float64 `json:"level" binding:"eq=0|gt=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupMerge struct {
|
|
||||||
CommandMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupBreak struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"uuid_rfc4122,nefield=NewID"`
|
|
||||||
NewID string `json:"newId" binding:"uuid_rfc4122,nefield=ID"`
|
|
||||||
Quantity int `json:"quantity" binding:"gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupDismantle struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupTransfer struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Acceptor string `json:"acceptor" binding:"required,notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandShipGroupJoinFleet struct {
|
|
||||||
CommandMeta
|
|
||||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandFleetMerge struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity,nefield=Target"`
|
|
||||||
Target string `json:"target" binding:"required,notblank,entity,nefield=Name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandFleetSend struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
Destination int `json:"planetNumber" binding:"gte=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandScienceCreate struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
Drive float64 `json:"drive" binding:"gte=0,lte=1"`
|
|
||||||
Weapons float64 `json:"weapons" binding:"gte=0,lte=1"`
|
|
||||||
Shields float64 `json:"shields" binding:"gte=0,lte=1"`
|
|
||||||
Cargo float64 `json:"cargo" binding:"gte=0,lte=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandScienceRemove struct {
|
|
||||||
CommandMeta
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandPlanetRename struct {
|
|
||||||
CommandMeta
|
|
||||||
Number int `json:"planetNumber" binding:"gte=0"`
|
|
||||||
Name string `json:"name" binding:"required,notblank,entity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandPlanetProduce struct {
|
|
||||||
CommandMeta
|
|
||||||
Number int `json:"planetNumber" binding:"gte=0"`
|
|
||||||
Production string `json:"production" binding:"oneof=MAT CAP DRIVE WEAPONS SHIELDS CARGO SCIENCE SHIP"`
|
|
||||||
Subject string `json:"subject" binding:"subject=Production"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandPlanetRouteSet struct {
|
|
||||||
CommandMeta
|
|
||||||
Origin int `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
|
|
||||||
Destination int `json:"toPlanetNumber" binding:"gte=0,nefield=Origin"`
|
|
||||||
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandPlanetRouteRemove struct {
|
|
||||||
CommandMeta
|
|
||||||
Origin int `json:"fromPlanetNumber" binding:"gte=0"`
|
|
||||||
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -79,15 +79,11 @@ func (f *fs) Exists(path string) (bool, error) {
|
|||||||
return fileExists(filepath.Join(f.root, path))
|
return fileExists(filepath.Join(f.root, path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fs) Write(path string, v encoding.BinaryMarshaler) error {
|
func (f *fs) WriteSafe(path string, v encoding.BinaryMarshaler) error {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return errors.New("cant't marshal from nil object")
|
return errors.New("cant't marshal from nil object")
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.lock == nil {
|
|
||||||
return errors.New("lock must be acquired before write")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetFilePath := filepath.Join(f.root, path)
|
targetFilePath := filepath.Join(f.root, path)
|
||||||
if targetFilePath == f.lockFilePath() {
|
if targetFilePath == f.lockFilePath() {
|
||||||
return errors.New("can't write to the lock file")
|
return errors.New("can't write to the lock file")
|
||||||
@@ -160,6 +156,14 @@ func (f *fs) Write(path string, v encoding.BinaryMarshaler) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fs) Write(path string, v encoding.BinaryMarshaler) error {
|
||||||
|
if f.lock == nil {
|
||||||
|
return errors.New("lock must be acquired before write")
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.WriteSafe(path, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fs) Read(path string, v encoding.BinaryUnmarshaler) error {
|
func (f *fs) Read(path string, v encoding.BinaryUnmarshaler) error {
|
||||||
if f.lock == nil {
|
if f.lock == nil {
|
||||||
return errors.New("lock must be acquired before read")
|
return errors.New("lock must be acquired before read")
|
||||||
@@ -183,7 +187,7 @@ func (f *fs) ReadSafe(path string, v encoding.BinaryUnmarshaler) error {
|
|||||||
}
|
}
|
||||||
case <-timeout.C:
|
case <-timeout.C:
|
||||||
checker.Stop()
|
checker.Stop()
|
||||||
return errors.New("lock acquired, timeout waiting for release")
|
return errors.New("timeout waiting for lock release")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+179
-32
@@ -4,16 +4,20 @@ package repo
|
|||||||
/state.json
|
/state.json
|
||||||
/0001/state.json
|
/0001/state.json
|
||||||
/0001/meta.json
|
/0001/meta.json
|
||||||
|
/0000/order/{UUID}.json
|
||||||
/0001/bombing.json
|
/0001/bombing.json
|
||||||
/0001/battle/{UUID}.json
|
/0001/battle/{UUID}.json
|
||||||
/0001/report/{UUID}.json
|
/0001/report/{UUID}.json
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/report"
|
"github.com/iliadenisov/galaxy/internal/model/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,36 +26,16 @@ const (
|
|||||||
metaPath = "meta.json"
|
metaPath = "meta.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *repo) SaveReport(t uint, rep *report.Report) error {
|
type storedOrder struct {
|
||||||
return saveReport(r.s, t, rep)
|
Commands []json.RawMessage `json:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveReport(s Storage, t uint, v *report.Report) error {
|
func (o storedOrder) MarshalBinary() (data []byte, err error) {
|
||||||
path := repDir(t, v.RaceID)
|
return json.Marshal(&o)
|
||||||
if err := s.Write(path, v); err != nil {
|
|
||||||
return NewStorageError(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repo) LoadReport(t uint, id uuid.UUID) (*report.Report, error) {
|
func (o *storedOrder) UnmarshalBinary(data []byte) error {
|
||||||
return loadReport(r.s, t, id)
|
return json.Unmarshal(data, o)
|
||||||
}
|
|
||||||
|
|
||||||
func loadReport(s Storage, t uint, id uuid.UUID) (*report.Report, error) {
|
|
||||||
path := repDir(t, id)
|
|
||||||
result := new(report.Report)
|
|
||||||
exist, err := s.Exists(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewStorageError(err)
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
return nil, NewReportNotFoundError()
|
|
||||||
}
|
|
||||||
if err := s.ReadSafe(path, result); err != nil {
|
|
||||||
return nil, NewStorageError(err)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repo) SaveNewTurn(t uint, g *game.Game) error {
|
func (r *repo) SaveNewTurn(t uint, g *game.Game) error {
|
||||||
@@ -59,7 +43,7 @@ func (r *repo) SaveNewTurn(t uint, g *game.Game) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveNewTurn(s Storage, t uint, g *game.Game) error {
|
func saveNewTurn(s Storage, t uint, g *game.Game) error {
|
||||||
path := fmt.Sprintf("%s/state.json", turnDir(t))
|
path := fmt.Sprintf("%s/state.json", TurnDir(t))
|
||||||
exist, err := s.Exists(path)
|
exist, err := s.Exists(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewStorageError(err)
|
return NewStorageError(err)
|
||||||
@@ -132,7 +116,7 @@ func loadMeta(s Storage) (*game.GameMeta, error) {
|
|||||||
|
|
||||||
func saveMeta(s Storage, t uint, gm *game.GameMeta) error {
|
func saveMeta(s Storage, t uint, gm *game.GameMeta) error {
|
||||||
// save turn's meta
|
// save turn's meta
|
||||||
path := fmt.Sprintf("%s/%s", turnDir(t), metaPath)
|
path := fmt.Sprintf("%s/%s", TurnDir(t), metaPath)
|
||||||
if err := s.Write(path, gm); err != nil {
|
if err := s.Write(path, gm); err != nil {
|
||||||
return NewStorageError(err)
|
return NewStorageError(err)
|
||||||
}
|
}
|
||||||
@@ -158,7 +142,7 @@ func (r *repo) SaveBattle(t uint, b *report.BattleReport, m *game.BattleMeta) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveBattle(s Storage, t uint, b *report.BattleReport) error {
|
func saveBattle(s Storage, t uint, b *report.BattleReport) error {
|
||||||
path := fmt.Sprintf("%s/battle/%s.json", turnDir(t), b.ID.String())
|
path := fmt.Sprintf("%s/battle/%s.json", TurnDir(t), b.ID.String())
|
||||||
exist, err := s.Exists(path)
|
exist, err := s.Exists(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewStorageError(err)
|
return NewStorageError(err)
|
||||||
@@ -183,10 +167,173 @@ func (r *repo) SaveBombings(t uint, b []*game.Bombing) error {
|
|||||||
return saveMeta(r.s, t, meta)
|
return saveMeta(r.s, t, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func repDir(t uint, id uuid.UUID) string {
|
func (r *repo) SaveReport(t uint, rep *report.Report) error {
|
||||||
return fmt.Sprintf("%s/report/%s.json", turnDir(t), id.String())
|
return saveReport(r.s, t, rep)
|
||||||
}
|
}
|
||||||
|
|
||||||
func turnDir(t uint) string {
|
func saveReport(s Storage, t uint, v *report.Report) error {
|
||||||
|
path := ReportDir(t, v.RaceID)
|
||||||
|
if err := s.Write(path, v); err != nil {
|
||||||
|
return NewStorageError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repo) LoadReport(t uint, id uuid.UUID) (*report.Report, error) {
|
||||||
|
return loadReport(r.s, t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadReport(s Storage, t uint, id uuid.UUID) (*report.Report, error) {
|
||||||
|
path := ReportDir(t, id)
|
||||||
|
result := new(report.Report)
|
||||||
|
exist, err := s.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewStorageError(err)
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return nil, NewReportNotFoundError()
|
||||||
|
}
|
||||||
|
if err := s.ReadSafe(path, result); err != nil {
|
||||||
|
return nil, NewStorageError(err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repo) SaveOrder(t uint, id uuid.UUID, o *order.Order) error {
|
||||||
|
return saveOrder(r.s, t, id, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveOrder(s Storage, t uint, id uuid.UUID, o *order.Order) error {
|
||||||
|
path := OrderDir(t, id)
|
||||||
|
if err := s.WriteSafe(path, o); err != nil {
|
||||||
|
return NewStorageError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repo) LoadOrder(t uint, id uuid.UUID) (*order.Order, bool, error) {
|
||||||
|
return loadOrder(r.s, t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOrder(s Storage, t uint, id uuid.UUID) (*order.Order, bool, error) {
|
||||||
|
path := OrderDir(t, id)
|
||||||
|
|
||||||
|
exist, err := s.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, NewStorageError(err)
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := new(storedOrder)
|
||||||
|
if err := s.ReadSafe(path, cmd); err != nil {
|
||||||
|
return nil, false, NewStorageError(err)
|
||||||
|
}
|
||||||
|
result := &order.Order{Commands: make([]order.DecodableCommand, len(cmd.Commands))}
|
||||||
|
if len(cmd.Commands) == 0 {
|
||||||
|
return nil, false, errors.New("no commands were stored")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range cmd.Commands {
|
||||||
|
command, err := ParseOrder(cmd.Commands[i], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
result.Commands[i] = command
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper funcs
|
||||||
|
|
||||||
|
func OrderDir(t uint, id uuid.UUID) string {
|
||||||
|
return fmt.Sprintf("%s/order/%s.json", TurnDir(t), id.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReportDir(t uint, id uuid.UUID) string {
|
||||||
|
return fmt.Sprintf("%s/report/%s.json", TurnDir(t), id.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TurnDir(t uint) string {
|
||||||
return fmt.Sprintf("%04d", t)
|
return fmt.Sprintf("%04d", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseOrder(c json.RawMessage, validator func(order.DecodableCommand) error) (order.DecodableCommand, error) {
|
||||||
|
meta := new(order.CommandMeta)
|
||||||
|
if err := json.Unmarshal(c, meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch t := meta.CmdType; t {
|
||||||
|
case order.CommandTypeRaceQuit:
|
||||||
|
return decodeCommand(c, new(order.CommandRaceQuit), validator)
|
||||||
|
case order.CommandTypeRaceVote:
|
||||||
|
return decodeCommand(c, new(order.CommandRaceVote), validator)
|
||||||
|
case order.CommandTypeRaceRelation:
|
||||||
|
return decodeCommand(c, new(order.CommandRaceRelation), validator)
|
||||||
|
case order.CommandTypeShipClassCreate:
|
||||||
|
return decodeCommand(c, new(order.CommandShipClassCreate), validator)
|
||||||
|
case order.CommandTypeShipClassMerge:
|
||||||
|
return decodeCommand(c, new(order.CommandShipClassMerge), validator)
|
||||||
|
case order.CommandTypeShipClassRemove:
|
||||||
|
return decodeCommand(c, new(order.CommandShipClassRemove), validator)
|
||||||
|
case order.CommandTypeShipGroupBreak:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupBreak), validator)
|
||||||
|
case order.CommandTypeShipGroupLoad:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupLoad), validator)
|
||||||
|
case order.CommandTypeShipGroupUnload:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupUnload), validator)
|
||||||
|
case order.CommandTypeShipGroupSend:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupSend), validator)
|
||||||
|
case order.CommandTypeShipGroupUpgrade:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupUpgrade), validator)
|
||||||
|
case order.CommandTypeShipGroupMerge:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupMerge), validator)
|
||||||
|
case order.CommandTypeShipGroupDismantle:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupDismantle), validator)
|
||||||
|
case order.CommandTypeShipGroupTransfer:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupTransfer), validator)
|
||||||
|
case order.CommandTypeShipGroupJoinFleet:
|
||||||
|
return decodeCommand(c, new(order.CommandShipGroupJoinFleet), validator)
|
||||||
|
case order.CommandTypeFleetMerge:
|
||||||
|
return decodeCommand(c, new(order.CommandFleetMerge), validator)
|
||||||
|
case order.CommandTypeFleetSend:
|
||||||
|
return decodeCommand(c, new(order.CommandFleetSend), validator)
|
||||||
|
case order.CommandTypeScienceCreate:
|
||||||
|
return decodeCommand(c, new(order.CommandScienceCreate), validator)
|
||||||
|
case order.CommandTypeScienceRemove:
|
||||||
|
return decodeCommand(c, new(order.CommandScienceRemove), validator)
|
||||||
|
case order.CommandTypePlanetRename:
|
||||||
|
return decodeCommand(c, new(order.CommandPlanetRename), validator)
|
||||||
|
case order.CommandTypePlanetProduce:
|
||||||
|
return decodeCommand(c, new(order.CommandPlanetProduce), validator)
|
||||||
|
case order.CommandTypePlanetRouteSet:
|
||||||
|
return decodeCommand(c, new(order.CommandPlanetRouteSet), validator)
|
||||||
|
case order.CommandTypePlanetRouteRemove:
|
||||||
|
return decodeCommand(c, new(order.CommandPlanetRouteRemove), validator)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown comman type: %s", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeCommand(m json.RawMessage, c order.DecodableCommand, validator func(order.DecodableCommand) error) (order.DecodableCommand, error) {
|
||||||
|
v, err := unmarshallCommand(m, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if validator != nil {
|
||||||
|
err = validator(v)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshallCommand[T order.DecodableCommand](c json.RawMessage, v T) (T, error) {
|
||||||
|
if err := json.Unmarshal(c, v); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Storage interface {
|
|||||||
Lock() (func() error, error)
|
Lock() (func() error, error)
|
||||||
Exists(string) (bool, error)
|
Exists(string) (bool, error)
|
||||||
Write(string, encoding.BinaryMarshaler) error
|
Write(string, encoding.BinaryMarshaler) error
|
||||||
|
WriteSafe(string, encoding.BinaryMarshaler) error
|
||||||
Read(string, encoding.BinaryUnmarshaler) error
|
Read(string, encoding.BinaryUnmarshaler) error
|
||||||
ReadSafe(string, encoding.BinaryUnmarshaler) error
|
ReadSafe(string, encoding.BinaryUnmarshaler) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadOrder_T(s Storage, t uint, id uuid.UUID) (*order.Order, bool, error) {
|
||||||
|
return loadOrder(s, t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveOrder_T(s Storage, t uint, id uuid.UUID, o *order.Order) error {
|
||||||
|
return saveOrder(s, t, id, o)
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package repo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/repo"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/repo/fs"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSaveOrder(t *testing.T) {
|
||||||
|
root := t.ArtifactDir()
|
||||||
|
s, err := fs.NewFileStorage(root)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
id := uuid.New()
|
||||||
|
o := &order.Order{
|
||||||
|
Commands: []order.DecodableCommand{
|
||||||
|
&order.CommandRaceVote{
|
||||||
|
CommandMeta: order.CommandMeta{
|
||||||
|
CmdType: order.CommandTypeRaceVote,
|
||||||
|
CmdID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
Acceptor: "Race_acc",
|
||||||
|
},
|
||||||
|
&order.CommandShipClassCreate{
|
||||||
|
CommandMeta: order.CommandMeta{
|
||||||
|
CmdType: order.CommandTypeShipClassCreate,
|
||||||
|
CmdID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
Name: "Fighter",
|
||||||
|
Drive: 20.5,
|
||||||
|
Armament: 5,
|
||||||
|
Weapons: 20,
|
||||||
|
Shields: 15.5,
|
||||||
|
Cargo: 0,
|
||||||
|
},
|
||||||
|
&order.CommandShipGroupMerge{
|
||||||
|
CommandMeta: order.CommandMeta{
|
||||||
|
CmdType: order.CommandTypeShipGroupMerge,
|
||||||
|
CmdID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&order.CommandShipClassCreate{
|
||||||
|
CommandMeta: order.CommandMeta{
|
||||||
|
CmdType: order.CommandTypeShipClassCreate,
|
||||||
|
CmdID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
Name: "Freighter",
|
||||||
|
Drive: 30.33,
|
||||||
|
Armament: 1,
|
||||||
|
Weapons: 1,
|
||||||
|
Shields: 10.1,
|
||||||
|
Cargo: 0,
|
||||||
|
},
|
||||||
|
&order.CommandRaceQuit{
|
||||||
|
CommandMeta: order.CommandMeta{
|
||||||
|
CmdType: order.CommandTypeRaceQuit,
|
||||||
|
CmdID: uuid.New().String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var turn uint = 2
|
||||||
|
|
||||||
|
for i := range o.Commands {
|
||||||
|
if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
|
||||||
|
m := &v.CommandMeta
|
||||||
|
m.Result(0)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandRaceQuit](o.Commands[i]); ok {
|
||||||
|
v.Result(10)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipClassCreate](o.Commands[i]); ok {
|
||||||
|
m := &v.CommandMeta
|
||||||
|
m.Result(33)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](o.Commands[i]); ok {
|
||||||
|
v.Result(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, repo.SaveOrder_T(s, turn, id, o))
|
||||||
|
assert.FileExists(t, filepath.Join(root, repo.OrderDir(turn, id)))
|
||||||
|
|
||||||
|
LoadOrderTest(t, s, root, turn, id, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadOrderTest(t *testing.T, s repo.Storage, root string, turn uint, id uuid.UUID, expected *order.Order) {
|
||||||
|
o, ok, err := repo.LoadOrder_T(s, turn, id)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Len(t, o.Commands, 5)
|
||||||
|
assert.ElementsMatch(t, expected.Commands, o.Commands)
|
||||||
|
|
||||||
|
CommandResultTest(t, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommandResultTest(t *testing.T, o *order.Order) {
|
||||||
|
assert.NotEmpty(t, o.Commands)
|
||||||
|
for i := range o.Commands {
|
||||||
|
if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
|
||||||
|
assert.NotNil(t, v.CmdApplied)
|
||||||
|
assert.True(t, *v.CmdApplied)
|
||||||
|
assert.Equal(t, 0, *v.CmdErrCode)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandRaceQuit](o.Commands[i]); ok {
|
||||||
|
assert.NotNil(t, v.CmdApplied)
|
||||||
|
assert.False(t, *v.CmdApplied)
|
||||||
|
assert.Equal(t, 10, *v.CmdErrCode)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipClassCreate](o.Commands[i]); ok {
|
||||||
|
assert.NotNil(t, v.CmdApplied)
|
||||||
|
assert.False(t, *v.CmdApplied)
|
||||||
|
assert.Equal(t, 33, *v.CmdErrCode)
|
||||||
|
} else if v, ok := order.AsCommand[*order.CommandShipGroupMerge](o.Commands[i]); ok {
|
||||||
|
assert.NotNil(t, v.CmdApplied)
|
||||||
|
assert.True(t, *v.CmdApplied)
|
||||||
|
assert.Equal(t, 0, *v.CmdErrCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,29 +6,19 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
commandNoErrorsStatus = http.StatusNoContent
|
|
||||||
commandDefaultActor = "Gorlum"
|
|
||||||
apiCommandMethod = "PUT"
|
|
||||||
apiCommandPath = "/api/v1/command"
|
|
||||||
validId1 = uuid.New().String()
|
|
||||||
validId2 = uuid.New().String()
|
|
||||||
invalidId = "fd091c69-5976-4775-b2f9-7ba77735afb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommandRaceQuit(t *testing.T) {
|
func TestCommandRaceQuit(t *testing.T) {
|
||||||
r := setupRouter()
|
r := setupRouter()
|
||||||
|
|
||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandRaceQuit{
|
encodeCommand(&order.CommandRaceQuit{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceQuit},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceQuit},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -56,8 +46,8 @@ func TestCommandRaceQuit(t *testing.T) {
|
|||||||
|
|
||||||
// unrecognized command type
|
// unrecognized command type
|
||||||
payload.Commands = []json.RawMessage{
|
payload.Commands = []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandRaceQuit{
|
encodeCommand(&order.CommandRaceQuit{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandType("-unknown-")},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandType("-unknown-")},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
@@ -95,8 +85,8 @@ func TestCommandRaceVote(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandRaceVote{
|
encodeCommand(&order.CommandRaceVote{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceVote},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
|
||||||
Acceptor: tc.acceptor,
|
Acceptor: tc.acceptor,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -133,8 +123,8 @@ func TestCommandRaceRelation(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandRaceRelation{
|
encodeCommand(&order.CommandRaceRelation{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceRelation},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
|
||||||
Acceptor: tc.acceptor,
|
Acceptor: tc.acceptor,
|
||||||
Relation: tc.relation,
|
Relation: tc.relation,
|
||||||
}),
|
}),
|
||||||
@@ -184,8 +174,8 @@ func TestCommandShipClassCreate(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipClassCreate{
|
encodeCommand(&order.CommandShipClassCreate{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassCreate},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassCreate},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
Drive: tc.D,
|
Drive: tc.D,
|
||||||
Armament: tc.A,
|
Armament: tc.A,
|
||||||
@@ -227,8 +217,8 @@ func TestCommandShipClassMerge(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipClassMerge{
|
encodeCommand(&order.CommandShipClassMerge{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassMerge},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassMerge},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
Target: tc.target,
|
Target: tc.target,
|
||||||
}),
|
}),
|
||||||
@@ -261,8 +251,8 @@ func TestCommandShipClassRemove(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipClassRemove{
|
encodeCommand(&order.CommandShipClassRemove{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassRemove},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassRemove},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -300,8 +290,8 @@ func TestCommandShipGroupBreak(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupBreak{
|
encodeCommand(&order.CommandShipGroupBreak{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupBreak},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupBreak},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
NewID: tc.newId,
|
NewID: tc.newId,
|
||||||
Quantity: tc.quantity,
|
Quantity: tc.quantity,
|
||||||
@@ -341,8 +331,8 @@ func TestCommandShipGroupLoad(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupLoad{
|
encodeCommand(&order.CommandShipGroupLoad{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupLoad},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupLoad},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Cargo: tc.cargo,
|
Cargo: tc.cargo,
|
||||||
Quantity: tc.quantity,
|
Quantity: tc.quantity,
|
||||||
@@ -378,8 +368,8 @@ func TestCommandShipGroupUnload(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupUnload{
|
encodeCommand(&order.CommandShipGroupUnload{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupUnload},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUnload},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Quantity: tc.quantity,
|
Quantity: tc.quantity,
|
||||||
}),
|
}),
|
||||||
@@ -414,8 +404,8 @@ func TestCommandShipGroupSend(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupSend{
|
encodeCommand(&order.CommandShipGroupSend{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupSend},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupSend},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Destination: tc.destination,
|
Destination: tc.destination,
|
||||||
}),
|
}),
|
||||||
@@ -456,8 +446,8 @@ func TestCommandShipGroupUpgrade(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupUpgrade{
|
encodeCommand(&order.CommandShipGroupUpgrade{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupUpgrade},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUpgrade},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Tech: tc.tech,
|
Tech: tc.tech,
|
||||||
Level: tc.level,
|
Level: tc.level,
|
||||||
@@ -487,8 +477,8 @@ func TestCommandShipGroupMerge(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupMerge{
|
encodeCommand(&order.CommandShipGroupMerge{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupMerge},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupMerge},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -518,8 +508,8 @@ func TestCommandShipGroupDismantle(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupDismantle{
|
encodeCommand(&order.CommandShipGroupDismantle{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupDismantle},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupDismantle},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -554,8 +544,8 @@ func TestCommandShipGroupTransfer(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupTransfer{
|
encodeCommand(&order.CommandShipGroupTransfer{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupTransfer},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupTransfer},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Acceptor: tc.acceptor,
|
Acceptor: tc.acceptor,
|
||||||
}),
|
}),
|
||||||
@@ -591,8 +581,8 @@ func TestCommandShipGroupJoinFleet(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandShipGroupJoinFleet{
|
encodeCommand(&order.CommandShipGroupJoinFleet{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupJoinFleet},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupJoinFleet},
|
||||||
ID: tc.id,
|
ID: tc.id,
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}),
|
}),
|
||||||
@@ -628,8 +618,8 @@ func TestCommandFleetMerge(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandFleetMerge{
|
encodeCommand(&order.CommandFleetMerge{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeFleetMerge},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetMerge},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
Target: tc.target,
|
Target: tc.target,
|
||||||
}),
|
}),
|
||||||
@@ -664,8 +654,8 @@ func TestCommandFleetSend(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandFleetSend{
|
encodeCommand(&order.CommandFleetSend{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeFleetSend},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetSend},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
Destination: tc.destination,
|
Destination: tc.destination,
|
||||||
}),
|
}),
|
||||||
@@ -706,8 +696,8 @@ func TestCommandScienceCreate(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandScienceCreate{
|
encodeCommand(&order.CommandScienceCreate{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeScienceCreate},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceCreate},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
Drive: tc.D,
|
Drive: tc.D,
|
||||||
Weapons: tc.W,
|
Weapons: tc.W,
|
||||||
@@ -743,8 +733,8 @@ func TestCommandScienceRemove(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandScienceRemove{
|
encodeCommand(&order.CommandScienceRemove{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeScienceRemove},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceRemove},
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -779,8 +769,8 @@ func TestCommandPlanetRename(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandPlanetRename{
|
encodeCommand(&order.CommandPlanetRename{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRename},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRename},
|
||||||
Number: tc.number,
|
Number: tc.number,
|
||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}),
|
}),
|
||||||
@@ -825,8 +815,8 @@ func TestCommandPlanetProduce(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandPlanetProduce{
|
encodeCommand(&order.CommandPlanetProduce{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetProduce},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetProduce},
|
||||||
Number: tc.number,
|
Number: tc.number,
|
||||||
Production: tc.production,
|
Production: tc.production,
|
||||||
Subject: tc.subject,
|
Subject: tc.subject,
|
||||||
@@ -866,8 +856,8 @@ func TestCommandPlanetRouteSet(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandPlanetRouteSet{
|
encodeCommand(&order.CommandPlanetRouteSet{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRouteSet},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteSet},
|
||||||
Origin: tc.origin,
|
Origin: tc.origin,
|
||||||
Destination: tc.destination,
|
Destination: tc.destination,
|
||||||
LoadType: tc.loadType,
|
LoadType: tc.loadType,
|
||||||
@@ -905,8 +895,8 @@ func TestCommandPlanetRouteRemove(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandPlanetRouteRemove{
|
encodeCommand(&order.CommandPlanetRouteRemove{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRouteRemove},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteRemove},
|
||||||
Origin: tc.origin,
|
Origin: tc.origin,
|
||||||
LoadType: tc.loadType,
|
LoadType: tc.loadType,
|
||||||
}),
|
}),
|
||||||
@@ -929,13 +919,13 @@ func TestMultipleCommands(t *testing.T) {
|
|||||||
payload := &rest.Command{
|
payload := &rest.Command{
|
||||||
Actor: commandDefaultActor,
|
Actor: commandDefaultActor,
|
||||||
Commands: []json.RawMessage{
|
Commands: []json.RawMessage{
|
||||||
encodeCommand(&rest.CommandRaceRelation{
|
encodeCommand(&order.CommandRaceRelation{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceRelation},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
|
||||||
Acceptor: "Opponent",
|
Acceptor: "Opponent",
|
||||||
Relation: "PEACE",
|
Relation: "PEACE",
|
||||||
}),
|
}),
|
||||||
encodeCommand(&rest.CommandRaceVote{
|
encodeCommand(&order.CommandRaceVote{
|
||||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceVote},
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
|
||||||
Acceptor: "Opponent",
|
Acceptor: "Opponent",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -11,29 +12,30 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CommandHandler(c *gin.Context, executor CommandExecutor) {
|
func CommandHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
var cmd rest.Command
|
var cmd rest.Command
|
||||||
if errorResponded(c, c.ShouldBindJSON(&cmd)) {
|
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := make([]Command, 0)
|
commands := make([]Command, len(cmd.Commands))
|
||||||
for i := range cmd.Commands {
|
for i := range cmd.Commands {
|
||||||
command, err := parseCommand(cmd.Actor, cmd.Commands[i])
|
command, err := parseCommand(cmd.Actor, cmd.Commands[i])
|
||||||
if errorResponded(c, err) {
|
if errorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commands = append(commands, command)
|
commands[i] = command
|
||||||
}
|
}
|
||||||
if len(commands) == 0 {
|
if len(commands) == 0 {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no commands given"})
|
errorResponse(c, errors.New("no commands given"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorResponded(c, executor.Execute(commands...)) {
|
if errorResponse(c, executor.Execute(commands...)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,56 +43,56 @@ func CommandHandler(c *gin.Context, executor CommandExecutor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseCommand(actor string, c json.RawMessage) (Command, error) {
|
func parseCommand(actor string, c json.RawMessage) (Command, error) {
|
||||||
meta := new(rest.CommandMeta)
|
meta := new(order.CommandMeta)
|
||||||
if err := json.Unmarshal(c, meta); err != nil {
|
if err := json.Unmarshal(c, meta); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch t := meta.Type; t {
|
switch t := meta.CmdType; t {
|
||||||
case rest.CommandTypeRaceQuit:
|
case order.CommandTypeRaceQuit:
|
||||||
return commandRaceQuit(actor)
|
return commandRaceQuit(actor)
|
||||||
case rest.CommandTypeRaceVote:
|
case order.CommandTypeRaceVote:
|
||||||
return commandRaceVote(actor, c)
|
return commandRaceVote(actor, c)
|
||||||
case rest.CommandTypeRaceRelation:
|
case order.CommandTypeRaceRelation:
|
||||||
return commandRaceRelation(actor, c)
|
return commandRaceRelation(actor, c)
|
||||||
case rest.CommandTypeShipClassCreate:
|
case order.CommandTypeShipClassCreate:
|
||||||
return commandShipClassCreate(actor, c)
|
return commandShipClassCreate(actor, c)
|
||||||
case rest.CommandTypeShipClassMerge:
|
case order.CommandTypeShipClassMerge:
|
||||||
return commandShipClassMerge(actor, c)
|
return commandShipClassMerge(actor, c)
|
||||||
case rest.CommandTypeShipClassRemove:
|
case order.CommandTypeShipClassRemove:
|
||||||
return commandShipClassRemove(actor, c)
|
return commandShipClassRemove(actor, c)
|
||||||
case rest.CommandTypeShipGroupBreak:
|
case order.CommandTypeShipGroupBreak:
|
||||||
return commandShipGroupBreak(actor, c)
|
return commandShipGroupBreak(actor, c)
|
||||||
case rest.CommandTypeShipGroupLoad:
|
case order.CommandTypeShipGroupLoad:
|
||||||
return commandShipGroupLoad(actor, c)
|
return commandShipGroupLoad(actor, c)
|
||||||
case rest.CommandTypeShipGroupUnload:
|
case order.CommandTypeShipGroupUnload:
|
||||||
return commandShipGroupUnload(actor, c)
|
return commandShipGroupUnload(actor, c)
|
||||||
case rest.CommandTypeShipGroupSend:
|
case order.CommandTypeShipGroupSend:
|
||||||
return commandShipGroupSend(actor, c)
|
return commandShipGroupSend(actor, c)
|
||||||
case rest.CommandTypeShipGroupUpgrade:
|
case order.CommandTypeShipGroupUpgrade:
|
||||||
return commandShipGroupUpgrade(actor, c)
|
return commandShipGroupUpgrade(actor, c)
|
||||||
case rest.CommandTypeShipGroupMerge:
|
case order.CommandTypeShipGroupMerge:
|
||||||
return commandShipGroupMerge(actor, c)
|
return commandShipGroupMerge(actor, c)
|
||||||
case rest.CommandTypeShipGroupDismantle:
|
case order.CommandTypeShipGroupDismantle:
|
||||||
return commandShipGroupDismantle(actor, c)
|
return commandShipGroupDismantle(actor, c)
|
||||||
case rest.CommandTypeShipGroupTransfer:
|
case order.CommandTypeShipGroupTransfer:
|
||||||
return commandShipGroupTransfer(actor, c)
|
return commandShipGroupTransfer(actor, c)
|
||||||
case rest.CommandTypeShipGroupJoinFleet:
|
case order.CommandTypeShipGroupJoinFleet:
|
||||||
return commandShipGroupJoinFleet(actor, c)
|
return commandShipGroupJoinFleet(actor, c)
|
||||||
case rest.CommandTypeFleetMerge:
|
case order.CommandTypeFleetMerge:
|
||||||
return commandFleetMerge(actor, c)
|
return commandFleetMerge(actor, c)
|
||||||
case rest.CommandTypeFleetSend:
|
case order.CommandTypeFleetSend:
|
||||||
return commandFleetSend(actor, c)
|
return commandFleetSend(actor, c)
|
||||||
case rest.CommandTypeScienceCreate:
|
case order.CommandTypeScienceCreate:
|
||||||
return commandScienceCreate(actor, c)
|
return commandScienceCreate(actor, c)
|
||||||
case rest.CommandTypeScienceRemove:
|
case order.CommandTypeScienceRemove:
|
||||||
return commandScienceRemove(actor, c)
|
return commandScienceRemove(actor, c)
|
||||||
case rest.CommandTypePlanetRename:
|
case order.CommandTypePlanetRename:
|
||||||
return commandPlanetRename(actor, c)
|
return commandPlanetRename(actor, c)
|
||||||
case rest.CommandTypePlanetProduce:
|
case order.CommandTypePlanetProduce:
|
||||||
return commandPlanetProduce(actor, c)
|
return commandPlanetProduce(actor, c)
|
||||||
case rest.CommandTypePlanetRouteSet:
|
case order.CommandTypePlanetRouteSet:
|
||||||
return commandPlanetRouteSet(actor, c)
|
return commandPlanetRouteSet(actor, c)
|
||||||
case rest.CommandTypePlanetRouteRemove:
|
case order.CommandTypePlanetRouteRemove:
|
||||||
return commandPlanetRouteRemove(actor, c)
|
return commandPlanetRouteRemove(actor, c)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown comman type: %s", t)
|
return nil, fmt.Errorf("unknown comman type: %s", t)
|
||||||
@@ -102,7 +104,7 @@ func commandRaceQuit(actor string) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
|
func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandRaceVote)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandRaceVote)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -112,7 +114,7 @@ func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
|
func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandRaceRelation)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandRaceRelation)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -122,7 +124,7 @@ func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
|
func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipClassCreate)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipClassCreate)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -132,7 +134,7 @@ func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
|
func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipClassMerge)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipClassMerge)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -142,7 +144,7 @@ func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
|
func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipClassRemove)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipClassRemove)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -152,7 +154,7 @@ func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupBreak)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupBreak)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -162,7 +164,7 @@ func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupLoad)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupLoad)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -172,7 +174,7 @@ func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupUnload)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupUnload)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -182,7 +184,7 @@ func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupSend)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupSend)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -192,7 +194,7 @@ func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupUpgrade(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupUpgrade(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupUpgrade)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupUpgrade)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -208,7 +210,7 @@ func commandShipGroupMerge(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupDismantle)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupDismantle)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -218,7 +220,7 @@ func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupTransfer)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupTransfer)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -228,7 +230,7 @@ func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error) {
|
func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupJoinFleet)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandShipGroupJoinFleet)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -238,7 +240,7 @@ func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
|
func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandFleetMerge)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandFleetMerge)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -248,7 +250,7 @@ func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
|
func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandFleetSend)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandFleetSend)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -258,7 +260,7 @@ func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
|
func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandScienceCreate)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandScienceCreate)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -268,7 +270,7 @@ func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
|
func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandScienceRemove)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandScienceRemove)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -278,7 +280,7 @@ func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
|
func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRename)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandPlanetRename)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -288,7 +290,7 @@ func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
|
func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandPlanetProduce)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandPlanetProduce)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -298,7 +300,7 @@ func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
|
func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRouteSet)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteSet)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -308,7 +310,7 @@ func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error) {
|
func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error) {
|
||||||
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRouteRemove)); err != nil {
|
if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteRemove)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return func(c controller.Ctrl) error {
|
return func(c controller.Ctrl) error {
|
||||||
@@ -319,17 +321,17 @@ func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error)
|
|||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
func unmarshallCommand[T rest.DecodableCommand](c json.RawMessage, v *T) (*T, error) {
|
func unmarshallCommand[T order.DecodableCommand](c json.RawMessage, v T) (T, error) {
|
||||||
if err := json.Unmarshal(c, v); err != nil {
|
if err := json.Unmarshal(c, v); err != nil {
|
||||||
return nil, err
|
return v, err
|
||||||
}
|
}
|
||||||
if err := validateCommand(v); err != nil {
|
if err := validateCommand(v); err != nil {
|
||||||
return nil, err
|
return v, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCommand(v any) error {
|
func validateCommand(v order.DecodableCommand) error {
|
||||||
if ve, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
if ve, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
if err := ve.Struct(v); err != nil {
|
if err := ve.Struct(v); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/iliadenisov/galaxy/internal/controller"
|
"github.com/iliadenisov/galaxy/internal/controller"
|
||||||
e "github.com/iliadenisov/galaxy/internal/error"
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ type CommandExecutor interface {
|
|||||||
GenerateTurn() (rest.StateResponse, error)
|
GenerateTurn() (rest.StateResponse, error)
|
||||||
GameState() (rest.StateResponse, error)
|
GameState() (rest.StateResponse, error)
|
||||||
Execute(cmd ...Command) error
|
Execute(cmd ...Command) error
|
||||||
|
ValidateOrder(actor string, cmd ...order.DecodableCommand) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Command func(controller.Ctrl) error
|
type Command func(controller.Ctrl) error
|
||||||
@@ -40,10 +42,10 @@ func NewDefaultConfigExecutor(configurer controller.Configurer) CommandExecutor
|
|||||||
return &executor{cfg: configurer}
|
return &executor{cfg: configurer}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executor) Execute(command ...Command) error {
|
func (e *executor) Execute(cmd ...Command) error {
|
||||||
return controller.ExecuteCommand(e.cfg, func(c controller.Ctrl) error {
|
return controller.ExecuteCommand(e.cfg, func(c controller.Ctrl) error {
|
||||||
for i := range command {
|
for i := range cmd {
|
||||||
if err := command[i](c); err != nil {
|
if err := cmd[i](c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +53,10 @@ func (e *executor) Execute(command ...Command) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *executor) ValidateOrder(actor string, cmd ...order.DecodableCommand) error {
|
||||||
|
return controller.ValidateOrder(e.cfg, actor, cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *executor) GenerateGame(races []string) (rest.StateResponse, error) {
|
func (e *executor) GenerateGame(races []string) (rest.StateResponse, error) {
|
||||||
s, err := controller.GenerateGame(e.cfg, races)
|
s, err := controller.GenerateGame(e.cfg, races)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,19 +96,17 @@ func stateResponse(s game.State) rest.StateResponse {
|
|||||||
return *result
|
return *result
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorResponded(c *gin.Context, err error) bool {
|
func errorResponse(c *gin.Context, err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var ge = new(e.GenericError)
|
|
||||||
|
|
||||||
if v, ok := err.(validator.ValidationErrors); ok {
|
if v, ok := err.(validator.ValidationErrors); ok {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": v.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": v.Error()})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.As(err, ge) {
|
if ge, ok := errors.AsType[*e.GenericError](err); ok {
|
||||||
switch ge.Code {
|
switch ge.Code {
|
||||||
case e.ErrGameNotInitialized:
|
case e.ErrGameNotInitialized:
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func InitHandler(c *gin.Context, executor CommandExecutor) {
|
func InitHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
var init rest.Init
|
var init rest.Init
|
||||||
if errorResponded(c, c.ShouldBindJSON(&init)) {
|
if errorResponse(c, c.ShouldBindJSON(&init)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ func InitHandler(c *gin.Context, executor CommandExecutor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s, err := executor.GenerateGame(races)
|
s, err := executor.GenerateGame(races)
|
||||||
if errorResponded(c, err) {
|
if errorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OrderHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
|
var cmd rest.Command
|
||||||
|
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
commands := make([]order.DecodableCommand, len(cmd.Commands))
|
||||||
|
for i := range cmd.Commands {
|
||||||
|
command, err := repo.ParseOrder(cmd.Commands[i], validateCommand)
|
||||||
|
if errorResponse(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
commands[i] = command
|
||||||
|
}
|
||||||
|
if len(commands) == 0 {
|
||||||
|
errorResponse(c, errors.New("no commands given"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorResponse(c, executor.ValidateOrder(cmd.Actor, commands...)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func StatusHandler(c *gin.Context, executor CommandExecutor) {
|
func StatusHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
state, err := executor.GameState()
|
state, err := executor.GameState()
|
||||||
|
|
||||||
if errorResponded(c, err) {
|
if errorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func TurnHandler(c *gin.Context, executor CommandExecutor) {
|
func TurnHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
state, err := executor.GenerateTurn()
|
state, err := executor.GenerateTurn()
|
||||||
|
|
||||||
if errorResponded(c, err) {
|
if errorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,941 @@
|
|||||||
|
package router_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrderRaceQuit(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandRaceQuit{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceQuit},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body)
|
||||||
|
|
||||||
|
// error: actor not set
|
||||||
|
payload.Actor = ""
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
req, _ = http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||||
|
|
||||||
|
payload.Actor = " "
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
req, _ = http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||||
|
|
||||||
|
// unrecognized command type
|
||||||
|
payload.Commands = []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandRaceQuit{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandType("-unknown-")},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
req, _ = http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||||
|
|
||||||
|
// error: no commands
|
||||||
|
payload = &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
}
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
req, _ = http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code, w.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderRaceVote(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
acceptor string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Empty acceptor", ""},
|
||||||
|
{http.StatusBadRequest, "Blank acceptor", " "},
|
||||||
|
{http.StatusBadRequest, "Invalid acceptor", "Race_👽"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandRaceVote{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
|
||||||
|
Acceptor: tc.acceptor,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderRaceRelation(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
relation string
|
||||||
|
acceptor string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request 1", "WAR", "Opponent"},
|
||||||
|
{commandNoErrorsStatus, "Valid request 2", "PEACE", "Opponent"},
|
||||||
|
{http.StatusBadRequest, "Empty relation", "", "Opponent"},
|
||||||
|
{http.StatusBadRequest, "Blank relation", " ", "Opponent"},
|
||||||
|
{http.StatusBadRequest, "Invalid relation", "Woina", "Opponent"},
|
||||||
|
{http.StatusBadRequest, "Empty acceptor", "WAR", ""},
|
||||||
|
{http.StatusBadRequest, "Blank acceptor", "WAR", " "},
|
||||||
|
{http.StatusBadRequest, "Invalid acceptor", "PEACE", "Race_👽"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandRaceRelation{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
|
||||||
|
Acceptor: tc.acceptor,
|
||||||
|
Relation: tc.relation,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipClassCreate(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
D float64
|
||||||
|
A int
|
||||||
|
W, S, C float64
|
||||||
|
name string
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{1, 0, 0, 0, 0, "Drone", commandNoErrorsStatus, "Simple Drone"},
|
||||||
|
{1, 1, 1, 0, 0, "Drone", commandNoErrorsStatus, "Armed Drone"},
|
||||||
|
{1, 0, 0, 1, 0, "Drone", commandNoErrorsStatus, "Shielded Drone"},
|
||||||
|
{1, 0, 0, 0, 1, "Drone", commandNoErrorsStatus, "Carrying Drone"},
|
||||||
|
{1, 0, 0, 0, 0, "", http.StatusBadRequest, "Empty name"},
|
||||||
|
{1, 0, 0, 0, 0, " ", http.StatusBadRequest, "Blank name"},
|
||||||
|
{1, 0, 0, 0, 0, "Drone🚀", http.StatusBadRequest, "Invalid name"},
|
||||||
|
{-0.5, 0, 0, 0, 0, "Drone", http.StatusBadRequest, "Drive less than 0"},
|
||||||
|
{0.9, 0, 0, 0, 0, "Drone", http.StatusBadRequest, "Drive less than 1"},
|
||||||
|
{1, 1, 0, 0, 0, "Drone", http.StatusBadRequest, "Ammo without Weapons"},
|
||||||
|
{1, 0, 1, 0, 0, "Drone", http.StatusBadRequest, "Weapons without Ammo"},
|
||||||
|
{1, -1, 1, 0, 0, "Drone", http.StatusBadRequest, "Ammo less than 0"},
|
||||||
|
{1, 1, 0.9, 0, 0, "Drone", http.StatusBadRequest, "Weapons less than 1"},
|
||||||
|
{1, 1, -0.5, 0, 0, "Drone", http.StatusBadRequest, "Weapons less than 0"},
|
||||||
|
{1, 0, 0, -0.5, 0, "Drone", http.StatusBadRequest, "Shields less than 0"},
|
||||||
|
{1, 0, 0, 0.9, 0, "Drone", http.StatusBadRequest, "Shields less than 1"},
|
||||||
|
{1, 0, 0, 0, -0.5, "Drone", http.StatusBadRequest, "Cargo less than 0"},
|
||||||
|
{1, 0, 0, 0, 0.9, "Drone", http.StatusBadRequest, "Cargo less than 1"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipClassCreate{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassCreate},
|
||||||
|
Name: tc.name,
|
||||||
|
Drive: tc.D,
|
||||||
|
Armament: tc.A,
|
||||||
|
Weapons: tc.W,
|
||||||
|
Shields: tc.S,
|
||||||
|
Cargo: tc.C,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipClassMerge(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", "Drone", "Spy"},
|
||||||
|
{http.StatusBadRequest, "Empty name", "", "Spy"},
|
||||||
|
{http.StatusBadRequest, "Blank name", " ", "Spy"},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Drone🚀", "Spy"},
|
||||||
|
{http.StatusBadRequest, "Empty name", "Drone", " "},
|
||||||
|
{http.StatusBadRequest, "Blank name", "Drone", " "},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Drone", "Spy🚀"},
|
||||||
|
{http.StatusBadRequest, "Equal names", "Drone", "Drone"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipClassMerge{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassMerge},
|
||||||
|
Name: tc.name,
|
||||||
|
Target: tc.target,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipClassRemove(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", "Drone"},
|
||||||
|
{http.StatusBadRequest, "Empty name", ""},
|
||||||
|
{http.StatusBadRequest, "Blank name", " "},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Drone🚀"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipClassRemove{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipClassRemove},
|
||||||
|
Name: tc.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupBreak(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
newId string
|
||||||
|
quantity int
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, validId2, 1},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", validId1, validId2, 0},
|
||||||
|
{http.StatusBadRequest, "Negative quantity", validId1, validId2, -1},
|
||||||
|
{http.StatusBadRequest, "Empty id", "", validId2, 1},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, validId2, 1},
|
||||||
|
{http.StatusBadRequest, "Empty newId", validId1, "", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid newId", validId1, invalidId, 1},
|
||||||
|
{http.StatusBadRequest, "Equal id and newId", validId1, validId1, 1},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupBreak{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupBreak},
|
||||||
|
ID: tc.id,
|
||||||
|
NewID: tc.newId,
|
||||||
|
Quantity: tc.quantity,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupLoad(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
cargo string
|
||||||
|
quantity float64
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "COL", 0},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", validId1, "MAT", 1},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", validId1, "CAP", 2},
|
||||||
|
{http.StatusBadRequest, "Invalid quantity", validId1, "COL", -0.5},
|
||||||
|
{http.StatusBadRequest, "Empty cargo", validId1, "", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid cargo", validId1, "IND", 1},
|
||||||
|
{http.StatusBadRequest, "Empty id", "", "COL", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, "COL", 1},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupLoad{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupLoad},
|
||||||
|
ID: tc.id,
|
||||||
|
Cargo: tc.cargo,
|
||||||
|
Quantity: tc.quantity,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupUnload(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
quantity float64
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, 0},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", validId1, 1},
|
||||||
|
{http.StatusBadRequest, "Invalid quantity", validId1, -0.5},
|
||||||
|
{http.StatusBadRequest, "Empty id", "", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, 1},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupUnload{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUnload},
|
||||||
|
ID: tc.id,
|
||||||
|
Quantity: tc.quantity,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupSend(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
destination int
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, 0},
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, 1},
|
||||||
|
{http.StatusBadRequest, "Invalid destination", validId1, -1},
|
||||||
|
{http.StatusBadRequest, "Empty id", "", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, 1},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupSend{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupSend},
|
||||||
|
ID: tc.id,
|
||||||
|
Destination: tc.destination,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupUpgrade(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
tech string
|
||||||
|
level float64
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "ALL", 0},
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "DRIVE", 1.1},
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "WEAPONS", 2.1},
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "SHIELDS", 3.1},
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", validId1, "CARGO", 4.1},
|
||||||
|
{http.StatusBadRequest, "Negative level", validId1, "DRIVE", -0.5},
|
||||||
|
{http.StatusBadRequest, "Invalid level 0.5", validId1, "DRIVE", 0.5},
|
||||||
|
{http.StatusBadRequest, "Invalid level 1.0", validId1, "DRIVE", 1.0},
|
||||||
|
{http.StatusBadRequest, "Empty id", "", "ALL", 0},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, "ALL", 0},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupUpgrade{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupUpgrade},
|
||||||
|
ID: tc.id,
|
||||||
|
Tech: tc.tech,
|
||||||
|
Level: tc.level,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupMerge(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupMerge{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupMerge},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupDismantle(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", validId1},
|
||||||
|
{http.StatusBadRequest, "Empty id", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupDismantle{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupDismantle},
|
||||||
|
ID: tc.id,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupTransfer(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
acceptor string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", validId1, "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Blank id", "", "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Empty acceptor", validId1, ""},
|
||||||
|
{http.StatusBadRequest, "Blank acceptor", validId1, " "},
|
||||||
|
{http.StatusBadRequest, "Invalid acceptor", validId1, "Race_👽"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupTransfer{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupTransfer},
|
||||||
|
ID: tc.id,
|
||||||
|
Acceptor: tc.acceptor,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderShipGroupJoinFleet(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", validId1, "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Blank id", "", "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Invalid id", invalidId, "AnotherRace"},
|
||||||
|
{http.StatusBadRequest, "Empty name", validId1, ""},
|
||||||
|
{http.StatusBadRequest, "Blank name", validId1, " "},
|
||||||
|
{http.StatusBadRequest, "Invalid name", validId1, "Fleet_🚢"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandShipGroupJoinFleet{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeShipGroupJoinFleet},
|
||||||
|
ID: tc.id,
|
||||||
|
Name: tc.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderFleetMerge(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", "Fleet", "Bomber"},
|
||||||
|
{http.StatusBadRequest, "Empty name", "", "Bomber"},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Fleet_🚢", "Bomber"},
|
||||||
|
{http.StatusBadRequest, "Empty target", "Fleet", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid target", "Fleet", "Bomber_🚢"},
|
||||||
|
{http.StatusBadRequest, "Equal name and target", "Fleet", "Fleet"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandFleetMerge{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetMerge},
|
||||||
|
Name: tc.name,
|
||||||
|
Target: tc.target,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderFleetSend(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
destination int
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", "Fleet", 0},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", "Fleet", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid destination", "Fleet", -1},
|
||||||
|
{http.StatusBadRequest, "Empty name", "", 1},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Fleet_🚢", 1},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandFleetSend{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeFleetSend},
|
||||||
|
Name: tc.name,
|
||||||
|
Destination: tc.destination,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderScienceCreate(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
D, W, S, C float64
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", 0.25, 0.25, 0.25, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Empty name", 0.25, 0.25, 0.25, 0.25, ""},
|
||||||
|
{http.StatusBadRequest, "Invalid name", 0.25, 0.25, 0.25, 0.25, "Science🧪"},
|
||||||
|
{http.StatusBadRequest, "Negative drive", -.5, 0.25, 0.25, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Negative weapons", 0.25, -.5, 0.25, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Negative shields", 0.25, 0.25, -.5, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Negative cargo", 0.25, 0.25, 0.25, -.5, "Science"},
|
||||||
|
{http.StatusBadRequest, "Too big drive", 1.1, 0.25, 0.25, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Too big weapons", 0.25, 1.05, 0.25, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Too big shields", 0.25, 0.25, 1.5, 0.25, "Science"},
|
||||||
|
{http.StatusBadRequest, "Too big cargo", 0.25, 0.25, 0.25, 1.01, "Science"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandScienceCreate{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceCreate},
|
||||||
|
Name: tc.name,
|
||||||
|
Drive: tc.D,
|
||||||
|
Weapons: tc.W,
|
||||||
|
Shields: tc.S,
|
||||||
|
Cargo: tc.C,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderScienceRemove(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request", "Drone"},
|
||||||
|
{http.StatusBadRequest, "Empty name", ""},
|
||||||
|
{http.StatusBadRequest, "Blank name", " "},
|
||||||
|
{http.StatusBadRequest, "Invalid name", "Science🧪"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandScienceRemove{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeScienceRemove},
|
||||||
|
Name: tc.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderPlanetRename(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
number int
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request #1", 0, "HW"},
|
||||||
|
{commandNoErrorsStatus, "Valid request #2", 1, "HW"},
|
||||||
|
{http.StatusBadRequest, "Invalid number", -1, "HW"},
|
||||||
|
{http.StatusBadRequest, "Empty name", 1, ""},
|
||||||
|
{http.StatusBadRequest, "Blank name", 1, " "},
|
||||||
|
{http.StatusBadRequest, "Invalid name", 1, "Planet🪐"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandPlanetRename{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRename},
|
||||||
|
Number: tc.number,
|
||||||
|
Name: tc.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderPlanetProduce(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
number int
|
||||||
|
production, subject string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request MAT", 0, "MAT", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request CAP", 1, "CAP", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request DRIVE", 2, "DRIVE", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request WEAPONS", 3, "WEAPONS", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request SHIELDS", 4, "SHIELDS", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request CARGO", 5, "CARGO", ""},
|
||||||
|
{commandNoErrorsStatus, "Valid request SCIENCE", 6, "SCIENCE", "Science"},
|
||||||
|
{commandNoErrorsStatus, "Valid request SHIP", 7, "SHIP", "Ship"},
|
||||||
|
{http.StatusBadRequest, "Empty production", 0, "", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid production", 0, "IND", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid planet", -1, "DRIVE", ""},
|
||||||
|
{http.StatusBadRequest, "Empty science subject", 6, "SCIENCE", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid science subject", 6, "SCIENCE", "Science🧪"},
|
||||||
|
{http.StatusBadRequest, "Empty ship subject", 6, "SHIP", ""},
|
||||||
|
{http.StatusBadRequest, "Invalid ship subject", 6, "SHIP", "Ship🚀"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandPlanetProduce{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetProduce},
|
||||||
|
Number: tc.number,
|
||||||
|
Production: tc.production,
|
||||||
|
Subject: tc.subject,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderPlanetRouteSet(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
origin, destination int
|
||||||
|
loadType string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request MAT", 1, 0, "MAT"},
|
||||||
|
{commandNoErrorsStatus, "Valid request CAP", 0, 1, "CAP"},
|
||||||
|
{commandNoErrorsStatus, "Valid request COL", 1, 2, "COL"},
|
||||||
|
{commandNoErrorsStatus, "Valid request EMP", 3, 0, "EMP"},
|
||||||
|
{http.StatusBadRequest, "Empty loadType", 0, 1, ""},
|
||||||
|
{http.StatusBadRequest, "Invalid loadType", 0, 1, "IND"},
|
||||||
|
{http.StatusBadRequest, "Invalid origin", -1, 1, "MAT"},
|
||||||
|
{http.StatusBadRequest, "Invalid destination", 1, -1, "MAT"},
|
||||||
|
{http.StatusBadRequest, "Origin equals destination", 1, 1, "COL"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandPlanetRouteSet{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteSet},
|
||||||
|
Origin: tc.origin,
|
||||||
|
Destination: tc.destination,
|
||||||
|
LoadType: tc.loadType,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderPlanetRouteRemove(t *testing.T) {
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
expectStatus int
|
||||||
|
description string
|
||||||
|
origin int
|
||||||
|
loadType string
|
||||||
|
}{
|
||||||
|
{commandNoErrorsStatus, "Valid request MAT", 0, "MAT"},
|
||||||
|
{commandNoErrorsStatus, "Valid request CAP", 1, "CAP"},
|
||||||
|
{commandNoErrorsStatus, "Valid request COL", 2, "COL"},
|
||||||
|
{commandNoErrorsStatus, "Valid request EMP", 0, "EMP"},
|
||||||
|
{http.StatusBadRequest, "Empty loadType", 1, ""},
|
||||||
|
{http.StatusBadRequest, "Invalid loadType", 1, "IND"},
|
||||||
|
{http.StatusBadRequest, "Invalid origin", -1, "MAT"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandPlanetRouteRemove{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypePlanetRouteRemove},
|
||||||
|
Origin: tc.origin,
|
||||||
|
LoadType: tc.loadType,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectStatus, w.Code, w.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleCommandOrder(t *testing.T) {
|
||||||
|
e := newExecutor()
|
||||||
|
r := setupRouterExecutor(e)
|
||||||
|
|
||||||
|
payload := &rest.Command{
|
||||||
|
Actor: commandDefaultActor,
|
||||||
|
Commands: []json.RawMessage{
|
||||||
|
encodeCommand(&order.CommandRaceRelation{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceRelation},
|
||||||
|
Acceptor: "Opponent",
|
||||||
|
Relation: "PEACE",
|
||||||
|
}),
|
||||||
|
encodeCommand(&order.CommandRaceVote{
|
||||||
|
CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote},
|
||||||
|
Acceptor: "Opponent",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload))
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, e.(*dummyExecutor).CommandsExecuted)
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ func setupRouter(executor handler.CommandExecutor) *gin.Engine {
|
|||||||
groupV1.GET("/status", func(ctx *gin.Context) { handler.StatusHandler(ctx, executor) })
|
groupV1.GET("/status", func(ctx *gin.Context) { handler.StatusHandler(ctx, executor) })
|
||||||
groupV1.POST("/init", func(ctx *gin.Context) { handler.InitHandler(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) })
|
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, executor) })
|
||||||
|
groupV1.PUT("/order", func(ctx *gin.Context) { handler.OrderHandler(ctx, executor) })
|
||||||
groupV1.PUT("/turn", func(ctx *gin.Context) { handler.TurnHandler(ctx, executor) })
|
groupV1.PUT("/turn", func(ctx *gin.Context) { handler.TurnHandler(ctx, executor) })
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -2,17 +2,40 @@ package router_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/order"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
"github.com/iliadenisov/galaxy/internal/router"
|
"github.com/iliadenisov/galaxy/internal/router"
|
||||||
"github.com/iliadenisov/galaxy/internal/router/handler"
|
"github.com/iliadenisov/galaxy/internal/router/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commandNoErrorsStatus = http.StatusNoContent
|
||||||
|
commandDefaultActor = "Gorlum"
|
||||||
|
apiCommandMethod = "PUT"
|
||||||
|
apiCommandPath = "/api/v1/command"
|
||||||
|
apiOrderPath = "/api/v1/order"
|
||||||
|
validId1 = id()
|
||||||
|
validId2 = id()
|
||||||
|
invalidId = "fd091c69-5976-4775-b2f9-7ba77735afb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func id() string {
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
type dummyExecutor struct {
|
type dummyExecutor struct {
|
||||||
CommandsExecuted int
|
CommandsExecuted int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *dummyExecutor) ValidateOrder(actor string, cmd ...order.DecodableCommand) error {
|
||||||
|
e.CommandsExecuted = len(cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *dummyExecutor) Execute(command ...handler.Command) error {
|
func (e *dummyExecutor) Execute(command ...handler.Command) error {
|
||||||
e.CommandsExecuted = len(command)
|
e.CommandsExecuted = len(command)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user