168 lines
5.4 KiB
Go
168 lines
5.4 KiB
Go
package notificationpublisher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"galaxy/notificationintent"
|
|
)
|
|
|
|
func newRedis(t *testing.T) (*redis.Client, *miniredis.Miniredis) {
|
|
t.Helper()
|
|
server := miniredis.RunT(t)
|
|
client := redis.NewClient(&redis.Options{Addr: server.Addr()})
|
|
t.Cleanup(func() { _ = client.Close() })
|
|
return client, server
|
|
}
|
|
|
|
func readStream(t *testing.T, client *redis.Client, stream string) []redis.XMessage {
|
|
t.Helper()
|
|
messages, err := client.XRange(context.Background(), stream, "-", "+").Result()
|
|
require.NoError(t, err)
|
|
return messages
|
|
}
|
|
|
|
func TestNewPublisherValidation(t *testing.T) {
|
|
t.Run("nil client", func(t *testing.T) {
|
|
_, err := NewPublisher(Config{})
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "nil redis client")
|
|
})
|
|
}
|
|
|
|
func TestPublishGameTurnReady(t *testing.T) {
|
|
client, _ := newRedis(t)
|
|
|
|
publisher, err := NewPublisher(Config{Client: client, Stream: "notification:intents"})
|
|
require.NoError(t, err)
|
|
|
|
intent, err := notificationintent.NewGameTurnReadyIntent(
|
|
notificationintent.Metadata{
|
|
IdempotencyKey: "gamemaster:turn:game-1:42",
|
|
OccurredAt: time.UnixMilli(1714200000000).UTC(),
|
|
},
|
|
[]string{"u-2", "u-1"},
|
|
notificationintent.GameTurnReadyPayload{
|
|
GameID: "game-1",
|
|
GameName: "Galaxy",
|
|
TurnNumber: 42,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NoError(t, publisher.Publish(context.Background(), intent))
|
|
|
|
messages := readStream(t, client, "notification:intents")
|
|
require.Len(t, messages, 1)
|
|
values := messages[0].Values
|
|
assert.Equal(t, "game.turn.ready", values["notification_type"])
|
|
assert.Equal(t, "game_master", values["producer"])
|
|
assert.Equal(t, "user", values["audience_kind"])
|
|
assert.Equal(t, "gamemaster:turn:game-1:42", values["idempotency_key"])
|
|
|
|
recipients, ok := values["recipient_user_ids_json"].(string)
|
|
require.True(t, ok)
|
|
var ids []string
|
|
require.NoError(t, json.Unmarshal([]byte(recipients), &ids))
|
|
assert.ElementsMatch(t, []string{"u-1", "u-2"}, ids)
|
|
|
|
payloadRaw, ok := values["payload_json"].(string)
|
|
require.True(t, ok)
|
|
var payload map[string]any
|
|
require.NoError(t, json.Unmarshal([]byte(payloadRaw), &payload))
|
|
assert.Equal(t, "game-1", payload["game_id"])
|
|
assert.Equal(t, float64(42), payload["turn_number"])
|
|
}
|
|
|
|
func TestPublishGameFinished(t *testing.T) {
|
|
client, _ := newRedis(t)
|
|
publisher, err := NewPublisher(Config{Client: client, Stream: "notification:intents"})
|
|
require.NoError(t, err)
|
|
|
|
intent, err := notificationintent.NewGameFinishedIntent(
|
|
notificationintent.Metadata{
|
|
IdempotencyKey: "gamemaster:finished:g-1",
|
|
OccurredAt: time.UnixMilli(1714200000000).UTC(),
|
|
},
|
|
[]string{"u-1"},
|
|
notificationintent.GameFinishedPayload{
|
|
GameID: "g-1",
|
|
GameName: "Galaxy",
|
|
FinalTurnNumber: 100,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NoError(t, publisher.Publish(context.Background(), intent))
|
|
|
|
messages := readStream(t, client, "notification:intents")
|
|
require.Len(t, messages, 1)
|
|
assert.Equal(t, "game.finished", messages[0].Values["notification_type"])
|
|
assert.Equal(t, "user", messages[0].Values["audience_kind"])
|
|
}
|
|
|
|
func TestPublishGameGenerationFailed(t *testing.T) {
|
|
client, _ := newRedis(t)
|
|
publisher, err := NewPublisher(Config{Client: client, Stream: "notification:intents"})
|
|
require.NoError(t, err)
|
|
|
|
intent, err := notificationintent.NewGameGenerationFailedIntent(
|
|
notificationintent.Metadata{
|
|
IdempotencyKey: "gamemaster:gen-failed:g-1:42",
|
|
OccurredAt: time.UnixMilli(1714200000000).UTC(),
|
|
},
|
|
notificationintent.GameGenerationFailedPayload{
|
|
GameID: "g-1",
|
|
GameName: "Galaxy",
|
|
FailureReason: "engine timeout",
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NoError(t, publisher.Publish(context.Background(), intent))
|
|
|
|
messages := readStream(t, client, "notification:intents")
|
|
require.Len(t, messages, 1)
|
|
values := messages[0].Values
|
|
assert.Equal(t, "game.generation_failed", values["notification_type"])
|
|
assert.Equal(t, "admin_email", values["audience_kind"])
|
|
_, hasRecipients := values["recipient_user_ids_json"]
|
|
assert.False(t, hasRecipients, "admin_email audience must not carry recipient ids")
|
|
}
|
|
|
|
func TestPublishForwardsValidationError(t *testing.T) {
|
|
client, _ := newRedis(t)
|
|
publisher, err := NewPublisher(Config{Client: client})
|
|
require.NoError(t, err)
|
|
|
|
bad := notificationintent.Intent{
|
|
NotificationType: notificationintent.NotificationTypeGameTurnReady,
|
|
Producer: notificationintent.ProducerGameMaster,
|
|
AudienceKind: notificationintent.AudienceKindUser,
|
|
IdempotencyKey: "k",
|
|
PayloadJSON: `{"game_id":"g","game_name":"x","turn_number":1}`,
|
|
}
|
|
require.Error(t, publisher.Publish(context.Background(), bad))
|
|
}
|
|
|
|
func TestPublishDefaultStream(t *testing.T) {
|
|
client, _ := newRedis(t)
|
|
publisher, err := NewPublisher(Config{Client: client, Stream: ""})
|
|
require.NoError(t, err)
|
|
|
|
intent, err := notificationintent.NewGameTurnReadyIntent(
|
|
notificationintent.Metadata{IdempotencyKey: "k", OccurredAt: time.UnixMilli(1).UTC()},
|
|
[]string{"u-1"},
|
|
notificationintent.GameTurnReadyPayload{GameID: "g", GameName: "n", TurnNumber: 1},
|
|
)
|
|
require.NoError(t, err)
|
|
require.NoError(t, publisher.Publish(context.Background(), intent))
|
|
|
|
messages := readStream(t, client, notificationintent.DefaultIntentsStream)
|
|
require.Len(t, messages, 1)
|
|
}
|