docs: reorder & testing

This commit is contained in:
Ilia Denisov
2026-05-07 00:58:53 +03:00
committed by GitHub
parent f446c6a2ac
commit 604fe40bcf
148 changed files with 9150 additions and 2757 deletions
+4 -4
View File
@@ -34,7 +34,7 @@ func BattleReportToPayload(report *model.BattleReport) ([]byte, error) {
shipVector := encodeBattleOffsetVector(builder, len(shipOffsets), fbs.BattleReportStartShipsVector, shipOffsets)
protocolVector := encodeBattleOffsetVector(builder, len(protocolOffsets), fbs.BattleReportStartProtocolVector, protocolOffsets)
idHi, idLo := reportUUIDToHiLo(report.ID)
idHi, idLo := uuidToHiLo(report.ID)
fbs.BattleReportStart(builder)
fbs.BattleReportAddId(builder, fbs.CreateUUID(builder, idHi, idLo))
@@ -85,7 +85,7 @@ func PayloadToBattleReport(data []byte) (result *model.BattleReport, err error)
}
result = &model.BattleReport{
ID: reportUUIDFromHiLo(id.Hi(), id.Lo()),
ID: uuidFromHiLo(id.Hi(), id.Lo()),
Planet: planet,
PlanetName: string(flatReport.PlanetName()),
}
@@ -116,7 +116,7 @@ func encodeBattleRaceEntryOffsets(builder *flatbuffers.Builder, races map[int]uu
offsets := make([]flatbuffers.UOffsetT, len(keys))
for i, key := range keys {
hi, lo := reportUUIDToHiLo(races[key])
hi, lo := uuidToHiLo(races[key])
fbs.RaceEntryStart(builder)
fbs.RaceEntryAddKey(builder, int64(key))
fbs.RaceEntryAddValue(builder, fbs.CreateUUID(builder, hi, lo))
@@ -240,7 +240,7 @@ func decodeBattleRaceMap(flatReport *fbs.BattleReport, result *model.BattleRepor
return fmt.Errorf("decode battle report race %d: race value is missing", i)
}
result.Races[key] = reportUUIDFromHiLo(value.Hi(), value.Lo())
result.Races[key] = uuidFromHiLo(value.Hi(), value.Lo())
}
return nil
File diff suppressed because it is too large Load Diff
+153 -241
View File
@@ -5,9 +5,17 @@ import (
"strings"
"testing"
commonfbs "galaxy/schema/fbs/common"
notificationfbs "galaxy/schema/fbs/notification"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/google/uuid"
)
var (
testNotificationGameID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
testNotificationApplicationID = uuid.MustParse("22222222-2222-2222-2222-222222222222")
testNotificationInviterID = uuid.MustParse("33333333-3333-3333-3333-333333333333")
)
func TestNotificationPayloadRoundTrips(t *testing.T) {
@@ -20,94 +28,123 @@ func TestNotificationPayloadRoundTrips(t *testing.T) {
decode func([]byte) (any, error)
}{
{
name: "game turn ready",
source: &GameTurnReadyEvent{GameID: "game-1", TurnNumber: 54},
encode: func(value any) ([]byte, error) { return GameTurnReadyEventToPayload(value.(*GameTurnReadyEvent)) },
decode: func(data []byte) (any, error) { return PayloadToGameTurnReadyEvent(data) },
name: "lobby invite received",
source: &LobbyInviteReceivedEvent{GameID: testNotificationGameID, InviterUserID: testNotificationInviterID},
encode: func(v any) ([]byte, error) {
return LobbyInviteReceivedEventToPayload(v.(*LobbyInviteReceivedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyInviteReceivedEvent(data) },
},
{
name: "game finished",
source: &GameFinishedEvent{GameID: "game-2", FinalTurnNumber: 99},
encode: func(value any) ([]byte, error) { return GameFinishedEventToPayload(value.(*GameFinishedEvent)) },
decode: func(data []byte) (any, error) { return PayloadToGameFinishedEvent(data) },
name: "lobby invite revoked",
source: &LobbyInviteRevokedEvent{GameID: testNotificationGameID},
encode: func(v any) ([]byte, error) {
return LobbyInviteRevokedEventToPayload(v.(*LobbyInviteRevokedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyInviteRevokedEvent(data) },
},
{
name: "lobby application submitted",
source: &LobbyApplicationSubmittedEvent{GameID: "game-3", ApplicantUserID: "user-7"},
encode: func(value any) ([]byte, error) {
return LobbyApplicationSubmittedEventToPayload(value.(*LobbyApplicationSubmittedEvent))
source: &LobbyApplicationSubmittedEvent{GameID: testNotificationGameID, ApplicationID: testNotificationApplicationID},
encode: func(v any) ([]byte, error) {
return LobbyApplicationSubmittedEventToPayload(v.(*LobbyApplicationSubmittedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyApplicationSubmittedEvent(data) },
},
{
name: "lobby membership approved",
source: &LobbyMembershipApprovedEvent{GameID: "game-4"},
encode: func(value any) ([]byte, error) {
return LobbyMembershipApprovedEventToPayload(value.(*LobbyMembershipApprovedEvent))
name: "lobby application approved",
source: &LobbyApplicationApprovedEvent{GameID: testNotificationGameID},
encode: func(v any) ([]byte, error) {
return LobbyApplicationApprovedEventToPayload(v.(*LobbyApplicationApprovedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyMembershipApprovedEvent(data) },
decode: func(data []byte) (any, error) { return PayloadToLobbyApplicationApprovedEvent(data) },
},
{
name: "lobby membership rejected",
source: &LobbyMembershipRejectedEvent{GameID: "game-5"},
encode: func(value any) ([]byte, error) {
return LobbyMembershipRejectedEventToPayload(value.(*LobbyMembershipRejectedEvent))
name: "lobby application rejected",
source: &LobbyApplicationRejectedEvent{GameID: testNotificationGameID},
encode: func(v any) ([]byte, error) {
return LobbyApplicationRejectedEventToPayload(v.(*LobbyApplicationRejectedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyMembershipRejectedEvent(data) },
decode: func(data []byte) (any, error) { return PayloadToLobbyApplicationRejectedEvent(data) },
},
{
name: "lobby invite created",
source: &LobbyInviteCreatedEvent{GameID: "game-6", InviterUserID: "user-8"},
encode: func(value any) ([]byte, error) {
return LobbyInviteCreatedEventToPayload(value.(*LobbyInviteCreatedEvent))
name: "lobby membership removed",
source: &LobbyMembershipRemovedEvent{Reason: "deleted"},
encode: func(v any) ([]byte, error) {
return LobbyMembershipRemovedEventToPayload(v.(*LobbyMembershipRemovedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyInviteCreatedEvent(data) },
decode: func(data []byte) (any, error) { return PayloadToLobbyMembershipRemovedEvent(data) },
},
{
name: "lobby invite redeemed",
source: &LobbyInviteRedeemedEvent{GameID: "game-7", InviteeUserID: "user-9"},
encode: func(value any) ([]byte, error) {
return LobbyInviteRedeemedEventToPayload(value.(*LobbyInviteRedeemedEvent))
name: "lobby membership blocked",
source: &LobbyMembershipBlockedEvent{GameID: testNotificationGameID, Reason: "permanent_blocked"},
encode: func(v any) ([]byte, error) {
return LobbyMembershipBlockedEventToPayload(v.(*LobbyMembershipBlockedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyInviteRedeemedEvent(data) },
},
{
name: "lobby race name registration eligible",
source: &LobbyRaceNameRegistrationEligibleEvent{
GameID: "game-8",
RaceName: "Skylancer",
EligibleUntilMs: 1775208100000,
},
encode: func(value any) ([]byte, error) {
return LobbyRaceNameRegistrationEligibleEventToPayload(value.(*LobbyRaceNameRegistrationEligibleEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyRaceNameRegistrationEligibleEvent(data) },
decode: func(data []byte) (any, error) { return PayloadToLobbyMembershipBlockedEvent(data) },
},
{
name: "lobby race name registered",
source: &LobbyRaceNameRegisteredEvent{RaceName: "Skylancer"},
encode: func(value any) ([]byte, error) {
return LobbyRaceNameRegisteredEventToPayload(value.(*LobbyRaceNameRegisteredEvent))
encode: func(v any) ([]byte, error) {
return LobbyRaceNameRegisteredEventToPayload(v.(*LobbyRaceNameRegisteredEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyRaceNameRegisteredEvent(data) },
},
{
name: "lobby race name pending",
source: &LobbyRaceNamePendingEvent{RaceName: "Skylancer", ExpiresAt: "2026-05-06T12:00:00Z"},
encode: func(v any) ([]byte, error) {
return LobbyRaceNamePendingEventToPayload(v.(*LobbyRaceNamePendingEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyRaceNamePendingEvent(data) },
},
{
name: "lobby race name expired",
source: &LobbyRaceNameExpiredEvent{RaceName: "Skylancer"},
encode: func(v any) ([]byte, error) {
return LobbyRaceNameExpiredEventToPayload(v.(*LobbyRaceNameExpiredEvent))
},
decode: func(data []byte) (any, error) { return PayloadToLobbyRaceNameExpiredEvent(data) },
},
{
name: "runtime image pull failed",
source: &RuntimeImagePullFailedEvent{GameID: testNotificationGameID, ImageRef: "gcr.io/example:1.0.0"},
encode: func(v any) ([]byte, error) {
return RuntimeImagePullFailedEventToPayload(v.(*RuntimeImagePullFailedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToRuntimeImagePullFailedEvent(data) },
},
{
name: "runtime container start failed",
source: &RuntimeContainerStartFailedEvent{GameID: testNotificationGameID},
encode: func(v any) ([]byte, error) {
return RuntimeContainerStartFailedEventToPayload(v.(*RuntimeContainerStartFailedEvent))
},
decode: func(data []byte) (any, error) { return PayloadToRuntimeContainerStartFailedEvent(data) },
},
{
name: "runtime start config invalid",
source: &RuntimeStartConfigInvalidEvent{GameID: testNotificationGameID, Reason: "missing engine version"},
encode: func(v any) ([]byte, error) {
return RuntimeStartConfigInvalidEventToPayload(v.(*RuntimeStartConfigInvalidEvent))
},
decode: func(data []byte) (any, error) { return PayloadToRuntimeStartConfigInvalidEvent(data) },
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
payload, err := tt.encode(tt.source)
if err != nil {
t.Fatalf("encode payload: %v", err)
}
decoded, err := tt.decode(payload)
if err != nil {
t.Fatalf("decode payload: %v", err)
}
if !reflect.DeepEqual(tt.source, decoded) {
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", tt.source, decoded)
}
@@ -122,76 +159,25 @@ func TestNotificationPayloadEncodersRejectNilInputs(t *testing.T) {
name string
call func() error
}{
{
name: "game turn ready",
call: func() error {
_, err := GameTurnReadyEventToPayload(nil)
return err
},
},
{
name: "game finished",
call: func() error {
_, err := GameFinishedEventToPayload(nil)
return err
},
},
{
name: "lobby application submitted",
call: func() error {
_, err := LobbyApplicationSubmittedEventToPayload(nil)
return err
},
},
{
name: "lobby membership approved",
call: func() error {
_, err := LobbyMembershipApprovedEventToPayload(nil)
return err
},
},
{
name: "lobby membership rejected",
call: func() error {
_, err := LobbyMembershipRejectedEventToPayload(nil)
return err
},
},
{
name: "lobby invite created",
call: func() error {
_, err := LobbyInviteCreatedEventToPayload(nil)
return err
},
},
{
name: "lobby invite redeemed",
call: func() error {
_, err := LobbyInviteRedeemedEventToPayload(nil)
return err
},
},
{
name: "lobby race name registration eligible",
call: func() error {
_, err := LobbyRaceNameRegistrationEligibleEventToPayload(nil)
return err
},
},
{
name: "lobby race name registered",
call: func() error {
_, err := LobbyRaceNameRegisteredEventToPayload(nil)
return err
},
},
{"lobby invite received", func() error { _, err := LobbyInviteReceivedEventToPayload(nil); return err }},
{"lobby invite revoked", func() error { _, err := LobbyInviteRevokedEventToPayload(nil); return err }},
{"lobby application submitted", func() error { _, err := LobbyApplicationSubmittedEventToPayload(nil); return err }},
{"lobby application approved", func() error { _, err := LobbyApplicationApprovedEventToPayload(nil); return err }},
{"lobby application rejected", func() error { _, err := LobbyApplicationRejectedEventToPayload(nil); return err }},
{"lobby membership removed", func() error { _, err := LobbyMembershipRemovedEventToPayload(nil); return err }},
{"lobby membership blocked", func() error { _, err := LobbyMembershipBlockedEventToPayload(nil); return err }},
{"lobby race name registered", func() error { _, err := LobbyRaceNameRegisteredEventToPayload(nil); return err }},
{"lobby race name pending", func() error { _, err := LobbyRaceNamePendingEventToPayload(nil); return err }},
{"lobby race name expired", func() error { _, err := LobbyRaceNameExpiredEventToPayload(nil); return err }},
{"runtime image pull failed", func() error { _, err := RuntimeImagePullFailedEventToPayload(nil); return err }},
{"runtime container start failed", func() error { _, err := RuntimeContainerStartFailedEventToPayload(nil); return err }},
{"runtime start config invalid", func() error { _, err := RuntimeStartConfigInvalidEventToPayload(nil); return err }},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if err := tt.call(); err == nil {
t.Fatal("expected error")
}
@@ -206,62 +192,25 @@ func TestNotificationPayloadDecodersRejectEmptyPayloads(t *testing.T) {
name string
call func() error
}{
{
name: "game turn ready",
call: func() error {
_, err := PayloadToGameTurnReadyEvent(nil)
return err
},
},
{
name: "game finished",
call: func() error {
_, err := PayloadToGameFinishedEvent(nil)
return err
},
},
{
name: "lobby application submitted",
call: func() error {
_, err := PayloadToLobbyApplicationSubmittedEvent(nil)
return err
},
},
{
name: "lobby membership approved",
call: func() error {
_, err := PayloadToLobbyMembershipApprovedEvent(nil)
return err
},
},
{
name: "lobby membership rejected",
call: func() error {
_, err := PayloadToLobbyMembershipRejectedEvent(nil)
return err
},
},
{
name: "lobby invite created",
call: func() error {
_, err := PayloadToLobbyInviteCreatedEvent(nil)
return err
},
},
{
name: "lobby invite redeemed",
call: func() error {
_, err := PayloadToLobbyInviteRedeemedEvent(nil)
return err
},
},
{"lobby invite received", func() error { _, err := PayloadToLobbyInviteReceivedEvent(nil); return err }},
{"lobby invite revoked", func() error { _, err := PayloadToLobbyInviteRevokedEvent(nil); return err }},
{"lobby application submitted", func() error { _, err := PayloadToLobbyApplicationSubmittedEvent(nil); return err }},
{"lobby application approved", func() error { _, err := PayloadToLobbyApplicationApprovedEvent(nil); return err }},
{"lobby application rejected", func() error { _, err := PayloadToLobbyApplicationRejectedEvent(nil); return err }},
{"lobby membership removed", func() error { _, err := PayloadToLobbyMembershipRemovedEvent(nil); return err }},
{"lobby membership blocked", func() error { _, err := PayloadToLobbyMembershipBlockedEvent(nil); return err }},
{"lobby race name registered", func() error { _, err := PayloadToLobbyRaceNameRegisteredEvent(nil); return err }},
{"lobby race name pending", func() error { _, err := PayloadToLobbyRaceNamePendingEvent(nil); return err }},
{"lobby race name expired", func() error { _, err := PayloadToLobbyRaceNameExpiredEvent(nil); return err }},
{"runtime image pull failed", func() error { _, err := PayloadToRuntimeImagePullFailedEvent(nil); return err }},
{"runtime container start failed", func() error { _, err := PayloadToRuntimeContainerStartFailedEvent(nil); return err }},
{"runtime start config invalid", func() error { _, err := PayloadToRuntimeStartConfigInvalidEvent(nil); return err }},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if err := tt.call(); err == nil {
t.Fatal("expected error")
}
@@ -269,7 +218,7 @@ func TestNotificationPayloadDecodersRejectEmptyPayloads(t *testing.T) {
}
}
func TestNotificationPayloadEncodersRejectMissingRequiredStrings(t *testing.T) {
func TestNotificationPayloadEncodersRejectMissingUUIDs(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -278,36 +227,29 @@ func TestNotificationPayloadEncodersRejectMissingRequiredStrings(t *testing.T) {
want string
}{
{
name: "game turn ready",
call: func() error {
_, err := GameTurnReadyEventToPayload(&GameTurnReadyEvent{})
return err
},
name: "lobby invite received game_id",
call: func() error { _, err := LobbyInviteReceivedEventToPayload(&LobbyInviteReceivedEvent{InviterUserID: testNotificationInviterID}); return err },
want: "game_id is empty",
},
{
name: "lobby application submitted",
call: func() error {
_, err := LobbyApplicationSubmittedEventToPayload(&LobbyApplicationSubmittedEvent{GameID: "game-1"})
return err
},
want: "applicant_user_id is empty",
},
{
name: "lobby invite created",
call: func() error {
_, err := LobbyInviteCreatedEventToPayload(&LobbyInviteCreatedEvent{GameID: "game-1"})
return err
},
name: "lobby invite received inviter",
call: func() error { _, err := LobbyInviteReceivedEventToPayload(&LobbyInviteReceivedEvent{GameID: testNotificationGameID}); return err },
want: "inviter_user_id is empty",
},
{
name: "lobby invite redeemed",
call: func() error {
_, err := LobbyInviteRedeemedEventToPayload(&LobbyInviteRedeemedEvent{GameID: "game-1"})
return err
},
want: "invitee_user_id is empty",
name: "lobby application submitted game_id",
call: func() error { _, err := LobbyApplicationSubmittedEventToPayload(&LobbyApplicationSubmittedEvent{ApplicationID: testNotificationApplicationID}); return err },
want: "game_id is empty",
},
{
name: "lobby application submitted application_id",
call: func() error { _, err := LobbyApplicationSubmittedEventToPayload(&LobbyApplicationSubmittedEvent{GameID: testNotificationGameID}); return err },
want: "application_id is empty",
},
{
name: "runtime image pull failed game_id",
call: func() error { _, err := RuntimeImagePullFailedEventToPayload(&RuntimeImagePullFailedEvent{ImageRef: "x"}); return err },
want: "game_id is empty",
},
}
@@ -315,7 +257,6 @@ func TestNotificationPayloadEncodersRejectMissingRequiredStrings(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.call()
if err == nil {
t.Fatal("expected error")
@@ -327,7 +268,7 @@ func TestNotificationPayloadEncodersRejectMissingRequiredStrings(t *testing.T) {
}
}
func TestNotificationPayloadDecodersRejectMissingRequiredStrings(t *testing.T) {
func TestNotificationPayloadDecodersRejectMissingUUIDs(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -337,70 +278,42 @@ func TestNotificationPayloadDecodersRejectMissingRequiredStrings(t *testing.T) {
want string
}{
{
name: "game turn ready",
name: "lobby invite received game_id",
payload: func() []byte {
builder := flatbuffers.NewBuilder(32)
notificationfbs.GameTurnReadyEventStart(builder)
offset := notificationfbs.GameTurnReadyEventEnd(builder)
notificationfbs.FinishGameTurnReadyEventBuffer(builder, offset)
notificationfbs.LobbyInviteReceivedEventStart(builder)
offset := notificationfbs.LobbyInviteReceivedEventEnd(builder)
notificationfbs.FinishLobbyInviteReceivedEventBuffer(builder, offset)
return builder.FinishedBytes()
},
decode: func(data []byte) error {
_, err := PayloadToGameTurnReadyEvent(data)
return err
},
want: "game_id is missing",
decode: func(data []byte) error { _, err := PayloadToLobbyInviteReceivedEvent(data); return err },
want: "game_id is missing",
},
{
name: "lobby application submitted",
name: "lobby invite received inviter",
payload: func() []byte {
builder := flatbuffers.NewBuilder(32)
notificationfbs.LobbyInviteReceivedEventStart(builder)
notificationfbs.LobbyInviteReceivedEventAddGameId(builder, commonfbs.CreateUUID(builder, 1, 2))
offset := notificationfbs.LobbyInviteReceivedEventEnd(builder)
notificationfbs.FinishLobbyInviteReceivedEventBuffer(builder, offset)
return builder.FinishedBytes()
},
decode: func(data []byte) error { _, err := PayloadToLobbyInviteReceivedEvent(data); return err },
want: "inviter_user_id is missing",
},
{
name: "lobby application submitted application_id",
payload: func() []byte {
builder := flatbuffers.NewBuilder(32)
gameID := builder.CreateString("game-1")
notificationfbs.LobbyApplicationSubmittedEventStart(builder)
notificationfbs.LobbyApplicationSubmittedEventAddGameId(builder, gameID)
notificationfbs.LobbyApplicationSubmittedEventAddGameId(builder, commonfbs.CreateUUID(builder, 1, 2))
offset := notificationfbs.LobbyApplicationSubmittedEventEnd(builder)
notificationfbs.FinishLobbyApplicationSubmittedEventBuffer(builder, offset)
return builder.FinishedBytes()
},
decode: func(data []byte) error {
_, err := PayloadToLobbyApplicationSubmittedEvent(data)
return err
},
want: "applicant_user_id is missing",
},
{
name: "lobby invite created",
payload: func() []byte {
builder := flatbuffers.NewBuilder(32)
gameID := builder.CreateString("game-1")
notificationfbs.LobbyInviteCreatedEventStart(builder)
notificationfbs.LobbyInviteCreatedEventAddGameId(builder, gameID)
offset := notificationfbs.LobbyInviteCreatedEventEnd(builder)
notificationfbs.FinishLobbyInviteCreatedEventBuffer(builder, offset)
return builder.FinishedBytes()
},
decode: func(data []byte) error {
_, err := PayloadToLobbyInviteCreatedEvent(data)
return err
},
want: "inviter_user_id is missing",
},
{
name: "lobby invite redeemed",
payload: func() []byte {
builder := flatbuffers.NewBuilder(32)
gameID := builder.CreateString("game-1")
notificationfbs.LobbyInviteRedeemedEventStart(builder)
notificationfbs.LobbyInviteRedeemedEventAddGameId(builder, gameID)
offset := notificationfbs.LobbyInviteRedeemedEventEnd(builder)
notificationfbs.FinishLobbyInviteRedeemedEventBuffer(builder, offset)
return builder.FinishedBytes()
},
decode: func(data []byte) error {
_, err := PayloadToLobbyInviteRedeemedEvent(data)
return err
},
want: "invitee_user_id is missing",
decode: func(data []byte) error { _, err := PayloadToLobbyApplicationSubmittedEvent(data); return err },
want: "application_id is missing",
},
}
@@ -408,7 +321,6 @@ func TestNotificationPayloadDecodersRejectMissingRequiredStrings(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.decode(tt.payload())
if err == nil {
t.Fatal("expected error")
+204 -98
View File
@@ -5,109 +5,12 @@ import (
"fmt"
model "galaxy/model/order"
commonfbs "galaxy/schema/fbs/common"
fbs "galaxy/schema/fbs/order"
flatbuffers "github.com/google/flatbuffers/go"
)
// OrderToPayload converts model.Order from the internal representation to
// FlatBuffers bytes that can be sent over network transports.
//
// The function returns an error when the input contains unsupported command
// types or values that cannot be represented by the current FlatBuffers schema.
func OrderToPayload(o *model.Order) ([]byte, error) {
if o == nil {
return nil, errors.New("encode order payload: order is nil")
}
builder := flatbuffers.NewBuilder(1024)
commandOffsets := make([]flatbuffers.UOffsetT, len(o.Commands))
for i := range o.Commands {
encoded, err := encodeOrderCommand(builder, o.Commands[i], i)
if err != nil {
return nil, err
}
cmdID := builder.CreateString(encoded.cmdID)
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
if encoded.cmdApplied != nil {
fbs.CommandItemAddCmdApplied(builder, *encoded.cmdApplied)
}
if encoded.cmdErrCode != nil {
fbs.CommandItemAddCmdErrorCode(builder, int64(*encoded.cmdErrCode))
}
fbs.CommandItemAddPayloadType(builder, encoded.payloadType)
fbs.CommandItemAddPayload(builder, encoded.payloadOffset)
commandOffsets[i] = fbs.CommandItemEnd(builder)
}
var commandsVector flatbuffers.UOffsetT
if len(commandOffsets) > 0 {
fbs.OrderStartCommandsVector(builder, len(commandOffsets))
for i := len(commandOffsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(commandOffsets[i])
}
commandsVector = builder.EndVector(len(commandOffsets))
}
fbs.OrderStart(builder)
fbs.OrderAddUpdatedAt(builder, int64(o.UpdatedAt))
if len(commandOffsets) > 0 {
fbs.OrderAddCommands(builder, commandsVector)
}
orderOffset := fbs.OrderEnd(builder)
fbs.FinishOrderBuffer(builder, orderOffset)
return builder.FinishedBytes(), nil
}
// PayloadToOrder converts FlatBuffers payload bytes into model.Order.
//
// The function validates payload structure, command payload type, enum values,
// and integer conversions. Malformed payloads are returned as errors.
func PayloadToOrder(data []byte) (result *model.Order, err error) {
if len(data) == 0 {
return nil, errors.New("decode order payload: data is empty")
}
defer func() {
if recovered := recover(); recovered != nil {
result = nil
err = fmt.Errorf("decode order payload: panic recovered: %v", recovered)
}
}()
flatOrder := fbs.GetRootAsOrder(data, 0)
updatedAt, err := int64ToInt(flatOrder.UpdatedAt(), "updated_at")
if err != nil {
return nil, fmt.Errorf("decode order payload: %w", err)
}
result = &model.Order{UpdatedAt: updatedAt}
commandsLen := flatOrder.CommandsLength()
if commandsLen > 0 {
result.Commands = make([]model.DecodableCommand, commandsLen)
}
flatCommand := new(fbs.CommandItem)
for i := 0; i < commandsLen; i++ {
if !flatOrder.Commands(flatCommand, i) {
return nil, fmt.Errorf("decode order command %d: command item is missing", i)
}
command, err := decodeOrderCommand(flatCommand, i)
if err != nil {
return nil, err
}
result.Commands[i] = command
}
return result, nil
}
type encodedCommand struct {
cmdID string
cmdApplied *bool
@@ -701,6 +604,10 @@ func decodeOrderCommand(flatCommand *fbs.CommandItem, index int) (model.Decodabl
}
}
// int64ToInt narrows v to a Go int. Returns an error when v overflows
// the platform `int` range (only possible on 32-bit builds; on 64-bit
// the check is a no-op). fieldName is used in the error for caller
// context.
func int64ToInt(value int64, field string) (int, error) {
maxInt := int64(int(^uint(0) >> 1))
minInt := -maxInt - 1
@@ -897,3 +804,202 @@ func cloneIntPointer(value *int) *int {
cloned := *value
return &cloned
}
// UserGamesCommandToPayload converts model.UserGamesCommand to
// FlatBuffers bytes suitable for the authenticated gateway transport.
// `GameID` is required.
func UserGamesCommandToPayload(req *model.UserGamesCommand) ([]byte, error) {
if req == nil {
return nil, errors.New("encode user games command payload: request is nil")
}
builder := flatbuffers.NewBuilder(1024)
commandsVec, err := encodeCommandItemVector(builder, req.Commands, "user games command")
if err != nil {
return nil, err
}
fbs.UserGamesCommandStart(builder)
hi, lo := uuidToHiLo(req.GameID)
fbs.UserGamesCommandAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
if commandsVec != 0 {
fbs.UserGamesCommandAddCommands(builder, commandsVec)
}
offset := fbs.UserGamesCommandEnd(builder)
fbs.FinishUserGamesCommandBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToUserGamesCommand converts FlatBuffers payload bytes into
// model.UserGamesCommand.
func PayloadToUserGamesCommand(data []byte) (result *model.UserGamesCommand, err error) {
if len(data) == 0 {
return nil, errors.New("decode user games command payload: data is empty")
}
defer func() {
if recovered := recover(); recovered != nil {
result = nil
err = fmt.Errorf("decode user games command payload: panic recovered: %v", recovered)
}
}()
flat := fbs.GetRootAsUserGamesCommand(data, 0)
gameID := flat.GameId(nil)
if gameID == nil {
return nil, errors.New("decode user games command payload: game_id is missing")
}
out := &model.UserGamesCommand{
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
}
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 command %d: command item is missing", i)
}
cmd, decodeErr := decodeOrderCommand(flatCommand, i)
if decodeErr != nil {
return nil, decodeErr
}
out.Commands[i] = cmd
}
}
return out, nil
}
// UserGamesOrderToPayload converts model.UserGamesOrder to FlatBuffers
// bytes suitable for the authenticated gateway transport.
func UserGamesOrderToPayload(req *model.UserGamesOrder) ([]byte, error) {
if req == nil {
return nil, errors.New("encode user games order payload: request is nil")
}
builder := flatbuffers.NewBuilder(1024)
commandsVec, err := encodeCommandItemVector(builder, req.Commands, "user games order")
if err != nil {
return nil, err
}
fbs.UserGamesOrderStart(builder)
hi, lo := uuidToHiLo(req.GameID)
fbs.UserGamesOrderAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
fbs.UserGamesOrderAddUpdatedAt(builder, int64(req.UpdatedAt))
if commandsVec != 0 {
fbs.UserGamesOrderAddCommands(builder, commandsVec)
}
offset := fbs.UserGamesOrderEnd(builder)
fbs.FinishUserGamesOrderBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToUserGamesOrder converts FlatBuffers payload bytes into
// model.UserGamesOrder.
func PayloadToUserGamesOrder(data []byte) (result *model.UserGamesOrder, err error) {
if len(data) == 0 {
return nil, errors.New("decode user games order payload: data is empty")
}
defer func() {
if recovered := recover(); recovered != nil {
result = nil
err = fmt.Errorf("decode user games order payload: panic recovered: %v", recovered)
}
}()
flat := fbs.GetRootAsUserGamesOrder(data, 0)
gameID := flat.GameId(nil)
if gameID == nil {
return nil, errors.New("decode user games order payload: game_id is missing")
}
updatedAt, convErr := int64ToInt(flat.UpdatedAt(), "updated_at")
if convErr != nil {
return nil, fmt.Errorf("decode user games order payload: %w", convErr)
}
out := &model.UserGamesOrder{
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
UpdatedAt: 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 %d: command item is missing", i)
}
cmd, decodeErr := decodeOrderCommand(flatCommand, i)
if decodeErr != nil {
return nil, decodeErr
}
out.Commands[i] = cmd
}
}
return out, nil
}
// EmptyUserGamesCommandResponsePayload returns a FlatBuffers-encoded
// empty `UserGamesCommandResponse` buffer. Used by gateway to ack a
// successful `MessageTypeUserGamesCommand` even though the engine
// returns 204 No Content — the typed envelope keeps the message-type
// contract symmetric with other authenticated routes.
func EmptyUserGamesCommandResponsePayload() []byte {
builder := flatbuffers.NewBuilder(16)
fbs.UserGamesCommandResponseStart(builder)
offset := fbs.UserGamesCommandResponseEnd(builder)
fbs.FinishUserGamesCommandResponseBuffer(builder, offset)
return builder.FinishedBytes()
}
// EmptyUserGamesOrderResponsePayload mirrors
// EmptyUserGamesCommandResponsePayload for `MessageTypeUserGamesOrder`.
func EmptyUserGamesOrderResponsePayload() []byte {
builder := flatbuffers.NewBuilder(16)
fbs.UserGamesOrderResponseStart(builder)
offset := fbs.UserGamesOrderResponseEnd(builder)
fbs.FinishUserGamesOrderResponseBuffer(builder, offset)
return builder.FinishedBytes()
}
// encodeCommandItemVector serialises a slice of DecodableCommand into a
// FlatBuffers vector of CommandItem. Used by UserGamesCommandToPayload
// and UserGamesOrderToPayload to keep the per-command encoding logic in
// one place.
func encodeCommandItemVector(builder *flatbuffers.Builder, commands []model.DecodableCommand, opLabel string) (flatbuffers.UOffsetT, error) {
offsets := make([]flatbuffers.UOffsetT, len(commands))
for i := range commands {
encoded, err := encodeOrderCommand(builder, commands[i], i)
if err != nil {
return 0, fmt.Errorf("encode %s: %w", opLabel, err)
}
cmdID := builder.CreateString(encoded.cmdID)
fbs.CommandItemStart(builder)
fbs.CommandItemAddCmdId(builder, cmdID)
if encoded.cmdApplied != nil {
fbs.CommandItemAddCmdApplied(builder, *encoded.cmdApplied)
}
if encoded.cmdErrCode != nil {
fbs.CommandItemAddCmdErrorCode(builder, int64(*encoded.cmdErrCode))
}
fbs.CommandItemAddPayloadType(builder, encoded.payloadType)
fbs.CommandItemAddPayload(builder, encoded.payloadOffset)
offsets[i] = fbs.CommandItemEnd(builder)
}
if len(offsets) == 0 {
return 0, nil
}
// `UserGamesCommandStartCommandsVector` and the corresponding
// `UserGamesOrderStartCommandsVector` are identical helpers (both
// expand to `builder.StartVector(4, numElems, 4)`); we use the
// command flavour for both message types so the helper has a
// single dependency point.
fbs.UserGamesCommandStartCommandsVector(builder, len(offsets))
for i := len(offsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(offsets[i])
}
return builder.EndVector(len(offsets)), nil
}
+35 -259
View File
@@ -3,61 +3,32 @@ package transcoder
import (
"reflect"
"strconv"
"strings"
"testing"
model "galaxy/model/order"
fbs "galaxy/schema/fbs/order"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/google/uuid"
)
func TestOrderToPayloadAndPayloadToOrderRoundTrip(t *testing.T) {
func TestUserGamesCommandPayloadRoundTrip(t *testing.T) {
t.Parallel()
appliedTrue := true
appliedFalse := false
errZero := 0
errThree := 3
errSeven := 7
source := &model.Order{
UpdatedAt: 42,
source := &model.UserGamesCommand{
GameID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
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"},
&model.CommandRaceVote{CommandMeta: commandMeta("cmd-01", model.CommandTypeRaceVote, nil, nil), Acceptor: "race-a"},
&model.CommandShipGroupSend{CommandMeta: commandMeta("cmd-02", model.CommandTypeShipGroupSend, nil, nil), ID: "group-1", Destination: 7},
},
}
payload, err := OrderToPayload(source)
payload, err := UserGamesCommandToPayload(source)
if err != nil {
t.Fatalf("encode order payload: %v", err)
t.Fatalf("encode user games command: %v", err)
}
decoded, err := PayloadToOrder(payload)
decoded, err := PayloadToUserGamesCommand(payload)
if err != nil {
t.Fatalf("decode order payload: %v", err)
t.Fatalf("decode user games command: %v", err)
}
if !reflect.DeepEqual(source, decoded) {
@@ -65,213 +36,46 @@ func TestOrderToPayloadAndPayloadToOrderRoundTrip(t *testing.T) {
}
}
func TestOrderToPayloadNilOrder(t *testing.T) {
func TestUserGamesOrderPayloadRoundTrip(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{
source := &model.UserGamesOrder{
GameID: uuid.MustParse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
UpdatedAt: 12345,
Commands: []model.DecodableCommand{
&model.CommandRaceRelation{
CommandMeta: commandMeta("cmd-1", model.CommandTypeRaceRelation, nil, nil),
Acceptor: "race-a",
Relation: "ALLY",
},
&model.CommandPlanetRename{CommandMeta: commandMeta("cmd-1", model.CommandTypePlanetRename, nil, nil), Number: 5, Name: "alpha"},
},
}
_, err := OrderToPayload(source)
if err == nil {
t.Fatal("expected error for invalid enum value")
payload, err := UserGamesOrderToPayload(source)
if err != nil {
t.Fatalf("encode user games order: %v", err)
}
if !strings.Contains(err.Error(), "unsupported relation value") {
t.Fatalf("unexpected error: %v", err)
decoded, err := PayloadToUserGamesOrder(payload)
if err != nil {
t.Fatalf("decode user games order: %v", err)
}
if !reflect.DeepEqual(source, decoded) {
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded:%#v", source, decoded)
}
}
func TestPayloadToOrderEmptyData(t *testing.T) {
func TestUserGamesCommandRejectsNilAndEmpty(t *testing.T) {
t.Parallel()
_, err := PayloadToOrder(nil)
if err == nil {
t.Fatal("expected error for empty payload")
if _, err := UserGamesCommandToPayload(nil); err == nil {
t.Fatalf("expected error encoding nil user games command")
}
}
func TestPayloadToOrderGarbageDataDoesNotPanic(t *testing.T) {
t.Parallel()
_, err := PayloadToOrder([]byte{0x01, 0x02, 0x03})
if err == nil {
t.Fatal("expected error for malformed payload")
if _, err := PayloadToUserGamesCommand(nil); err == nil {
t.Fatalf("expected error decoding empty user games command")
}
}
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 _, err := UserGamesOrderToPayload(nil); err == nil {
t.Fatalf("expected error encoding nil user games order")
}
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)
if _, err := PayloadToUserGamesOrder(nil); err == nil {
t.Fatalf("expected error decoding empty user games order")
}
}
@@ -295,16 +99,6 @@ func TestInt64ToInt(t *testing.T) {
}
}
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,
@@ -313,21 +107,3 @@ func commandMeta(id string, cmdType model.CommandType, applied *bool, errCode *i
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()
}
+52 -19
View File
@@ -1,12 +1,12 @@
package transcoder
import (
"encoding/binary"
"errors"
"fmt"
"sort"
model "galaxy/model/report"
commonfbs "galaxy/schema/fbs/common"
fbs "galaxy/schema/fbs/report"
flatbuffers "github.com/google/flatbuffers/go"
@@ -521,7 +521,7 @@ func encodeReportLocalGroup(builder *flatbuffers.Builder, group *model.LocalGrou
fleet = builder.CreateString(*group.Fleet)
}
idHi, idLo := reportUUIDToHiLo(group.ID)
idHi, idLo := uuidToHiLo(group.ID)
fbs.LocalGroupStart(builder)
fbs.LocalGroupAddNumber(builder, uint64(group.Number))
@@ -540,7 +540,7 @@ func encodeReportLocalGroup(builder *flatbuffers.Builder, group *model.LocalGrou
}
fbs.LocalGroupAddSpeed(builder, reportFloatToFBS(group.Speed))
fbs.LocalGroupAddMass(builder, reportFloatToFBS(group.Mass))
fbs.LocalGroupAddId(builder, fbs.CreateUUID(builder, idHi, idLo))
fbs.LocalGroupAddId(builder, commonfbs.CreateUUID(builder, idHi, idLo))
fbs.LocalGroupAddState(builder, state)
if group.Fleet != nil {
fbs.LocalGroupAddFleet(builder, fleet)
@@ -735,12 +735,12 @@ func decodeReportBattleVector(flatReport *fbs.Report, result *model.Report) erro
}
result.Battle = make([]uuid.UUID, length)
item := new(fbs.UUID)
item := new(commonfbs.UUID)
for i := 0; i < length; i++ {
if !flatReport.Battle(item, i) {
return fmt.Errorf("decode report battle %d: battle id is missing", i)
}
result.Battle[i] = reportUUIDFromHiLo(item.Hi(), item.Lo())
result.Battle[i] = uuidFromHiLo(item.Hi(), item.Lo())
}
return nil
@@ -1120,7 +1120,7 @@ func decodeReportLocalGroupVector(flatReport *fbs.Report, result *model.Report)
Speed: reportFloatFromFBS(item.Speed()),
Mass: reportFloatFromFBS(item.Mass()),
},
ID: reportUUIDFromHiLo(id.Hi(), id.Lo()),
ID: uuidFromHiLo(id.Hi(), id.Lo()),
State: string(item.State()),
}
@@ -1306,8 +1306,8 @@ func encodeReportUUIDVector(builder *flatbuffers.Builder, ids []uuid.UUID) flatb
fbs.ReportStartBattleVector(builder, len(ids))
for i := len(ids) - 1; i >= 0; i-- {
hi, lo := reportUUIDToHiLo(ids[i])
fbs.CreateUUID(builder, hi, lo)
hi, lo := uuidToHiLo(ids[i])
commonfbs.CreateUUID(builder, hi, lo)
}
return builder.EndVector(len(ids))
}
@@ -1374,17 +1374,6 @@ func reportFloatFromFBS(value float32) model.Float {
return model.Float(float64(value))
}
func reportUUIDToHiLo(value uuid.UUID) (uint64, uint64) {
return binary.BigEndian.Uint64(value[0:8]), binary.BigEndian.Uint64(value[8:16])
}
func reportUUIDFromHiLo(hi uint64, lo uint64) uuid.UUID {
var value uuid.UUID
binary.BigEndian.PutUint64(value[0:8], hi)
binary.BigEndian.PutUint64(value[8:16], lo)
return value
}
func uint64ToUint(value uint64, field string) (uint, error) {
maxUint := uint64(^uint(0))
if value > maxUint {
@@ -1392,3 +1381,47 @@ func uint64ToUint(value uint64, field string) (uint, error) {
}
return uint(value), nil
}
// GameReportRequestToPayload converts model.GameReportRequest to
// FlatBuffers bytes suitable for the authenticated gateway transport.
func GameReportRequestToPayload(req *model.GameReportRequest) ([]byte, error) {
if req == nil {
return nil, errors.New("encode game report request payload: request is nil")
}
builder := flatbuffers.NewBuilder(64)
fbs.GameReportRequestStart(builder)
hi, lo := uuidToHiLo(req.GameID)
fbs.GameReportRequestAddGameId(builder, commonfbs.CreateUUID(builder, hi, lo))
fbs.GameReportRequestAddTurn(builder, uint32(req.Turn))
offset := fbs.GameReportRequestEnd(builder)
fbs.FinishGameReportRequestBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToGameReportRequest converts FlatBuffers payload bytes into
// model.GameReportRequest.
func PayloadToGameReportRequest(data []byte) (result *model.GameReportRequest, err error) {
if len(data) == 0 {
return nil, errors.New("decode game report request payload: data is empty")
}
defer func() {
if recovered := recover(); recovered != nil {
result = nil
err = fmt.Errorf("decode game report request payload: panic recovered: %v", recovered)
}
}()
req := fbs.GetRootAsGameReportRequest(data, 0)
gameID := req.GameId(nil)
if gameID == nil {
return nil, errors.New("decode game report request payload: game_id is missing")
}
return &model.GameReportRequest{
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
Turn: uint(req.Turn()),
}, nil
}
+34
View File
@@ -14,6 +14,40 @@ import (
"github.com/google/uuid"
)
func TestGameReportRequestPayloadRoundTrip(t *testing.T) {
t.Parallel()
source := &model.GameReportRequest{
GameID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
Turn: 42,
}
payload, err := GameReportRequestToPayload(source)
if err != nil {
t.Fatalf("encode game report request: %v", err)
}
decoded, err := PayloadToGameReportRequest(payload)
if err != nil {
t.Fatalf("decode game report request: %v", err)
}
if !reflect.DeepEqual(source, decoded) {
t.Fatalf("round-trip mismatch\nsource: %#v\ndecoded: %#v", source, decoded)
}
}
func TestGameReportRequestRejectsEmptyAndNil(t *testing.T) {
t.Parallel()
if _, err := GameReportRequestToPayload(nil); err == nil {
t.Fatalf("expected error encoding nil request")
}
if _, err := PayloadToGameReportRequest(nil); err == nil {
t.Fatalf("expected error decoding empty payload")
}
}
func TestReportToPayloadAndPayloadToReportRoundTrip(t *testing.T) {
t.Parallel()
+265
View File
@@ -510,3 +510,268 @@ func recoverUserDecodePanic[T any](message string, result **T, err *error) {
*err = fmt.Errorf("%s: panic recovered: %v", message, recovered)
}
}
// ListMySessionsRequestToPayload converts usermodel.ListMySessionsRequest
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func ListMySessionsRequestToPayload(request *usermodel.ListMySessionsRequest) ([]byte, error) {
if request == nil {
return nil, errors.New("encode list my sessions request payload: request is nil")
}
builder := flatbuffers.NewBuilder(32)
userfbs.ListMySessionsRequestStart(builder)
offset := userfbs.ListMySessionsRequestEnd(builder)
userfbs.FinishListMySessionsRequestBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToListMySessionsRequest converts FlatBuffers payload bytes into
// usermodel.ListMySessionsRequest.
func PayloadToListMySessionsRequest(data []byte) (result *usermodel.ListMySessionsRequest, err error) {
if len(data) == 0 {
return nil, errors.New("decode list my sessions request payload: data is empty")
}
defer recoverUserDecodePanic("decode list my sessions request payload", &result, &err)
_ = userfbs.GetRootAsListMySessionsRequest(data, 0)
return &usermodel.ListMySessionsRequest{}, nil
}
// ListMySessionsResponseToPayload converts usermodel.ListMySessionsResponse
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func ListMySessionsResponseToPayload(response *usermodel.ListMySessionsResponse) ([]byte, error) {
if response == nil {
return nil, errors.New("encode list my sessions response payload: response is nil")
}
builder := flatbuffers.NewBuilder(256)
itemOffsets := make([]flatbuffers.UOffsetT, len(response.Items))
for index := range response.Items {
itemOffsets[index] = encodeDeviceSession(builder, response.Items[index])
}
var itemsVector flatbuffers.UOffsetT
if len(itemOffsets) > 0 {
userfbs.ListMySessionsResponseStartItemsVector(builder, len(itemOffsets))
for index := len(itemOffsets) - 1; index >= 0; index-- {
builder.PrependUOffsetT(itemOffsets[index])
}
itemsVector = builder.EndVector(len(itemOffsets))
}
userfbs.ListMySessionsResponseStart(builder)
if itemsVector != 0 {
userfbs.ListMySessionsResponseAddItems(builder, itemsVector)
}
offset := userfbs.ListMySessionsResponseEnd(builder)
userfbs.FinishListMySessionsResponseBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToListMySessionsResponse converts FlatBuffers payload bytes into
// usermodel.ListMySessionsResponse.
func PayloadToListMySessionsResponse(data []byte) (result *usermodel.ListMySessionsResponse, err error) {
if len(data) == 0 {
return nil, errors.New("decode list my sessions response payload: data is empty")
}
defer recoverUserDecodePanic("decode list my sessions response payload", &result, &err)
response := userfbs.GetRootAsListMySessionsResponse(data, 0)
count := response.ItemsLength()
out := &usermodel.ListMySessionsResponse{
Items: make([]usermodel.DeviceSession, 0, count),
}
view := new(userfbs.DeviceSessionView)
for index := 0; index < count; index++ {
if !response.Items(view, index) {
return nil, fmt.Errorf("decode list my sessions response payload: item %d is missing", index)
}
out.Items = append(out.Items, decodeDeviceSession(view))
}
return out, nil
}
// RevokeMySessionRequestToPayload converts usermodel.RevokeMySessionRequest
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func RevokeMySessionRequestToPayload(request *usermodel.RevokeMySessionRequest) ([]byte, error) {
if request == nil {
return nil, errors.New("encode revoke my session request payload: request is nil")
}
builder := flatbuffers.NewBuilder(64)
deviceSessionID := builder.CreateString(request.DeviceSessionID)
userfbs.RevokeMySessionRequestStart(builder)
userfbs.RevokeMySessionRequestAddDeviceSessionId(builder, deviceSessionID)
offset := userfbs.RevokeMySessionRequestEnd(builder)
userfbs.FinishRevokeMySessionRequestBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToRevokeMySessionRequest converts FlatBuffers payload bytes into
// usermodel.RevokeMySessionRequest.
func PayloadToRevokeMySessionRequest(data []byte) (result *usermodel.RevokeMySessionRequest, err error) {
if len(data) == 0 {
return nil, errors.New("decode revoke my session request payload: data is empty")
}
defer recoverUserDecodePanic("decode revoke my session request payload", &result, &err)
request := userfbs.GetRootAsRevokeMySessionRequest(data, 0)
return &usermodel.RevokeMySessionRequest{
DeviceSessionID: string(request.DeviceSessionId()),
}, nil
}
// RevokeMySessionResponseToPayload converts usermodel.RevokeMySessionResponse
// to FlatBuffers bytes suitable for the authenticated gateway transport.
func RevokeMySessionResponseToPayload(response *usermodel.RevokeMySessionResponse) ([]byte, error) {
if response == nil {
return nil, errors.New("encode revoke my session response payload: response is nil")
}
builder := flatbuffers.NewBuilder(128)
sessionOffset := encodeDeviceSession(builder, response.Session)
userfbs.RevokeMySessionResponseStart(builder)
userfbs.RevokeMySessionResponseAddSession(builder, sessionOffset)
offset := userfbs.RevokeMySessionResponseEnd(builder)
userfbs.FinishRevokeMySessionResponseBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToRevokeMySessionResponse converts FlatBuffers payload bytes into
// usermodel.RevokeMySessionResponse.
func PayloadToRevokeMySessionResponse(data []byte) (result *usermodel.RevokeMySessionResponse, err error) {
if len(data) == 0 {
return nil, errors.New("decode revoke my session response payload: data is empty")
}
defer recoverUserDecodePanic("decode revoke my session response payload", &result, &err)
response := userfbs.GetRootAsRevokeMySessionResponse(data, 0)
view := response.Session(nil)
if view == nil {
return nil, errors.New("decode revoke my session response payload: session is missing")
}
return &usermodel.RevokeMySessionResponse{Session: decodeDeviceSession(view)}, nil
}
// RevokeAllMySessionsRequestToPayload converts
// usermodel.RevokeAllMySessionsRequest to FlatBuffers bytes suitable for
// the authenticated gateway transport.
func RevokeAllMySessionsRequestToPayload(request *usermodel.RevokeAllMySessionsRequest) ([]byte, error) {
if request == nil {
return nil, errors.New("encode revoke all my sessions request payload: request is nil")
}
builder := flatbuffers.NewBuilder(32)
userfbs.RevokeAllMySessionsRequestStart(builder)
offset := userfbs.RevokeAllMySessionsRequestEnd(builder)
userfbs.FinishRevokeAllMySessionsRequestBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToRevokeAllMySessionsRequest converts FlatBuffers payload bytes
// into usermodel.RevokeAllMySessionsRequest.
func PayloadToRevokeAllMySessionsRequest(data []byte) (result *usermodel.RevokeAllMySessionsRequest, err error) {
if len(data) == 0 {
return nil, errors.New("decode revoke all my sessions request payload: data is empty")
}
defer recoverUserDecodePanic("decode revoke all my sessions request payload", &result, &err)
_ = userfbs.GetRootAsRevokeAllMySessionsRequest(data, 0)
return &usermodel.RevokeAllMySessionsRequest{}, nil
}
// RevokeAllMySessionsResponseToPayload converts
// usermodel.RevokeAllMySessionsResponse to FlatBuffers bytes suitable
// for the authenticated gateway transport.
func RevokeAllMySessionsResponseToPayload(response *usermodel.RevokeAllMySessionsResponse) ([]byte, error) {
if response == nil {
return nil, errors.New("encode revoke all my sessions response payload: response is nil")
}
builder := flatbuffers.NewBuilder(64)
userID := builder.CreateString(response.Summary.UserID)
userfbs.DeviceSessionRevocationSummaryViewStart(builder)
userfbs.DeviceSessionRevocationSummaryViewAddUserId(builder, userID)
userfbs.DeviceSessionRevocationSummaryViewAddRevokedCount(builder, int32(response.Summary.RevokedCount))
summaryOffset := userfbs.DeviceSessionRevocationSummaryViewEnd(builder)
userfbs.RevokeAllMySessionsResponseStart(builder)
userfbs.RevokeAllMySessionsResponseAddSummary(builder, summaryOffset)
offset := userfbs.RevokeAllMySessionsResponseEnd(builder)
userfbs.FinishRevokeAllMySessionsResponseBuffer(builder, offset)
return builder.FinishedBytes(), nil
}
// PayloadToRevokeAllMySessionsResponse converts FlatBuffers payload bytes
// into usermodel.RevokeAllMySessionsResponse.
func PayloadToRevokeAllMySessionsResponse(data []byte) (result *usermodel.RevokeAllMySessionsResponse, err error) {
if len(data) == 0 {
return nil, errors.New("decode revoke all my sessions response payload: data is empty")
}
defer recoverUserDecodePanic("decode revoke all my sessions response payload", &result, &err)
response := userfbs.GetRootAsRevokeAllMySessionsResponse(data, 0)
summary := response.Summary(nil)
if summary == nil {
return nil, errors.New("decode revoke all my sessions response payload: summary is missing")
}
return &usermodel.RevokeAllMySessionsResponse{
Summary: usermodel.DeviceSessionRevocationSummary{
UserID: string(summary.UserId()),
RevokedCount: int(summary.RevokedCount()),
},
}, nil
}
func encodeDeviceSession(builder *flatbuffers.Builder, sess usermodel.DeviceSession) flatbuffers.UOffsetT {
deviceSessionID := builder.CreateString(sess.DeviceSessionID)
userID := builder.CreateString(sess.UserID)
status := builder.CreateString(sess.Status)
var clientPublicKey flatbuffers.UOffsetT
if sess.ClientPublicKey != "" {
clientPublicKey = builder.CreateString(sess.ClientPublicKey)
}
userfbs.DeviceSessionViewStart(builder)
userfbs.DeviceSessionViewAddDeviceSessionId(builder, deviceSessionID)
userfbs.DeviceSessionViewAddUserId(builder, userID)
userfbs.DeviceSessionViewAddStatus(builder, status)
if clientPublicKey != 0 {
userfbs.DeviceSessionViewAddClientPublicKey(builder, clientPublicKey)
}
userfbs.DeviceSessionViewAddCreatedAtMs(builder, sess.CreatedAt.UTC().UnixMilli())
if sess.RevokedAt != nil {
userfbs.DeviceSessionViewAddRevokedAtMs(builder, sess.RevokedAt.UTC().UnixMilli())
}
if sess.LastSeenAt != nil {
userfbs.DeviceSessionViewAddLastSeenAtMs(builder, sess.LastSeenAt.UTC().UnixMilli())
}
return userfbs.DeviceSessionViewEnd(builder)
}
func decodeDeviceSession(view *userfbs.DeviceSessionView) usermodel.DeviceSession {
return usermodel.DeviceSession{
DeviceSessionID: string(view.DeviceSessionId()),
UserID: string(view.UserId()),
Status: string(view.Status()),
ClientPublicKey: string(view.ClientPublicKey()),
CreatedAt: time.UnixMilli(view.CreatedAtMs()).UTC(),
RevokedAt: optionalUnixMilli(view.RevokedAtMs()),
LastSeenAt: optionalUnixMilli(view.LastSeenAtMs()),
}
}
+107
View File
@@ -61,6 +61,113 @@ func TestUserRequestPayloadRoundTrips(t *testing.T) {
}
}
func TestUserSessionsPayloadRoundTrips(t *testing.T) {
t.Parallel()
emptyList, err := ListMySessionsRequestToPayload(&usermodel.ListMySessionsRequest{})
if err != nil {
t.Fatalf("encode list-my-sessions request: %v", err)
}
if _, err := PayloadToListMySessionsRequest(emptyList); err != nil {
t.Fatalf("decode list-my-sessions request: %v", err)
}
revokeAll, err := RevokeAllMySessionsRequestToPayload(&usermodel.RevokeAllMySessionsRequest{})
if err != nil {
t.Fatalf("encode revoke-all-my-sessions request: %v", err)
}
if _, err := PayloadToRevokeAllMySessionsRequest(revokeAll); err != nil {
t.Fatalf("decode revoke-all-my-sessions request: %v", err)
}
revokeReq := &usermodel.RevokeMySessionRequest{DeviceSessionID: "device-7c8f"}
revokePayload, err := RevokeMySessionRequestToPayload(revokeReq)
if err != nil {
t.Fatalf("encode revoke-my-session request: %v", err)
}
revokeDecoded, err := PayloadToRevokeMySessionRequest(revokePayload)
if err != nil {
t.Fatalf("decode revoke-my-session request: %v", err)
}
if !reflect.DeepEqual(revokeReq, revokeDecoded) {
t.Fatalf("revoke-my-session request mismatch\nsource: %#v\ndecoded:%#v", revokeReq, revokeDecoded)
}
now := time.Date(2026, time.April, 9, 10, 0, 0, 0, time.UTC)
revokedAt := now.Add(time.Minute)
lastSeenAt := now.Add(time.Second)
listResp := &usermodel.ListMySessionsResponse{
Items: []usermodel.DeviceSession{
{
DeviceSessionID: "ds-1",
UserID: "user-1",
Status: "active",
ClientPublicKey: "AAAAAAAAAAA=",
CreatedAt: now,
LastSeenAt: &lastSeenAt,
},
{
DeviceSessionID: "ds-2",
UserID: "user-1",
Status: "revoked",
CreatedAt: now,
RevokedAt: &revokedAt,
},
},
}
listPayload, err := ListMySessionsResponseToPayload(listResp)
if err != nil {
t.Fatalf("encode list-my-sessions response: %v", err)
}
listDecoded, err := PayloadToListMySessionsResponse(listPayload)
if err != nil {
t.Fatalf("decode list-my-sessions response: %v", err)
}
if !reflect.DeepEqual(listResp, listDecoded) {
t.Fatalf("list-my-sessions response mismatch\nsource: %#v\ndecoded:%#v", listResp, listDecoded)
}
revokeResp := &usermodel.RevokeMySessionResponse{
Session: usermodel.DeviceSession{
DeviceSessionID: "ds-1",
UserID: "user-1",
Status: "revoked",
CreatedAt: now,
RevokedAt: &revokedAt,
},
}
revokeRespPayload, err := RevokeMySessionResponseToPayload(revokeResp)
if err != nil {
t.Fatalf("encode revoke-my-session response: %v", err)
}
revokeRespDecoded, err := PayloadToRevokeMySessionResponse(revokeRespPayload)
if err != nil {
t.Fatalf("decode revoke-my-session response: %v", err)
}
if !reflect.DeepEqual(revokeResp, revokeRespDecoded) {
t.Fatalf("revoke-my-session response mismatch\nsource: %#v\ndecoded:%#v", revokeResp, revokeRespDecoded)
}
revokeAllResp := &usermodel.RevokeAllMySessionsResponse{
Summary: usermodel.DeviceSessionRevocationSummary{
UserID: "user-1",
RevokedCount: 3,
},
}
revokeAllPayload, err := RevokeAllMySessionsResponseToPayload(revokeAllResp)
if err != nil {
t.Fatalf("encode revoke-all-my-sessions response: %v", err)
}
revokeAllDecoded, err := PayloadToRevokeAllMySessionsResponse(revokeAllPayload)
if err != nil {
t.Fatalf("decode revoke-all-my-sessions response: %v", err)
}
if !reflect.DeepEqual(revokeAllResp, revokeAllDecoded) {
t.Fatalf("revoke-all-my-sessions response mismatch\nsource: %#v\ndecoded:%#v", revokeAllResp, revokeAllDecoded)
}
}
func TestAccountResponsePayloadRoundTrip(t *testing.T) {
t.Parallel()
+22
View File
@@ -0,0 +1,22 @@
package transcoder
import (
"encoding/binary"
"github.com/google/uuid"
)
// uuidToHiLo splits a 16-byte UUID into the two big-endian uint64
// halves used by the FlatBuffers `common.UUID` struct (`hi` carries
// bytes 0..7, `lo` carries bytes 8..15).
func uuidToHiLo(value uuid.UUID) (uint64, uint64) {
return binary.BigEndian.Uint64(value[0:8]), binary.BigEndian.Uint64(value[8:16])
}
// uuidFromHiLo reverses uuidToHiLo.
func uuidFromHiLo(hi, lo uint64) uuid.UUID {
var value uuid.UUID
binary.BigEndian.PutUint64(value[0:8], hi)
binary.BigEndian.PutUint64(value[8:16], lo)
return value
}