Files
endorphin-tabularium-docs/apps/docs/tests/docs.test.ts
codex-bot d57e9b77d7
Verify database-backed documents / verify (pull_request) Failing after 6m6s
feat: make the site database authoritative for documents and catalog
2026-10-30 16:23:54 +08:00

100 lines
5.1 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { allDocuments, documentHistory, importDocuments, readDocument, saveDocument,
type DocInput, type DocumentExport } from "../src/lib/docs";
const input: DocInput = { slug: "endor/example", sourceSlug: "00-example", title: "Example",
category: "Endor", collection: "Endor 文档", group: "讨论", description: "Example description",
draft: true, featured: false, content: "# Example\n\nOriginal body" };
function isolated(run: (directory: string) => void) {
const directory = mkdtempSync(join(tmpdir(), "tabularium-docs-"));
const previous = process.env.AUTH_CONFIG_FILE;
process.env.AUTH_CONFIG_FILE = join(directory, "config.json");
try { run(directory); } finally {
if (previous === undefined) delete process.env.AUTH_CONFIG_FILE;
else process.env.AUTH_CONFIG_FILE = previous;
rmSync(directory, { recursive: true, force: true });
}
}
function bundle(documents: DocInput[]): DocumentExport {
return { format: "tabularium-documents-v1", documents: documents.map((doc) => ({ ...doc, revision: 1 })) };
}
test("writes and metadata persist across independent connections; no repository seed", () => isolated(() => {
assert.deepEqual(allDocuments(), []);
const saved = saveDocument(input, 0);
assert.deepEqual(readDocument(input.slug), saved);
assert.deepEqual(readDocument(input.sourceSlug), saved);
const changed = saveDocument({ ...input, content: "# Revised", collection: "新合集", group: "新目录" }, 1);
assert.equal(changed.revision, 2);
assert.equal(allDocuments()[0].collection, "新合集");
assert.deepEqual(documentHistory(input.sourceSlug).map((doc) => doc.content), [input.content, "# Revised"]);
}));
test("optimistic concurrency prevents lost updates and requires explicit revision", () => isolated(() => {
saveDocument(input, 0);
assert.throws(() => saveDocument({ ...input, content: "Lost" }, 0), /conflict/);
assert.throws(() => saveDocument(input, NaN), /expected revision/);
assert.equal(readDocument(input.slug)?.content, input.content);
assert.equal(documentHistory(input.sourceSlug).length, 1);
}));
test("renames preserve aliases and alias collisions roll back document and revision", () => isolated(() => {
saveDocument(input, 0);
saveDocument({ ...input, slug: "endor/renamed" }, 1);
assert.equal(readDocument(input.slug)?.slug, "endor/renamed");
assert.throws(() => saveDocument({ ...input, sourceSlug: "another", slug: input.slug }, 0), /collision/);
assert.equal(allDocuments().length, 1);
assert.equal(readDocument("another"), null);
assert.equal(documentHistory(input.sourceSlug).length, 2);
}));
test("bootstrap is atomic and repeat imports never replace DB edits", () => isolated(() => {
const snapshot = bundle([input]);
assert.equal(importDocuments(snapshot), 1);
saveDocument({ ...input, content: "Written directly to DB" }, 1);
assert.equal(importDocuments(snapshot), 0);
assert.equal(importDocuments(bundle([{ ...input, content: "Stale release" }]), true), 0);
assert.throws(() => importDocuments(bundle([{ ...input, content: "Other" }])), /empty/);
assert.equal(readDocument(input.slug)?.content, "Written directly to DB");
}));
test("colliding bootstrap leaves no partial rows and can be retried", () => isolated(() => {
assert.throws(() => importDocuments(bundle([input, { ...input, sourceSlug: "other" }])));
assert.deepEqual(allDocuments(), []);
assert.equal(importDocuments(bundle([input])), 1);
}));
test("migration preserves unrelated authentication and research tables", () => isolated((directory) => {
const db = new DatabaseSync(join(directory, "login-state.sqlite"));
db.exec("CREATE TABLE login_state(value TEXT); INSERT INTO login_state VALUES ('preserved'); CREATE TABLE research_datasets(value TEXT); INSERT INTO research_datasets VALUES ('research');");
db.close();
importDocuments(bundle([input]));
const reopened = new DatabaseSync(join(directory, "login-state.sqlite"));
assert.equal(reopened.prepare("SELECT value FROM login_state").get()?.value, "preserved");
assert.equal(reopened.prepare("SELECT value FROM research_datasets").get()?.value, "research");
assert.deepEqual(reopened.prepare("PRAGMA foreign_key_check").all(), []);
reopened.close();
}));
test("invalid content cannot enter the database", () => isolated(() => {
for (const invalid of [ { ...input, slug: "../private" }, { ...input, slug: "endor/01-example" },
{ ...input, content: "![original](/figures/raw.png)" }, { ...input, category: "" } ])
assert.throws(() => saveDocument(invalid, 0));
assert.deepEqual(allDocuments(), []);
assert.equal(readDocument("../private"), null);
}));
test("missing database configuration fails closed", () => {
const previous = process.env.AUTH_CONFIG_FILE;
delete process.env.AUTH_CONFIG_FILE;
try { assert.throws(() => allDocuments(), /not configured/); }
finally { if (previous !== undefined) process.env.AUTH_CONFIG_FILE = previous; }
});