1a88ab3199
The v0.8 milestone: installable on any Debian-based server via docker (Gitea registry image, honest sandbox degrade), deb (amd64+arm64, advisory postinst), and arch-aware binary — with release consistency enforced by pre-commit hooks + Gitea Actions (3-runner fleet on CT 113, machine-made releases with the full 8-asset matrix + registry image), the CLI carrying the full suite (guide, channel-aware doctor), and the admin runbook (deploy/README.md, evidence-backed recipes). ---ci--- phase: 6 milestone: v0.8 status: complete requirements: covered: [REQ-8-001, REQ-8-002, REQ-8-003, REQ-8-004, REQ-8-005] partial: [] ---/ci---
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
// guide ↔ runbook parity (v0.8 P5, G-210): the deploy guide's channel
|
|
// sections are the canonical text; `nextcraft guide` renders the same
|
|
// recipes. This test pins the AGREEMENT on every load-bearing marker —
|
|
// divergence is a test failure, never silent doc drift.
|
|
//
|
|
// It reads deploy/README.md (the runbook) and the rendered guide output,
|
|
// then asserts the markers that make the recipes work appear in BOTH:
|
|
// commands, flags, paths, ports, and the honest-limit notices. When
|
|
// either side changes a recipe, this test forces the other to follow.
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { run } from "../src/index.ts";
|
|
import { testCtx } from "./helpers.ts";
|
|
|
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
// Markdown reflows (line-wraps split phrases) — parity compares on
|
|
// whitespace-normalized text so a wrap never fakes divergence.
|
|
const runbook = readFileSync(join(repoRoot, "deploy", "README.md"), "utf8")
|
|
.replace(/\s+/g, " ");
|
|
|
|
async function renderGuide(topic: string): Promise<string> {
|
|
const ctx = testCtx();
|
|
assert.equal(await run(["guide", topic], ctx), 0);
|
|
return ctx.out().replace(/\s+/g, " ");
|
|
}
|
|
|
|
// Markers that MUST appear in both the runbook and the guide per channel
|
|
// (commands, paths, ports — the things an admin copies).
|
|
const dockerMarkers = [
|
|
"docker run -d",
|
|
"-p 8420:8420",
|
|
"/home/nextcraft/.nextcraft",
|
|
"git.coreci.dev/coreci/nextcraft",
|
|
"seccomp=unconfined",
|
|
"SANDBOX FABRIC DEGRADED",
|
|
"docker pull",
|
|
];
|
|
const debMarkers = [
|
|
"dpkg -i",
|
|
"nodejs python3 git util-linux",
|
|
"deploy --service",
|
|
];
|
|
const binaryMarkers = [
|
|
"scripts/install.sh",
|
|
"aarch64",
|
|
"deploy --service",
|
|
];
|
|
const upgradeMarkers = [
|
|
"docker pull",
|
|
"dpkg -i",
|
|
"idempotent",
|
|
];
|
|
|
|
test("parity: guide docker topic agrees with the runbook's docker channel", async () => {
|
|
const guide = await renderGuide("docker");
|
|
for (const m of dockerMarkers) {
|
|
assert.ok(runbook.includes(m), `runbook carries: ${m}`);
|
|
assert.ok(guide.includes(m), `guide docker carries: ${m}`);
|
|
}
|
|
});
|
|
|
|
test("parity: guide deb topic agrees with the runbook's deb channel", async () => {
|
|
const guide = await renderGuide("deb");
|
|
for (const m of debMarkers) {
|
|
assert.ok(runbook.includes(m), `runbook carries: ${m}`);
|
|
assert.ok(guide.includes(m), `guide deb carries: ${m}`);
|
|
}
|
|
});
|
|
|
|
test("parity: guide binary topic agrees with the runbook's binary channel", async () => {
|
|
const guide = await renderGuide("binary");
|
|
for (const m of binaryMarkers) {
|
|
assert.ok(runbook.includes(m), `runbook carries: ${m}`);
|
|
assert.ok(guide.includes(m), `guide binary carries: ${m}`);
|
|
}
|
|
});
|
|
|
|
test("parity: guide upgrade topic agrees with the runbook's upgrade matrix", async () => {
|
|
const guide = await renderGuide("upgrade");
|
|
for (const m of upgradeMarkers) {
|
|
assert.ok(runbook.includes(m), `runbook carries: ${m}`);
|
|
assert.ok(guide.includes(m), `guide upgrade carries: ${m}`);
|
|
}
|
|
});
|
|
|
|
test("parity: every channel named in the runbook has a guide topic", async () => {
|
|
// The runbook's Pick-your-channel table names the three channels; the
|
|
// guide must carry exactly those recipes.
|
|
for (const channel of ["docker", "deb", "binary"]) {
|
|
assert.ok(runbook.includes(`**${channel}**`), `runbook names channel: ${channel}`);
|
|
const ctx = testCtx();
|
|
assert.equal(await run(["guide", channel], ctx), 0, `guide topic exists: ${channel}`);
|
|
}
|
|
}); |