tests/graphperf.test.ts
Rama main · réplica de lectura del repositorio original.
// The reverse link index and the graph revision — the two halves of the perf
// audit's "graph" section that live on the server.
//
// Neither is a feature: both replace a walk over the whole vault with a lookup,
// and the ONLY way either can go wrong is by answering with LESS than the walk
// did. So every case below is a shape that used to be found the slow way and
// has to keep being found: a basename link, a path-form link, a `./` prefix, an
// explicit `.md`, an anchor tail, a pipe alias, a frontmatter alias, a `\cite`
// and a `\ref`. Plus the tie-break the superset must NOT smuggle a false
// positive through: two notes sharing a basename, where only the shortest path
// actually owns the name.
import assert from "node:assert/strict";
import { rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import { after, before, describe, it } from "node:test";
import {
backlinks,
graphRevision,
indexFile,
initIndexer,
notesLinkingTo,
} from "../server/indexer.ts";
import { graphBody } from "../server/graphCache.ts";
import { initSite } from "../server/site.ts";
import { initVault } from "../server/vault.ts";
import { makeDir, makeVault, note, removeVault } from "./helpers/vault.ts";
const data = makeDir();
const root = makeVault({
// The target every link below is aimed at.
"Notes/Machine Learning.md": note(
{ publish: "true", aliases: "[ML, تعلم الآلة]", citekey: "ml2024" },
"# Machine Learning\n",
),
// A DECOY with the same basename at a deeper path: `[[Machine Learning]]`
// resolves to the shorter path, so this note must never collect the
// basename's backlinks even though the reverse index files them under the
// same key.
"Archive/Old/Machine Learning.md": "# Machine Learning (old)\n",
"byBasename.md": note({ publish: "true" }, "Everything begins with [[Machine Learning]] and its ideas.\n"),
"byPath.md": "The canonical page is [[Notes/Machine Learning]] over there.\n",
"byDotPath.md": "Written by a folder-relative hand: [[./Notes/Machine Learning]] today.\n",
"byExtension.md": "An explicit file reference, [[Notes/Machine Learning.md]], survives.\n",
"byAnchor.md": "Straight to a section: [[Machine Learning#Overview]] and onward.\n",
"byPipe.md": "Reading a piece on [[Machine Learning|the subject]] this evening.\n",
"byAlias.md": "Today I read about [[ML]] and could not stop thinking.\n",
"byArabicAlias.md": "قرأت اليوم عن [[تعلم الآلة]] ولم أتوقف عن التفكير.\n",
// A note nobody should confuse with the target.
"unrelated.md": "This one points at [[byPipe]] and nothing else.\n",
// Not published, so the visitor scope must lose it.
"Private.md": "A private page linking [[Machine Learning]] all the same.\n",
// LaTeX: a `\cite` of a note's citekey, and a `\ref` of a label another
// document defines — the two keys that reach a note without a wikilink.
"Paper.tex":
"\\documentclass{article}\n\\begin{document}\nAs shown in \\cite{ml2024} the method holds, following \\ref{sec:method} throughout.\n\\end{document}\n",
"Theory.tex":
"\\documentclass{article}\n\\begin{document}\n\\section{Method}\\label{sec:method}\nThe method is stated here in full.\n\\end{document}\n",
});
before(async () => {
initSite({ ASTROLABE_DATA: data });
initVault(root);
await initIndexer();
});
after(() => {
removeVault(root);
removeVault(data);
});
const sources = (target: string, publishedOnly = false): string[] =>
[...new Set(backlinks(target, publishedOnly, null).map((h) => h.path))].sort();
describe("the reverse link index finds every shape the walk used to find", () => {
const target = "Notes/Machine Learning.md";
it("basename, path form, ./ prefix, explicit .md, anchor tail, pipe and alias", () => {
assert.deepEqual(sources(target), [
"Paper.tex",
"Private.md",
"byAlias.md",
"byAnchor.md",
"byArabicAlias.md",
"byBasename.md",
"byDotPath.md",
"byExtension.md",
"byPath.md",
"byPipe.md",
]);
});
it("keeps the backlink card's context line", () => {
const hit = backlinks(target, false, null).find((h) => h.path === "byAlias.md");
assert.ok(hit, "the aliased link should still be a backlink");
assert.match(hit.context, /\[\[ML\]\]/);
assert.match(hit.context, /Today I read about/);
});
it("a `\\cite` of the note's citekey is a backlink", () => {
const hit = backlinks(target, false, null).find((h) => h.path === "Paper.tex");
assert.ok(hit, "the citing paper should be a backlink");
assert.match(hit.context, /method holds/);
});
it("a `\\ref` of a label another document defines is a backlink", () => {
// The label table is the second key a note answers to without ever being
// wikilinked, so the reverse index has to file xrefs beside links.
assert.deepEqual(sources("Theory.tex"), ["Paper.tex"]);
});
it("the DECOY sharing the basename collects none of them", () => {
// The reverse index files `[[Machine Learning]]` under one key and both
// notes answer to it; only resolveLink() knows which one wins, and it is
// still the one asked.
assert.deepEqual(sources("Archive/Old/Machine Learning.md"), []);
});
it("a visitor loses the unpublished sources and nothing else", () => {
assert.ok(!sources(target, true).includes("Private.md"));
assert.ok(sources(target, true).includes("byBasename.md"));
});
it("notesLinkingTo answers the same set, minus the cross-references", () => {
const linking = notesLinkingTo(target);
assert.ok(linking.includes("byBasename.md"));
assert.ok(linking.includes("byAlias.md"));
assert.ok(linking.includes("Private.md"));
assert.ok(!linking.includes("Paper.tex"), "a \\cite is not a wikilink");
assert.ok(!linking.includes("unrelated.md"));
});
it("a note with no incoming links answers empty, not everything", () => {
assert.deepEqual(sources("unrelated.md"), []);
});
});
describe("the reverse index is maintained, not rebuilt", () => {
const target = "Notes/Machine Learning.md";
const write = async (rel: string, body: string): Promise<void> => {
writeFileSync(path.join(root, rel), body);
await indexFile(rel);
};
it("a link added to an existing note appears", async () => {
await write("unrelated.md", "Now it mentions [[ML]] after all.\n");
assert.ok(sources(target).includes("unrelated.md"));
});
it("a link removed from an existing note disappears", async () => {
await write("unrelated.md", "This one points at [[byPipe]] and nothing else.\n");
assert.ok(!sources(target).includes("unrelated.md"));
});
it("a link retargeted moves from one note's backlinks to the other's", async () => {
await write("byBasename.md", note({ publish: "true" }, "Everything begins with [[byPipe]] instead.\n"));
assert.ok(!sources(target).includes("byBasename.md"));
assert.ok(sources("byPipe.md").includes("byBasename.md"));
await write("byBasename.md", note({ publish: "true" }, "Everything begins with [[Machine Learning]] and its ideas.\n"));
assert.ok(sources(target).includes("byBasename.md"));
});
});
describe("the graph revision moves only when the graph would", () => {
const write = async (rel: string, body: string): Promise<void> => {
writeFileSync(path.join(root, rel), body);
await indexFile(rel);
};
it("holds still through a prose-only edit — the memo's whole reason to exist", async () => {
await write("byPath.md", "The canonical page is [[Notes/Machine Learning]] over there.\n");
const before = graphRevision();
await write(
"byPath.md",
"The canonical page is [[Notes/Machine Learning]] over there, and here is a new paragraph.\n",
);
assert.equal(graphRevision(), before, "rewriting prose must not cost a graph rebuild");
});
it("moves when a link is added", async () => {
const before = graphRevision();
await write("byPath.md", "The canonical page is [[Notes/Machine Learning]] and also [[byPipe]].\n");
assert.notEqual(graphRevision(), before);
});
it("moves when a tag is added", async () => {
const before = graphRevision();
await write("byPath.md", "A #freshtag and the canonical [[Notes/Machine Learning]].\n");
assert.notEqual(graphRevision(), before);
});
it("moves when the publish flag flips", async () => {
const before = graphRevision();
await write("byPath.md", note({ publish: "true" }, "The canonical [[Notes/Machine Learning]].\n"));
assert.notEqual(graphRevision(), before);
});
it("moves when an alias appears — it is how other notes' links land here", async () => {
const before = graphRevision();
await write(
"byPath.md",
note({ publish: "true", aliases: "[Canon]" }, "The canonical [[Notes/Machine Learning]].\n"),
);
assert.notEqual(graphRevision(), before);
});
it("holds still for a path the index never had", async () => {
const before = graphRevision();
await indexFile("Notes/gone-that-never-existed.md"); // a stat miss on an unknown path
assert.equal(graphRevision(), before, "forgetting a note we never had is not a change");
});
it("moves when a note is deleted, and its backlinks go with it", async () => {
const before = graphRevision();
rmSync(path.join(root, "byDotPath.md"));
await indexFile("byDotPath.md");
assert.notEqual(graphRevision(), before);
assert.ok(!sources("Notes/Machine Learning.md").includes("byDotPath.md"));
});
});
describe("the /api/graph memo is validated, not thrown away", () => {
const write = async (rel: string, body: string): Promise<void> => {
writeFileSync(path.join(root, rel), body);
await indexFile(rel);
};
it("survives a prose-only save — the storm this exists for", async () => {
const first = graphBody(false, null);
assert.equal(graphBody(false, null), first, "two reads in a row must share one build");
await write("byAnchor.md", "Straight to a section: [[Machine Learning#Overview]] and onward, at length.\n");
assert.equal(graphBody(false, null), first, "a body edit that moved no link must not rebuild");
});
it("is dropped the moment a link changes", async () => {
const before = graphBody(false, null);
await write("byAnchor.md", "Straight to a section: [[Machine Learning#Overview]] and also [[byPipe]].\n");
assert.notEqual(graphBody(false, null), before);
});
it("keeps the visitor and admin answers apart", () => {
assert.notEqual(graphBody(true, null), graphBody(false, null));
});
});