package metricsracenamedir_test import ( "context" "testing" "time" "galaxy/lobby/internal/adapters/metricsracenamedir" "galaxy/lobby/internal/adapters/racenameinmem" "galaxy/lobby/internal/ports" "galaxy/lobby/internal/telemetry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) func newRuntime(t *testing.T) (*telemetry.Runtime, sdkmetric.Reader) { t.Helper() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) t.Cleanup(func() { _ = provider.Shutdown(context.Background()) }) runtime, err := telemetry.NewWithProviders(provider, nil) require.NoError(t, err) return runtime, reader } func newInner(t *testing.T) ports.RaceNameDirectory { t.Helper() stub, err := racenameinmem.NewDirectory() require.NoError(t, err) return stub } func TestDirectoryRecordsReserveAndReleaseOutcomes(t *testing.T) { t.Parallel() runtime, reader := newRuntime(t) dir := metricsracenamedir.New(newInner(t), runtime) ctx := context.Background() require.NoError(t, dir.Reserve(ctx, "game-a", "user-1", "Apollon")) require.NoError(t, dir.ReleaseReservation(ctx, "game-a", "user-1", "Apollon")) rm := collect(t, reader) counts := raceNameCounts(rm) assert.Equal(t, int64(1), counts["reserved"]) assert.Equal(t, int64(1), counts["reservation_released"]) } func TestDirectoryRecordsPendingAndRegistered(t *testing.T) { t.Parallel() runtime, reader := newRuntime(t) dir := metricsracenamedir.New(newInner(t), runtime) ctx := context.Background() now := time.Date(2026, 4, 25, 12, 0, 0, 0, time.UTC) eligibleUntil := now.Add(30 * 24 * time.Hour) require.NoError(t, dir.Reserve(ctx, "game-finished", "user-7", "Helios")) require.NoError(t, dir.MarkPendingRegistration(ctx, "game-finished", "user-7", "Helios", eligibleUntil)) require.NoError(t, dir.Register(ctx, "game-finished", "user-7", "Helios")) rm := collect(t, reader) counts := raceNameCounts(rm) assert.Equal(t, int64(1), counts["pending_created"]) assert.Equal(t, int64(1), counts["registered"]) } func TestDirectoryRecordsExpiredPending(t *testing.T) { t.Parallel() runtime, reader := newRuntime(t) dir := metricsracenamedir.New(newInner(t), runtime) ctx := context.Background() old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) require.NoError(t, dir.Reserve(ctx, "game-old", "user-9", "Aether")) require.NoError(t, dir.MarkPendingRegistration(ctx, "game-old", "user-9", "Aether", old)) expired, err := dir.ExpirePendingRegistrations(ctx, old.Add(time.Hour)) require.NoError(t, err) require.Len(t, expired, 1) rm := collect(t, reader) assert.Equal(t, int64(1), raceNameCounts(rm)["pending_released"]) } func TestDirectoryReleaseAllByUserSnapshotsCounts(t *testing.T) { t.Parallel() runtime, reader := newRuntime(t) dir := metricsracenamedir.New(newInner(t), runtime) ctx := context.Background() now := time.Date(2026, 4, 25, 12, 0, 0, 0, time.UTC) eligibleUntil := now.Add(30 * 24 * time.Hour) require.NoError(t, dir.Reserve(ctx, "game-active", "user-z", "Boreas")) require.NoError(t, dir.Reserve(ctx, "game-finished", "user-z", "Notos")) require.NoError(t, dir.MarkPendingRegistration(ctx, "game-finished", "user-z", "Notos", eligibleUntil)) require.NoError(t, dir.Reserve(ctx, "game-other", "user-z", "Eurus")) require.NoError(t, dir.MarkPendingRegistration(ctx, "game-other", "user-z", "Eurus", eligibleUntil)) require.NoError(t, dir.Register(ctx, "game-other", "user-z", "Eurus")) require.NoError(t, dir.ReleaseAllByUser(ctx, "user-z")) rm := collect(t, reader) counts := raceNameCounts(rm) assert.GreaterOrEqual(t, counts["reservation_released"], int64(1)) assert.GreaterOrEqual(t, counts["pending_released"], int64(1)) assert.GreaterOrEqual(t, counts["registered_released"], int64(1)) } func collect(t *testing.T, reader sdkmetric.Reader) metricdata.ResourceMetrics { t.Helper() var rm metricdata.ResourceMetrics require.NoError(t, reader.Collect(context.Background(), &rm)) return rm } func raceNameCounts(rm metricdata.ResourceMetrics) map[string]int64 { counts := map[string]int64{} for _, scope := range rm.ScopeMetrics { for _, m := range scope.Metrics { if m.Name != "lobby.race_name.outcomes" { continue } sum, ok := m.Data.(metricdata.Sum[int64]) if !ok { continue } for _, point := range sum.DataPoints { outcome, _ := point.Attributes.Value("outcome") counts[outcome.AsString()] += point.Value } } } return counts }