Files
Epicnabbo-Catalogus-Updated…/src/lib/services/catalog-git-core.test.ts
T

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);
});