|
1 | 1 | import { expect, test } from "bun:test" |
2 | | -import { generateSandboxCredentials, pollSandboxHealth, sandboxBootEnv } from "./boot" |
| 2 | +import { checkSandboxHealth, generateSandboxCredentials, pollSandboxHealth, sandboxBootEnv } from "./boot" |
3 | 3 |
|
4 | 4 | test("boot env mirrors the WSL sidecar contract and carries the server address", () => { |
5 | 5 | expect( |
@@ -62,3 +62,42 @@ test("health polling returns once the server responds", async () => { |
62 | 62 | ) |
63 | 63 | expect(checks).toBe(3) |
64 | 64 | }) |
| 65 | + |
| 66 | +// pollSandboxHealth awaits each probe in turn, so a request that never answers |
| 67 | +// used to stall the whole loop: the sandbox stayed "starting" until the start |
| 68 | +// timeout gave up, minutes later, even though the guest had come up seconds in. |
| 69 | +// A probe must always settle. |
| 70 | +test("health probe gives up on a connection that never answers", async () => { |
| 71 | + const started = Date.now() |
| 72 | + const healthy = await checkSandboxHealth( |
| 73 | + "http://127.0.0.1:1", |
| 74 | + { username: "codev", password: "secret" }, |
| 75 | + // Opens and then hangs, the way an accepted socket to a guest that is not |
| 76 | + // listening yet can: no response, no error. |
| 77 | + ((_url: string, init?: RequestInit) => |
| 78 | + new Promise((_resolve, reject) => { |
| 79 | + init?.signal?.addEventListener("abort", () => reject(new Error("aborted"))) |
| 80 | + })) as unknown as typeof fetch, |
| 81 | + ) |
| 82 | + |
| 83 | + expect(healthy).toBe(false) |
| 84 | + expect(Date.now() - started).toBeLessThan(10_000) |
| 85 | +}) |
| 86 | + |
| 87 | +// The caller's signal must cancel an in-flight probe too, so aborting the poll |
| 88 | +// does not leave a request outliving the start it belonged to. |
| 89 | +test("health probe aborts with the caller's signal", async () => { |
| 90 | + const controller = new AbortController() |
| 91 | + const probe = checkSandboxHealth( |
| 92 | + "http://127.0.0.1:1", |
| 93 | + { username: "codev", password: "secret" }, |
| 94 | + ((_url: string, init?: RequestInit) => |
| 95 | + new Promise((_resolve, reject) => { |
| 96 | + init?.signal?.addEventListener("abort", () => reject(new Error("aborted"))) |
| 97 | + })) as unknown as typeof fetch, |
| 98 | + controller.signal, |
| 99 | + ) |
| 100 | + controller.abort() |
| 101 | + |
| 102 | + expect(await probe).toBe(false) |
| 103 | +}) |
0 commit comments