tests/configMirror.test.ts
Rama main · réplica de lectura del repositorio original.
// The mirror (server/configMirror.ts): its one pure decision, `pickSource`,
// the caps, and then the pass itself over two real temp directories — the
// shape the maturity brief measured (§4.1: a font uploaded on A never reached
// a fresh clone B, because `DIRS` named a directory that no longer existed).
import { test } from "node:test";
import assert from "node:assert/strict";
import { chmodSync, mkdirSync, readFileSync, statSync, utimesSync, writeFileSync } from "node:fs";
import path from "node:path";
import {
DIRS,
FILE_MAX_BYTES,
FILES,
applyCaps,
mirrorConfig,
pickSource,
setFontWarmer,
travelStatus,
} from "../server/configMirror.ts";
import { initSite } from "../server/site.ts";
import { initVault } from "../server/vault.ts";
import { makeDir } from "./helpers/vault.ts";
test("a file on one side only is copied to the other", () => {
assert.equal(pickSource({ mtimeMs: 10_000, size: 5 }, null), "data");
assert.equal(pickSource(null, { mtimeMs: 10_000, size: 5 }), "vault");
assert.equal(pickSource(null, null), null);
});
test("the newer copy wins, in either direction", () => {
assert.equal(pickSource({ mtimeMs: 50_000, size: 5 }, { mtimeMs: 10_000, size: 9 }), "data");
assert.equal(pickSource({ mtimeMs: 10_000, size: 5 }, { mtimeMs: 50_000, size: 9 }), "vault");
});
test("copies a rounding error apart are the same write and settle", () => {
assert.equal(pickSource({ mtimeMs: 10_000.4, size: 5 }, { mtimeMs: 10_000, size: 5 }), null);
assert.equal(pickSource({ mtimeMs: 10_000, size: 5 }, { mtimeMs: 10_000.9, size: 5 }), null);
// Same moment, different bytes: something is off; the later one still wins.
assert.equal(pickSource({ mtimeMs: 10_000.4, size: 5 }, { mtimeMs: 10_000, size: 6 }), "data");
});
test("on first contact the vault wins whatever the clocks say", () => {
// The desktop's 31-byte defaults, a day younger than the site's settings.
assert.equal(pickSource({ mtimeMs: 90_000, size: 31 }, { mtimeMs: 10_000, size: 1373 }, true), "vault");
// Afterwards, the clocks decide again.
assert.equal(pickSource({ mtimeMs: 90_000, size: 31 }, { mtimeMs: 10_000, size: 1373 }, false), "data");
// A vault with no copy still takes this side's file.
assert.equal(pickSource({ mtimeMs: 90_000, size: 31 }, null, true), "data");
});
test("what travels with the vault: the site's files, the person's ledgers, the uploaded fonts", () => {
// 3.16.0 added the last three files: named layouts, the book shelf with its
// reading positions, and the annotations. The owner's test is "run the
// executable, give it the vault, and everything is there".
for (const f of ["settings.json", "designs.json", "custom.css", "layouts.json", "books.json", "annotations.json"]) {
assert.ok(FILES.includes(f), `${f} must travel`);
}
// …and the things that must NOT: a token is a device's, history is git's.
for (const f of ["git-credentials.json", "versions", "session-epoch", "comments.db", "pdftext.json"]) {
assert.ok(!FILES.includes(f), `${f} must stay on the server`);
}
// 3.18.0: the directory entry names the directory the uploads actually
// live in. `fonts` matched nothing for two releases; the catalog is
// re-fetchable and is warmed on the receiving side instead of copied.
assert.deepEqual(DIRS, ["fonts/custom"]);
});
test("the caps: one file over 5 MB is skipped, the rest of a 40 MB folder is skipped, index.json is counted first", () => {
const MB = 1024 * 1024;
const { allowed, problems } = applyCaps(
[
{ rel: "fonts/custom/zz.ttf", size: 3 * MB },
{ rel: "fonts/custom/index.json", size: 100 },
{ rel: "fonts/custom/huge.ttf", size: 6 * MB },
{ rel: "fonts/custom/aa.woff2", size: 3 * MB },
],
5 * MB,
7 * MB,
);
assert.deepEqual(allowed, ["fonts/custom/index.json", "fonts/custom/aa.woff2", "fonts/custom/zz.ttf"]);
assert.deepEqual(
problems.map((p) => [p.rel, p.reason]),
[["fonts/custom/huge.ttf", "too-large"]],
);
// The total: 3 + 3 fit under 7; a third 3 MB face would not.
const over = applyCaps(
[
{ rel: "fonts/custom/a.ttf", size: 3 * MB },
{ rel: "fonts/custom/b.ttf", size: 3 * MB },
{ rel: "fonts/custom/c.ttf", size: 3 * MB },
],
5 * MB,
7 * MB,
);
assert.deepEqual(over.allowed, ["fonts/custom/a.ttf", "fonts/custom/b.ttf"]);
assert.deepEqual(over.problems.map((p) => [p.rel, p.reason]), [["fonts/custom/c.ttf", "over-total"]]);
});
// ── The pass over real directories ──────────────────────────────────────
/** Point the server at a data directory and a vault. `mirrorConfig` reads
* both through the same accessors the server does. */
function over(data: string, vault: string): void {
initSite({ ASTROLABE_DATA: data });
initVault(vault);
}
function write(root: string, rel: string, content: string | Buffer, mtimeSec?: number): string {
const abs = path.join(root, rel);
mkdirSync(path.dirname(abs), { recursive: true });
writeFileSync(abs, content);
if (mtimeSec !== undefined) utimesSync(abs, mtimeSec, mtimeSec);
return abs;
}
function exists(abs: string): boolean {
try {
return statSync(abs).isFile();
} catch {
return false;
}
}
const INDEX = JSON.stringify({ version: 1, faces: { "prose-face.woff2": { family: "Prose Face" } } });
test("an uploaded font and its index travel data → vault, and vault → a fresh data directory", async () => {
setFontWarmer(async () => {});
const vault = makeDir();
const dataA = makeDir();
over(dataA, vault);
write(dataA, "fonts/custom/prose-face.woff2", "wOF2 not really a font");
write(dataA, "fonts/custom/index.json", INDEX);
write(dataA, "settings.json", JSON.stringify({ siteName: "A" }));
const first = await mirrorConfig();
assert.deepEqual(first.problems, []);
assert.ok(first.toVault.includes("fonts/custom/prose-face.woff2"));
assert.ok(first.toVault.includes("fonts/custom/index.json"));
assert.ok(exists(path.join(vault, ".astrolabe/fonts/custom/prose-face.woff2")));
assert.equal(readFileSync(path.join(vault, ".astrolabe/fonts/custom/index.json"), "utf8"), INDEX);
// Machine B: a fresh ASTROLABE_DATA over the same vault.
const dataB = makeDir();
over(dataB, vault);
const second = await mirrorConfig();
assert.ok(second.toData.includes("fonts/custom/prose-face.woff2"));
assert.ok(second.toData.includes("settings.json"));
assert.equal(readFileSync(path.join(dataB, "fonts/custom/prose-face.woff2"), "utf8"), "wOF2 not really a font");
assert.equal(readFileSync(path.join(dataB, "settings.json"), "utf8"), JSON.stringify({ siteName: "A" }));
// A third pass moves nothing: the sides agree.
const third = await mirrorConfig();
assert.deepEqual([third.toData, third.toVault], [[], []]);
});
test("the catalog never travels; a 6 MB face is skipped with a named problem and never lands in the vault", async () => {
setFontWarmer(async () => {});
const vault = makeDir();
const data = makeDir();
over(data, vault);
write(data, "fonts/catalog/lora/lora-latin.woff2", "cached");
write(data, "fonts/custom/index.json", INDEX);
write(data, "fonts/custom/huge-face.ttf", Buffer.alloc(FILE_MAX_BYTES + 1));
const pass = await mirrorConfig();
assert.ok(!exists(path.join(vault, ".astrolabe/fonts/catalog/lora/lora-latin.woff2")));
assert.ok(!exists(path.join(vault, ".astrolabe/fonts/custom/huge-face.ttf")));
assert.ok(exists(path.join(vault, ".astrolabe/fonts/custom/index.json")));
assert.deepEqual(
pass.problems.map((p) => [p.rel, p.reason]),
[["fonts/custom/huge-face.ttf", "too-large"]],
);
// …and the travel row is told.
const status = await travelStatus();
assert.equal(status.lastPass?.problems[0]?.rel, "fonts/custom/huge-face.ttf");
});
test("a file absent on both sides does not consume first contact", async () => {
setFontWarmer(async () => {});
const vault = makeDir();
const data = makeDir();
over(data, vault);
write(data, "settings.json", "{}");
await mirrorConfig(); // layouts.json absent everywhere
// This machine then makes its own layouts, and the vault's copy arrives
// afterwards by git — older by the clock, but the vault's.
write(data, "layouts.json", JSON.stringify({ mine: true }), 2_000_000_000);
write(vault, ".astrolabe/layouts.json", JSON.stringify({ theirs: true }), 1_000_000_000);
const pass = await mirrorConfig();
assert.ok(pass.toData.includes("layouts.json"), "first contact: the vault wins");
assert.equal(readFileSync(path.join(data, "layouts.json"), "utf8"), JSON.stringify({ theirs: true }));
// Met now: from here the clocks decide.
write(data, "layouts.json", JSON.stringify({ edited: true }), 2_000_000_100);
const later = await mirrorConfig();
assert.ok(later.toVault.includes("layouts.json"));
});
test("mirror-state.json is keyed by the vault: a data directory pointed at another vault is first contact again", async () => {
setFontWarmer(async () => {});
const data = makeDir();
const vaultA = makeDir();
over(data, vaultA);
write(data, "settings.json", JSON.stringify({ siteName: "mine" }), 2_000_000_000);
await mirrorConfig();
const state = JSON.parse(readFileSync(path.join(data, "mirror-state.json"), "utf8")) as { vault: string; reconciled: string[] };
assert.ok(state.vault.endsWith(path.basename(vaultA)));
assert.ok(state.reconciled.includes("settings.json"));
// The same data directory opens a different vault whose settings are older.
const vaultB = makeDir();
write(vaultB, ".astrolabe/settings.json", JSON.stringify({ siteName: "B's site" }), 1_000_000_000);
over(data, vaultB);
const pass = await mirrorConfig();
assert.ok(pass.toData.includes("settings.json"), "the new vault wins its first contact");
assert.equal(readFileSync(path.join(data, "settings.json"), "utf8"), JSON.stringify({ siteName: "B's site" }));
const rekeyed = JSON.parse(readFileSync(path.join(data, "mirror-state.json"), "utf8")) as { vault: string };
assert.ok(rekeyed.vault.endsWith(path.basename(vaultB)));
});
test("a ledger arriving from the vault is private again (0600), whatever mode the clone gave it", async () => {
if (process.platform === "win32") return;
setFontWarmer(async () => {});
const vault = makeDir();
const data = makeDir();
over(data, vault);
const inVault = write(vault, ".astrolabe/books.json", JSON.stringify({ version: 1, books: {} }));
chmodSync(inVault, 0o644);
await mirrorConfig();
assert.equal(statSync(path.join(data, "books.json")).mode & 0o777, 0o600);
});
test("settings.json arriving from the vault warms the catalog faces it names", async () => {
const asked: string[][] = [];
setFontWarmer(async (ids) => {
asked.push(ids);
});
const vault = makeDir();
const data = makeDir();
over(data, vault);
write(vault, ".astrolabe/settings.json", JSON.stringify({ fonts: { ui: "lora" } }));
await mirrorConfig();
assert.deepEqual(asked, [["lora"]]);
// A pass that imports nothing warms nothing.
await mirrorConfig();
assert.equal(asked.length, 1);
setFontWarmer(null);
});
test("the travel report: one item per travelling thing, with what the vault holds", async () => {
setFontWarmer(async () => {});
const vault = makeDir();
const data = makeDir();
over(data, vault);
write(data, "settings.json", "{}");
write(data, "fonts/custom/index.json", INDEX);
write(data, "fonts/custom/prose-face.woff2", "wOF2");
write(vault, ".astrolabe/prefs.json", JSON.stringify({ version: 1, keys: { "astrolabe.theme": { v: "paper", t: 1 } } }));
await mirrorConfig();
const status = await travelStatus();
assert.deepEqual(
status.items.map((i) => i.id),
["settings", "designs", "customCss", "layouts", "books", "annotations", "fonts", "prefs"],
);
const by = Object.fromEntries(status.items.map((i) => [i.id, i]));
assert.deepEqual(by.settings, { id: "settings", inData: true, inVault: true, count: null });
assert.deepEqual(by.designs, { id: "designs", inData: false, inVault: false, count: null });
assert.deepEqual(by.fonts, { id: "fonts", inData: true, inVault: true, count: 1 });
assert.deepEqual(by.prefs, { id: "prefs", inData: true, inVault: true, count: 1 });
assert.ok(status.lastPass !== null && status.lastPass.toVault.includes("settings.json"));
assert.ok(status.reconciled >= 3);
});