From 7c4e2a6f88014017132ee32660af031456293e77 Mon Sep 17 00:00:00 2001 From: IliaDenisov Date: Wed, 11 Feb 2026 20:00:32 +0300 Subject: [PATCH] test: command api --- internal/controller/report_test.go | 2 +- internal/model/game/race.go | 4 +- internal/model/rest/command.go | 32 +- internal/router/command_test.go | 873 ++++++++++++++++++++++++++++- internal/router/validator.go | 6 +- 5 files changed, 865 insertions(+), 52 deletions(-) diff --git a/internal/controller/report_test.go b/internal/controller/report_test.go index f031cf5..008583e 100644 --- a/internal/controller/report_test.go +++ b/internal/controller/report_test.go @@ -46,7 +46,7 @@ func TestReportRace(t *testing.T) { assert.Equal(t, 0., float64(p.Industry)) assert.Equal(t, 1, int(p.Planets)) assert.Equal(t, 0., float64(p.Votes)) - assert.Equal(t, "War", p.Relation) + assert.Equal(t, "WAR", p.Relation) } } } diff --git a/internal/model/game/race.go b/internal/model/game/race.go index dafe409..dd7f211 100644 --- a/internal/model/game/race.go +++ b/internal/model/game/race.go @@ -9,8 +9,8 @@ import ( type Relation string const ( - RelationWar Relation = "War" - RelationPeace Relation = "Peace" + RelationWar Relation = "WAR" + RelationPeace Relation = "PEACE" ) var ( diff --git a/internal/model/rest/command.go b/internal/model/rest/command.go index e29b010..fcf380a 100644 --- a/internal/model/rest/command.go +++ b/internal/model/rest/command.go @@ -3,7 +3,7 @@ package rest import "encoding/json" type Command struct { - Actor string `json:"actor" binding:"required,notblank"` + Actor string `json:"actor" binding:"notblank"` Commands []json.RawMessage `json:"cmd" binding:"min=1"` } @@ -40,7 +40,7 @@ type DecodableCommand interface { } type CommandMeta struct { - Type CommandType `json:"@type" binding:"required,notblank"` + Type CommandType `json:"@type" binding:"notblank"` } func (cm CommandMeta) CommandType() CommandType { @@ -53,18 +53,18 @@ type CommandRaceQuit struct { type CommandRaceVote struct { CommandMeta - Acceptor string `json:"acceptor" binding:"required,notblank"` + Acceptor string `json:"acceptor" binding:"notblank,entity"` } type CommandRaceRelation struct { CommandMeta - Acceptor string `json:"acceptor" binding:"required,notblank"` - Relation string `json:"relation" binding:"required,notblank"` + Acceptor string `json:"acceptor" binding:"notblank,entity"` + Relation string `json:"relation" binding:"oneof=WAR PEACE"` } type CommandShipClassCreate struct { CommandMeta - Name string `json:"name" binding:"required,notblank,entity"` + 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"` @@ -74,8 +74,8 @@ type CommandShipClassCreate struct { type CommandShipClassMerge struct { CommandMeta - Name string `json:"name" binding:"required,notblank,entity,nefield=Target"` - Target string `json:"target" binding:"required,notblank,entity,nefield=Class"` + Name string `json:"name" binding:"notblank,entity,nefield=Target"` + Target string `json:"target" binding:"notblank,entity,nefield=Name"` } type CommandShipClassRemove struct { @@ -86,7 +86,7 @@ type CommandShipClassRemove struct { type CommandShipGroupLoad struct { CommandMeta ID string `json:"id" binding:"required,uuid_rfc4122"` - Cargo string `json:"cargo" binding:"required,notblank,oneof=COL MAT CAP"` + Cargo string `json:"cargo" binding:"oneof=COL MAT CAP"` Quantity float64 `json:"quantity" binding:"gte=0"` } @@ -106,7 +106,7 @@ 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:"gte=1"` + Level float64 `json:"level" binding:"eq=0|gt=1"` } type CommandShipGroupMerge struct { @@ -115,8 +115,8 @@ type CommandShipGroupMerge struct { type CommandShipGroupBreak struct { CommandMeta - ID string `json:"id" binding:"required,uuid_rfc4122"` - NewID string `json:"newId" binding:"required,uuid_rfc4122"` + 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"` } @@ -128,7 +128,7 @@ type CommandShipGroupDismantle struct { type CommandShipGroupTransfer struct { CommandMeta ID string `json:"id" binding:"required,uuid_rfc4122"` - Acceptor string `json:"acceptor" binding:"required,notblank"` + Acceptor string `json:"acceptor" binding:"required,notblank,entity"` } type CommandShipGroupJoinFleet struct { @@ -173,18 +173,18 @@ 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"` + 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=Number"` + 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,nefield=Destination"` + Origin int `json:"fromPlanetNumber" binding:"gte=0"` LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"` } diff --git a/internal/router/command_test.go b/internal/router/command_test.go index 12b4ec0..bdbb77b 100644 --- a/internal/router/command_test.go +++ b/internal/router/command_test.go @@ -6,60 +6,150 @@ import ( "net/http/httptest" "testing" + "github.com/google/uuid" "github.com/iliadenisov/galaxy/internal/model/rest" "github.com/stretchr/testify/assert" ) var ( - commandNoErrorCode = http.StatusNoContent + 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 TestCommand(t *testing.T) { +func TestCommandRaceQuit(t *testing.T) { r := setupRouter() payload := &rest.Command{ - Actor: "SomeRace", + Actor: commandDefaultActor, Commands: []json.RawMessage{ - encodeCommand(&rest.CommandRaceVote{ - CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceVote}, - Acceptor: "AnotherRace", + encodeCommand(&rest.CommandRaceQuit{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceQuit}, }), }, } w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", "/api/v1/command", asBody(payload)) + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) r.ServeHTTP(w, req) - assert.Equal(t, commandNoErrorCode, w.Code, w.Body) + assert.Equal(t, commandNoErrorsStatus, w.Code, w.Body) - // error: notblank validator + // error: actor not set payload.Actor = "" w = httptest.NewRecorder() - req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload)) + req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) payload.Actor = " " w = httptest.NewRecorder() - req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload)) + req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) + + // unrecognized command type + payload.Commands = []json.RawMessage{ + encodeCommand(&rest.CommandRaceQuit{ + CommandMeta: rest.CommandMeta{Type: rest.CommandType("-unknown-")}, + }), + } + w = httptest.NewRecorder() + req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) // error: no commands payload = &rest.Command{ - Actor: "SomeRace", + Actor: commandDefaultActor, } w = httptest.NewRecorder() - req, _ = http.NewRequest("PUT", "/api/v1/command", asBody(payload)) + req, _ = http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) } +func TestCommandRaceVote(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(&rest.CommandRaceVote{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceVote}, + Acceptor: tc.acceptor, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandRaceRelation(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(&rest.CommandRaceRelation{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceRelation}, + Acceptor: tc.acceptor, + Relation: tc.relation, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + func TestCommandShipClassCreate(t *testing.T) { r := setupRouter() @@ -67,32 +157,36 @@ func TestCommandShipClassCreate(t *testing.T) { D float64 A int W, S, C float64 + name string expectStatus int description string }{ - {1, 0, 0, 0, 0, commandNoErrorCode, "Simple Drone"}, - {1, 1, 1, 0, 0, commandNoErrorCode, "Armed Drone"}, - {1, 0, 0, 1, 0, commandNoErrorCode, "Shielded Drone"}, - {1, 0, 0, 0, 1, commandNoErrorCode, "Carrying Drone"}, - {-0.5, 0, 0, 0, 0, http.StatusBadRequest, "Drive less than 0"}, - {0.9, 0, 0, 0, 0, http.StatusBadRequest, "Drive less than 1"}, - {1, 1, 0, 0, 0, http.StatusBadRequest, "Ammo without Weapons"}, - {1, 0, 1, 0, 0, http.StatusBadRequest, "Weapons without Ammo"}, - {1, -1, 1, 0, 0, http.StatusBadRequest, "Ammo less than 0"}, - {1, 1, 0.9, 0, 0, http.StatusBadRequest, "Weapons less than 1"}, - {1, 1, -0.5, 0, 0, http.StatusBadRequest, "Weapons less than 0"}, - {1, 0, 0, -0.5, 0, http.StatusBadRequest, "Shields less than 0"}, - {1, 0, 0, 0.9, 0, http.StatusBadRequest, "Shields less than 1"}, - {1, 0, 0, 0, -0.5, http.StatusBadRequest, "Cargo less than 0"}, - {1, 0, 0, 0, 0.9, http.StatusBadRequest, "Cargo less than 1"}, + {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: "SomeRace", + Actor: commandDefaultActor, Commands: []json.RawMessage{ encodeCommand(&rest.CommandShipClassCreate{ CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassCreate}, - Name: "Ship", + Name: tc.name, Drive: tc.D, Armament: tc.A, Weapons: tc.W, @@ -103,7 +197,724 @@ func TestCommandShipClassCreate(t *testing.T) { } w := httptest.NewRecorder() - req, _ := http.NewRequest("PUT", "/api/v1/command", asBody(payload)) + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipClassMerge(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(&rest.CommandShipClassMerge{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassMerge}, + Name: tc.name, + Target: tc.target, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipClassRemove(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(&rest.CommandShipClassRemove{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipClassRemove}, + Name: tc.name, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupBreak(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(&rest.CommandShipGroupBreak{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupBreak}, + ID: tc.id, + NewID: tc.newId, + Quantity: tc.quantity, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupLoad(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(&rest.CommandShipGroupLoad{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupLoad}, + ID: tc.id, + Cargo: tc.cargo, + Quantity: tc.quantity, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupUnload(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(&rest.CommandShipGroupUnload{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupUnload}, + ID: tc.id, + Quantity: tc.quantity, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupSend(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(&rest.CommandShipGroupSend{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupSend}, + ID: tc.id, + Destination: tc.destination, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupUpgrade(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(&rest.CommandShipGroupUpgrade{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupUpgrade}, + ID: tc.id, + Tech: tc.tech, + Level: tc.level, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupMerge(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(&rest.CommandShipGroupMerge{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupMerge}, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupDismantle(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(&rest.CommandShipGroupDismantle{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupDismantle}, + ID: tc.id, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupTransfer(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(&rest.CommandShipGroupTransfer{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupTransfer}, + ID: tc.id, + Acceptor: tc.acceptor, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandShipGroupJoinFleet(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(&rest.CommandShipGroupJoinFleet{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeShipGroupJoinFleet}, + ID: tc.id, + Name: tc.name, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandFleetMerge(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(&rest.CommandFleetMerge{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeFleetMerge}, + Name: tc.name, + Target: tc.target, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandFleetSend(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(&rest.CommandFleetSend{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeFleetSend}, + Name: tc.name, + Destination: tc.destination, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandScienceCreate(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(&rest.CommandScienceCreate{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeScienceCreate}, + Name: tc.name, + Drive: tc.D, + Weapons: tc.W, + Shields: tc.S, + Cargo: tc.C, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandScienceRemove(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(&rest.CommandScienceRemove{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypeScienceRemove}, + Name: tc.name, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandPlanetRename(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(&rest.CommandPlanetRename{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRename}, + Number: tc.number, + Name: tc.name, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandPlanetProduce(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(&rest.CommandPlanetProduce{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetProduce}, + Number: tc.number, + Production: tc.production, + Subject: tc.subject, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandPlanetRouteSet(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(&rest.CommandPlanetRouteSet{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRouteSet}, + Origin: tc.origin, + Destination: tc.destination, + LoadType: tc.loadType, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) + r.ServeHTTP(w, req) + + assert.Equal(t, tc.expectStatus, w.Code, w.Body) + }) + } +} + +func TestCommandPlanetRouteRemove(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(&rest.CommandPlanetRouteRemove{ + CommandMeta: rest.CommandMeta{Type: rest.CommandTypePlanetRouteRemove}, + Origin: tc.origin, + LoadType: tc.loadType, + }), + }, + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest(apiCommandMethod, apiCommandPath, asBody(payload)) r.ServeHTTP(w, req) assert.Equal(t, tc.expectStatus, w.Code, w.Body) diff --git a/internal/router/validator.go b/internal/router/validator.go index 4c10408..af4ff86 100644 --- a/internal/router/validator.go +++ b/internal/router/validator.go @@ -31,8 +31,10 @@ var productionTypeStringValidator validator.Func = func(fl validator.FieldLevel) v, ok := fl.Field().Interface().(string) if ok { f := fl.Parent().FieldByName(fl.Param()) - if s, ok := f.Interface().(string); ok && (s == "SHIP" || s == "SCIENCE") && len(strings.TrimSpace(v)) == 0 { - return false + if f.String() == "SHIP" || f.String() == "SCIENCE" { + if _, ok := util.ValidateTypeName(v); !ok { + return false + } } } return true