package notification import ( "testing" "time" ) // TestRouteBackoffMonotonic locks the documented schedule: // attempt 1 == ~backoffBase, each subsequent attempt grows by // backoffFactor up to backoffMax. The check uses the lower bound of // the jitter window so the assertion is robust under random output. func TestRouteBackoffMonotonic(t *testing.T) { t.Parallel() lower := func(d time.Duration) time.Duration { return time.Duration(float64(d) * (1 - backoffJitter)) } upper := func(d time.Duration) time.Duration { return time.Duration(float64(d) * (1 + backoffJitter)) } cases := []struct { attempt int32 want time.Duration }{ {attempt: 1, want: backoffBase}, {attempt: 2, want: time.Duration(float64(backoffBase) * backoffFactor)}, {attempt: 3, want: time.Duration(float64(backoffBase) * backoffFactor * backoffFactor)}, } for _, tc := range cases { got := routeBackoff(tc.attempt) if got < lower(tc.want) || got > upper(tc.want) { t.Fatalf("attempt=%d got=%s want ~%s (±%.0f%%)", tc.attempt, got, tc.want, backoffJitter*100) } } } // TestRouteBackoffCap asserts the schedule clamps at backoffMax. func TestRouteBackoffCap(t *testing.T) { t.Parallel() upper := time.Duration(float64(backoffMax) * (1 + backoffJitter)) got := routeBackoff(50) if got > upper { t.Fatalf("attempt=50 got=%s exceeds cap (max=%s)", got, backoffMax) } }