package gamemaster import ( "testing" "time" "github.com/stretchr/testify/require" "galaxy/notificationintent" ) // TestNotificationIntentConstructorsForGameMaster freezes the producer-side // surface of the three GM-owned notification types against // `pkg/notificationintent`. It complements the YAML-level catalog freeze in // `notification/contract_asyncapi_test.go` by binding the contract at compile // time: any rename of a constant, constructor, payload struct, or struct field // breaks this file's build before it can reach a YAML edit. // // The three types frozen here are documented in `gamemaster/README.md` // §Notification Contracts as the GM-owned producer catalog. func TestNotificationIntentConstructorsForGameMaster(t *testing.T) { t.Parallel() metadata := notificationintent.Metadata{ IdempotencyKey: "gm-stage07-freeze", OccurredAt: time.UnixMilli(1775121700000).UTC(), } recipientUserIDs := []string{"user-1", "user-2"} t.Run("game.turn.ready", func(t *testing.T) { t.Parallel() intent, err := notificationintent.NewGameTurnReadyIntent( metadata, recipientUserIDs, notificationintent.GameTurnReadyPayload{ GameID: "game-1", GameName: "Nebula Clash", TurnNumber: 7, }, ) require.NoError(t, err) require.Equal(t, notificationintent.NotificationTypeGameTurnReady, intent.NotificationType) require.Equal(t, "game.turn.ready", intent.NotificationType.String()) require.Equal(t, notificationintent.ProducerGameMaster, intent.Producer) require.Equal(t, notificationintent.AudienceKindUser, intent.AudienceKind) require.Equal(t, []string{"user-1", "user-2"}, intent.RecipientUserIDs) require.Equal(t, metadata.IdempotencyKey, intent.IdempotencyKey) require.True(t, intent.OccurredAt.Equal(metadata.OccurredAt)) require.NoError(t, intent.Validate()) require.Contains(t, intent.PayloadJSON, `"game_id":"game-1"`) require.Contains(t, intent.PayloadJSON, `"game_name":"Nebula Clash"`) require.Contains(t, intent.PayloadJSON, `"turn_number":7`) }) t.Run("game.finished", func(t *testing.T) { t.Parallel() intent, err := notificationintent.NewGameFinishedIntent( metadata, recipientUserIDs, notificationintent.GameFinishedPayload{ GameID: "game-1", GameName: "Nebula Clash", FinalTurnNumber: 7, }, ) require.NoError(t, err) require.Equal(t, notificationintent.NotificationTypeGameFinished, intent.NotificationType) require.Equal(t, "game.finished", intent.NotificationType.String()) require.Equal(t, notificationintent.ProducerGameMaster, intent.Producer) require.Equal(t, notificationintent.AudienceKindUser, intent.AudienceKind) require.Equal(t, []string{"user-1", "user-2"}, intent.RecipientUserIDs) require.NoError(t, intent.Validate()) require.Contains(t, intent.PayloadJSON, `"game_id":"game-1"`) require.Contains(t, intent.PayloadJSON, `"game_name":"Nebula Clash"`) require.Contains(t, intent.PayloadJSON, `"final_turn_number":7`) }) t.Run("game.generation_failed", func(t *testing.T) { t.Parallel() intent, err := notificationintent.NewGameGenerationFailedIntent( metadata, notificationintent.GameGenerationFailedPayload{ GameID: "game-1", GameName: "Nebula Clash", FailureReason: "engine_timeout", }, ) require.NoError(t, err) require.Equal(t, notificationintent.NotificationTypeGameGenerationFailed, intent.NotificationType) require.Equal(t, "game.generation_failed", intent.NotificationType.String()) require.Equal(t, notificationintent.ProducerGameMaster, intent.Producer) require.Equal(t, notificationintent.AudienceKindAdminEmail, intent.AudienceKind) require.Empty(t, intent.RecipientUserIDs) require.NoError(t, intent.Validate()) require.Contains(t, intent.PayloadJSON, `"game_id":"game-1"`) require.Contains(t, intent.PayloadJSON, `"game_name":"Nebula Clash"`) require.Contains(t, intent.PayloadJSON, `"failure_reason":"engine_timeout"`) }) t.Run("audience and channel matrix", func(t *testing.T) { t.Parallel() userTypes := []notificationintent.NotificationType{ notificationintent.NotificationTypeGameTurnReady, notificationintent.NotificationTypeGameFinished, } for _, notificationType := range userTypes { notificationType := notificationType t.Run(notificationType.String(), func(t *testing.T) { t.Parallel() require.Equal(t, notificationintent.ProducerGameMaster, notificationType.ExpectedProducer()) require.True(t, notificationType.SupportsAudience(notificationintent.AudienceKindUser)) require.False(t, notificationType.SupportsAudience(notificationintent.AudienceKindAdminEmail)) require.True(t, notificationType.SupportsChannel(notificationintent.AudienceKindUser, notificationintent.ChannelPush)) require.True(t, notificationType.SupportsChannel(notificationintent.AudienceKindUser, notificationintent.ChannelEmail)) require.False(t, notificationType.SupportsChannel(notificationintent.AudienceKindAdminEmail, notificationintent.ChannelEmail)) }) } t.Run("game.generation_failed", func(t *testing.T) { t.Parallel() notificationType := notificationintent.NotificationTypeGameGenerationFailed require.Equal(t, notificationintent.ProducerGameMaster, notificationType.ExpectedProducer()) require.True(t, notificationType.SupportsAudience(notificationintent.AudienceKindAdminEmail)) require.False(t, notificationType.SupportsAudience(notificationintent.AudienceKindUser)) require.True(t, notificationType.SupportsChannel(notificationintent.AudienceKindAdminEmail, notificationintent.ChannelEmail)) require.False(t, notificationType.SupportsChannel(notificationintent.AudienceKindAdminEmail, notificationintent.ChannelPush)) require.False(t, notificationType.SupportsChannel(notificationintent.AudienceKindUser, notificationintent.ChannelEmail)) }) }) }