package notification import ( "path/filepath" "testing" "time" "galaxy/notification/internal/api/intentstream" "galaxy/notificationintent" "github.com/stretchr/testify/require" ) func TestNotificationProducerIntegrationDocsStayInSync(t *testing.T) { t.Parallel() readme := loadTextFile(t, "README.md") runtimeDoc := loadTextFile(t, filepath.Join("docs", "runtime.md")) examplesDoc := loadTextFile(t, filepath.Join("docs", "examples.md")) docsIndex := loadTextFile(t, filepath.Join("docs", "README.md")) architecture := loadTextFile(t, filepath.Join("..", "ARCHITECTURE.md")) geoProfileReadme := loadTextFile(t, filepath.Join("..", "geoprofile", "README.md")) require.Contains(t, docsIndex, "- [Runtime and components](runtime.md)") require.Contains(t, docsIndex, "- [Configuration and contract examples](examples.md)") for _, content := range []string{readme, runtimeDoc, architecture, geoProfileReadme} { normalizedContent := normalizeWhitespace(content) require.Contains(t, normalizedContent, normalizeWhitespace("`galaxy/notificationintent`")) require.Contains(t, normalizedContent, normalizeWhitespace("notification degradation")) } require.Contains(t, normalizeWhitespace(readme), normalizeWhitespace("producer publication uses plain `XADD` without stream trimming or hidden helper retries")) require.Contains(t, normalizeWhitespace(examplesDoc), normalizeWhitespace("redis-cli XADD notification:intents")) } func TestNotificationProducerIntentsDecodeThroughServiceContract(t *testing.T) { t.Parallel() for _, original := range compatibleProducerIntents(t) { original := original t.Run(original.NotificationType.String()+"/"+original.AudienceKind.String(), func(t *testing.T) { t.Parallel() values, err := original.Values() require.NoError(t, err) decoded, err := intentstream.DecodeIntent(values) require.NoError(t, err) require.Equal(t, original.NotificationType, decoded.NotificationType) require.Equal(t, original.Producer, decoded.Producer) require.Equal(t, original.AudienceKind, decoded.AudienceKind) require.Equal(t, original.RecipientUserIDs, decoded.RecipientUserIDs) require.Equal(t, original.IdempotencyKey, decoded.IdempotencyKey) require.Equal(t, original.OccurredAt, decoded.OccurredAt) require.JSONEq(t, original.PayloadJSON, decoded.PayloadJSON) }) } } func compatibleProducerIntents(t *testing.T) []notificationintent.Intent { t.Helper() metadata := notificationintent.Metadata{ IdempotencyKey: "idempotency-1", OccurredAt: time.UnixMilli(1775121700000), } builders := []func() (notificationintent.Intent, error){ func() (notificationintent.Intent, error) { return notificationintent.NewGeoReviewRecommendedIntent(metadata, notificationintent.GeoReviewRecommendedPayload{ UserID: "user-1", UserEmail: "pilot@example.com", ObservedCountry: "DE", UsualConnectionCountry: "PL", ReviewReason: "country_mismatch", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewGameTurnReadyIntent(metadata, []string{"user-1", "user-2"}, notificationintent.GameTurnReadyPayload{ GameID: "game-1", GameName: "Nebula Clash", TurnNumber: 54, }) }, func() (notificationintent.Intent, error) { return notificationintent.NewGameFinishedIntent(metadata, []string{"user-1", "user-2"}, notificationintent.GameFinishedPayload{ GameID: "game-1", GameName: "Nebula Clash", FinalTurnNumber: 55, }) }, func() (notificationintent.Intent, error) { return notificationintent.NewGameGenerationFailedIntent(metadata, notificationintent.GameGenerationFailedPayload{ GameID: "game-1", GameName: "Nebula Clash", FailureReason: "engine_timeout", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyRuntimePausedAfterStartIntent(metadata, notificationintent.LobbyRuntimePausedAfterStartPayload{ GameID: "game-1", GameName: "Nebula Clash", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewPrivateLobbyApplicationSubmittedIntent(metadata, "owner-1", notificationintent.LobbyApplicationSubmittedPayload{ GameID: "game-1", GameName: "Nebula Clash", ApplicantUserID: "user-2", ApplicantName: "Nova Pilot", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewPublicLobbyApplicationSubmittedIntent(metadata, notificationintent.LobbyApplicationSubmittedPayload{ GameID: "game-1", GameName: "Nebula Clash", ApplicantUserID: "user-2", ApplicantName: "Nova Pilot", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyMembershipApprovedIntent(metadata, "applicant-1", notificationintent.LobbyMembershipApprovedPayload{ GameID: "game-1", GameName: "Nebula Clash", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyMembershipRejectedIntent(metadata, "applicant-1", notificationintent.LobbyMembershipRejectedPayload{ GameID: "game-1", GameName: "Nebula Clash", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyMembershipBlockedIntent(metadata, "owner-1", notificationintent.LobbyMembershipBlockedPayload{ GameID: "game-1", GameName: "Nebula Clash", MembershipUserID: "user-2", MembershipUserName: "player-aabbccdd", Reason: "permanent_blocked", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyInviteCreatedIntent(metadata, "invited-1", notificationintent.LobbyInviteCreatedPayload{ GameID: "game-1", GameName: "Nebula Clash", InviterUserID: "owner-1", InviterName: "Owner Pilot", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyInviteRedeemedIntent(metadata, "owner-1", notificationintent.LobbyInviteRedeemedPayload{ GameID: "game-1", GameName: "Nebula Clash", InviteeUserID: "invitee-1", InviteeName: "Nova Pilot", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyInviteExpiredIntent(metadata, "owner-1", notificationintent.LobbyInviteExpiredPayload{ GameID: "game-1", GameName: "Nebula Clash", InviteeUserID: "invitee-1", InviteeName: "Nova Pilot", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyRaceNameRegistrationEligibleIntent(metadata, "user-7", notificationintent.LobbyRaceNameRegistrationEligiblePayload{ GameID: "game-1", GameName: "Nebula Clash", RaceName: "Skylancer", EligibleUntilMs: 1775208100000, }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyRaceNameRegisteredIntent(metadata, "user-8", notificationintent.LobbyRaceNameRegisteredPayload{ RaceName: "Skylancer", }) }, func() (notificationintent.Intent, error) { return notificationintent.NewLobbyRaceNameRegistrationDeniedIntent(metadata, "user-9", notificationintent.LobbyRaceNameRegistrationDeniedPayload{ GameID: "game-1", GameName: "Nebula Clash", RaceName: "Skylancer", Reason: "capability_not_met", }) }, } intents := make([]notificationintent.Intent, 0, len(builders)) for _, build := range builders { intent, err := build() require.NoError(t, err) intents = append(intents, intent) } return intents }