tests/clip.test.ts
Rama main · réplica de lectura del repositorio original.
// The clipper's writes (server/clip.ts) against a throwaway vault: a page
// becomes a note under Clips/ that never overwrites, a thought goes under
// `## Captured` today, the token lives in the data directory at 0600 and a
// rotation retires the old one. The HTTP door is exercised by the browser
// harness; this is the write half, which is what the vault keeps.
import assert from "node:assert/strict";
import { after, before, describe, it } from "node:test";
import { existsSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
import { captureLine, clipToken, dailyNotePathToday, ensureClipToken, performClip, rotateClipToken } from "../server/clip.ts";
import { initIndexer } from "../server/indexer.ts";
import { initSite } from "../server/site.ts";
import { initVault, VaultError } from "../server/vault.ts";
import { makeDir, makeVault, removeVault } from "./helpers/vault.ts";
const data = makeDir();
const root = makeVault({
"Inbox.md": "# Inbox\n\nThings.\n",
"Clips/Taken.md": "---\nclipped: 2026-01-01\n---\n\n# Taken\n",
});
before(async () => {
initSite({ ASTROLABE_DATA: data });
initVault(root);
await initIndexer();
});
after(() => {
removeVault(root);
removeVault(data);
});
describe("performClip", () => {
it("files a page under Clips/ with its source, converted", async () => {
const out = await performClip({
url: "https://ex.org/a/b?c=1",
title: " A / Title ",
html: "<article><h1>A / Title</h1><p>Hello <a href='../x'>there</a>.</p></article>",
selection: null,
text: null,
});
assert.equal(out.kind, "clipped");
assert.equal(out.path, "Clips/A Title.md");
const text = readFileSync(path.join(root, out.path), "utf8");
assert.match(text, /^---\nsource: "https:\/\/ex\.org\/a\/b\?c=1"\nclipped: \d{4}-\d{2}-\d{2}\n---\n\n# A \/ Title\n\nHello \[there\]\(https:\/\/ex\.org\/x\)\.\n$/);
});
it("never overwrites: the same title lands as (2), (3)…", async () => {
const a = await performClip({ url: "https://ex.org/t", title: "Taken", html: null, selection: null, text: null });
assert.equal(a.path, "Clips/Taken (2).md");
const b = await performClip({ url: "https://ex.org/t2", title: "Taken", html: null, selection: null, text: "Some words https://ex.org/t2 after" });
assert.equal(b.path, "Clips/Taken (3).md");
assert.match(readFileSync(path.join(root, b.path), "utf8"), /# Taken\n\nSome words after\n$/);
assert.equal(readFileSync(path.join(root, "Clips/Taken.md"), "utf8"), "---\nclipped: 2026-01-01\n---\n\n# Taken\n");
});
it("takes the title from the page, then the host; refuses what is not a web address", async () => {
const titled = await performClip({ url: "https://ex.org/p", title: null, html: "<html><head><title>From <the> page</title></head><body><p>x</p></body></html>", selection: null, text: null });
assert.equal(titled.path, "Clips/From the page.md");
const host = await performClip({ url: "https://host.example/deep", title: "", html: null, selection: "picked words", text: null });
assert.equal(host.path, "Clips/host.example.md");
assert.match(readFileSync(path.join(root, host.path), "utf8"), /picked words/);
await assert.rejects(
performClip({ url: "javascript:alert(1)", title: "x", html: null, selection: null, text: null }),
(err: unknown) => err instanceof VaultError && err.code === "clipBadUrl",
);
});
it("a thought with no address is captured into today's note", async () => {
const out = await performClip({ url: null, title: null, html: null, selection: null, text: "just a thought" });
assert.equal(out.kind, "captured");
assert.equal(out.path, dailyNotePathToday());
const text = readFileSync(path.join(root, out.path), "utf8");
assert.match(text, /^## Captured\n\n- \d\d:\d\d just a thought\n$/);
// A second one joins the section.
await performClip({ url: null, title: null, html: null, selection: null, text: "and another" });
assert.match(readFileSync(path.join(root, out.path), "utf8"), /- \d\d:\d\d just a thought\n- \d\d:\d\d and another\n$/);
await assert.rejects(
performClip({ url: null, title: null, html: null, selection: null, text: " " }),
(err: unknown) => err instanceof VaultError && err.code === "captureEmpty",
);
});
});
describe("captureLine", () => {
it("appends into a named note with the caller's clock, and creates a missing one", async () => {
await captureLine("Inbox.md", "into the inbox", "08:30");
assert.equal(readFileSync(path.join(root, "Inbox.md"), "utf8"), "# Inbox\n\nThings.\n\n## Captured\n\n- 08:30 into the inbox\n");
const made = await captureLine("New/Place.md", "first", "09:00");
assert.equal(made, "New/Place.md");
assert.equal(readFileSync(path.join(root, "New/Place.md"), "utf8"), "## Captured\n\n- 09:00 first\n");
});
it("lands every one of a burst of captures and clips: the writes are serialised", async () => {
// Six sheets at once into one note. Without the queue each read the same
// file, each appended to what it read, and the last write won — five
// lines gone with a 200 for each of them.
await Promise.all([1, 2, 3, 4, 5, 6].map((n) => captureLine("Burst.md", `line ${n}`, "10:00")));
const lines = readFileSync(path.join(root, "Burst.md"), "utf8").split("\n").filter((l) => l.startsWith("- 10:00 line "));
assert.equal(lines.length, 6);
// Four clips of one title at once: four notes, not one overwritten thrice.
const outs = await Promise.all(
[1, 2, 3, 4].map((n) => performClip({ url: "https://ex.org/burst", title: "Burst clip", html: `<p>${n}</p>`, selection: null, text: null })),
);
assert.deepEqual(new Set(outs.map((o) => o.path)).size, 4);
});
});
describe("the token", () => {
it("is made on first ask, kept at 0600 in the data directory, and retired by a rotation", () => {
assert.equal(clipToken(), null);
const first = ensureClipToken();
assert.match(first, /^[a-f0-9]{48}$/);
assert.equal(ensureClipToken(), first);
const file = path.join(data, "clip-token");
assert.ok(existsSync(file));
if (process.platform !== "win32") assert.equal(statSync(file).mode & 0o777, 0o600);
const next = rotateClipToken();
assert.notEqual(next, first);
assert.equal(clipToken(), next);
});
});