Consume commons' nav-golden fixture class byte-exact from portfolio-optimiser-commons @ b641741 (nav-golden-hierarchy positive + nav-golden-escape boundary) into test/nav-golden-corpus/ and wire a thin gate scripts/check-nav-golden.mjs (+ .test.mjs, 10 tests). Scope (operator decision, thin): the catalog is the convention owner and holds no consumer (null konsument), and the shared retrieval skill is deferred (spec.md §10 Stage 3). So the gate does NOT run a read-context navigator and does NOT assert the goldens byte-exact. It asserts only what the catalog owns: each fixture bundle/ is a conformant OKF bundle under the existing §3 checkBundle (every concept typed + root okf_version), and the committed expected-read-context.md golden is present and non-empty. Byte-exact navigator conformance stays a consumer concern (okr STEG 4 / Stage 3 skill). Fixtures pinned byte-exact (.gitattributes -text); manifest carries source repo+SHA per the vendoring-provenance rule. Naming: the stale "§5 pts 1-5" read-context label maps to the settled method-spec §3 Step 1 (commons 9801d35); no open §5 dependency. commons confirmed b641741 complete + goldens derived from §3 Step 1 (via coord). Verified: check-nav-golden 10/10; gate CLI exit 0 (2 fixtures PASS); full suite 73/73 across the six test files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
194 lines
7.5 KiB
JavaScript
194 lines
7.5 KiB
JavaScript
// Tests for the STEG 0 nav-golden corpus gate (check-nav-golden.mjs).
|
|
// Style mirrors check-okf-parity.test.mjs: node:test, temp fixtures, a subprocess for
|
|
// the CLI exit contract, zero npm deps. Two layers:
|
|
// 1. Over the COMMITTED corpus — both consumed fixtures conform + their goldens are present.
|
|
// 2. Runtime-materialized meta-tests that drive each FAIL axis (untyped concept, missing
|
|
// okf_version, absent bundle/, absent golden, empty golden) + the orphan-dir report.
|
|
// SCOPE mirror: this gate is THIN (conformance + golden presence). It does NOT run a
|
|
// navigator, so there is no byte-exact golden assertion here — that is a consumer concern.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { checkFixture, runCorpus, GOLDEN_FILE } from './check-nav-golden.mjs';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const RUNNER = join(HERE, 'check-nav-golden.mjs');
|
|
const CORPUS = join(HERE, '..', 'test', 'nav-golden-corpus');
|
|
|
|
function tmpRoot() {
|
|
return mkdtempSync(join(tmpdir(), 'nav-golden-'));
|
|
}
|
|
|
|
// Materialize a minimal fixture directory: <base>/<name>/{bundle/{index.md,concept.md},golden}.
|
|
// Options flip each FAIL axis independently.
|
|
function makeFixture(base, name, opts = {}) {
|
|
const {
|
|
withBundle = true,
|
|
okfVersion = true,
|
|
conceptTyped = true,
|
|
golden = 'read-context body\n',
|
|
} = opts;
|
|
const fdir = join(base, name);
|
|
mkdirSync(fdir, { recursive: true });
|
|
if (withBundle) {
|
|
const bundle = join(fdir, 'bundle');
|
|
mkdirSync(bundle, { recursive: true });
|
|
writeFileSync(
|
|
join(bundle, 'index.md'),
|
|
okfVersion ? '---\ntype: index\nokf_version: 0.1\n---\n# root\n' : '---\ntype: index\n---\n# root\n',
|
|
);
|
|
writeFileSync(
|
|
join(bundle, 'concept.md'),
|
|
conceptTyped ? '---\ntype: project\ntitle: C\n---\n# c\n' : '---\ntitle: C\n---\n# c\n',
|
|
);
|
|
}
|
|
if (golden !== null) writeFileSync(join(fdir, GOLDEN_FILE), golden);
|
|
return fdir;
|
|
}
|
|
|
|
// --- Layer 1: the committed corpus (the acceptance binding for the consumed fixtures) ---
|
|
test('committed corpus: both nav-golden fixtures conform and their goldens are present', () => {
|
|
const { results, failed, orphans, exitCode } = runCorpus(CORPUS);
|
|
assert.equal(exitCode, 0, `committed corpus must be green; failed: ${JSON.stringify(failed.map((f) => f.fixture))}`);
|
|
assert.deepEqual(failed, []);
|
|
assert.deepEqual(orphans, [], `no unmanifested corpus dirs; orphans: ${orphans.join(', ')}`);
|
|
const byName = Object.fromEntries(results.map((r) => [r.fixture, r]));
|
|
for (const name of ['nav-golden-hierarchy', 'nav-golden-escape']) {
|
|
const r = byName[name];
|
|
assert.ok(r, `${name} must be in the manifest`);
|
|
assert.equal(r.status, 'PASS', `${name} must PASS`);
|
|
assert.equal(r.okf.okfVersion, '0.1', `${name} bundle root must echo okf_version 0.1`);
|
|
assert.equal(r.goldenPresent, true);
|
|
assert.equal(r.goldenNonEmpty, true);
|
|
assert.deepEqual(r.problems, []);
|
|
}
|
|
});
|
|
|
|
test('CLI over committed corpus: exit 0 + "OK: nav-golden" line', () => {
|
|
const stdout = execFileSync('node', [RUNNER], { encoding: 'utf8' });
|
|
assert.match(stdout, /OK: nav-golden fixtures conform/);
|
|
});
|
|
|
|
// --- Layer 2: materialized FAIL axes (checkFixture is pure over a fixture dir) ---
|
|
test('PASS: a well-formed fixture (typed concept + okf_version + non-empty golden)', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'ok');
|
|
const r = checkFixture(fdir, 'ok');
|
|
assert.equal(r.status, 'PASS');
|
|
assert.deepEqual(r.problems, []);
|
|
assert.equal(r.okf.okfVersion, '0.1');
|
|
assert.equal(r.goldenNonEmpty, true);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('FAIL: a concept file without type', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'untyped', { conceptTyped: false });
|
|
const r = checkFixture(fdir, 'untyped');
|
|
assert.equal(r.status, 'FAIL');
|
|
assert.ok(r.problems.some((p) => /without type/.test(p)), `problems: ${r.problems.join('; ')}`);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('FAIL: root index.md without okf_version', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'nover', { okfVersion: false });
|
|
const r = checkFixture(fdir, 'nover');
|
|
assert.equal(r.status, 'FAIL');
|
|
assert.ok(r.problems.some((p) => /okf_version/.test(p)), `problems: ${r.problems.join('; ')}`);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('FAIL: missing bundle/ directory', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'nobundle', { withBundle: false });
|
|
const r = checkFixture(fdir, 'nobundle');
|
|
assert.equal(r.status, 'FAIL');
|
|
assert.ok(r.problems.some((p) => /bundle\//.test(p)), `problems: ${r.problems.join('; ')}`);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('FAIL: the committed golden is absent', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'nogolden', { golden: null });
|
|
const r = checkFixture(fdir, 'nogolden');
|
|
assert.equal(r.status, 'FAIL');
|
|
assert.equal(r.goldenPresent, false);
|
|
assert.ok(r.problems.some((p) => new RegExp(GOLDEN_FILE).test(p)), `problems: ${r.problems.join('; ')}`);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('FAIL: the golden is present but empty (whitespace only)', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
const fdir = makeFixture(base, 'emptygolden', { golden: ' \n\n' });
|
|
const r = checkFixture(fdir, 'emptygolden');
|
|
assert.equal(r.status, 'FAIL');
|
|
assert.equal(r.goldenPresent, true);
|
|
assert.equal(r.goldenNonEmpty, false);
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// --- Layer 2b: runCorpus over a materialized corpus (manifest + orphan report + exit code) ---
|
|
test('runCorpus: a FAIL fixture drives exit 1; an unmanifested dir is reported as an orphan', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
makeFixture(base, 'good');
|
|
makeFixture(base, 'bad', { conceptTyped: false });
|
|
makeFixture(base, 'stowaway'); // on disk but NOT in the manifest -> orphan
|
|
writeFileSync(
|
|
join(base, 'manifest.json'),
|
|
JSON.stringify({
|
|
fixtures: {
|
|
good: { axis: 'a' },
|
|
bad: { axis: 'b' },
|
|
},
|
|
}),
|
|
);
|
|
const { results, orphans, failed, exitCode } = runCorpus(base);
|
|
assert.equal(exitCode, 1, 'a FAIL fixture must make the corpus red');
|
|
assert.deepEqual(failed.map((f) => f.fixture).sort(), ['bad']);
|
|
assert.deepEqual(orphans, ['stowaway']);
|
|
const good = results.find((r) => r.fixture === 'good');
|
|
assert.equal(good.status, 'PASS');
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('runCorpus: a manifested fixture absent on disk is reported MISSING (exit 1)', () => {
|
|
const base = tmpRoot();
|
|
try {
|
|
makeFixture(base, 'present');
|
|
writeFileSync(
|
|
join(base, 'manifest.json'),
|
|
JSON.stringify({ fixtures: { present: { axis: 'a' }, ghost: { axis: 'b' } } }),
|
|
);
|
|
const { failed, exitCode } = runCorpus(base);
|
|
assert.equal(exitCode, 1);
|
|
assert.ok(failed.some((f) => f.fixture === 'ghost' && f.status === 'MISSING'));
|
|
} finally {
|
|
rmSync(base, { recursive: true, force: true });
|
|
}
|
|
});
|