146 lines
5.2 KiB
Go
146 lines
5.2 KiB
Go
package intentstream
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDecodeIntentNormalizesUserRecipientsAndPayload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fields := map[string]any{
|
|
fieldNotificationType: NotificationTypeGameTurnReady.String(),
|
|
fieldProducer: ProducerGameMaster.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-2","user-1"]`,
|
|
fieldIdempotencyKey: "game-123:turn-54",
|
|
fieldOccurredAtMS: "1775121700000",
|
|
fieldPayloadJSON: `{"turn_number":54,"game_name":"Nebula Clash","game_id":"game-123"}`,
|
|
fieldRequestID: "request-123",
|
|
fieldTraceID: "trace-123",
|
|
}
|
|
|
|
intent, err := DecodeIntent(fields)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{"user-1", "user-2"}, intent.RecipientUserIDs)
|
|
require.Equal(t, `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`, intent.PayloadJSON)
|
|
require.Equal(t, time.UnixMilli(1775121700000).UTC(), intent.OccurredAt)
|
|
}
|
|
|
|
func TestDecodeIntentCanonicalizesEquivalentPayloadJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fieldsA := map[string]any{
|
|
fieldNotificationType: NotificationTypeGameFinished.String(),
|
|
fieldProducer: ProducerGameMaster.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-1"]`,
|
|
fieldIdempotencyKey: "game-123:finished",
|
|
fieldOccurredAtMS: "1775121700001",
|
|
fieldPayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","final_turn_number":54}`,
|
|
}
|
|
fieldsB := map[string]any{
|
|
fieldNotificationType: NotificationTypeGameFinished.String(),
|
|
fieldProducer: ProducerGameMaster.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-1"]`,
|
|
fieldIdempotencyKey: "game-123:finished",
|
|
fieldOccurredAtMS: "1775121709999",
|
|
fieldPayloadJSON: `{"final_turn_number":54,"game_name":"Nebula Clash","game_id":"game-123"}`,
|
|
}
|
|
|
|
intentA, err := DecodeIntent(fieldsA)
|
|
require.NoError(t, err)
|
|
intentB, err := DecodeIntent(fieldsB)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, intentA.PayloadJSON, intentB.PayloadJSON)
|
|
}
|
|
|
|
func TestDecodeIntentRejectsUnsupportedTopLevelField(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fields := map[string]any{
|
|
fieldNotificationType: NotificationTypeGameTurnReady.String(),
|
|
fieldProducer: ProducerGameMaster.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-1"]`,
|
|
fieldIdempotencyKey: "game-123:turn-54",
|
|
fieldOccurredAtMS: "1775121700000",
|
|
fieldPayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`,
|
|
"unexpected": "boom",
|
|
}
|
|
|
|
_, err := DecodeIntent(fields)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "unsupported fields")
|
|
require.Equal(t, malformedFailureCodeInvalidIntent(), string(ClassifyDecodeError(err)))
|
|
}
|
|
|
|
func TestDecodeIntentRejectsDuplicateRecipientUserIDs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fields := map[string]any{
|
|
fieldNotificationType: NotificationTypeGameTurnReady.String(),
|
|
fieldProducer: ProducerGameMaster.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-1","user-1"]`,
|
|
fieldIdempotencyKey: "game-123:turn-54",
|
|
fieldOccurredAtMS: "1775121700000",
|
|
fieldPayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","turn_number":54}`,
|
|
}
|
|
|
|
_, err := DecodeIntent(fields)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "duplicates user id")
|
|
require.Equal(t, malformedFailureCodeInvalidIntent(), string(ClassifyDecodeError(err)))
|
|
}
|
|
|
|
func TestDecodeIntentRejectsInvalidPayloadJSON(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fields := map[string]any{
|
|
fieldNotificationType: NotificationTypeLobbyInviteCreated.String(),
|
|
fieldProducer: ProducerGameLobby.String(),
|
|
fieldAudienceKind: AudienceKindUser.String(),
|
|
fieldRecipientUserIDs: `["user-1"]`,
|
|
fieldIdempotencyKey: "invite-created:user-1",
|
|
fieldOccurredAtMS: "1775121700000",
|
|
fieldPayloadJSON: `{"game_id":"game-123","game_name":"Nebula Clash","inviter_user_id":"user-2"}`,
|
|
}
|
|
|
|
_, err := DecodeIntent(fields)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "payload_json.inviter_name is required")
|
|
require.Equal(t, malformedFailureCodeInvalidPayload(), string(ClassifyDecodeError(err)))
|
|
}
|
|
|
|
func TestDecodeIntentRejectsAdminRecipientsField(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fields := map[string]any{
|
|
fieldNotificationType: NotificationTypeGeoReviewRecommended.String(),
|
|
fieldProducer: ProducerGeoProfile.String(),
|
|
fieldAudienceKind: AudienceKindAdminEmail.String(),
|
|
fieldRecipientUserIDs: `["user-1"]`,
|
|
fieldIdempotencyKey: "geo:user-1",
|
|
fieldOccurredAtMS: "1775121700000",
|
|
fieldPayloadJSON: `{"user_id":"user-1","user_email":"pilot@example.com","observed_country":"DE","usual_connection_country":"PL","review_reason":"country_mismatch"}`,
|
|
}
|
|
|
|
_, err := DecodeIntent(fields)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "must not be present")
|
|
require.Equal(t, malformedFailureCodeInvalidIntent(), string(ClassifyDecodeError(err)))
|
|
}
|
|
|
|
func malformedFailureCodeInvalidIntent() string {
|
|
return "invalid_intent"
|
|
}
|
|
|
|
func malformedFailureCodeInvalidPayload() string {
|
|
return "invalid_payload"
|
|
}
|