import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { extractManifestYaml, parseManifest, validateAllManifests, } from '../../lib/parsers/manifest-yaml.mjs'; const STEP_BODY_GOOD = `### Step 1: Add validator - Files: lib/foo.mjs - Verify: \`npm test\` → expected: pass - Checkpoint: \`git commit -m "feat(lib): foo"\` - Manifest: \`\`\`yaml manifest: expected_paths: - lib/foo.mjs min_file_count: 1 commit_message_pattern: "^feat\\\\(lib\\\\):" bash_syntax_check: [] forbidden_paths: [] must_contain: [] \`\`\` `; const STEP_BODY_NO_MANIFEST = `### Step 1: oops no manifest here `; const STEP_BODY_INVALID_REGEX = `### Step 1: bad regex - Manifest: \`\`\`yaml manifest: expected_paths: - x min_file_count: 1 commit_message_pattern: "[unclosed" bash_syntax_check: [] forbidden_paths: [] must_contain: [] \`\`\` `; test('extractManifestYaml — finds fenced manifest block', () => { const yaml = extractManifestYaml(STEP_BODY_GOOD); assert.ok(yaml); assert.match(yaml, /expected_paths/); }); test('extractManifestYaml — null when missing', () => { assert.equal(extractManifestYaml(STEP_BODY_NO_MANIFEST), null); }); test('parseManifest — happy path produces all required keys', () => { const r = parseManifest(STEP_BODY_GOOD); assert.equal(r.valid, true, JSON.stringify(r.errors)); assert.deepEqual(r.parsed.expected_paths, ['lib/foo.mjs']); assert.equal(r.parsed.min_file_count, 1); assert.match(r.parsed.commit_message_pattern, /^\^feat/); }); test('parseManifest — missing manifest produces MANIFEST_MISSING', () => { const r = parseManifest(STEP_BODY_NO_MANIFEST); assert.equal(r.valid, false); assert.ok(r.errors.find(e => e.code === 'MANIFEST_MISSING')); }); test('parseManifest — invalid regex caught', () => { const r = parseManifest(STEP_BODY_INVALID_REGEX); assert.equal(r.valid, false); assert.ok(r.errors.find(e => e.code === 'MANIFEST_PATTERN_INVALID')); }); test('parseManifest — missing required key flagged', () => { const noCount = `### Step 1 - Manifest: \`\`\`yaml manifest: expected_paths: - x commit_message_pattern: "^x:" bash_syntax_check: [] forbidden_paths: [] must_contain: [] \`\`\` `; const r = parseManifest(noCount); assert.equal(r.valid, false); assert.ok(r.errors.find(e => e.code === 'MANIFEST_MISSING_KEY' && /min_file_count/.test(e.message))); }); test('parseManifest — commit_message_pattern compiles via new RegExp', () => { const r = parseManifest(STEP_BODY_GOOD); const re = new RegExp(r.parsed.commit_message_pattern); assert.ok(re.test('feat(lib): added foo')); assert.ok(!re.test('chore: not it')); }); test('validateAllManifests — aggregates per-step issues', () => { const steps = [ { n: 1, body: STEP_BODY_GOOD }, { n: 2, body: STEP_BODY_NO_MANIFEST }, ]; const r = validateAllManifests(steps); assert.equal(r.valid, false); assert.ok(r.errors.find(e => /Step 2/.test(e.message))); });