package scheduler import ( "testing" "time" ) func TestSchedulerLoopSleepCapsManualPollWithoutForcingAutoRun(t *testing.T) { now := time.Date(2026, 5, 20, 10, 0, 0, 0, time.UTC) nextAutoAt := now.Add(time.Minute) got := schedulerLoopSleep(now, nextAutoAt) if got != schedulerManualPollInterval { t.Fatalf("schedulerLoopSleep() = %s, want %s", got, schedulerManualPollInterval) } } func TestNextIntervalAutoAtUsesLastRunOnInitialStart(t *testing.T) { now := time.Date(2026, 5, 20, 10, 0, 0, 0, time.UTC) last := now.Add(-time.Minute) got := nextIntervalAutoAt(now, 5*time.Minute, true, &last) want := last.Add(5 * time.Minute) if !got.Equal(want) { t.Fatalf("nextIntervalAutoAt() = %s, want %s", got, want) } } func TestNextIntervalAutoAtRunsImmediatelyWhenOverdue(t *testing.T) { now := time.Date(2026, 5, 20, 10, 0, 0, 0, time.UTC) last := now.Add(-10 * time.Minute) got := nextIntervalAutoAt(now, 5*time.Minute, true, &last) if !got.Equal(now) { t.Fatalf("nextIntervalAutoAt() = %s, want %s", got, now) } } func TestNextIntervalAutoAtSchedulesAfterCurrentCompletion(t *testing.T) { now := time.Date(2026, 5, 20, 10, 0, 0, 0, time.UTC) got := nextIntervalAutoAt(now, 5*time.Minute, false, nil) want := now.Add(5 * time.Minute) if !got.Equal(want) { t.Fatalf("nextIntervalAutoAt() = %s, want %s", got, want) } } func TestNextRandomWindowAutoAtIsStableInsideWindow(t *testing.T) { loc := time.FixedZone("CST", 8*60*60) window, ok := parseRandomRunWindowLocal(map[string]any{ "random_window_start": "01:00", "random_window_end": "04:00", }) if !ok { t.Fatal("expected random window config to parse") } now := time.Date(2026, 6, 2, 0, 30, 0, 0, loc) got := nextRandomWindowAutoAt("media_supply_cache_sync", now, loc, window, true, nil) gotAgain := nextRandomWindowAutoAt("media_supply_cache_sync", now.Add(10*time.Minute), loc, window, true, nil) start := time.Date(2026, 6, 2, 1, 0, 0, 0, loc) end := time.Date(2026, 6, 2, 4, 0, 0, 0, loc) if got.Before(start) || !got.Before(end) { t.Fatalf("nextRandomWindowAutoAt() = %s, want inside [%s, %s)", got, start, end) } if !got.Equal(gotAgain) { t.Fatalf("expected stable candidate, got %s and %s", got, gotAgain) } } func TestNextRandomWindowAutoAtSkipsTodayAfterAutoRun(t *testing.T) { loc := time.FixedZone("CST", 8*60*60) window, _ := parseRandomRunWindowLocal(map[string]any{ "random_window_start": "01:00", "random_window_end": "04:00", }) now := time.Date(2026, 6, 2, 2, 0, 0, 0, loc) last := time.Date(2026, 6, 2, 1, 30, 0, 0, loc) got := nextRandomWindowAutoAt("media_supply_cache_sync", now, loc, window, true, &last) wantStart := time.Date(2026, 6, 3, 1, 0, 0, 0, loc) wantEnd := time.Date(2026, 6, 3, 4, 0, 0, 0, loc) if got.Before(wantStart) || !got.Before(wantEnd) { t.Fatalf("nextRandomWindowAutoAt() = %s, want inside tomorrow window [%s, %s)", got, wantStart, wantEnd) } } func TestIsEmptySuccessRunRequiresConfiguredZeroMetrics(t *testing.T) { stats := map[string]any{ "hydrated_count": 0, "claimed_count": 0, } config := map[string]any{ "suppress_empty_success_runs": true, "empty_success_metric_keys": []any{"hydrated_count", "claimed_count"}, } if !isEmptySuccessRun(stats, config) { t.Fatal("expected empty success run to be suppressed") } } func TestIsEmptySuccessRunKeepsRunsWithOutput(t *testing.T) { stats := map[string]any{ "recoverable_count": 1, "republished_count": 0, } config := map[string]any{ "suppress_empty_success_runs": true, "empty_success_metric_keys": []any{"recoverable_count", "republished_count"}, } if isEmptySuccessRun(stats, config) { t.Fatal("expected non-empty success run to be retained") } } func TestShouldSuppressEmptySuccessRunOnlyForAutoNonDryRun(t *testing.T) { config := map[string]any{"suppress_empty_success_runs": true} if !shouldSuppressEmptySuccessRun("auto", false, config) { t.Fatal("expected auto non-dry-run to allow suppression") } if shouldSuppressEmptySuccessRun("manual", false, config) { t.Fatal("manual runs must not be suppressed") } if shouldSuppressEmptySuccessRun("auto", true, config) { t.Fatal("dry runs must not be suppressed") } }