package router_test import ( "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "galaxy/model/order" "galaxy/model/rest" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) 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) // empty cmd[] is a valid PUT — the player cleared their draft; // the engine stores the empty batch and answers with the // canonical `UserGamesOrder` envelope. ValidateOrder receives a // zero-length variadic and the response carries no commands. payload = &rest.Command{ Actor: commandDefaultActor, } exec := &dummyExecutor{} emptyRouter := setupRouterExecutor(exec) w = httptest.NewRecorder() req, _ = http.NewRequest(apiCommandMethod, apiOrderPath, asBody(payload)) emptyRouter.ServeHTTP(w, req) assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body) assert.Equal(t, 0, exec.CommandsExecuted) var stored order.UserGamesOrder require.NoError(t, json.Unmarshal(w.Body.Bytes(), &stored)) assert.Empty(t, stored.Commands) } 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) } func TestPutOrderResponseBody(t *testing.T) { e := &dummyExecutor{ ValidateOrderResult: &order.UserGamesOrder{ GameID: uuid.New(), UpdatedAt: 1700, Commands: []order.DecodableCommand{ &order.CommandRaceVote{ CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote}, Acceptor: "Opponent", }, }, }, } r := setupRouterExecutor(e) payload := &rest.Command{ Actor: commandDefaultActor, Commands: []json.RawMessage{ 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) require.Equal(t, http.StatusAccepted, w.Code, w.Body) var got struct { GameID uuid.UUID `json:"game_id"` UpdatedAt int64 `json:"updatedAt"` Commands []json.RawMessage `json:"cmd"` } require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) assert.Equal(t, e.ValidateOrderResult.GameID, got.GameID) assert.Equal(t, e.ValidateOrderResult.UpdatedAt, got.UpdatedAt) assert.Len(t, got.Commands, 1) } func TestPutOrderEngineError(t *testing.T) { e := &dummyExecutor{ValidateOrderErr: errors.New("engine boom")} r := setupRouterExecutor(e) payload := &rest.Command{ Actor: commandDefaultActor, Commands: []json.RawMessage{ 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, http.StatusInternalServerError, w.Code, w.Body) } func TestGetOrderQueryValidation(t *testing.T) { for _, tc := range []struct { description string query string expectStatus int }{ {"Missing player param", "", http.StatusBadRequest}, {"Empty player", "?player=", http.StatusBadRequest}, {"Blank player", "?player=%20%20%20", http.StatusBadRequest}, {"Negative turn", "?player=Race_01&turn=-1", http.StatusBadRequest}, {"Non-numeric turn", "?player=Race_01&turn=abc", http.StatusBadRequest}, } { t.Run(tc.description, func(t *testing.T) { e := &dummyExecutor{} r := setupRouterExecutor(e) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, apiOrderPath+tc.query, nil) r.ServeHTTP(w, req) assert.Equal(t, tc.expectStatus, w.Code, w.Body) assert.Empty(t, e.FetchOrderActor, "FetchOrder must not be called on validation error") }) } } func TestGetOrderFound(t *testing.T) { stored := &order.UserGamesOrder{ GameID: uuid.New(), UpdatedAt: 4242, Commands: []order.DecodableCommand{ &order.CommandRaceVote{ CommandMeta: order.CommandMeta{CmdID: id(), CmdType: order.CommandTypeRaceVote}, Acceptor: "Opponent", }, }, } e := &dummyExecutor{ FetchOrderResult: stored, FetchOrderOK: true, } r := setupRouterExecutor(e) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, apiOrderPath+"?player=Race_01&turn=3", nil) r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body) assert.Equal(t, "Race_01", e.FetchOrderActor) assert.Equal(t, uint(3), e.FetchOrderTurn) var got struct { GameID uuid.UUID `json:"game_id"` UpdatedAt int64 `json:"updatedAt"` Commands []json.RawMessage `json:"cmd"` } require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) assert.Equal(t, stored.GameID, got.GameID) assert.Equal(t, stored.UpdatedAt, got.UpdatedAt) assert.Len(t, got.Commands, 1) } func TestGetOrderTurnDefaultsToZero(t *testing.T) { e := &dummyExecutor{ FetchOrderResult: &order.UserGamesOrder{GameID: uuid.New(), UpdatedAt: 1, Commands: []order.DecodableCommand{}}, FetchOrderOK: true, } r := setupRouterExecutor(e) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, apiOrderPath+"?player=Race_01", nil) r.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body) assert.Equal(t, uint(0), e.FetchOrderTurn) } func TestGetOrderNotFound(t *testing.T) { e := &dummyExecutor{FetchOrderOK: false} r := setupRouterExecutor(e) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, apiOrderPath+"?player=Race_01&turn=2", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code, w.Body) assert.Empty(t, w.Body.Bytes(), "204 response must carry no body") assert.Equal(t, "Race_01", e.FetchOrderActor) assert.Equal(t, uint(2), e.FetchOrderTurn) } func TestGetOrderEngineError(t *testing.T) { e := &dummyExecutor{FetchOrderErr: errors.New("engine boom")} r := setupRouterExecutor(e) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, apiOrderPath+"?player=Race_01&turn=0", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code, w.Body) }