Files
galaxy-game/pkg/transcoder/order_test.go
T
2026-03-31 19:16:34 +02:00

334 lines
11 KiB
Go

package transcoder
import (
"reflect"
"strconv"
"strings"
"testing"
model "galaxy/model/order"
fbs "galaxy/schema/fbs/order"
flatbuffers "github.com/google/flatbuffers/go"
)
func TestOrderToPayloadAndPayloadToOrderRoundTrip(t *testing.T) {
t.Parallel()
appliedTrue := true
appliedFalse := false
errZero := 0
errThree := 3
errSeven := 7
source := &model.Order{
UpdatedAt: 42,
Commands: []model.DecodableCommand{
&model.CommandRaceQuit{CommandMeta: commandMeta("cmd-01", model.CommandTypeRaceQuit, &appliedTrue, &errZero)},
&model.CommandRaceVote{CommandMeta: commandMeta("cmd-02", model.CommandTypeRaceVote, nil, nil), Acceptor: "race-a"},
&model.CommandRaceRelation{CommandMeta: commandMeta("cmd-03", model.CommandTypeRaceRelation, &appliedFalse, nil), Acceptor: "race-b", Relation: "WAR"},
&model.CommandShipClassCreate{CommandMeta: commandMeta("cmd-04", model.CommandTypeShipClassCreate, nil, &errThree), Name: "frigate", Drive: 1.5, Armament: 5, Weapons: 2.5, Shields: 3.5, Cargo: 4.5},
&model.CommandShipClassMerge{CommandMeta: commandMeta("cmd-05", model.CommandTypeShipClassMerge, nil, nil), Name: "alpha", Target: "beta"},
&model.CommandShipClassRemove{CommandMeta: commandMeta("cmd-06", model.CommandTypeShipClassRemove, nil, nil), Name: "obsolete"},
&model.CommandShipGroupBreak{CommandMeta: commandMeta("cmd-07", model.CommandTypeShipGroupBreak, nil, nil), ID: "group-1", NewID: "group-2", Quantity: 12},
&model.CommandShipGroupLoad{CommandMeta: commandMeta("cmd-08", model.CommandTypeShipGroupLoad, nil, nil), ID: "group-3", Cargo: "MAT", Quantity: 7.25},
&model.CommandShipGroupUnload{CommandMeta: commandMeta("cmd-09", model.CommandTypeShipGroupUnload, nil, nil), ID: "group-4", Quantity: 1.75},
&model.CommandShipGroupSend{CommandMeta: commandMeta("cmd-10", model.CommandTypeShipGroupSend, nil, nil), ID: "group-5", Destination: 19},
&model.CommandShipGroupUpgrade{CommandMeta: commandMeta("cmd-11", model.CommandTypeShipGroupUpgrade, nil, nil), ID: "group-6", Tech: "SHIELDS", Level: 2.0},
&model.CommandShipGroupMerge{CommandMeta: commandMeta("cmd-12", model.CommandTypeShipGroupMerge, nil, nil)},
&model.CommandShipGroupDismantle{CommandMeta: commandMeta("cmd-13", model.CommandTypeShipGroupDismantle, nil, nil), ID: "group-7"},
&model.CommandShipGroupTransfer{CommandMeta: commandMeta("cmd-14", model.CommandTypeShipGroupTransfer, nil, &errSeven), ID: "group-8", Acceptor: "race-c"},
&model.CommandShipGroupJoinFleet{CommandMeta: commandMeta("cmd-15", model.CommandTypeShipGroupJoinFleet, nil, nil), ID: "group-9", Name: "fleet-a"},
&model.CommandFleetMerge{CommandMeta: commandMeta("cmd-16", model.CommandTypeFleetMerge, nil, nil), Name: "fleet-b", Target: "fleet-c"},
&model.CommandFleetSend{CommandMeta: commandMeta("cmd-17", model.CommandTypeFleetSend, nil, nil), Name: "fleet-d", Destination: 31},
&model.CommandScienceCreate{CommandMeta: commandMeta("cmd-18", model.CommandTypeScienceCreate, nil, nil), Name: "science-a", Drive: 0.1, Weapons: 0.2, Shields: 0.3, Cargo: 0.4},
&model.CommandScienceRemove{CommandMeta: commandMeta("cmd-19", model.CommandTypeScienceRemove, nil, nil), Name: "science-b"},
&model.CommandPlanetRename{CommandMeta: commandMeta("cmd-20", model.CommandTypePlanetRename, nil, nil), Number: 7, Name: "new-name"},
&model.CommandPlanetProduce{CommandMeta: commandMeta("cmd-21", model.CommandTypePlanetProduce, nil, nil), Number: 8, Production: "SHIP", Subject: "frigate"},
&model.CommandPlanetRouteSet{CommandMeta: commandMeta("cmd-22", model.CommandTypePlanetRouteSet, nil, nil), Origin: 9, Destination: 10, LoadType: "EMP"},
&model.CommandPlanetRouteRemove{CommandMeta: commandMeta("cmd-23", model.CommandTypePlanetRouteRemove, nil, nil), Origin: 11, LoadType: "COL"},
},
}
payload, err := OrderToPayload(source)
if err != nil {
t.Fatalf("encode order payload: %v", err)
}
decoded, err := PayloadToOrder(payload)
if err != nil {
t.Fatalf("decode order payload: %v", err)
}
if !reflect.DeepEqual(source, decoded) {
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded)
}
}
func TestOrderToPayloadNilOrder(t *testing.T) {
t.Parallel()
_, err := OrderToPayload(nil)
if err == nil {
t.Fatal("expected error for nil order")
}
}
func TestOrderToPayloadUnsupportedCommandType(t *testing.T) {
t.Parallel()
source := &model.Order{
Commands: []model.DecodableCommand{unsupportedCommand{}},
}
_, err := OrderToPayload(source)
if err == nil {
t.Fatal("expected error for unsupported command type")
}
if !strings.Contains(err.Error(), "unsupported command type") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestOrderToPayloadTypedNilCommand(t *testing.T) {
t.Parallel()
var typedNil *model.CommandRaceQuit
source := &model.Order{
Commands: []model.DecodableCommand{typedNil},
}
_, err := OrderToPayload(source)
if err == nil {
t.Fatal("expected error for typed nil command")
}
if !strings.Contains(err.Error(), "command is nil") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestOrderToPayloadInvalidEnum(t *testing.T) {
t.Parallel()
source := &model.Order{
Commands: []model.DecodableCommand{
&model.CommandRaceRelation{
CommandMeta: commandMeta("cmd-1", model.CommandTypeRaceRelation, nil, nil),
Acceptor: "race-a",
Relation: "ALLY",
},
},
}
_, err := OrderToPayload(source)
if err == nil {
t.Fatal("expected error for invalid enum value")
}
if !strings.Contains(err.Error(), "unsupported relation value") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestPayloadToOrderEmptyData(t *testing.T) {
t.Parallel()
_, err := PayloadToOrder(nil)
if err == nil {
t.Fatal("expected error for empty payload")
}
}
func TestPayloadToOrderGarbageDataDoesNotPanic(t *testing.T) {
t.Parallel()
_, err := PayloadToOrder([]byte{0x01, 0x02, 0x03})
if err == nil {
t.Fatal("expected error for malformed payload")
}
}
func TestPayloadToOrderUnknownPayloadType(t *testing.T) {
t.Parallel()
payload := buildSingleCommandOrderPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
fbs.CommandRaceQuitStart(builder)
commandPayload := fbs.CommandRaceQuitEnd(builder)
cmdID := builder.CreateString("cmd-1")
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
fbs.CommandItemAddPayloadType(builder, fbs.CommandPayload(127))
fbs.CommandItemAddPayload(builder, commandPayload)
return fbs.CommandItemEnd(builder)
})
_, err := PayloadToOrder(payload)
if err == nil {
t.Fatal("expected error for unknown payload type")
}
if !strings.Contains(err.Error(), "unknown command payload type") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestPayloadToOrderMissingPayload(t *testing.T) {
t.Parallel()
payload := buildSingleCommandOrderPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
cmdID := builder.CreateString("cmd-1")
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
fbs.CommandItemAddPayloadType(builder, fbs.CommandPayloadCommandRaceQuit)
return fbs.CommandItemEnd(builder)
})
_, err := PayloadToOrder(payload)
if err == nil {
t.Fatal("expected error for missing payload")
}
if !strings.Contains(err.Error(), "payload is missing") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestPayloadToOrderPayloadTypeNone(t *testing.T) {
t.Parallel()
payload := buildSingleCommandOrderPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
fbs.CommandRaceQuitStart(builder)
commandPayload := fbs.CommandRaceQuitEnd(builder)
cmdID := builder.CreateString("cmd-1")
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
fbs.CommandItemAddPayload(builder, commandPayload)
return fbs.CommandItemEnd(builder)
})
_, err := PayloadToOrder(payload)
if err == nil {
t.Fatal("expected error for NONE payload type")
}
if !strings.Contains(err.Error(), "payload type is NONE") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestPayloadToOrderUnknownEnum(t *testing.T) {
t.Parallel()
payload := buildSingleCommandOrderPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
acceptor := builder.CreateString("race-a")
fbs.CommandRaceRelationStart(builder)
fbs.CommandRaceRelationAddAcceptor(builder, acceptor)
fbs.CommandRaceRelationAddRelation(builder, fbs.RelationUNKNOWN)
commandPayload := fbs.CommandRaceRelationEnd(builder)
cmdID := builder.CreateString("cmd-1")
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
fbs.CommandItemAddPayloadType(builder, fbs.CommandPayloadCommandRaceRelation)
fbs.CommandItemAddPayload(builder, commandPayload)
return fbs.CommandItemEnd(builder)
})
_, err := PayloadToOrder(payload)
if err == nil {
t.Fatal("expected error for UNKNOWN enum")
}
if !strings.Contains(err.Error(), "UNKNOWN") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestPayloadToOrderOverflow(t *testing.T) {
t.Parallel()
if strconv.IntSize == 64 {
t.Skip("int overflow from int64 is not possible on 64-bit runtime")
}
maxInt := int(^uint(0) >> 1)
overflowValue := int64(maxInt) + 1
payload := buildSingleCommandOrderPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
name := builder.CreateString("planet-a")
fbs.CommandPlanetRenameStart(builder)
fbs.CommandPlanetRenameAddNumber(builder, overflowValue)
fbs.CommandPlanetRenameAddName(builder, name)
commandPayload := fbs.CommandPlanetRenameEnd(builder)
cmdID := builder.CreateString("cmd-1")
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
fbs.CommandItemAddPayloadType(builder, fbs.CommandPayloadCommandPlanetRename)
fbs.CommandItemAddPayload(builder, commandPayload)
return fbs.CommandItemEnd(builder)
})
_, err := PayloadToOrder(payload)
if err == nil {
t.Fatal("expected overflow error")
}
if !strings.Contains(err.Error(), "overflows int") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestInt64ToInt(t *testing.T) {
t.Parallel()
value, err := int64ToInt(123, "field")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if value != 123 {
t.Fatalf("unexpected int value: %d", value)
}
if strconv.IntSize == 32 {
maxInt := int(^uint(0) >> 1)
_, err = int64ToInt(int64(maxInt)+1, "field")
if err == nil {
t.Fatal("expected overflow error")
}
}
}
type unsupportedCommand struct{}
func (unsupportedCommand) CommandID() string {
return "unsupported"
}
func (unsupportedCommand) CommandType() model.CommandType {
return model.CommandType("unsupported")
}
func commandMeta(id string, cmdType model.CommandType, applied *bool, errCode *int) model.CommandMeta {
return model.CommandMeta{
CmdType: cmdType,
CmdID: id,
CmdApplied: applied,
CmdErrCode: errCode,
}
}
func buildSingleCommandOrderPayload(itemBuilder func(*flatbuffers.Builder) flatbuffers.UOffsetT) []byte {
builder := flatbuffers.NewBuilder(256)
itemOffset := itemBuilder(builder)
fbs.OrderStartCommandsVector(builder, 1)
builder.PrependUOffsetT(itemOffset)
commands := builder.EndVector(1)
fbs.OrderStart(builder)
fbs.OrderAddUpdatedAt(builder, 1)
fbs.OrderAddCommands(builder, commands)
orderOffset := fbs.OrderEnd(builder)
fbs.FinishOrderBuffer(builder, orderOffset)
return builder.FinishedBytes()
}