276 lines
9.6 KiB
Go
276 lines
9.6 KiB
Go
package publishmail
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/notification/internal/api/intentstream"
|
|
"galaxy/notification/internal/service/acceptintent"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestEncoderEncodesUserAndAdminEmailCommands(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.UnixMilli(1775121700000).UTC()
|
|
tests := []struct {
|
|
name string
|
|
notification acceptintent.NotificationRecord
|
|
route acceptintent.NotificationRoute
|
|
wantDeliveryID string
|
|
wantIdempotency string
|
|
wantPayloadJSON string
|
|
}{
|
|
{
|
|
name: "user route",
|
|
notification: acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700000-0",
|
|
NotificationType: intentstream.NotificationTypeGameTurnReady,
|
|
Producer: intentstream.ProducerGameMaster,
|
|
AudienceKind: intentstream.AudienceKindUser,
|
|
RecipientUserIDs: []string{"user-1"},
|
|
PayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`,
|
|
IdempotencyKey: "game-123:turn-54",
|
|
RequestFingerprint: "sha256:deadbeef",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
route: acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700000-0",
|
|
RouteID: "email:user:user-1",
|
|
Channel: intentstream.ChannelEmail,
|
|
RecipientRef: "user:user-1",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 7,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "pilot@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
wantDeliveryID: "1775121700000-0/email:user:user-1",
|
|
wantIdempotency: "notification:1775121700000-0/email:user:user-1",
|
|
wantPayloadJSON: `{"to":["pilot@example.com"],"cc":[],"bcc":[],"reply_to":[],"template_id":"game.turn.ready","locale":"en","variables":{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54},"attachments":[]}`,
|
|
},
|
|
{
|
|
name: "admin route",
|
|
notification: acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700001-0",
|
|
NotificationType: intentstream.NotificationTypeLobbyApplicationSubmitted,
|
|
Producer: intentstream.ProducerGameLobby,
|
|
AudienceKind: intentstream.AudienceKindAdminEmail,
|
|
PayloadJSON: `{"applicant_name":"Nova Pilot","applicant_user_id":"user-42","game_id":"game-456","game_name":"Orion Front"}`,
|
|
IdempotencyKey: "game-456:application-submitted:user-42",
|
|
RequestFingerprint: "sha256:cafebabe",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
route: acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700001-0",
|
|
RouteID: "email:email:owner@example.com",
|
|
Channel: intentstream.ChannelEmail,
|
|
RecipientRef: "email:owner@example.com",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 7,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "owner@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
wantDeliveryID: "1775121700001-0/email:email:owner@example.com",
|
|
wantIdempotency: "notification:1775121700001-0/email:email:owner@example.com",
|
|
wantPayloadJSON: `{"to":["owner@example.com"],"cc":[],"bcc":[],"reply_to":[],"template_id":"lobby.application.submitted","locale":"en","variables":{"applicant_name":"Nova Pilot","applicant_user_id":"user-42","game_id":"game-456","game_name":"Orion Front"},"attachments":[]}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
command, err := Encoder{}.Encode(tt.notification, tt.route)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.wantDeliveryID, command.DeliveryID)
|
|
require.Equal(t, tt.wantIdempotency, command.IdempotencyKey)
|
|
require.Equal(t, now, command.RequestedAt)
|
|
require.JSONEq(t, tt.wantPayloadJSON, command.PayloadJSON)
|
|
|
|
values := command.Values()
|
|
require.Equal(t, tt.wantDeliveryID, values["delivery_id"])
|
|
require.Equal(t, "notification", values["source"])
|
|
require.Equal(t, "template", values["payload_mode"])
|
|
require.Equal(t, tt.wantIdempotency, values["idempotency_key"])
|
|
require.Equal(t, "1775121700000", values["requested_at_ms"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncoderPropagatesTracingMetadata(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.UnixMilli(1775121700000).UTC()
|
|
command, err := Encoder{}.Encode(
|
|
acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700000-0",
|
|
NotificationType: intentstream.NotificationTypeGameTurnReady,
|
|
Producer: intentstream.ProducerGameMaster,
|
|
AudienceKind: intentstream.AudienceKindUser,
|
|
RecipientUserIDs: []string{"user-1"},
|
|
PayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`,
|
|
IdempotencyKey: "game-123:turn-54",
|
|
RequestFingerprint: "sha256:deadbeef",
|
|
RequestID: "request-1",
|
|
TraceID: "trace-1",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700000-0",
|
|
RouteID: "email:user:user-1",
|
|
Channel: intentstream.ChannelEmail,
|
|
RecipientRef: "user:user-1",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 7,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "pilot@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
values := command.Values()
|
|
require.Equal(t, "request-1", values["request_id"])
|
|
require.Equal(t, "trace-1", values["trace_id"])
|
|
}
|
|
|
|
func TestEncoderPreservesNormalizedPayloadAsTemplateVariables(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.UnixMilli(1775121700000).UTC()
|
|
command, err := Encoder{}.Encode(
|
|
acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700000-0",
|
|
NotificationType: intentstream.NotificationTypeGameFinished,
|
|
Producer: intentstream.ProducerGameMaster,
|
|
AudienceKind: intentstream.AudienceKindUser,
|
|
RecipientUserIDs: []string{"user-1"},
|
|
PayloadJSON: `{"final_turn_number":81,"game_id":"game-123","game_name":"Nebula Clash"}`,
|
|
IdempotencyKey: "game-123:final",
|
|
RequestFingerprint: "sha256:deadbeef",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700000-0",
|
|
RouteID: "email:user:user-1",
|
|
Channel: intentstream.ChannelEmail,
|
|
RecipientRef: "user:user-1",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 7,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "pilot@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
var payload struct {
|
|
Variables map[string]any `json:"variables"`
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(command.PayloadJSON), &payload))
|
|
require.Equal(t, map[string]any{
|
|
"final_turn_number": float64(81),
|
|
"game_id": "game-123",
|
|
"game_name": "Nebula Clash",
|
|
}, payload.Variables)
|
|
}
|
|
|
|
func TestEncoderUsesEmptyAncillaryEnvelopeFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.UnixMilli(1775121700000).UTC()
|
|
command, err := Encoder{}.Encode(
|
|
acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700000-0",
|
|
NotificationType: intentstream.NotificationTypeLobbyInviteExpired,
|
|
Producer: intentstream.ProducerGameLobby,
|
|
AudienceKind: intentstream.AudienceKindUser,
|
|
RecipientUserIDs: []string{"user-1"},
|
|
PayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","invitee_name":"Nova Pilot","invitee_user_id":"user-2"}`,
|
|
IdempotencyKey: "game-123:invite-expired",
|
|
RequestFingerprint: "sha256:deadbeef",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700000-0",
|
|
RouteID: "email:user:user-1",
|
|
Channel: intentstream.ChannelEmail,
|
|
RecipientRef: "user:user-1",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 7,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "pilot@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
require.JSONEq(
|
|
t,
|
|
`{"to":["pilot@example.com"],"cc":[],"bcc":[],"reply_to":[],"template_id":"lobby.invite.expired","locale":"en","variables":{"game_id":"game-123","game_name":"Nebula Clash","invitee_name":"Nova Pilot","invitee_user_id":"user-2"},"attachments":[]}`,
|
|
command.PayloadJSON,
|
|
)
|
|
}
|
|
|
|
func TestEncoderRejectsInvalidRouteForMailPublication(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.UnixMilli(1775121700000).UTC()
|
|
_, err := Encoder{}.Encode(
|
|
acceptintent.NotificationRecord{
|
|
NotificationID: "1775121700000-0",
|
|
NotificationType: intentstream.NotificationTypeGameTurnReady,
|
|
Producer: intentstream.ProducerGameMaster,
|
|
AudienceKind: intentstream.AudienceKindUser,
|
|
RecipientUserIDs: []string{"user-1"},
|
|
PayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`,
|
|
IdempotencyKey: "game-123:turn-54",
|
|
RequestFingerprint: "sha256:deadbeef",
|
|
AcceptedAt: now,
|
|
OccurredAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
acceptintent.NotificationRoute{
|
|
NotificationID: "1775121700000-0",
|
|
RouteID: "push:user:user-1",
|
|
Channel: intentstream.ChannelPush,
|
|
RecipientRef: "user:user-1",
|
|
Status: acceptintent.RouteStatusPending,
|
|
MaxAttempts: 3,
|
|
NextAttemptAt: now,
|
|
ResolvedEmail: "pilot@example.com",
|
|
ResolvedLocale: "en",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
)
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, `route channel "push" is unsupported`)
|
|
}
|