test/scripts/turso-migration-file.test.ts
Rama main · réplica de lectura del repositorio original.
// test-groups: run-alone
import { createClient } from "@libsql/client";
import { expect } from "@std/expect";
import { join, toFileUrl } from "@std/path";
import { describe, it as test } from "@std/testing/bdd";
import {
uploadTursoDatabaseFile,
verifyTursoUploadFile,
} from "#scripts/turso-migration-file.ts";
import { withTempDir } from "#test-utils/files.ts";
import { TEST_TURSO_CREDENTIALS } from "#test-utils/turso-api.ts";
import {
expectUploadRejection,
uploadCredentials,
withUploadServer,
} from "./turso-upload-harness.ts";
const createSqliteFile = async (
path: string,
pragmas: string[],
): Promise<void> => {
const client = createClient({ url: toFileUrl(path).href });
try {
for (const pragma of pragmas) await client.execute(pragma);
await client.execute("CREATE TABLE example (value TEXT)");
await client.execute("INSERT INTO example VALUES ('saved')");
await client.execute("PRAGMA wal_checkpoint(TRUNCATE)");
} finally {
client.close();
}
};
describe("Turso migration file", () => {
test("accepts a complete SQLite file prepared for Turso", () =>
withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
await createSqliteFile(path, [
"PRAGMA page_size = 4096",
"PRAGMA auto_vacuum = 0",
"PRAGMA encoding = 'UTF-8'",
"PRAGMA journal_mode = WAL",
]);
await expect(
verifyTursoUploadFile(path, createClient),
).resolves.toBeUndefined();
}));
test("rejects every SQLite setting Turso cannot upload", async () => {
for (const example of [
{
message: "Database file must use WAL journal mode",
pragmas: ["PRAGMA journal_mode = DELETE"],
},
{
message: "Database file must use 4096-byte pages",
pragmas: ["PRAGMA page_size = 1024", "PRAGMA journal_mode = WAL"],
},
{
message: "Database file must have auto-vacuum disabled",
pragmas: ["PRAGMA auto_vacuum = FULL", "PRAGMA journal_mode = WAL"],
},
{
message: "Database file must use UTF-8 encoding",
pragmas: ["PRAGMA encoding = 'UTF-16le'", "PRAGMA journal_mode = WAL"],
},
]) {
await withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
await createSqliteFile(path, example.pragmas);
await expect(verifyTursoUploadFile(path, createClient)).rejects.toThrow(
example.message,
);
});
}
});
test("streams the full SQLite file with its exact byte length", () =>
withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
const bytes = new Uint8Array(130_000).map((_, index) => index % 251);
await Deno.writeFile(path, bytes);
let uploaded: Uint8Array | undefined;
let authorization: string | undefined;
let contentLength: string | undefined;
await withUploadServer(
async (request) => {
authorization = request.headers.get("authorization") ?? undefined;
contentLength = request.headers.get("content-length") ?? undefined;
uploaded = new Uint8Array(await request.arrayBuffer());
return new Response();
},
(dbUrl, transport) =>
uploadTursoDatabaseFile(
path,
uploadCredentials(dbUrl),
new AbortController().signal,
transport,
),
);
expect(authorization).toBe("Bearer database-token");
expect(contentLength).toBe(String(bytes.byteLength));
expect(uploaded).toEqual(bytes);
}));
test("rejects a snapshot path that is not a file", () =>
withTempDir(async (dir) => {
await expect(
uploadTursoDatabaseFile(dir, TEST_TURSO_CREDENTIALS),
).rejects.toThrow(`Database snapshot is not a file: ${dir}`);
}));
test("reports an upload API failure", () =>
withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
await Deno.writeTextFile(path, "sqlite bytes");
await expectUploadRejection(
path,
async (request) => {
// Read the whole upload first so the reply never races the body.
await request.arrayBuffer();
return new Response("invalid", { status: 400 });
},
(error) => {
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe(
"Upload database failed (400): invalid",
);
},
);
}));
test("rejects an interrupted upload before opening the snapshot", () =>
withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
await Deno.writeFile(path, new Uint8Array(130_000));
const controller = new AbortController();
controller.abort(new Error("interrupted"));
await expect(
uploadTursoDatabaseFile(
path,
TEST_TURSO_CREDENTIALS,
controller.signal,
),
).rejects.toThrow("interrupted");
await Deno.remove(path);
}));
test("rejects database upload URLs without TLS", () =>
withTempDir(async (dir) => {
const path = join(dir, "database.sqlite");
await Deno.writeTextFile(path, "sqlite bytes");
await expect(
uploadTursoDatabaseFile(
path,
uploadCredentials("http://database.example.com"),
),
).rejects.toThrow("Turso database URL must use TLS");
}));
});