Step 3 av v4.1-execute (Wave 1, Session 1). Legg ny eksportert const OPTIONAL_STRING_KEYS = ['profile_used'] parallel til eksisterende OPTIONAL_KEYS. Utvid parseManifest med ny dispatch-loop etter OPTIONAL_BOOLEAN_KEYS. Returnerer MANIFEST_OPTIONAL_TYPE hvis profile_used finnes men ikke er string. Forskjell fra OPTIONAL_BOOLEAN_KEYS: absence == not-present (NOT defaulted til false, unlike boolean). Downstream-konsumenter kan dermed skille mellom unset og empty-string. Tester (5 nye, baseline 372 → 377): - OPTIONAL_STRING_KEYS export drift-pin - profile_used: economy parses successfully (SC #10 forward-compat) - profile_used: numeric rejected - absence: field NOT in parsed (string-key semantics) - profile_used + skip_commit_check + memory_write co-existence Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
133 lines
5.2 KiB
JavaScript
133 lines
5.2 KiB
JavaScript
// tests/lib/manifest-schema-extensions.test.mjs
|
|
// Cover the OPTIONAL_KEYS extension to lib/parsers/manifest-yaml.mjs:
|
|
// - skip_commit_check (boolean, default false)
|
|
// - memory_write (boolean, default false)
|
|
//
|
|
// Defaults must NOT break the REQUIRED_KEYS contract.
|
|
// Non-boolean values must produce MANIFEST_OPTIONAL_TYPE error.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { parseManifest, OPTIONAL_KEYS, OPTIONAL_STRING_KEYS } from '../../lib/parsers/manifest-yaml.mjs';
|
|
|
|
const BASE = `### Step 1: Cover
|
|
- Manifest:
|
|
\`\`\`yaml
|
|
manifest:
|
|
expected_paths:
|
|
- lib/foo.mjs
|
|
min_file_count: 1
|
|
commit_message_pattern: "^feat:"
|
|
bash_syntax_check: []
|
|
forbidden_paths: []
|
|
must_contain: []`;
|
|
|
|
function bodyWithExtras(extras) {
|
|
return `${BASE}\n${extras}\n \`\`\`\n`;
|
|
}
|
|
|
|
function bodyOnlyRequired() {
|
|
return `${BASE}\n \`\`\`\n`;
|
|
}
|
|
|
|
test('OPTIONAL_KEYS exports skip_commit_check + memory_write', () => {
|
|
assert.deepEqual(
|
|
[...OPTIONAL_KEYS].sort(),
|
|
['memory_write', 'skip_commit_check'].sort(),
|
|
'OPTIONAL_KEYS export drift — pin contract',
|
|
);
|
|
});
|
|
|
|
test('absence of optional keys → defaults to false (both fields)', () => {
|
|
const r = parseManifest(bodyOnlyRequired());
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.skip_commit_check, false);
|
|
assert.equal(r.parsed.memory_write, false);
|
|
});
|
|
|
|
test('skip_commit_check: true honored', () => {
|
|
const r = parseManifest(bodyWithExtras(' skip_commit_check: true'));
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.skip_commit_check, true);
|
|
assert.equal(r.parsed.memory_write, false);
|
|
});
|
|
|
|
test('memory_write: true honored', () => {
|
|
const r = parseManifest(bodyWithExtras(' memory_write: true'));
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.memory_write, true);
|
|
assert.equal(r.parsed.skip_commit_check, false);
|
|
});
|
|
|
|
test('both optional fields together — both honored', () => {
|
|
const r = parseManifest(bodyWithExtras(' skip_commit_check: true\n memory_write: true'));
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.skip_commit_check, true);
|
|
assert.equal(r.parsed.memory_write, true);
|
|
});
|
|
|
|
test('skip_commit_check: non-boolean rejected with MANIFEST_OPTIONAL_TYPE', () => {
|
|
const r = parseManifest(bodyWithExtras(' skip_commit_check: "yes"'));
|
|
assert.equal(r.valid, false);
|
|
const found = r.errors.find(e => e.code === 'MANIFEST_OPTIONAL_TYPE');
|
|
assert.ok(found, `expected MANIFEST_OPTIONAL_TYPE, got: ${JSON.stringify(r.errors)}`);
|
|
assert.match(found.message, /skip_commit_check/);
|
|
});
|
|
|
|
test('memory_write: numeric rejected with MANIFEST_OPTIONAL_TYPE', () => {
|
|
const r = parseManifest(bodyWithExtras(' memory_write: 1'));
|
|
assert.equal(r.valid, false);
|
|
const found = r.errors.find(e => e.code === 'MANIFEST_OPTIONAL_TYPE');
|
|
assert.ok(found, `expected MANIFEST_OPTIONAL_TYPE, got: ${JSON.stringify(r.errors)}`);
|
|
assert.match(found.message, /memory_write/);
|
|
});
|
|
|
|
test('extension does NOT break REQUIRED_KEYS contract', () => {
|
|
const r = parseManifest(bodyOnlyRequired());
|
|
assert.equal(r.valid, true);
|
|
for (const k of ['expected_paths', 'min_file_count', 'commit_message_pattern',
|
|
'bash_syntax_check', 'forbidden_paths', 'must_contain']) {
|
|
assert.ok(k in r.parsed, `required key ${k} missing after extension`);
|
|
}
|
|
});
|
|
|
|
// v4.1 Step 3 — OPTIONAL_STRING_KEYS dispatch (profile_used)
|
|
|
|
test('OPTIONAL_STRING_KEYS exports profile_used', () => {
|
|
assert.deepEqual(
|
|
[...OPTIONAL_STRING_KEYS].sort(),
|
|
['profile_used'].sort(),
|
|
'OPTIONAL_STRING_KEYS export drift — pin contract',
|
|
);
|
|
});
|
|
|
|
test('profile_used: economy parses successfully (SC #10 forward-compat)', () => {
|
|
const r = parseManifest(bodyWithExtras(' profile_used: economy'));
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.profile_used, 'economy');
|
|
});
|
|
|
|
test('profile_used: numeric rejected with MANIFEST_OPTIONAL_TYPE', () => {
|
|
const r = parseManifest(bodyWithExtras(' profile_used: 42'));
|
|
assert.equal(r.valid, false);
|
|
const found = r.errors.find(e => e.code === 'MANIFEST_OPTIONAL_TYPE');
|
|
assert.ok(found, `expected MANIFEST_OPTIONAL_TYPE, got: ${JSON.stringify(r.errors)}`);
|
|
assert.match(found.message, /profile_used/);
|
|
assert.match(found.message, /string/);
|
|
});
|
|
|
|
test('absence of profile_used: field is NOT in parsed (NOT defaulted, unlike boolean)', () => {
|
|
const r = parseManifest(bodyOnlyRequired());
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
// Absence semantics differ from boolean: parsed should NOT contain the key
|
|
assert.equal('profile_used' in r.parsed, false,
|
|
'profile_used must NOT be auto-defaulted when absent — string-key semantics');
|
|
});
|
|
|
|
test('profile_used works alongside boolean optional keys (skip_commit_check + memory_write)', () => {
|
|
const r = parseManifest(bodyWithExtras(' skip_commit_check: true\n memory_write: true\n profile_used: balanced'));
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
assert.equal(r.parsed.skip_commit_check, true);
|
|
assert.equal(r.parsed.memory_write, true);
|
|
assert.equal(r.parsed.profile_used, 'balanced');
|
|
});
|