Ctrl K
ComunidadGuías…

test/scripts/turso-upload.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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<unknown>;
  controller: AbortController;
  outcome: Promise<unknown>;
}

const withPendingUploadResponse = (
  run: (upload: PendingResponseUpload) => Promise<void>,
): Promise<void> =>
  withUploadFile(async (path) => {
    const controller = new AbortController();
    const receivedHeaders = Promise.withResolvers<void>();
    const cancelledBody = Promise.withResolvers<unknown>();
    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<void>();
    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<Response>();
    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<void>();
        const release = Promise.withResolvers<void>();
        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");
    });
  });
});