@@ -49,6 +49,25 @@ async function expectPathMissing(targetPath: string): Promise<void> {
|
49 | 49 | throw new Error(`expected missing path: ${targetPath}`); |
50 | 50 | } |
51 | 51 | |
| 52 | +function cancelTrackedResponse(init?: ResponseInit): { |
| 53 | +response: Response; |
| 54 | +wasCanceled: () => boolean; |
| 55 | +} { |
| 56 | +let canceled = false; |
| 57 | +const stream = new ReadableStream<Uint8Array>({ |
| 58 | +start(controller) { |
| 59 | +controller.enqueue(new TextEncoder().encode("ignored")); |
| 60 | +}, |
| 61 | +cancel() { |
| 62 | +canceled = true; |
| 63 | +}, |
| 64 | +}); |
| 65 | +return { |
| 66 | +response: new Response(stream, init), |
| 67 | +wasCanceled: () => canceled, |
| 68 | +}; |
| 69 | +} |
| 70 | + |
52 | 71 | describe("nodes camera helpers", () => { |
53 | 72 | beforeAll(async () => { |
54 | 73 | ({ |
@@ -289,6 +308,52 @@ describe("nodes camera helpers", () => {
|
289 | 308 | }, |
290 | 309 | ); |
291 | 310 | |
| 311 | +it.each([ |
| 312 | +{ |
| 313 | +name: "non-ok status", |
| 314 | +response: () => cancelTrackedResponse({ status: 503, statusText: "Service Unavailable" }), |
| 315 | +expectedMessage: /503/i, |
| 316 | +}, |
| 317 | +{ |
| 318 | +name: "oversized content-length", |
| 319 | +response: () => |
| 320 | +cancelTrackedResponse({ |
| 321 | +status: 200, |
| 322 | +headers: { "content-length": String(999_999_999) }, |
| 323 | +}), |
| 324 | +expectedMessage: /exceeds max/i, |
| 325 | +}, |
| 326 | +] as const)( |
| 327 | +"cancels rejected url response bodies: $name", |
| 328 | +async ({ response, expectedMessage }) => { |
| 329 | +const tracked = response(); |
| 330 | +stubFetchResponse(tracked.response); |
| 331 | + |
| 332 | +await expect( |
| 333 | +writeUrlToFile("/tmp/ignored", "https://198.51.100.42/down.bin", { |
| 334 | +expectedHost: "198.51.100.42", |
| 335 | +}), |
| 336 | +).rejects.toThrow(expectedMessage); |
| 337 | +expect(tracked.wasCanceled()).toBe(true); |
| 338 | +}, |
| 339 | +); |
| 340 | + |
| 341 | +it("cancels response bodies when a redirect changes host", async () => { |
| 342 | +const tracked = cancelTrackedResponse({ status: 200 }); |
| 343 | +fetchGuardMocks.fetchWithSsrFGuard.mockResolvedValueOnce({ |
| 344 | +response: tracked.response, |
| 345 | +finalUrl: "https://198.51.100.43/clip.mp4", |
| 346 | +release: async () => {}, |
| 347 | +}); |
| 348 | + |
| 349 | +await expect( |
| 350 | +writeUrlToFile("/tmp/ignored", "https://198.51.100.42/clip.mp4", { |
| 351 | +expectedHost: "198.51.100.42", |
| 352 | +}), |
| 353 | +).rejects.toThrow(/redirect host/i); |
| 354 | +expect(tracked.wasCanceled()).toBe(true); |
| 355 | +}); |
| 356 | + |
292 | 357 | it("removes partially written file when url stream fails", async () => { |
293 | 358 | const stream = new ReadableStream<Uint8Array>({ |
294 | 359 | start(controller) { |
|