Files
openwork/apps/app/tests/engine-reload-escalation.test.ts

115 lines
4.6 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { OpenworkServerError } from "../src/app/lib/openwork-server";
import { reloadEngineWithDesktopFallback } from "../src/react-app/shell/engine-reload-escalation";
function unreachableError() {
return new OpenworkServerError(503, "opencode_engine_unreachable", "engine unreachable");
}
function unconfiguredError() {
return new OpenworkServerError(400, "opencode_unconfigured", "engine unconfigured");
}
type Harness = {
reloadCalls: number;
restartCalls: number;
client: { reloadEngine: (workspaceId: string) => Promise<void> };
options: {
retryDelayMs: number;
restartEngine: () => Promise<void>;
isDesktop: () => boolean;
};
};
function makeHarness(reloadResults: Array<Error | null>, isDesktop = true): Harness {
const harness: Harness = {
reloadCalls: 0,
restartCalls: 0,
client: {
reloadEngine: async () => {
const result = reloadResults[harness.reloadCalls];
harness.reloadCalls += 1;
if (result) throw result;
},
},
options: {
retryDelayMs: 1,
restartEngine: async () => {
harness.restartCalls += 1;
},
isDesktop: () => isDesktop,
},
};
return harness;
}
describe("reloadEngineWithDesktopFallback", () => {
test("a successful reload never restarts", async () => {
const harness = makeHarness([null]);
const result = await reloadEngineWithDesktopFallback(harness.client, "ws", harness.options);
expect(result.restartedEngine).toBe(false);
expect(harness.reloadCalls).toBe(1);
expect(harness.restartCalls).toBe(0);
});
test("a transient unreachable engine recovers on the retry without a restart", async () => {
const harness = makeHarness([unreachableError(), null]);
const result = await reloadEngineWithDesktopFallback(harness.client, "ws", harness.options);
expect(result.restartedEngine).toBe(false);
expect(harness.reloadCalls).toBe(2);
expect(harness.restartCalls).toBe(0);
});
test("a persistently unreachable engine escalates to a restart", async () => {
const harness = makeHarness([unreachableError(), unreachableError()]);
const result = await reloadEngineWithDesktopFallback(harness.client, "ws", harness.options);
expect(result.restartedEngine).toBe(true);
expect(harness.reloadCalls).toBe(2);
expect(harness.restartCalls).toBe(1);
});
test("an unconfigured engine restarts immediately — there is nothing to re-probe", async () => {
const harness = makeHarness([unconfiguredError()]);
const result = await reloadEngineWithDesktopFallback(harness.client, "ws", harness.options);
expect(result.restartedEngine).toBe(true);
expect(harness.reloadCalls).toBe(1);
expect(harness.restartCalls).toBe(1);
});
test("an aborted fetch is transport noise, never a restart", async () => {
const harness = makeHarness([new DOMException("aborted", "AbortError")]);
await expect(reloadEngineWithDesktopFallback(harness.client, "ws", harness.options)).rejects.toThrow("aborted");
expect(harness.restartCalls).toBe(0);
});
test("an aborted retry surfaces instead of restarting", async () => {
const harness = makeHarness([unreachableError(), new DOMException("aborted", "AbortError")]);
await expect(reloadEngineWithDesktopFallback(harness.client, "ws", harness.options)).rejects.toThrow("aborted");
expect(harness.reloadCalls).toBe(2);
expect(harness.restartCalls).toBe(0);
});
test("a non-restartable retry failure surfaces to the caller", async () => {
const retryFailure = new OpenworkServerError(502, "opencode_reload_failed", "dispose failed");
const harness = makeHarness([unreachableError(), retryFailure]);
await expect(reloadEngineWithDesktopFallback(harness.client, "ws", harness.options)).rejects.toBe(retryFailure);
expect(harness.restartCalls).toBe(0);
});
test("non-desktop runtimes never restart", async () => {
const error = unreachableError();
const harness = makeHarness([error], false);
await expect(reloadEngineWithDesktopFallback(harness.client, "ws", harness.options)).rejects.toBe(error);
expect(harness.reloadCalls).toBe(1);
expect(harness.restartCalls).toBe(0);
});
test("reload timeouts surface instead of restarting mid-teardown", async () => {
const timeout = new OpenworkServerError(504, "opencode_reload_timeout", "dispose still tearing down");
const harness = makeHarness([timeout]);
await expect(reloadEngineWithDesktopFallback(harness.client, "ws", harness.options)).rejects.toBe(timeout);
expect(harness.restartCalls).toBe(0);
});
});