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() }