105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
CatalogExportQueue,
|
|
publishCatalogFiles,
|
|
recoverCatalogQueue,
|
|
sqlValue,
|
|
} from "./catalog-git-core";
|
|
|
|
describe("catalog export", () => {
|
|
it("recovers queue entries owned by a terminated local process", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "catalog-recovery-"));
|
|
const deadPid = Number(
|
|
execFileSync(
|
|
process.execPath,
|
|
["-e", "process.stdout.write(String(process.pid))"],
|
|
{ encoding: "utf8" },
|
|
),
|
|
);
|
|
const owner = JSON.stringify({ pid: deadPid, host: os.hostname() });
|
|
await writeFile(path.join(root, "12345678.active"), owner);
|
|
await writeFile(path.join(root, "worker.lock"), owner);
|
|
await recoverCatalogQueue(root);
|
|
const q = new CatalogExportQueue(root);
|
|
expect(await q.batch()).toEqual(["12345678.pending"]);
|
|
expect(await q.entries()).not.toContain("worker.lock");
|
|
});
|
|
|
|
it("keeps new requests pending when an older snapshot completes", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "catalog-queue-"));
|
|
const q = new CatalogExportQueue(root);
|
|
const finish = await q.begin();
|
|
expect(await q.batch()).toBeNull();
|
|
await finish();
|
|
const batch = await q.batch();
|
|
expect(batch).toHaveLength(1);
|
|
await q.request();
|
|
if (!batch) throw new Error("Expected pending batch");
|
|
await q.complete(batch);
|
|
expect(await q.batch()).toHaveLength(1);
|
|
});
|
|
it("encodes SQL strings without quote or backslash ambiguity", () => {
|
|
expect(sqlValue("Valentine's \\ Day\n")).toBe(
|
|
"CONVERT(X'56616c656e74696e652773205c204461790a' USING utf8mb4)",
|
|
);
|
|
expect(sqlValue(null)).toBe("NULL");
|
|
expect(sqlValue(42)).toBe("42");
|
|
expect(() => sqlValue(Number.NaN)).toThrow();
|
|
});
|
|
it("publishes to a bare remote, retries a failed push and avoids empty commits", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "catalog-git-"));
|
|
const remote = path.join(root, "remote.git");
|
|
const checkout = path.join(root, "checkout");
|
|
const git = (cwd: string, ...args: string[]) =>
|
|
execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
|
|
git(root, "init", "--bare", remote);
|
|
git(root, "clone", remote, checkout);
|
|
git(checkout, "checkout", "-b", "Beta-3");
|
|
git(checkout, "config", "user.name", "Catalog test");
|
|
git(checkout, "config", "user.email", "[email protected]");
|
|
await writeFile(path.join(checkout, "readme.md"), "preserved");
|
|
git(checkout, "add", "readme.md");
|
|
git(checkout, "commit", "-m", "initial");
|
|
git(checkout, "push", "-u", "origin", "Beta-3");
|
|
const source = path.join(root, "chair.nitro");
|
|
await writeFile(source, "bundle");
|
|
const files = [
|
|
{ source, target: "Gamedata/bundled/furniture/chair.nitro" },
|
|
];
|
|
const options = { checkout, remote, branch: "Beta-3", files };
|
|
const first = await publishCatalogFiles(options);
|
|
expect(await publishCatalogFiles(options)).toBe(first);
|
|
await writeFile(source, "updated");
|
|
git(
|
|
checkout,
|
|
"remote",
|
|
"set-url",
|
|
"--push",
|
|
"origin",
|
|
path.join(root, "missing.git"),
|
|
);
|
|
await expect(publishCatalogFiles(options)).rejects.toThrow();
|
|
git(checkout, "remote", "set-url", "--push", "origin", remote);
|
|
const retried = await publishCatalogFiles(options);
|
|
expect(retried).not.toBe(first);
|
|
expect(git(remote, "rev-parse", "Beta-3")).toBe(retried);
|
|
expect(await readFile(path.join(checkout, "readme.md"), "utf8")).toBe(
|
|
"preserved",
|
|
);
|
|
await expect(
|
|
publishCatalogFiles({
|
|
...options,
|
|
files: [{ source, target: "../escape" }],
|
|
}),
|
|
).rejects.toThrow();
|
|
await mkdir(path.join(checkout, "unrelated"));
|
|
await writeFile(path.join(checkout, "unrelated", "local.txt"), "local");
|
|
await expect(publishCatalogFiles(options)).rejects.toThrow(/clean/);
|
|
// This integration test launches multiple real Git processes on the CI runner.
|
|
}, 60_000);
|
|
});
|