fix: game order api & tests
This commit is contained in:
@@ -46,23 +46,22 @@ type orderParam struct {
|
||||
|
||||
func GetOrderHandler(c *gin.Context, executor CommandExecutor) {
|
||||
p := &orderParam{}
|
||||
err := c.ShouldBindQuery(p)
|
||||
if errorResponse(c, err) {
|
||||
// ShouldBindQuery surfaces both validator failures and strconv parse
|
||||
// errors; both are client-side faults, so 400 is the correct mapping.
|
||||
if err := c.ShouldBindQuery(p); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
order, ok, err := executor.FetchOrder(p.Player, uint(p.Turn))
|
||||
o, ok, err := executor.FetchOrder(p.Player, uint(p.Turn))
|
||||
if errorResponse(c, err) {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
// there was no order previously sent by player
|
||||
// no order has been previously stored by the player for this turn
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
var cmd rest.Command
|
||||
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, order)
|
||||
c.JSON(http.StatusOK, o)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package router_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -9,7 +10,9 @@ import (
|
||||
"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) {
|
||||
@@ -940,3 +943,168 @@ func TestMultipleCommandOrder(t *testing.T) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -32,15 +32,40 @@ func id() string {
|
||||
|
||||
type dummyExecutor struct {
|
||||
CommandsExecuted int
|
||||
|
||||
// ValidateOrderResult, when non-nil, is returned from ValidateOrder.
|
||||
// When nil, ValidateOrder synthesises an order from the received args
|
||||
// so the response body is non-empty for status assertions.
|
||||
ValidateOrderResult *order.UserGamesOrder
|
||||
ValidateOrderErr error
|
||||
|
||||
// FetchOrder controls and observes calls to FetchOrder.
|
||||
FetchOrderActor string
|
||||
FetchOrderTurn uint
|
||||
FetchOrderResult *order.UserGamesOrder
|
||||
FetchOrderOK bool
|
||||
FetchOrderErr error
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) ValidateOrder(actor string, cmd ...order.DecodableCommand) (*order.UserGamesOrder, error) {
|
||||
e.CommandsExecuted = len(cmd)
|
||||
return nil, nil
|
||||
if e.ValidateOrderErr != nil {
|
||||
return nil, e.ValidateOrderErr
|
||||
}
|
||||
if e.ValidateOrderResult != nil {
|
||||
return e.ValidateOrderResult, nil
|
||||
}
|
||||
return &order.UserGamesOrder{
|
||||
GameID: uuid.New(),
|
||||
UpdatedAt: 1,
|
||||
Commands: append([]order.DecodableCommand(nil), cmd...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) FetchOrder(actor string, turn uint) (*order.UserGamesOrder, bool, error) {
|
||||
return nil, true, nil
|
||||
e.FetchOrderActor = actor
|
||||
e.FetchOrderTurn = turn
|
||||
return e.FetchOrderResult, e.FetchOrderOK, e.FetchOrderErr
|
||||
}
|
||||
|
||||
func (e *dummyExecutor) Execute(command ...handler.Command) error {
|
||||
|
||||
Reference in New Issue
Block a user