import { assertInstanceOf, assertStringIncludes } from "@std/assert"; import { expect } from "@std/expect"; import { describe, it as test } from "@std/testing/bdd"; import { FakeTime } from "@std/testing/time"; import { uploadTursoDatabaseFile } from "#scripts/turso-migration-file.ts"; import { expectUploadRejection, localUploadFetch, readUploadChunk, UPLOAD_SIZE, uploadCredentials, withinDeadline, withUploadFile, withUploadServer, } from "./turso-upload-harness.ts"; const expectTransportFailure = (error: unknown): void => { assertInstanceOf(error, TypeError); expect(error.message).toMatch(/error sending request|error writing a body/); }; interface PendingResponseUpload { cancelledBody: Promise; controller: AbortController; outcome: Promise; } const withPendingUploadResponse = ( run: (upload: PendingResponseUpload) => Promise, ): Promise => withUploadFile(async (path) => { const controller = new AbortController(); const receivedHeaders = Promise.withResolvers(); const cancelledBody = Promise.withResolvers(); await withUploadServer( async (request) => { await request.arrayBuffer(); return new Response( new ReadableStream({ cancel(reason) { cancelledBody.resolve(reason); }, start(body) { body.enqueue(new TextEncoder().encode("unfinished")); }, }), { status: 400 }, ); }, async (dbUrl, fetcher) => { const outcome = uploadTursoDatabaseFile( path, uploadCredentials(dbUrl), controller.signal, async (input, init) => { const response = await fetcher(input, init); receivedHeaders.resolve(); return response; }, ).catch((error: unknown) => error); try { await withinDeadline(receivedHeaders.promise, "Response headers"); await run({ cancelledBody: cancelledBody.promise, controller, outcome, }); } finally { controller.abort(); await outcome; } }, ); }); describe("Turso upload lifecycle", () => { test("ends a stalled wait even when the upload clock is frozen", async () => { const schedule = globalThis.setTimeout; using time = new FakeTime(0); const work = Promise.withResolvers(); const outcome = withinDeadline(work.promise, "Stalled upload", 0).catch( (error: unknown) => error, ); schedule(work.resolve, 0); const error = await outcome; assertInstanceOf(error, Error); assertStringIncludes(error.message, "Stalled upload"); expect(time.now).toBe(0); }); test("reports an upload handler error after a successful client check", async () => { const failure = new Error("Upload handler failed"); await expect( withUploadServer( () => { throw failure; }, async (dbUrl, fetcher) => { const response = await fetcher(dbUrl); await response.text(); expect(response.status).toBe(500); }, ), ).rejects.toBe(failure); }); test("closes the server when a handler outlives its client", async () => { const never = Promise.withResolvers(); await withUploadServer( () => never.promise, async (dbUrl, fetcher) => { // Start a request and walk away from it, as an aborted upload does. void fetcher(dbUrl).catch(() => { // The client walked away; the answer's failure is not this test's. }); }, ); }); test("rejects an early answer without a timeout or stray rejection", () => withUploadFile(async (path) => { let received = 0; await expectUploadRejection( path, async (request) => { received = await readUploadChunk(request); return new Response("invalid", { status: 400 }); }, (error) => { if (error instanceof TypeError) { expectTransportFailure(error); } else { assertInstanceOf(error, Error); expect(error.message).toBe("Upload database failed (400): invalid"); } }, ); expect(received).toBeLessThan(UPLOAD_SIZE); })); test("rejects a connection that closes before any answer", () => withUploadFile(async (path) => { using listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const server = (async () => { using connection = await listener.accept(); const buffer = new Uint8Array(64 * 1024); let received = 0; while (received < 2_000_000) { const count = await connection.read(buffer); if (count === null) throw new Error("Upload ended before the cut"); received += count; } return received; })(); const outcome = await withinDeadline( uploadTursoDatabaseFile( path, uploadCredentials(`https://127.0.0.1:${listener.addr.port}`), undefined, localUploadFetch, ).catch((error: unknown) => error), "Dropped upload", ); expectTransportFailure(outcome); expect(await server).toBeLessThan(UPLOAD_SIZE); })); for (const reason of [new Error("stop now"), "stop now"]) { test(`aborts a request under backpressure with a ${typeof reason} reason`, () => withUploadFile(async (path) => { const controller = new AbortController(); const started = Promise.withResolvers(); const release = Promise.withResolvers(); await withUploadServer( async (request) => { await readUploadChunk(request); started.resolve(); await release.promise; return new Response(); }, async (dbUrl, fetcher) => { const outcome = uploadTursoDatabaseFile( path, uploadCredentials(dbUrl), controller.signal, fetcher, ).catch((error: unknown) => error); try { await withinDeadline(started.promise, "Upload body"); controller.abort(reason); const error = await withinDeadline(outcome, "Cancelled upload"); assertInstanceOf(error, Error); expect(error.message).toBe("Upload aborted: stop now"); } finally { controller.abort(); release.resolve(); await outcome; } }, ); })); } test("aborts after response headers arrive but before the response ends", () => withPendingUploadResponse( async ({ controller, outcome, cancelledBody }) => { controller.abort(new Error("stop the response")); const error = await withinDeadline(outcome, "Cancelled response"); assertInstanceOf(error, Error); expect(error.message).toBe("Upload aborted: stop the response"); await withinDeadline(cancelledBody, "Response cancellation"); }, )); test("keeps the deadline active until the response body ends", async () => { using time = new FakeTime(); await withPendingUploadResponse(async ({ outcome, cancelledBody }) => { let settled = false; const observed = outcome.finally(() => { settled = true; }); await time.tickAsync(599_999); expect(settled).toBe(false); await time.tickAsync(1); const error = await withinDeadline(observed, "Upload deadline"); assertInstanceOf(error, Error); expect(error.message).toBe( "Upload aborted: Upload exceeded the 10-minute deadline", ); await withinDeadline(cancelledBody, "Response cancellation"); }); }); });