Files
galaxy-game/notification/producer_integration_contract_test.go
T
2026-04-25 23:20:55 +02:00

198 lines
7.5 KiB
Go

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
}