52 lines
3.9 KiB
JavaScript
52 lines
3.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync, rmSync, symlinkSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import * as api from '../src/index.mjs';
|
|
import { git, prdFixture } from './prd-fixture.mjs';
|
|
|
|
test('candidate reads exact committed blobs and tolerates unrelated HEAD commits', async t => {
|
|
const f = await prdFixture(t), candidate = await api.readPrdCandidate(f.args);
|
|
assert.equal(candidate.prd_blob, git(f.repo, 'rev-parse', `${f.args.records_commit}:docs/prd-1.0.md`));
|
|
assert.equal(candidate.requirements[0].blob, git(f.repo, 'rev-parse', `${f.args.records_commit}:requirements/REQ-POMO-1.json`));
|
|
assert.equal(candidate.spec_commit, git(f.repo, 'log', '-1', '--format=%H', '--', 'spec/north-star.md'));
|
|
assert.deepEqual(Object.keys(candidate).sort(), ['version','request','project','prd_version','records_commit','prd_path','prd_blob','requirements','spec_commit','mode_revision'].sort());
|
|
f.save('docs/unrelated.md', 'Unrelated');
|
|
assert.deepEqual(await api.readPrdCandidate(f.args), candidate);
|
|
});
|
|
for (const path of ['docs/prd-1.0.md', 'requirements/REQ-POMO-1.json']) test(`changed selected blob is stale: ${path}`, async t => {
|
|
const f = await prdFixture(t);
|
|
f.save(path, path.endsWith('.md') ? 'Edited' : JSON.stringify({ recordVersion: 1, id: 'REQ-POMO-1', title: 'Edited', text: 'Changed', status: 'accepted' }));
|
|
await assert.rejects(api.readPrdCandidate(f.args), { code: 'prd-candidate-stale' });
|
|
});
|
|
test('candidate rejects extra assertions, duplicate IDs, wrong version and unreachable commits', async t => {
|
|
const f = await prdFixture(t);
|
|
for (const patch of [{ prd_blob: 'forged' }, { requirement_ids: ['REQ-POMO-1','REQ-POMO-1'] }, { prd_version: '2.0' }, { records_commit: 'a'.repeat(40) }])
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, ...patch }));
|
|
await assert.rejects(api.readProjectFile({ ...f.context, path: 'requirements/REQ-POMO-1.json' }), { code: 'project-read-path' });
|
|
});
|
|
test('candidate rejects malformed requirements, mismatched IDs and nonregular blobs', async t => {
|
|
const f = await prdFixture(t);
|
|
const original = JSON.parse(readFileSync(join(f.repo, 'requirements/REQ-POMO-1.json')));
|
|
for (const record of [{ ...original, extra: true }, { ...original, id: 'OTHER' }, { ...original, title: ' ' }]) {
|
|
const records_commit = f.save('requirements/REQ-POMO-1.json', JSON.stringify(record), 'pm.pm');
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, records_commit }));
|
|
}
|
|
rmSync(join(f.repo, 'docs/prd-1.0.md')); symlinkSync('../spec/north-star.md', join(f.repo, 'docs/prd-1.0.md'));
|
|
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'Link');
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, records_commit: git(f.repo, 'rev-parse', 'HEAD') }));
|
|
});
|
|
test('candidate enforces 64 requirements, 32 KiB PRD, 16 KiB requirement and 256 KiB aggregate', async t => {
|
|
const f = await prdFixture(t);
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, requirement_ids: Array.from({ length: 65 }, (_, i) => `R${i}`) }));
|
|
let records_commit = f.save('docs/prd-1.0.md', 'x'.repeat(32769));
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, records_commit }), { code: 'prd-candidate-size' });
|
|
f.save('docs/prd-1.0.md', 'x'.repeat(32768), 'pm.pm');
|
|
const record = { recordVersion: 1, id: 'REQ-POMO-1', title: 'Timer', text: 'x'.repeat(16385), status: 'draft' };
|
|
records_commit = f.save('requirements/REQ-POMO-1.json', JSON.stringify(record), 'pm.pm');
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, records_commit }), { code: 'prd-candidate-size' });
|
|
const ids = Array.from({ length: 16 }, (_, i) => `R${i}`);
|
|
for (const id of ids) f.save(`requirements/${id}.json`, JSON.stringify({ ...record, id, text: 'x'.repeat(16384) }), 'pm.pm');
|
|
await assert.rejects(api.readPrdCandidate({ ...f.args, records_commit: git(f.repo, 'rev-parse', 'HEAD'), requirement_ids: ids }), { code: 'prd-candidate-size' });
|
|
});
|