Files
DeskClaw/tests/unit/gateway-restart-governor.test.ts

107 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { GatewayRestartGovernor } from '@electron/gateway/restart-governor';
describe('GatewayRestartGovernor', () => {
it('suppresses restart during exponential cooldown window', () => {
const governor = new GatewayRestartGovernor({
baseCooldownMs: 1000,
maxCooldownMs: 8000,
maxRestartsPerWindow: 10,
windowMs: 60000,
stableResetMs: 60000,
circuitOpenMs: 60000,
});
expect(governor.decide(1000).allow).toBe(true);
governor.recordExecuted(1000);
const blocked = governor.decide(1500);
expect(blocked.allow).toBe(false);
expect(blocked.allow ? '' : blocked.reason).toBe('cooldown_active');
expect(blocked.allow ? 0 : blocked.retryAfterMs).toBeGreaterThan(0);
expect(governor.decide(3000).allow).toBe(true);
});
it('opens circuit after restart budget is exceeded', () => {
const governor = new GatewayRestartGovernor({
maxRestartsPerWindow: 2,
windowMs: 60000,
baseCooldownMs: 0,
maxCooldownMs: 0,
stableResetMs: 120000,
circuitOpenMs: 30000,
});
expect(governor.decide(1000).allow).toBe(true);
governor.recordExecuted(1000);
expect(governor.decide(2000).allow).toBe(true);
governor.recordExecuted(2000);
const budgetBlocked = governor.decide(3000);
expect(budgetBlocked.allow).toBe(false);
expect(budgetBlocked.allow ? '' : budgetBlocked.reason).toBe('budget_exceeded');
const circuitBlocked = governor.decide(4000);
expect(circuitBlocked.allow).toBe(false);
expect(circuitBlocked.allow ? '' : circuitBlocked.reason).toBe('circuit_open');
expect(governor.decide(62001).allow).toBe(true);
});
it('resets consecutive backoff after stable running period', () => {
const governor = new GatewayRestartGovernor({
baseCooldownMs: 1000,
maxCooldownMs: 8000,
maxRestartsPerWindow: 10,
windowMs: 600000,
stableResetMs: 5000,
circuitOpenMs: 60000,
});
governor.recordExecuted(0);
governor.recordExecuted(1000);
const blockedBeforeStable = governor.decide(2500);
expect(blockedBeforeStable.allow).toBe(false);
expect(blockedBeforeStable.allow ? '' : blockedBeforeStable.reason).toBe('cooldown_active');
governor.onRunning(3000);
const allowedAfterStable = governor.decide(9000);
expect(allowedAfterStable.allow).toBe(true);
});
it('resets time-based state when clock moves backwards', () => {
const governor = new GatewayRestartGovernor({
maxRestartsPerWindow: 2,
windowMs: 60000,
baseCooldownMs: 1000,
maxCooldownMs: 8000,
stableResetMs: 60000,
circuitOpenMs: 30000,
});
governor.recordExecuted(10_000);
governor.recordExecuted(11_000);
const blocked = governor.decide(11_500);
expect(blocked.allow).toBe(false);
// Simulate clock rewind and verify stale guard state does not lock out restarts.
const afterRewind = governor.decide(9_000);
expect(afterRewind.allow).toBe(true);
});
it('wraps counters safely at MAX_SAFE_INTEGER', () => {
const governor = new GatewayRestartGovernor();
(governor as unknown as { executedTotal: number; suppressedTotal: number }).executedTotal = Number.MAX_SAFE_INTEGER;
(governor as unknown as { executedTotal: number; suppressedTotal: number }).suppressedTotal = Number.MAX_SAFE_INTEGER;
governor.recordExecuted(1000);
governor.decide(1000);
expect(governor.getCounters()).toEqual({
executedTotal: 0,
suppressedTotal: 0,
});
});
});