package redisstate_test import ( "context" "strconv" "testing" "time" "galaxy/lobby/internal/adapters/redisstate" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func newLagTestProbe(t *testing.T, now time.Time) (*redisstate.StreamLagProbe, *miniredis.Miniredis, *redis.Client) { t.Helper() server := miniredis.RunT(t) client := redis.NewClient(&redis.Options{Addr: server.Addr()}) t.Cleanup(func() { _ = client.Close() }) probe, err := redisstate.NewStreamLagProbe(client, func() time.Time { return now }) require.NoError(t, err) return probe, server, client } func TestStreamLagProbeReturnsAgeOfNextEntry(t *testing.T) { now := time.UnixMilli(2_000_000_000_000).UTC() probe, _, client := newLagTestProbe(t, now) ctx := context.Background() addEntry := func(ms int64) string { id, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "demo", ID: formatEntryID(ms, 0), Values: map[string]any{"k": "v"}, }).Result() require.NoError(t, err) return id } saved := addEntry(now.UnixMilli() - 5_000) // already processed addEntry(now.UnixMilli() - 1_500) // first unprocessed → 1.5s old age, ok, err := probe.OldestUnprocessedAge(ctx, "demo", saved) require.NoError(t, err) require.True(t, ok) assert.InDelta(t, (1_500 * time.Millisecond).Milliseconds(), age.Milliseconds(), 50) } func TestStreamLagProbeReturnsFalseWhenAtTail(t *testing.T) { now := time.UnixMilli(2_000_000_000_000).UTC() probe, _, client := newLagTestProbe(t, now) ctx := context.Background() id, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "demo", ID: formatEntryID(now.UnixMilli()-2_000, 0), Values: map[string]any{"k": "v"}, }).Result() require.NoError(t, err) age, ok, err := probe.OldestUnprocessedAge(ctx, "demo", id) require.NoError(t, err) require.False(t, ok) assert.Zero(t, age) } func TestStreamLagProbeFallsBackToHeadOnEmptyOffset(t *testing.T) { now := time.UnixMilli(2_000_000_000_000).UTC() probe, _, client := newLagTestProbe(t, now) ctx := context.Background() _, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "demo", ID: formatEntryID(now.UnixMilli()-3_000, 0), Values: map[string]any{"k": "v"}, }).Result() require.NoError(t, err) age, ok, err := probe.OldestUnprocessedAge(ctx, "demo", "") require.NoError(t, err) require.True(t, ok) assert.InDelta(t, (3 * time.Second).Milliseconds(), age.Milliseconds(), 50) } func TestStreamLagProbeReturnsFalseOnEmptyStream(t *testing.T) { now := time.UnixMilli(2_000_000_000_000).UTC() probe, _, _ := newLagTestProbe(t, now) ctx := context.Background() age, ok, err := probe.OldestUnprocessedAge(ctx, "demo", "") require.NoError(t, err) require.False(t, ok) assert.Zero(t, age) } func formatEntryID(ms int64, seq int64) string { return strconv.FormatInt(ms, 10) + "-" + strconv.FormatInt(seq, 10) }