ktg-plugin-marketplace/plugins/ultraplan-local/tests/lib/manifest-yaml.test.mjs
Kjell Tore Guttormsen 1a65d8e4d5 feat(graceful-handoff): 2.0 — migrate to skills/ with disable-model-invocation [skip-docs]
Step 1 of v2.0 plan. Hard cut from commands/ to skills/ per Anthropic
recommendation for new plugins. Frontmatter sets disable-model-invocation:
true and pins model: claude-sonnet-4-6. Docs (README, CLAUDE.md, root
README) deferred to Step 9 per plan.
2026-05-01 05:45:26 +02:00

138 lines
3.8 KiB
JavaScript

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('parseManifest — must_contain list-of-dicts (real-world template form)', () => {
const body = `### Step 1: Real
- Manifest:
\`\`\`yaml
manifest:
expected_paths:
- a.json
- b.md
min_file_count: 2
commit_message_pattern: "^chore:"
bash_syntax_check: []
forbidden_paths:
- CHANGELOG.md
must_contain:
- path: a.json
pattern: '"version": "2\\.3\\.0"'
- path: b.md
pattern: "version-blue"
\`\`\`
`;
const r = parseManifest(body);
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.equal(r.parsed.must_contain.length, 2);
assert.equal(r.parsed.must_contain[0].path, 'a.json');
assert.equal(r.parsed.must_contain[1].path, 'b.md');
assert.equal(r.parsed.forbidden_paths[0], 'CHANGELOG.md');
});
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)));
});