Ctrl K
ComunidadGuías…

tests/configMirror.test.ts

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

Archivo original ↗Preguntar a KiBot
// 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);
});