Ren value-preserving label-relabel av de to norske header-labelene til engelsk på 51 ref-filer (22 bærer begge). Ny testet ren primitiv relabelHeaderDialect() (header-blokk-scoped, kollisjons-/multiforekomst-guard) + manifest-drevet driver relabel-dialect.mjs (frosset 51-fil-manifest, hard per-fil-invariant, idempotent, isMain-guard). **Dato:** bevisst UTE (body-template-felle → Enhet 2). Premiss-korreksjon i roadmap R22: tredje Dato-dialekt (16), 0 bold-duplikater (ikke 4), 1 datoløs (ikke 5), category-none = vindus-artefakt. test-relabel-dialect 11/11; diff +73/-73 0 linjer utover label; suite 782/782 exit 0.
117 lines
5.8 KiB
JavaScript
117 lines
5.8 KiB
JavaScript
// tests/kb-update/test-relabel-dialect.test.mjs
|
|
// TDD for Enhet 1 (decision-b, path A, ⊥ R7) — pure value-preserving dialect relabel of the
|
|
// Norwegian header labels to their English equivalents on the 51 ref files that carry them:
|
|
// **Sist oppdatert:** → **Last updated:** (31 files)
|
|
// **Kategori:** → **Category:** (42 files) [22 files carry both → 73 relabels]
|
|
//
|
|
// The applier (relabel-dialect.mjs) is a manifest-driven driver over the pure primitive
|
|
// relabelHeaderDialect(). The ONLY new production logic is that primitive + the frozen MANIFEST.
|
|
// This suite pins the primitive's safety contract and the manifest's structural well-formedness.
|
|
//
|
|
// Explicitly OUT of Enhet 1 (deferred to later units, per the corrected residual):
|
|
// - **Dato:** → **Last updated:** (16) — Dato also appears in BODY templates ([YYYY-MM-DD]),
|
|
// a blind replace would corrupt them; needs a header-scoped ISO-date guard (Unit 2).
|
|
// - Status backfill (27 none + 4 plain) and the ai-act dual-block dedup (Unit 3).
|
|
// - decision-trees.md date backfill (Unit 4).
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
relabelHeaderDialect,
|
|
LABEL_MAP,
|
|
MANIFEST,
|
|
} from '../../scripts/kb-update/relabel-dialect.mjs';
|
|
|
|
// ── primitive: value-preserving relabel ────────────────────────────────────
|
|
|
|
test('relabels **Sist oppdatert:** → **Last updated:**, value byte-identical', () => {
|
|
const input = '# T\n\n**Sist oppdatert:** 2026-02 (v1.0)\n**Status:** GA\n\n---\n\n## Body\n';
|
|
const { content, applied } = relabelHeaderDialect(input);
|
|
assert.equal(applied.length, 1);
|
|
assert.match(content, /\*\*Last updated:\*\* 2026-02 \(v1\.0\)/);
|
|
assert.doesNotMatch(content, /\*\*Sist oppdatert:\*\*/);
|
|
// Only the label token changed — reversing the sub reconstructs the original exactly.
|
|
assert.equal(content.replace('**Last updated:**', '**Sist oppdatert:**'), input);
|
|
});
|
|
|
|
test('relabels **Kategori:** → **Category:**, value byte-identical', () => {
|
|
const input = '# T\n\n**Kategori:** AI Security Engineering\n**Type:** reference\n\n## Body\n';
|
|
const { content, applied } = relabelHeaderDialect(input);
|
|
assert.equal(applied.length, 1);
|
|
assert.match(content, /\*\*Category:\*\* AI Security Engineering/);
|
|
assert.doesNotMatch(content, /\*\*Kategori:\*\*/);
|
|
assert.equal(content.replace('**Category:**', '**Kategori:**'), input);
|
|
});
|
|
|
|
test('applies BOTH relabels when both norsk labels are present', () => {
|
|
const input = '# T\n\n**Sist oppdatert:** 2026-06-19\n**Kategori:** X\n**Status:** GA\n\n---\n\n## Body\n';
|
|
const { content, applied } = relabelHeaderDialect(input);
|
|
assert.equal(applied.length, 2);
|
|
assert.match(content, /\*\*Last updated:\*\* 2026-06-19/);
|
|
assert.match(content, /\*\*Category:\*\* X/);
|
|
});
|
|
|
|
test('header-scoping: a norsk label in the BODY (past first --- / ##) is NOT touched', () => {
|
|
// The template-contamination trap: only the header block above the first thematic break
|
|
// or section heading is in scope.
|
|
const input =
|
|
'# T\n\n**Kategori:** Real Header Value\n\n---\n\n## Mal\n**Kategori:** [fyll inn]\n';
|
|
const { content, applied } = relabelHeaderDialect(input);
|
|
assert.equal(applied.length, 1);
|
|
assert.match(content, /\*\*Category:\*\* Real Header Value/);
|
|
// The body template line survives verbatim.
|
|
assert.match(content, /## Mal\n\*\*Kategori:\*\* \[fyll inn\]/);
|
|
});
|
|
|
|
test('no-op when no norsk label is present (English-dialect file)', () => {
|
|
const input = '# T\n\n**Last updated:** 2026-06\n**Category:** X\n**Status:** GA\n\n## Body\n';
|
|
const { content, applied } = relabelHeaderDialect(input);
|
|
assert.equal(applied.length, 0);
|
|
assert.equal(content, input); // byte-identical
|
|
});
|
|
|
|
test('idempotent: re-running on the output is a no-op', () => {
|
|
const input = '# T\n\n**Sist oppdatert:** 2026-02\n**Kategori:** X\n\n## Body\n';
|
|
const once = relabelHeaderDialect(input);
|
|
const twice = relabelHeaderDialect(once.content);
|
|
assert.equal(twice.applied.length, 0);
|
|
assert.equal(twice.content, once.content);
|
|
});
|
|
|
|
test('collision-guard: throws if the English target label already exists in the header', () => {
|
|
// A dual-header file (bold **Category:** + norsk **Kategori:**) would duplicate on relabel.
|
|
const input = '# T\n\n**Category:** X\n**Kategori:** X\n\n## Body\n';
|
|
assert.throws(() => relabelHeaderDialect(input), /already present/i);
|
|
});
|
|
|
|
test('multi-occurrence-guard: throws if a norsk label appears twice in the header block', () => {
|
|
const input = '# T\n\n**Kategori:** X\n**Kategori:** Y\n\n## Body\n';
|
|
assert.throws(() => relabelHeaderDialect(input), /appears/i);
|
|
});
|
|
|
|
test('LABEL_MAP is exactly the two norsk→english pairs of Enhet 1 (no Dato)', () => {
|
|
const froms = LABEL_MAP.map((p) => p[0]);
|
|
assert.deepEqual(froms.sort(), ['**Kategori:**', '**Sist oppdatert:**']);
|
|
// **Dato:** is intentionally excluded (body-template trap → deferred to Unit 2).
|
|
assert.ok(!froms.some((f) => /Dato/.test(f)));
|
|
});
|
|
|
|
// ── MANIFEST: structural well-formedness ───────────────────────────────────
|
|
|
|
test('MANIFEST: exactly 51 distinct, well-formed reference paths', () => {
|
|
assert.equal(MANIFEST.length, 51);
|
|
const set = new Set(MANIFEST);
|
|
assert.equal(set.size, 51, 'duplicate path in MANIFEST');
|
|
for (const p of MANIFEST) {
|
|
assert.match(p, /^skills\/ms-ai-[a-z]+\/references\/.+\.md$/, p);
|
|
}
|
|
});
|
|
|
|
test('MANIFEST: excludes advisor files already English-normalised by R21', () => {
|
|
// adr-template / poc-template etc. got their Status via R21 but were already English-dialect
|
|
// for Category/Last-updated → they must NOT be in this relabel manifest.
|
|
for (const p of MANIFEST) {
|
|
assert.ok(!p.endsWith('adr-template.md'));
|
|
assert.ok(!p.endsWith('poc-template.md'));
|
|
}
|
|
});
|