package lane import ( "context" "sync" "testing" "time" ) // recordingSink captures every RecordLaneSample call. type recordingSink struct { mu sync.Mutex samples []sampleRow } type sampleRow struct { lane string running int queued int sampledAt time.Time } func (r *recordingSink) RecordLaneSample(_ context.Context, lane string, running, queued int, sampledAt time.Time) error { r.mu.Lock() defer r.mu.Unlock() r.samples = append(r.samples, sampleRow{lane, running, queued, sampledAt}) return nil } // recordingPurger captures PurgeLaneSamples cutoffs. type recordingPurger struct { mu sync.Mutex cutoffs []time.Time } func (r *recordingPurger) PurgeLaneSamples(_ context.Context, olderThan time.Time) (int64, error) { r.mu.Lock() defer r.mu.Unlock() r.cutoffs = append(r.cutoffs, olderThan) return 0, nil } func TestSampler_SamplesAllLanes(t *testing.T) { reg := NewRegistry(nil) reg.GetOrCreate(context.Background(), "ollama") reg.GetOrCreate(context.Background(), "anthropic-default") sink := &recordingSink{} now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) clock := func() time.Time { return now } s := NewSampler(reg, sink, nil, 30*time.Second, 7*24*time.Hour, clock) s.Sample(context.Background()) sink.mu.Lock() defer sink.mu.Unlock() if len(sink.samples) != 2 { t.Fatalf("expected 2 samples (one per lane), got %d", len(sink.samples)) } seen := map[string]bool{} for _, sm := range sink.samples { seen[sm.lane] = true if !sm.sampledAt.Equal(now) { t.Errorf("expected sampledAt=%v, got %v", now, sm.sampledAt) } } if !seen["ollama"] || !seen["anthropic-default"] { t.Fatalf("missing lane: %+v", seen) } } func TestSampler_PurgeOnceUsesRetentionWindow(t *testing.T) { reg := NewRegistry(nil) purger := &recordingPurger{} now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC) clock := func() time.Time { return now } s := NewSampler(reg, nil, purger, 30*time.Second, 7*24*time.Hour, clock) s.PurgeOnce(context.Background()) purger.mu.Lock() defer purger.mu.Unlock() if len(purger.cutoffs) != 1 { t.Fatalf("expected 1 purge call, got %d", len(purger.cutoffs)) } want := now.Add(-7 * 24 * time.Hour) if !purger.cutoffs[0].Equal(want) { t.Fatalf("cutoff: want %v, got %v", want, purger.cutoffs[0]) } } func TestSampler_NilSinkOrRegistryIsSafe(t *testing.T) { // nil registry — no-op, no panic. s := NewSampler(nil, &recordingSink{}, nil, 30*time.Second, 7*24*time.Hour, nil) s.Sample(context.Background()) // nil sink — no-op. reg := NewRegistry(nil) reg.GetOrCreate(context.Background(), "ollama") s2 := NewSampler(reg, nil, nil, 30*time.Second, 7*24*time.Hour, nil) s2.Sample(context.Background()) } func TestSampler_StartStopIdempotent(t *testing.T) { reg := NewRegistry(nil) sink := &recordingSink{} s := NewSampler(reg, sink, nil, 30*time.Second, 7*24*time.Hour, nil) ctx := context.Background() s.Start(ctx) s.Start(ctx) // second Start is a no-op s.Stop() s.Stop() // second Stop is a no-op }