Pin three untested guards: download timer, URL fragment, short APP1 (closes #89)
check / check (push) Successful in 24s
check / check (push) Successful in 24s
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 accepts an APP1 segment only when its length is at least 8, since a shorter one cannot hold the six-byte Exif header; tests cover lengths 7 and 8. Model: opus-5-5
This commit was merged in pull request #91.
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -51,6 +51,20 @@ describe("extractExifFromJpeg", () => {
|
||||
expect(extractExifFromJpeg(bytes(SOI, app0, SOS))).toEqual({});
|
||||
});
|
||||
|
||||
it("ignores an APP1 segment too short to hold the Exif header", () => {
|
||||
// A length under 8 cannot hold the six-byte "Exif\0\0" header, so the
|
||||
// segment is not EXIF. This one has length 7 and holds only "Exif\0",
|
||||
// which the old code, lacking the length check, returned as EXIF.
|
||||
const short = app1(EXIF_HEADER.slice(0, 5));
|
||||
expect(extractExifFromJpeg(bytes(SOI, short, SOS))).toEqual({});
|
||||
});
|
||||
|
||||
it("accepts an APP1 segment of length 8 holding just the Exif header", () => {
|
||||
const scan = extractExifFromJpeg(bytes(SOI, app1(EXIF_HEADER), SOS));
|
||||
expect(scan.error).toBeUndefined();
|
||||
expect([...scan.exif!]).toEqual(EXIF_HEADER);
|
||||
});
|
||||
|
||||
it("reports a JPEG truncated inside a segment header", () => {
|
||||
const scan = extractExifFromJpeg(bytes(SOI, [0xff, 0xe1, 0x00]));
|
||||
expect(scan.exif).toBeUndefined();
|
||||
|
||||
Reference in New Issue
Block a user