Files
msd-core/tests/plan-document.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

301 lines
9.7 KiB
JavaScript

'use strict';
/**
* Unit tests for plan-document.cjs
*
* Module: msd-core/bin/lib/plan-document.cjs
*
* Covers the `tracker-id` attribute (ADR-3646 Phase 1, #3970) added to the
* `<task>` element grammar, plus regression coverage proving the addition
* does not alter pre-existing task-parsing behaviour.
*
* Matrix rows referenced below are from
* .msd/phase/feat-3970-task-content-resolution-seam/50-test-matrix.md
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const { parsePlanDocument } = require('../msd-core/bin/lib/plan-document.cjs');
describe('plan-document: tracker-id attribute', () => {
test('row 1 — no tracker-id attribute yields trackerId: null', () => {
const doc = parsePlanDocument(`
<task type="auto">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].trackerId, null);
});
test('row 2 — tracker-id is read verbatim, never split', () => {
const doc = parsePlanDocument(`
<task type="auto" tracker-id="beads:MSD-42">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].trackerId, 'beads:MSD-42');
});
test('row 3 — tracker-id="" (empty string) normalises to null', () => {
const doc = parsePlanDocument(`
<task type="auto" tracker-id="">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].trackerId, null);
});
test('row 4 — checkpoint tasks never read tracker-id, even when present', () => {
const doc = parsePlanDocument(`
<task type="checkpoint:decision" tracker-id="beads:MSD-99">
<decision>Ship it</decision>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].kind, 'checkpoint');
assert.equal(doc.tasks[0].trackerId, null);
});
});
describe('plan-document: tdd attribute (#4273)', () => {
test('row 1 — tdd="true" is read verbatim', () => {
const doc = parsePlanDocument(`
<task type="auto" tdd="true">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].tdd, 'true');
});
test('row 2 — no tdd attribute yields tdd: null', () => {
const doc = parsePlanDocument(`
<task type="auto">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].tdd, null);
});
test('row 3 — tdd="" (empty string) normalises to null', () => {
const doc = parsePlanDocument(`
<task type="auto" tdd="">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].tdd, null);
});
test('row 4 — tdd="TRUE" and tdd="1" are read verbatim, never coerced to a boolean', () => {
const docUpper = parsePlanDocument(`
<task type="auto" tdd="TRUE">
<name>Do a thing</name>
</task>
`);
assert.equal(docUpper.tasks[0].tdd, 'TRUE');
assert.notEqual(docUpper.tasks[0].tdd, 'true');
const docNumeric = parsePlanDocument(`
<task type="auto" tdd="1">
<name>Do a thing</name>
</task>
`);
assert.equal(docNumeric.tasks[0].tdd, '1');
});
test('row 5 — checkpoint tasks never read tdd, even when present', () => {
const doc = parsePlanDocument(`
<task type="checkpoint:decision" tdd="true">
<decision>Ship it</decision>
</task>
`);
assert.equal(doc.tasks.length, 1);
assert.equal(doc.tasks[0].kind, 'checkpoint');
assert.equal(doc.tasks[0].tdd, null);
});
});
describe('plan-document: frontmatter type (#4273)', () => {
test('row 6 — frontmatter type: tdd is read verbatim onto doc.type', () => {
const doc = parsePlanDocument(`---
type: tdd
---
<task type="auto">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.type, 'tdd');
});
test('row 7 — frontmatter type: standard is read verbatim, not coerced to a boolean', () => {
const doc = parsePlanDocument(`---
type: standard
---
<task type="auto">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.type, 'standard');
});
test('row 8 — no frontmatter type yields doc.type: null', () => {
const doc = parsePlanDocument(`
<task type="auto">
<name>Do a thing</name>
</task>
`);
assert.equal(doc.type, null);
});
});
describe('plan-document: regression — legacy behaviour unchanged', () => {
test('legacy `## Task N` markdown fallback still parses with trackerId: null', () => {
const doc = parsePlanDocument(`
## Task 1: Do a thing
Some body text.
## Task 2: Do another thing
`);
assert.equal(doc.tasks.length, 2);
for (const t of doc.tasks) {
assert.equal(t.kind, 'auto');
assert.equal(t.type, null);
assert.equal(t.trackerId, null);
assert.deepEqual(t.plannedFiles, []);
assert.deepEqual(t.acceptanceCriteria, []);
assert.equal(t.done, null);
}
assert.equal(doc.tasks[0].name, 'Task 1: Do a thing');
assert.equal(doc.tasks[1].name, 'Task 2: Do another thing');
});
test('ordinary task with name/files/acceptance_criteria still parses correctly alongside trackerId', () => {
const doc = parsePlanDocument(`
<task type="auto" tracker-id="beads:MSD-7">
<name>Implement the seam</name>
<files>src/a.cts, src/b.cts</files>
<acceptance_criteria>
- criterion one
- criterion two
</acceptance_criteria>
<done>Merged.</done>
</task>
`);
assert.equal(doc.tasks.length, 1);
const t = doc.tasks[0];
assert.equal(t.kind, 'auto');
assert.equal(t.type, 'auto');
assert.equal(t.name, 'Implement the seam');
assert.deepEqual(t.plannedFiles, ['src/a.cts', 'src/b.cts']);
assert.deepEqual(t.acceptanceCriteria, ['criterion one', 'criterion two']);
assert.equal(t.done, 'Merged.');
assert.equal(t.trackerId, 'beads:MSD-7');
});
});
describe('plan-document: extractThreatRegisterIds (#4683)', () => {
const { extractThreatRegisterIds } = require('../msd-core/bin/lib/plan-document.cjs');
const register = (rows) => [
'<threat_model>',
'| Threat ID | Category | Component | Severity | Disposition | Mitigation |',
'|-----------|----------|-----------|----------|-------------|------------|',
...rows,
'</threat_model>',
].join('\n');
test('extracts first-cell IDs in document order', () => {
const ids = extractThreatRegisterIds(register([
'| T-47-01 | Tampering | c | high | mitigate | fix |',
'| T-47-02 | Repudiation | c | low | accept | rationale |',
'| T-47-19 | DoS | c | medium | mitigate | fix |',
]));
assert.deepEqual(ids, ['T-47-01', 'T-47-02', 'T-47-19']);
});
test('the reserved -SC supply-chain row never matches', () => {
const ids = extractThreatRegisterIds(register([
'| T-47-SC | Tampering | npm installs | high | mitigate | gate |',
'| T-47-01 | Tampering | c | high | mitigate | fix |',
]));
assert.deepEqual(ids, ['T-47-01']);
});
test('decimal phases match (T-4.1-05)', () => {
const ids = extractThreatRegisterIds(register(['| T-4.1-05 | Tampering | c | low | accept | r |']));
assert.deepEqual(ids, ['T-4.1-05']);
});
test('rows outside a threat_model block never count', () => {
const ids = extractThreatRegisterIds([
'# Plan',
'',
'See T-47-01 in SECURITY.md. | T-47-02 | not a register |',
'',
register(['| T-47-03 | Tampering | c | high | mitigate | fix |']),
].join('\n'));
assert.deepEqual(ids, ['T-47-03']);
});
test('a register quoted inside a backtick fence is prose, not a claim', () => {
const quoted = ['```markdown', register(['| T-47-01 | Tampering | c | high | mitigate | fix |']), '```'].join('\n');
const live = register(['| T-47-02 | Repudiation | c | low | accept | r |']);
assert.deepEqual(extractThreatRegisterIds(`${quoted}\n\n${live}`), ['T-47-02']);
});
test('a tilde fence is stripped the same way', () => {
const quoted = ['~~~', register(['| T-47-01 | Tampering | c | high | mitigate | fix |']), '~~~'].join('\n');
const live = register(['| T-47-02 | Repudiation | c | low | accept | r |']);
assert.deepEqual(extractThreatRegisterIds(`${quoted}\n\n${live}`), ['T-47-02']);
});
test('an unclosed fence suppresses everything after it', () => {
const doc = [register(['| T-47-01 | Tampering | c | high | mitigate | fix |']), '```', register(['| T-47-02 | DoS | c | low | accept | r |'])].join('\n');
assert.deepEqual(extractThreatRegisterIds(doc), ['T-47-01']);
});
test('block tags are case-insensitive', () => {
const ids = extractThreatRegisterIds([
'<THREAT_MODEL>', '| T-47-05 | Tampering | c | high | mitigate | fix |', '</THREAT_MODEL>',
].join('\n'));
assert.deepEqual(ids, ['T-47-05']);
});
test('multiple blocks yield IDs across both, in order', () => {
const doc = [
register(['| T-47-01 | Tampering | c | high | mitigate | fix |']),
'',
register(['| T-47-02 | Repudiation | c | low | accept | r |']),
].join('\n');
assert.deepEqual(extractThreatRegisterIds(doc), ['T-47-01', 'T-47-02']);
});
test('CRLF rows are read the same as LF', () => {
const ids = extractThreatRegisterIds(register(['| T-47-07 | Tampering | c | high | mitigate | fix |']).replace(/\n/g, '\r\n'));
assert.deepEqual(ids, ['T-47-07']);
});
test('indented rows still match on the first cell', () => {
const ids = extractThreatRegisterIds([
'<threat_model>', ' | T-47-09 | Tampering | c | high | mitigate | fix |', '</threat_model>',
].join('\n'));
assert.deepEqual(ids, ['T-47-09']);
});
test('annotated first cells are knowingly unmatched (accepted residual)', () => {
const ids = extractThreatRegisterIds(register(['| T-47-06 (revised) | Tampering | c | high | mitigate | fix |']));
assert.deepEqual(ids, []);
});
test('content without any threat_model block yields an empty list', () => {
assert.deepEqual(extractThreatRegisterIds('# Plan\n\nDo things.\n'), []);
});
});