import { describe, expect, it, vi } from "vitest"; import type { BrowserManagedDownloadCapabilityReceipt, BrowserManagedDownloadCapabilityResolver, DownloadDeliveryPort, DownloadSource, DownloadStrategy, FileByteSource, } from "../../src/application/ports/browser-file-storage/file.ts"; import { DEFAULT_OBJECT_URL_RELEASE_GRACE_MS, createDownloadDeliveryAdapter, type SaveFileHandle, } from "../../src/adapters/browser-files/download-delivery-adapter.ts"; import { ObjectUrlLeaseRegistry } from "../../src/adapters/browser-files/object-url-lease.ts"; import { BrowserFilePolicyRegistry, browserFilePolicyReference, } from "../../src/adapters/browser-files/browser-file-policy-registry.ts"; const HARD_LIMITS = Object.freeze({ hardMaxObjectUrlBytes: 64, hardMaxTransferBytes: 128, }); const capabilityReceipt = "capability:download-1" as BrowserManagedDownloadCapabilityReceipt; const policyByStrategy = Object.freeze({ BROWSER_MANAGED: browserFilePolicyReference( "download", "browser-managed-pdf", ), PROMPT_AND_STREAM: browserFilePolicyReference( "download", "prompt-and-stream-pdf", ), BOUNDED_OBJECT_URL: browserFilePolicyReference( "download", "bounded-object-url-pdf", ), }); const policies = new BrowserFilePolicyRegistry({ profiles: ( Object.entries(policyByStrategy) as Array< [DownloadStrategy, (typeof policyByStrategy)[DownloadStrategy]] > ).map(([strategy, reference]) => ({ reference, download: { strategy, mediaType: "application/pdf", safeExtension: ".pdf", maxTransferBytes: 32, maxBufferedBytes: 16, integrity: "OPTIONAL" as const, }, })), hardLimits: { maxInspectionBytes: 64, maxRetainedFileBytes: 128, maxPreviewBytes: 64, maxObjectUrlBytes: 64, maxTransferBytes: 128, }, }); function capabilityResolver( href: (resourceId: string) => string = () => "/unused", overrides: Readonly< Partial<{ capabilityReceipt: BrowserManagedDownloadCapabilityReceipt; resourceId: string; mediaType: string; safeExtension: string; maxBytes: number; expectedSha256: string; expiresAtEpochMs: number; }> > = {}, ): BrowserManagedDownloadCapabilityResolver { return { resolve(input) { return { ok: true, value: { capabilityReceipt: overrides.capabilityReceipt ?? input.capabilityReceipt, href: href(input.resourceId), resourceId: overrides.resourceId ?? input.resourceId, mediaType: overrides.mediaType ?? "application/pdf", safeExtension: overrides.safeExtension ?? ".pdf", maxBytes: overrides.maxBytes ?? 32, ...(overrides.expectedSha256 ? { expectedSha256: overrides.expectedSha256 } : {}), expiresAtEpochMs: overrides.expiresAtEpochMs ?? Number.MAX_SAFE_INTEGER, }, }; }, }; } function successfulChunk(bytes: Uint8Array) { return Object.freeze({ ok: true as const, value: bytes }); } function byteSource( chunks: readonly Uint8Array[], byteLength: number | null = chunks.reduce( (total, chunk) => total + chunk.byteLength, 0, ), afterChunk?: (index: number) => void, ): FileByteSource { return Object.freeze({ byteLength, async *stream( signal: AbortSignal, ): AsyncIterable> { for (const [index, chunk] of chunks.entries()) { if (signal.aborted) { throw new DOMException("aborted", "AbortError"); } yield successfulChunk(chunk); afterChunk?.(index); } }, }); } function deliveryInput( source: DownloadSource, strategy: DownloadStrategy, signal = new AbortController().signal, ) { return { policy: policyByStrategy[strategy], source, suggestedFileName: "../../invoice.exe.pdf", maxTransferBytes: 32, maxBufferedBytes: 16, signal, onProgress: vi.fn(), } satisfies Parameters[0]; } function writableHandle(events: string[]): SaveFileHandle { return Object.freeze({ async createWritable() { return new WritableStream({ write(chunk) { events.push(`write:${chunk.byteLength}`); }, close() { events.push("close"); }, abort() { events.push("abort"); }, }); }, }); } describe("browser download delivery", () => { it("captures composition dependencies instead of re-reading mutable option objects", async () => { const originalHandoff = vi.fn(); const replacedHandoff = vi.fn(); const originalResolve = vi.fn( capabilityResolver( () => "/downloads/composition-bound", ).resolve, ); const replacedResolve = vi.fn( capabilityResolver( () => "https://evil.example/replaced", ).resolve, ); const host = { handoff: originalHandoff }; const browserManagedCapabilities = { resolve: originalResolve, }; const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host, browserManagedCapabilities, baseOrigin: "https://app.example", createTransferId: () => "transfer:composition", userActivation: { isActive: true }, }); host.handoff = replacedHandoff; browserManagedCapabilities.resolve = replacedResolve; expect( await adapter.deliver( deliveryInput( { kind: "BROWSER_MANAGED_RESOURCE", resourceId: "artifact-1", capabilityReceipt, }, "BROWSER_MANAGED", ), ), ).toMatchObject({ ok: true, value: { kind: "BROWSER_HANDOFF" }, }); expect(originalResolve).toHaveBeenCalledOnce(); expect(replacedResolve).not.toHaveBeenCalled(); expect(originalHandoff).toHaveBeenCalledOnce(); expect(replacedHandoff).not.toHaveBeenCalled(); }); it("sanitizes browser-managed handoff and reports only handoff truth", async () => { const handoff = vi.fn(); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff }, browserManagedCapabilities: capabilityResolver( (resourceId) => `/downloads/${resourceId}`, ), baseOrigin: "https://app.example", createTransferId: () => "transfer:1", userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput( { kind: "BROWSER_MANAGED_RESOURCE", resourceId: "artifact-1", capabilityReceipt, }, "BROWSER_MANAGED", ), ), ).toEqual({ ok: true, value: { kind: "BROWSER_HANDOFF", transferId: "transfer:1" }, }); expect(handoff).toHaveBeenCalledWith( "/downloads/artifact-1", "invoice_exe.pdf", ); }); it("rejects cross-origin or query-bearing browser-managed targets", async () => { const handoff = vi.fn(); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff }, browserManagedCapabilities: capabilityResolver( () => "https://evil.example/a?token=secret", ), baseOrigin: "https://app.example", userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput( { kind: "BROWSER_MANAGED_RESOURCE", resourceId: "artifact-1", capabilityReceipt, }, "BROWSER_MANAGED", ), ), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(handoff).not.toHaveBeenCalled(); }); it("requires an exact, unexpired, server-bound synchronous capability receipt", async () => { const source = { kind: "BROWSER_MANAGED_RESOURCE" as const, resourceId: "artifact-1", capabilityReceipt, }; const cases = [ { resolver: capabilityResolver(() => "/unused", { capabilityReceipt: "capability:other" as BrowserManagedDownloadCapabilityReceipt, }), expectedCode: "POLICY_REJECTED", }, { resolver: capabilityResolver(undefined, { resourceId: "artifact-2", }), expectedCode: "POLICY_REJECTED", }, { resolver: capabilityResolver(undefined, { mediaType: "text/plain", }), expectedCode: "POLICY_REJECTED", }, { resolver: capabilityResolver(undefined, { expiresAtEpochMs: 99, }), expectedCode: "EXPIRED_RESOURCE", }, ] as const; for (const testCase of cases) { const handoff = vi.fn(); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff }, browserManagedCapabilities: testCase.resolver, now: () => 100, baseOrigin: "https://app.example", userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput(source, "BROWSER_MANAGED"), ), ).toMatchObject({ ok: false, error: { code: testCase.expectedCode }, }); expect(handoff).not.toHaveBeenCalled(); } const missingReceipt = { kind: "BROWSER_MANAGED_RESOURCE", resourceId: "artifact-1", } as unknown as DownloadSource; const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput(missingReceipt, "BROWSER_MANAGED"), ), ).toMatchObject({ ok: false, error: { code: "INVALID_INPUT" }, }); }); it("streams with backpressure and succeeds only after close", async () => { const events: string[] = []; const controller = new AbortController(); const handle = writableHandle(events); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: async () => handle, createTransferId: () => "transfer:stream", progressMinIntervalMs: 0, userActivation: { isActive: true }, }); const input = deliveryInput( { kind: "GENERATED", bytes: byteSource([ new Uint8Array([1, 2]), new Uint8Array([3]), ]), }, "PROMPT_AND_STREAM", controller.signal, ); expect(await adapter.deliver(input)).toEqual({ ok: true, value: { kind: "SAVED", transferId: "transfer:stream", bytesWritten: 3, integrity: "NOT_PROVIDED", }, }); expect(events).toEqual(["write:2", "write:1", "close"]); expect(input.onProgress.mock.calls.map(([progress]) => progress.phase)).toEqual( ["PREPARING", "TRANSFERRING", "TRANSFERRING", "VERIFYING", "FINALIZING"], ); }); it("snapshots the generated source before awaiting the save picker", async () => { const events: string[] = []; let resolvePicker: | ((handle: SaveFileHandle) => void) | undefined; const originalStream = vi.fn( async function* () { yield successfulChunk(new Uint8Array([1, 2])); }, ); const replacedStream = vi.fn( async function* () { yield successfulChunk(new Uint8Array([9])); }, ); const bytes = { byteLength: 2, stream: originalStream, }; const source = { kind: "GENERATED" as const, bytes, }; const request = deliveryInput( source, "PROMPT_AND_STREAM", ); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: () => new Promise((resolve) => { resolvePicker = resolve; }), createTransferId: () => "transfer:snapshot", userActivation: { isActive: true }, }); const pending = adapter.deliver(request); bytes.byteLength = 1; bytes.stream = replacedStream; ( request as { source: DownloadSource } ).source = { kind: "BROWSER_MANAGED_RESOURCE", resourceId: "replaced", capabilityReceipt, }; resolvePicker?.(writableHandle(events)); expect(await pending).toMatchObject({ ok: true, value: { kind: "SAVED", bytesWritten: 2 }, }); expect(originalStream).toHaveBeenCalledOnce(); expect(replacedStream).not.toHaveBeenCalled(); expect(events).toEqual(["write:2", "close"]); }); it("does not turn a completed close into a late abort failure", async () => { const controller = new AbortController(); const handle: SaveFileHandle = { async createWritable() { return new WritableStream({ close() { controller.abort(); }, }); }, }; const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: async () => handle, createTransferId: () => "transfer:committed", userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput( { kind: "GENERATED", bytes: byteSource([new Uint8Array([1])]), }, "PROMPT_AND_STREAM", controller.signal, ), ), ).toMatchObject({ ok: true, value: { kind: "SAVED", transferId: "transfer:committed" }, }); }); it("aborts the writable before close on integrity failure", async () => { const events: string[] = []; const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: async () => writableHandle(events), createIntegrityVerifier: () => ({ update: () => { events.push("hash"); }, verify: () => false, }), userActivation: { isActive: true }, }); const input = { ...deliveryInput( { kind: "GENERATED" as const, bytes: byteSource([new Uint8Array([1, 2])]), expectedSha256: "a".repeat(64), }, "PROMPT_AND_STREAM", ), }; expect(await adapter.deliver(input)).toMatchObject({ ok: false, error: { code: "INTEGRITY_FAILED" }, }); expect(events).toEqual(["hash", "write:2", "abort"]); }); it("distinguishes save-picker dismissal from active cancellation", async () => { const dismissed = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: async () => { throw new DOMException("dismissed", "AbortError"); }, userActivation: { isActive: true }, }); expect( await dismissed.deliver( deliveryInput( { kind: "GENERATED", bytes: byteSource([new Uint8Array([1])]), }, "PROMPT_AND_STREAM", ), ), ).toEqual({ ok: true, value: { kind: "DISMISSED" }, }); const controller = new AbortController(); let rejectPicker: | ((reason: DOMException) => void) | undefined; const aborted = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: () => new Promise((_, reject: (reason: DOMException) => void) => { rejectPicker = reject; }), userActivation: { isActive: true }, }); const pending = aborted.deliver( deliveryInput( { kind: "GENERATED", bytes: byteSource([new Uint8Array([1])]), }, "PROMPT_AND_STREAM", controller.signal, ), ); controller.abort(); rejectPicker?.(new DOMException("closed", "AbortError")); expect(await pending).toMatchObject({ ok: false, error: { code: "ABORTED" }, }); }); it("bounds Blob buffering and revokes the lease after handoff", async () => { const revoked: string[] = []; const leases = new ObjectUrlLeaseRegistry({ createObjectURL: () => "blob:download-1", revokeObjectURL: (url) => revoked.push(url), }); let scheduled: (() => void) | undefined; let scheduledDelay: number | undefined; const handoff = vi.fn(); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff }, browserManagedCapabilities: capabilityResolver(), objectUrls: leases, scheduler: { setTimeout(callback, delayMs) { scheduled = callback; scheduledDelay = delayMs; return 1; }, }, createTransferId: () => "transfer:blob", userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput( { kind: "GENERATED", bytes: byteSource([ new Uint8Array([1, 2]), new Uint8Array([3]), ]), }, "BOUNDED_OBJECT_URL", ), ), ).toEqual({ ok: true, value: { kind: "BROWSER_HANDOFF", transferId: "transfer:blob", }, }); expect(handoff).toHaveBeenCalledWith( "blob:download-1", "invoice_exe.pdf", ); expect(leases.activeLeaseCount).toBe(1); expect(scheduledDelay).toBe( DEFAULT_OBJECT_URL_RELEASE_GRACE_MS, ); scheduled?.(); expect(leases.activeLeaseCount).toBe(0); expect(revoked).toEqual(["blob:download-1"]); }); it("captures object URL methods at registry construction", () => { const originalCreate = vi.fn(() => "blob:captured"); const originalRevoke = vi.fn(); const replacedCreate = vi.fn(() => "blob:replaced"); const replacedRevoke = vi.fn(); const urlApi = { createObjectURL: originalCreate, revokeObjectURL: originalRevoke, }; const leases = new ObjectUrlLeaseRegistry(urlApi); urlApi.createObjectURL = replacedCreate; urlApi.revokeObjectURL = replacedRevoke; const lease = leases.create(new Blob(["safe"])); lease.release(); expect(lease.url).toBe("blob:captured"); expect(originalCreate).toHaveBeenCalledOnce(); expect(originalRevoke).toHaveBeenCalledWith( "blob:captured", ); expect(replacedCreate).not.toHaveBeenCalled(); expect(replacedRevoke).not.toHaveBeenCalled(); }); it("rejects a stream that crosses the bounded Blob cap", async () => { const handoff = vi.fn(); const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff }, browserManagedCapabilities: capabilityResolver(), userActivation: { isActive: true }, }); const input = { ...deliveryInput( { kind: "GENERATED" as const, bytes: byteSource( [new Uint8Array(10), new Uint8Array(10)], null, ), }, "BOUNDED_OBJECT_URL", ), maxBufferedBytes: 16, }; expect(await adapter.deliver(input)).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(handoff).not.toHaveBeenCalled(); }); it("rejects caller limits above runtime hard caps before side effects", async () => { const handoff = vi.fn(); const showSaveFilePicker = vi.fn(async () => writableHandle([])); const stream = vi.fn( async function* (): AsyncIterable< ReturnType > { yield successfulChunk(new Uint8Array([1])); }, ); const adapter = createDownloadDeliveryAdapter({ hardMaxObjectUrlBytes: 8, hardMaxTransferBytes: 12, policies, host: { handoff }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker, userActivation: { isActive: true }, }); const source = { kind: "GENERATED" as const, bytes: { byteLength: null, stream }, }; expect( await adapter.deliver({ ...deliveryInput(source, "BOUNDED_OBJECT_URL"), maxBufferedBytes: 9, maxTransferBytes: 12, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect( await adapter.deliver({ ...deliveryInput(source, "PROMPT_AND_STREAM"), maxBufferedBytes: 8, maxTransferBytes: 13, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect( await adapter.deliver({ ...deliveryInput( { kind: "GENERATED", bytes: { byteLength: 13, stream }, }, "PROMPT_AND_STREAM", ), maxBufferedBytes: 8, maxTransferBytes: 12, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(stream).not.toHaveBeenCalled(); expect(showSaveFilePicker).not.toHaveBeenCalled(); expect(handoff).not.toHaveBeenCalled(); }); it("aborts streaming when actual bytes cross the transfer hard cap", async () => { const events: string[] = []; const adapter = createDownloadDeliveryAdapter({ hardMaxObjectUrlBytes: 8, hardMaxTransferBytes: 8, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), showSaveFilePicker: async () => writableHandle(events), userActivation: { isActive: true }, }); expect( await adapter.deliver({ ...deliveryInput( { kind: "GENERATED", bytes: byteSource( [new Uint8Array(5), new Uint8Array(4)], null, ), }, "PROMPT_AND_STREAM", ), maxBufferedBytes: 8, maxTransferBytes: 8, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(events).toEqual(["write:5", "abort"]); }); it("treats a declared-length mismatch as integrity failure", async () => { const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), userActivation: { isActive: true }, }); expect( await adapter.deliver( deliveryInput( { kind: "GENERATED", bytes: byteSource([new Uint8Array([1])], 2), }, "BOUNDED_OBJECT_URL", ), ), ).toMatchObject({ ok: false, error: { code: "INTEGRITY_FAILED" }, }); }); it("maps typed and defensive raw stream failures to closed download errors", async () => { const adapter = createDownloadDeliveryAdapter({ ...HARD_LIMITS, policies, host: { handoff: vi.fn() }, browserManagedCapabilities: capabilityResolver(), userActivation: { isActive: true }, }); const common = { ...deliveryInput( { kind: "GENERATED" as const, bytes: { byteLength: null, async *stream() { yield { ok: false as const, error: { code: "NOT_READABLE" as const, operation: "FILE_READ" as const, retryable: true, recovery: "REOPEN" as const, }, }; }, }, }, "BOUNDED_OBJECT_URL", ), }; expect(await adapter.deliver(common)).toMatchObject({ ok: false, error: { code: "NOT_READABLE", operation: "DOWNLOAD", }, }); expect( await adapter.deliver({ ...common, source: { kind: "GENERATED", bytes: { byteLength: null, async *stream(signal: AbortSignal) { if (signal.aborted) { yield { ok: false as const, error: { code: "ABORTED" as const, operation: "FILE_READ" as const, retryable: false, recovery: "NONE" as const, }, }; return; } throw new DOMException( "native detail", "NotReadableError", ); }, }, }, }), ).toMatchObject({ ok: false, error: { code: "NOT_READABLE", operation: "DOWNLOAD", }, }); }); });