feat: runtime manager
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
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 TestPublisherWritesIntent(t *testing.T) {
|
||||
client, _ := newRedis(t)
|
||||
|
||||
publisher, err := NewPublisher(Config{Client: client, Stream: "notification:intents"})
|
||||
require.NoError(t, err)
|
||||
|
||||
intent, err := notificationintent.NewRuntimeImagePullFailedIntent(
|
||||
notificationintent.Metadata{
|
||||
IdempotencyKey: "rtmanager:start:game-1:abc",
|
||||
OccurredAt: time.UnixMilli(1714200000000).UTC(),
|
||||
},
|
||||
notificationintent.RuntimeImagePullFailedPayload{
|
||||
GameID: "game-1",
|
||||
ImageRef: "galaxy/game:1.4.2",
|
||||
ErrorCode: "image_pull_failed",
|
||||
ErrorMessage: "registry timeout",
|
||||
AttemptedAtMs: 1714200000000,
|
||||
},
|
||||
)
|
||||
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, "runtime.image_pull_failed", values["notification_type"])
|
||||
assert.Equal(t, "runtime_manager", values["producer"])
|
||||
assert.Equal(t, "admin_email", values["audience_kind"])
|
||||
assert.Equal(t, "rtmanager:start:game-1:abc", values["idempotency_key"])
|
||||
|
||||
// recipient_user_ids_json must be absent for admin_email audience.
|
||||
_, hasRecipients := values["recipient_user_ids_json"]
|
||||
assert.False(t, hasRecipients)
|
||||
|
||||
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, "galaxy/game:1.4.2", payload["image_ref"])
|
||||
}
|
||||
|
||||
func TestPublisherForwardsValidationError(t *testing.T) {
|
||||
client, _ := newRedis(t)
|
||||
publisher, err := NewPublisher(Config{Client: client})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Intent with a zero OccurredAt fails the shared validator.
|
||||
bad := notificationintent.Intent{
|
||||
NotificationType: notificationintent.NotificationTypeRuntimeImagePullFailed,
|
||||
Producer: notificationintent.ProducerRuntimeManager,
|
||||
AudienceKind: notificationintent.AudienceKindAdminEmail,
|
||||
IdempotencyKey: "k",
|
||||
PayloadJSON: `{"game_id":"g","image_ref":"r","error_code":"c","error_message":"m","attempted_at_ms":1}`,
|
||||
}
|
||||
require.Error(t, publisher.Publish(context.Background(), bad))
|
||||
}
|
||||
|
||||
func TestPublisherDefaultsStreamName(t *testing.T) {
|
||||
client, _ := newRedis(t)
|
||||
publisher, err := NewPublisher(Config{Client: client, Stream: ""})
|
||||
require.NoError(t, err)
|
||||
|
||||
intent, err := notificationintent.NewRuntimeContainerStartFailedIntent(
|
||||
notificationintent.Metadata{
|
||||
IdempotencyKey: "k",
|
||||
OccurredAt: time.UnixMilli(1714200000000).UTC(),
|
||||
},
|
||||
notificationintent.RuntimeContainerStartFailedPayload{
|
||||
GameID: "g",
|
||||
ImageRef: "r",
|
||||
ErrorCode: "container_start_failed",
|
||||
ErrorMessage: "boom",
|
||||
AttemptedAtMs: 1714200000000,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, publisher.Publish(context.Background(), intent))
|
||||
|
||||
messages := readStream(t, client, notificationintent.DefaultIntentsStream)
|
||||
require.Len(t, messages, 1)
|
||||
}
|
||||
Reference in New Issue
Block a user