diff --git a/tests/runner-e2e/controller-outage.test.ts b/tests/runner-e2e/controller-outage.test.ts index d366c3f1096..f95778b617c 100644 --- a/tests/runner-e2e/controller-outage.test.ts +++ b/tests/runner-e2e/controller-outage.test.ts @@ -88,10 +88,14 @@ describe("controller outage qualification", () => { expect(f.input.start).not.toHaveBeenCalled(); }); - it.each([-1, 300001, 0.5, NaN, Infinity, "300000", null])("rejects invalid duration %s before signaling", holdMs => { + it.each([-1, 600001, 0.5, NaN, Infinity, "300000", null])("rejects invalid duration %s before signaling", holdMs => { expect(() => parseControllerOutage({ signal: "SIGKILL", holdMs })).toThrow(); }); - it.each(["", "-1", "1e3", "NaN", " 1000", "300001"])("rejects invalid environment duration %s", value => { + it("accepts chaos holds up to ten minutes", () => { + expect(parseControllerOutage({ signal: "SIGKILL", holdMs: 360_000 })).toEqual({ signal: "SIGKILL", holdMs: 360_000 }); + expect(parseControllerOutage({ signal: "SIGTERM", holdMs: 600_000 })).toEqual({ signal: "SIGTERM", holdMs: 600_000 }); + }); + it.each(["", "-1", "1e3", "NaN", " 1000", "600001"])("rejects invalid environment duration %s", value => { expect(() => configuredControllerOutage({ PAPERCLIP_RUNNER_E2E_CONTROLLER_OUTAGE_MS: value })).toThrow(); }); it("accepts the five-minute contract without extending the runner grace", () => { diff --git a/tests/runner-e2e/controller-outage.ts b/tests/runner-e2e/controller-outage.ts index 758f80a8c33..2f07b834089 100644 --- a/tests/runner-e2e/controller-outage.ts +++ b/tests/runner-e2e/controller-outage.ts @@ -26,11 +26,21 @@ export interface ControllerOutageReceipt extends ControllerOutage { recoveryProgress?: Partial; } +/** + * Longest controller outage a harness may inject. Ten minutes lets chaos + * holds pass Daytona's sandbox auto-stop and the 7-minute runner reconnect + * grace. The recover-controller receipt check below still requires readiness + * inside that grace, so this does not loosen the qualification contract. + */ +export const MAX_CONTROLLER_OUTAGE_MS = 600_000; + export function parseControllerOutage(value: unknown): ControllerOutage { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid controller outage"); const { signal, holdMs } = value as Record; if ((signal !== "SIGTERM" && signal !== "SIGKILL") || !Number.isSafeInteger(holdMs) - || Number(holdMs) < 0 || Number(holdMs) > 300_000) throw new Error("Controller outage requires SIGTERM/SIGKILL and 0..300000 integer milliseconds"); + || Number(holdMs) < 0 || Number(holdMs) > MAX_CONTROLLER_OUTAGE_MS) { + throw new Error(`Controller outage requires SIGTERM/SIGKILL and 0..${MAX_CONTROLLER_OUTAGE_MS} integer milliseconds`); + } return { signal, holdMs: Number(holdMs) }; } diff --git a/tests/runner-e2e/server-restart-request.test.ts b/tests/runner-e2e/server-restart-request.test.ts index bfec299ead9..958c9face87 100644 --- a/tests/runner-e2e/server-restart-request.test.ts +++ b/tests/runner-e2e/server-restart-request.test.ts @@ -62,7 +62,7 @@ it("rejects cache replacement for unselected, local and legacy cells", async () it.each([ { controllerOutage: { signal: "SIGKILL", holdMs: 125_000 }, discardControllerCache: false }, - { controllerOutage: { signal: "SIGKILL", holdMs: 300_001 }, discardControllerCache: true }, + { controllerOutage: { signal: "SIGKILL", holdMs: 600_001 }, discardControllerCache: true }, { controllerOutage: { signal: "SIGKILL", holdMs: 125_000 }, discardControllerCache: true, preserveRunner: {} }, { controllerOutage: { signal: "SIGKILL", holdMs: 125_000 }, discardControllerCache: true, crashAfterResult: {} }, { recoveryBinding: {} },