Pin three untested guards: download timer, URL fragment, short APP1 (closes #89)
check / check (push) Successful in 15s

The download idle deadline's timer is unref'd so it never holds the
process open, and a test checks no timer is left after a download
completes or fails. A test covers the rejection of "#" in a request
path. The EXIF scan compares the Exif header only when the APP1 length
is at least 8, so it never reads the next segment's bytes.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-23 01:17:43 +00:00
parent 28a2beeab8
commit a0e6828693
5 changed files with 75 additions and 3 deletions
+56
View File
@@ -474,6 +474,16 @@ describe("ApiClient request URLs", () => {
);
expect(calls).toHaveLength(0);
});
it("rejects a path that carries a fragment", async () => {
const { fetch, calls } = recordingFetch();
const client = new ApiClient({ fetch });
await expect(client.getJSON("/diff#top")).rejects.toThrow(
/must not contain "\?" or "#"/,
);
expect(calls).toHaveLength(0);
});
});
describe("ApiError", () => {
@@ -980,6 +990,52 @@ describe("ApiClient timeouts", () => {
}
expect(joined).toEqual(payload);
});
it("leaves no timer pending after a download completes or fails", async () => {
vi.useFakeTimers();
try {
const { fetch } = scriptedFetch(
streamResponse(new Uint8Array([1, 2, 3])),
textResponse("gone", 404),
);
const client = new ApiClient({
fetch,
retry: { ...noWait, attempts: 1 },
});
expect(await readAll(await client.getFileStream(1))).toBe(3);
expect(vi.getTimerCount()).toBe(0);
await expect(client.getFileStream(2)).rejects.toBeInstanceOf(
ApiError,
);
expect(vi.getTimerCount()).toBe(0);
} finally {
vi.useRealTimers();
}
});
it("never lets the download timer keep the process alive", async () => {
const spy = vi.spyOn(globalThis, "setTimeout");
try {
const { fetch } = scriptedFetch(
streamResponse(new Uint8Array([1])),
);
const client = new ApiClient({
fetch,
downloadTimeoutMs: 12_345,
retry: noWait,
});
const stream = await client.getFileStream(1);
const i = spy.mock.calls.findIndex((call) => call[1] === 12_345);
const timer = spy.mock.results[i]!.value as NodeJS.Timeout;
expect(timer.hasRef()).toBe(false);
await stream.cancel();
} finally {
spy.mockRestore();
}
});
});
describe("ApiClient error typing", () => {