Files
geo/server/internal/scheduler/control_plane_test.go
T
root 842782b3dd feat(scheduler): random run-window scheduling and deduped manual triggers
Support a random daily run window (random_window_start/end) where the
next auto-run time is derived from a stable per-job hash, avoiding
thundering-herd starts while keeping one run per day. Honors the last
auto-run on initial scheduling so restarts don't double-fire.

Manual triggers can opt into dedupe (dedupe_manual_triggers): an
advisory-locked path collapses concurrent requests onto an existing
pending/running job instead of queueing duplicates. Scheduler error
messages are localized to Chinese.

Register the media_supply_cache_sync job (dry-run aware) and add the
ops migration that seeds it.
2026-06-02 14:50:25 +08:00

132 lines
4.1 KiB
Go

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")
}
}