ui/phase-14: rename planet end-to-end + order read-back
Wires the first end-to-end command through the full pipeline:
inspector rename action → local order draft → user.games.order
submit → optimistic overlay on map / inspector → server hydration
on cache miss via the new user.games.order.get message type.
Backend: GET /api/v1/user/games/{id}/orders forwards to engine
GET /api/v1/order. Gateway parses the engine PUT response into the
extended UserGamesOrderResponse FBS envelope and adds
executeUserGamesOrderGet for the read-back path. Frontend ports
ValidateTypeName to TS, lands the inline rename editor + Submit
button, and exposes a renderedReport context so consumers see the
overlay-applied snapshot.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+321
-5
@@ -1,6 +1,7 @@
|
||||
package transcoder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -9,8 +10,117 @@ import (
|
||||
fbs "galaxy/schema/fbs/order"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// JSONToUserGamesOrder decodes the engine's JSON response body for
|
||||
// `PUT /api/v1/order` and `GET /api/v1/order` into the typed
|
||||
// `*model.UserGamesOrder`. The model's `Commands` field is an
|
||||
// interface (`order.DecodableCommand`), so plain `json.Unmarshal`
|
||||
// can't reach it — this helper performs the same per-`@type`
|
||||
// dispatch as `game/internal/repo.ParseOrder`, but stays inside the
|
||||
// shared transcoder so non-engine callers (the gateway, tests) can
|
||||
// reuse it without crossing module boundaries.
|
||||
func JSONToUserGamesOrder(payload []byte) (*model.UserGamesOrder, error) {
|
||||
if len(payload) == 0 {
|
||||
return nil, errors.New("decode user games order json: payload is empty")
|
||||
}
|
||||
var raw struct {
|
||||
GameID string `json:"game_id"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
Commands []json.RawMessage `json:"cmd"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &raw); err != nil {
|
||||
return nil, fmt.Errorf("decode user games order json: %w", err)
|
||||
}
|
||||
out := &model.UserGamesOrder{
|
||||
UpdatedAt: raw.UpdatedAt,
|
||||
}
|
||||
if raw.GameID != "" {
|
||||
gameID, err := uuid.Parse(raw.GameID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode user games order json: invalid game_id %q: %w", raw.GameID, err)
|
||||
}
|
||||
out.GameID = gameID
|
||||
}
|
||||
if len(raw.Commands) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
out.Commands = make([]model.DecodableCommand, len(raw.Commands))
|
||||
for i, rawCmd := range raw.Commands {
|
||||
cmd, err := decodeJSONCommand(rawCmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode user games order json command %d: %w", i, err)
|
||||
}
|
||||
out.Commands[i] = cmd
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func decodeJSONCommand(raw json.RawMessage) (model.DecodableCommand, error) {
|
||||
meta := new(model.CommandMeta)
|
||||
if err := json.Unmarshal(raw, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch meta.CmdType {
|
||||
case model.CommandTypeRaceQuit:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandRaceQuit))
|
||||
case model.CommandTypeRaceVote:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandRaceVote))
|
||||
case model.CommandTypeRaceRelation:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandRaceRelation))
|
||||
case model.CommandTypeShipClassCreate:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipClassCreate))
|
||||
case model.CommandTypeShipClassMerge:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipClassMerge))
|
||||
case model.CommandTypeShipClassRemove:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipClassRemove))
|
||||
case model.CommandTypeShipGroupBreak:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupBreak))
|
||||
case model.CommandTypeShipGroupLoad:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupLoad))
|
||||
case model.CommandTypeShipGroupUnload:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupUnload))
|
||||
case model.CommandTypeShipGroupSend:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupSend))
|
||||
case model.CommandTypeShipGroupUpgrade:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupUpgrade))
|
||||
case model.CommandTypeShipGroupMerge:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupMerge))
|
||||
case model.CommandTypeShipGroupDismantle:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupDismantle))
|
||||
case model.CommandTypeShipGroupTransfer:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupTransfer))
|
||||
case model.CommandTypeShipGroupJoinFleet:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandShipGroupJoinFleet))
|
||||
case model.CommandTypeFleetMerge:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandFleetMerge))
|
||||
case model.CommandTypeFleetSend:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandFleetSend))
|
||||
case model.CommandTypeScienceCreate:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandScienceCreate))
|
||||
case model.CommandTypeScienceRemove:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandScienceRemove))
|
||||
case model.CommandTypePlanetRename:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandPlanetRename))
|
||||
case model.CommandTypePlanetProduce:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandPlanetProduce))
|
||||
case model.CommandTypePlanetRouteSet:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandPlanetRouteSet))
|
||||
case model.CommandTypePlanetRouteRemove:
|
||||
return unmarshalJSONCommand(raw, new(model.CommandPlanetRouteRemove))
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown command type: %s", meta.CmdType)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalJSONCommand[T model.DecodableCommand](raw json.RawMessage, v T) (model.DecodableCommand, error) {
|
||||
if err := json.Unmarshal(raw, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type encodedCommand struct {
|
||||
cmdID string
|
||||
cmdApplied *bool
|
||||
@@ -955,14 +1065,220 @@ func EmptyUserGamesCommandResponsePayload() []byte {
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
|
||||
// EmptyUserGamesOrderResponsePayload mirrors
|
||||
// EmptyUserGamesCommandResponsePayload for `MessageTypeUserGamesOrder`.
|
||||
func EmptyUserGamesOrderResponsePayload() []byte {
|
||||
builder := flatbuffers.NewBuilder(16)
|
||||
// UserGamesOrderResponseToPayload encodes the engine's response body
|
||||
// for `PUT /api/v1/order` into the wire FlatBuffers envelope expected
|
||||
// for `MessageTypeUserGamesOrder`. The engine populates per-command
|
||||
// `cmdApplied` / `cmdErrorCode` fields, and they round-trip into the
|
||||
// FBS `CommandItem` entries unchanged. A nil response is encoded as
|
||||
// an empty envelope so the gateway can fall back to a batch-level
|
||||
// "ok" answer when the engine body is unavailable.
|
||||
func UserGamesOrderResponseToPayload(req *model.UserGamesOrder) ([]byte, error) {
|
||||
builder := flatbuffers.NewBuilder(1024)
|
||||
if req == nil {
|
||||
fbs.UserGamesOrderResponseStart(builder)
|
||||
offset := fbs.UserGamesOrderResponseEnd(builder)
|
||||
fbs.FinishUserGamesOrderResponseBuffer(builder, offset)
|
||||
return builder.FinishedBytes(), nil
|
||||
}
|
||||
|
||||
commandsVec, err := encodeCommandItemVector(builder, req.Commands, "user games order response")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fbs.UserGamesOrderResponseStart(builder)
|
||||
hi, lo := uuidToHiLo(req.GameID)
|
||||
fbs.UserGamesOrderResponseAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
|
||||
fbs.UserGamesOrderResponseAddUpdatedAt(builder, req.UpdatedAt)
|
||||
if commandsVec != 0 {
|
||||
fbs.UserGamesOrderResponseAddCommands(builder, commandsVec)
|
||||
}
|
||||
offset := fbs.UserGamesOrderResponseEnd(builder)
|
||||
fbs.FinishUserGamesOrderResponseBuffer(builder, offset)
|
||||
return builder.FinishedBytes()
|
||||
return builder.FinishedBytes(), nil
|
||||
}
|
||||
|
||||
// PayloadToUserGamesOrderGet decodes the FlatBuffers payload of
|
||||
// `MessageTypeUserGamesOrderGet` into the typed model. `Turn` is
|
||||
// validated to be non-negative; the gateway and backend reject
|
||||
// negative values before forwarding to the engine.
|
||||
func PayloadToUserGamesOrderGet(data []byte) (result *model.UserGamesOrderGet, err error) {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("decode user games order get payload: data is empty")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
result = nil
|
||||
err = fmt.Errorf("decode user games order get payload: panic recovered: %v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
flat := fbs.GetRootAsUserGamesOrderGet(data, 0)
|
||||
gameID := flat.GameId(nil)
|
||||
if gameID == nil {
|
||||
return nil, errors.New("decode user games order get payload: game_id is missing")
|
||||
}
|
||||
turn := flat.Turn()
|
||||
if turn < 0 {
|
||||
return nil, fmt.Errorf("decode user games order get payload: turn must be non-negative, got %d", turn)
|
||||
}
|
||||
return &model.UserGamesOrderGet{
|
||||
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
||||
Turn: int(turn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UserGamesOrderGetToPayload encodes a `model.UserGamesOrderGet`
|
||||
// request into FlatBuffers bytes suitable for the authenticated
|
||||
// gateway transport.
|
||||
func UserGamesOrderGetToPayload(req *model.UserGamesOrderGet) ([]byte, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("encode user games order get payload: request is nil")
|
||||
}
|
||||
if req.Turn < 0 {
|
||||
return nil, fmt.Errorf("encode user games order get payload: turn must be non-negative, got %d", req.Turn)
|
||||
}
|
||||
builder := flatbuffers.NewBuilder(64)
|
||||
fbs.UserGamesOrderGetStart(builder)
|
||||
hi, lo := uuidToHiLo(req.GameID)
|
||||
fbs.UserGamesOrderGetAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
|
||||
fbs.UserGamesOrderGetAddTurn(builder, int64(req.Turn))
|
||||
offset := fbs.UserGamesOrderGetEnd(builder)
|
||||
fbs.FinishUserGamesOrderGetBuffer(builder, offset)
|
||||
return builder.FinishedBytes(), nil
|
||||
}
|
||||
|
||||
// UserGamesOrderGetResponseToPayload encodes the typed response of
|
||||
// `MessageTypeUserGamesOrderGet`. `found = false` corresponds to the
|
||||
// engine's `204 No Content` answer; `order` is omitted in that case.
|
||||
func UserGamesOrderGetResponseToPayload(order *model.UserGamesOrder, found bool) ([]byte, error) {
|
||||
builder := flatbuffers.NewBuilder(1024)
|
||||
|
||||
var orderOffset flatbuffers.UOffsetT
|
||||
if found && order != nil {
|
||||
commandsVec, err := encodeCommandItemVector(builder, order.Commands, "user games order get response")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fbs.UserGamesOrderStart(builder)
|
||||
hi, lo := uuidToHiLo(order.GameID)
|
||||
fbs.UserGamesOrderAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
|
||||
fbs.UserGamesOrderAddUpdatedAt(builder, order.UpdatedAt)
|
||||
if commandsVec != 0 {
|
||||
fbs.UserGamesOrderAddCommands(builder, commandsVec)
|
||||
}
|
||||
orderOffset = fbs.UserGamesOrderEnd(builder)
|
||||
}
|
||||
|
||||
fbs.UserGamesOrderGetResponseStart(builder)
|
||||
fbs.UserGamesOrderGetResponseAddFound(builder, found)
|
||||
if orderOffset != 0 {
|
||||
fbs.UserGamesOrderGetResponseAddOrder(builder, orderOffset)
|
||||
}
|
||||
offset := fbs.UserGamesOrderGetResponseEnd(builder)
|
||||
fbs.FinishUserGamesOrderGetResponseBuffer(builder, offset)
|
||||
return builder.FinishedBytes(), nil
|
||||
}
|
||||
|
||||
// PayloadToUserGamesOrderResponse decodes the engine's PUT response
|
||||
// envelope into a typed `*UserGamesOrder`. Empty payloads decode to
|
||||
// nil so callers can fall back to batch-level handling without a
|
||||
// dedicated marker.
|
||||
func PayloadToUserGamesOrderResponse(data []byte) (result *model.UserGamesOrder, err error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
result = nil
|
||||
err = fmt.Errorf("decode user games order response payload: panic recovered: %v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
flat := fbs.GetRootAsUserGamesOrderResponse(data, 0)
|
||||
gameID := flat.GameId(nil)
|
||||
if gameID == nil {
|
||||
// Empty envelope (gateway fallback). The caller treats this
|
||||
// as "no per-command detail" and synthesises a batch-level
|
||||
// answer.
|
||||
if flat.CommandsLength() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("decode user games order response payload: game_id is missing")
|
||||
}
|
||||
out := &model.UserGamesOrder{
|
||||
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
||||
UpdatedAt: flat.UpdatedAt(),
|
||||
}
|
||||
count := flat.CommandsLength()
|
||||
if count > 0 {
|
||||
out.Commands = make([]model.DecodableCommand, count)
|
||||
flatCommand := new(fbs.CommandItem)
|
||||
for i := 0; i < count; i++ {
|
||||
if !flat.Commands(flatCommand, i) {
|
||||
return nil, fmt.Errorf("decode user games order response %d: command item is missing", i)
|
||||
}
|
||||
cmd, decodeErr := decodeOrderCommand(flatCommand, i)
|
||||
if decodeErr != nil {
|
||||
return nil, decodeErr
|
||||
}
|
||||
out.Commands[i] = cmd
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// PayloadToUserGamesOrderGetResponse decodes the FlatBuffers response
|
||||
// of `MessageTypeUserGamesOrderGet`. When `found = false`, returns
|
||||
// `(nil, false, nil)` matching the engine's `204 No Content`
|
||||
// semantics.
|
||||
func PayloadToUserGamesOrderGetResponse(data []byte) (order *model.UserGamesOrder, found bool, err error) {
|
||||
if len(data) == 0 {
|
||||
return nil, false, errors.New("decode user games order get response payload: data is empty")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
order = nil
|
||||
found = false
|
||||
err = fmt.Errorf("decode user games order get response payload: panic recovered: %v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
flat := fbs.GetRootAsUserGamesOrderGetResponse(data, 0)
|
||||
if !flat.Found() {
|
||||
return nil, false, nil
|
||||
}
|
||||
inner := flat.Order(nil)
|
||||
if inner == nil {
|
||||
return nil, true, errors.New("decode user games order get response payload: order is missing while found=true")
|
||||
}
|
||||
gameID := inner.GameId(nil)
|
||||
if gameID == nil {
|
||||
return nil, true, errors.New("decode user games order get response payload: order.game_id is missing")
|
||||
}
|
||||
out := &model.UserGamesOrder{
|
||||
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
||||
UpdatedAt: inner.UpdatedAt(),
|
||||
}
|
||||
count := inner.CommandsLength()
|
||||
if count > 0 {
|
||||
out.Commands = make([]model.DecodableCommand, count)
|
||||
flatCommand := new(fbs.CommandItem)
|
||||
for i := 0; i < count; i++ {
|
||||
if !inner.Commands(flatCommand, i) {
|
||||
return nil, true, fmt.Errorf("decode user games order get response %d: command item is missing", i)
|
||||
}
|
||||
cmd, decodeErr := decodeOrderCommand(flatCommand, i)
|
||||
if decodeErr != nil {
|
||||
return nil, true, decodeErr
|
||||
}
|
||||
out.Commands[i] = cmd
|
||||
}
|
||||
}
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
// encodeCommandItemVector serialises a slice of DecodableCommand into a
|
||||
|
||||
@@ -77,6 +77,160 @@ func TestUserGamesCommandRejectsNilAndEmpty(t *testing.T) {
|
||||
if _, err := PayloadToUserGamesOrder(nil); err == nil {
|
||||
t.Fatalf("expected error decoding empty user games order")
|
||||
}
|
||||
if _, err := UserGamesOrderGetToPayload(nil); err == nil {
|
||||
t.Fatalf("expected error encoding nil user games order get")
|
||||
}
|
||||
if _, err := PayloadToUserGamesOrderGet(nil); err == nil {
|
||||
t.Fatalf("expected error decoding empty user games order get")
|
||||
}
|
||||
if _, _, err := PayloadToUserGamesOrderGetResponse(nil); err == nil {
|
||||
t.Fatalf("expected error decoding empty user games order get response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderResponsePayloadRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
applied := true
|
||||
rejected := false
|
||||
errCode := 7
|
||||
source := &model.UserGamesOrder{
|
||||
GameID: uuid.MustParse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
|
||||
UpdatedAt: 99,
|
||||
Commands: []model.DecodableCommand{
|
||||
&model.CommandPlanetRename{
|
||||
CommandMeta: commandMeta("cmd-1", model.CommandTypePlanetRename, &applied, nil),
|
||||
Number: 5,
|
||||
Name: "alpha",
|
||||
},
|
||||
&model.CommandPlanetRename{
|
||||
CommandMeta: commandMeta("cmd-2", model.CommandTypePlanetRename, &rejected, &errCode),
|
||||
Number: 6,
|
||||
Name: "beta",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := UserGamesOrderResponseToPayload(source)
|
||||
if err != nil {
|
||||
t.Fatalf("encode user games order response: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := PayloadToUserGamesOrderResponse(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode user games order response: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(source, decoded) {
|
||||
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderResponseEmptyPayload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload, err := UserGamesOrderResponseToPayload(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("encode empty user games order response: %v", err)
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
t.Fatal("empty envelope payload must be non-zero length")
|
||||
}
|
||||
|
||||
decoded, err := PayloadToUserGamesOrderResponse(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode empty user games order response: %v", err)
|
||||
}
|
||||
if decoded != nil {
|
||||
t.Fatalf("empty envelope must decode to nil, got %#v", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderGetPayloadRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := &model.UserGamesOrderGet{
|
||||
GameID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
|
||||
Turn: 7,
|
||||
}
|
||||
|
||||
payload, err := UserGamesOrderGetToPayload(source)
|
||||
if err != nil {
|
||||
t.Fatalf("encode user games order get: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := PayloadToUserGamesOrderGet(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode user games order get: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(source, decoded) {
|
||||
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderGetRejectsNegativeTurn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := UserGamesOrderGetToPayload(&model.UserGamesOrderGet{
|
||||
GameID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
|
||||
Turn: -1,
|
||||
}); err == nil {
|
||||
t.Fatalf("expected error encoding negative turn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderGetResponseRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
applied := true
|
||||
stored := &model.UserGamesOrder{
|
||||
GameID: uuid.MustParse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
|
||||
UpdatedAt: 1234,
|
||||
Commands: []model.DecodableCommand{
|
||||
&model.CommandPlanetRename{
|
||||
CommandMeta: commandMeta("cmd-1", model.CommandTypePlanetRename, &applied, nil),
|
||||
Number: 5,
|
||||
Name: "stored",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := UserGamesOrderGetResponseToPayload(stored, true)
|
||||
if err != nil {
|
||||
t.Fatalf("encode user games order get response: %v", err)
|
||||
}
|
||||
|
||||
decoded, found, err := PayloadToUserGamesOrderGetResponse(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode user games order get response: %v", err)
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("expected found=true round-trip")
|
||||
}
|
||||
if !reflect.DeepEqual(stored, decoded) {
|
||||
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", stored, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGamesOrderGetResponseNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload, err := UserGamesOrderGetResponseToPayload(nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("encode not-found response: %v", err)
|
||||
}
|
||||
|
||||
decoded, found, err := PayloadToUserGamesOrderGetResponse(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode not-found response: %v", err)
|
||||
}
|
||||
if found {
|
||||
t.Fatal("expected found=false")
|
||||
}
|
||||
if decoded != nil {
|
||||
t.Fatalf("expected nil order, got %#v", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64ToInt(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user