package transcoder import ( "reflect" "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) { t.Parallel() tests := []struct { name string source any encode func(any) ([]byte, error) decode func([]byte) (any, error) }{ { 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: "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: testNotificationGameID, ApplicationID: testNotificationApplicationID}, encode: func(v any) ([]byte, error) { return LobbyApplicationSubmittedEventToPayload(v.(*LobbyApplicationSubmittedEvent)) }, decode: func(data []byte) (any, error) { return PayloadToLobbyApplicationSubmittedEvent(data) }, }, { 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 PayloadToLobbyApplicationApprovedEvent(data) }, }, { 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 PayloadToLobbyApplicationRejectedEvent(data) }, }, { 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 PayloadToLobbyMembershipRemovedEvent(data) }, }, { 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 PayloadToLobbyMembershipBlockedEvent(data) }, }, { name: "lobby race name registered", source: &LobbyRaceNameRegisteredEvent{RaceName: "Skylancer"}, 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) } }) } } func TestNotificationPayloadEncodersRejectNilInputs(t *testing.T) { t.Parallel() tests := []struct { name string call func() error }{ {"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") } }) } } func TestNotificationPayloadDecodersRejectEmptyPayloads(t *testing.T) { t.Parallel() tests := []struct { name string call func() error }{ {"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") } }) } } func TestNotificationPayloadEncodersRejectMissingUUIDs(t *testing.T) { t.Parallel() tests := []struct { name string call func() error want string }{ { name: "lobby invite received game_id", call: func() error { _, err := LobbyInviteReceivedEventToPayload(&LobbyInviteReceivedEvent{InviterUserID: testNotificationInviterID}); return err }, want: "game_id is empty", }, { name: "lobby invite received inviter", call: func() error { _, err := LobbyInviteReceivedEventToPayload(&LobbyInviteReceivedEvent{GameID: testNotificationGameID}); return err }, want: "inviter_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", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := tt.call() if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), tt.want) { t.Fatalf("unexpected error: %v", err) } }) } } func TestNotificationPayloadDecodersRejectMissingUUIDs(t *testing.T) { t.Parallel() tests := []struct { name string payload func() []byte decode func([]byte) error want string }{ { name: "lobby invite received game_id", payload: func() []byte { builder := flatbuffers.NewBuilder(32) notificationfbs.LobbyInviteReceivedEventStart(builder) offset := notificationfbs.LobbyInviteReceivedEventEnd(builder) notificationfbs.FinishLobbyInviteReceivedEventBuffer(builder, offset) return builder.FinishedBytes() }, decode: func(data []byte) error { _, err := PayloadToLobbyInviteReceivedEvent(data); return err }, want: "game_id is missing", }, { 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) notificationfbs.LobbyApplicationSubmittedEventStart(builder) 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: "application_id is missing", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := tt.decode(tt.payload()) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), tt.want) { t.Fatalf("unexpected error: %v", err) } }) } }