feat: notification service
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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.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",
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
intents := make([]notificationintent.Intent, 0, len(builders))
|
||||
for _, build := range builders {
|
||||
intent, err := build()
|
||||
require.NoError(t, err)
|
||||
intents = append(intents, intent)
|
||||
}
|
||||
|
||||
return intents
|
||||
}
|
||||
Reference in New Issue
Block a user