* fix(#2788): audit-uat reads frontmatter human_verification array parseVerificationItems only searched the body for a '## Human Verification' section. gsd-verifier writes items to the frontmatter human_verification: YAML array, so audit-uat returned total_items: 0 for all such files. Two fixes: 1. Read frontmatter human_verification: array first (via extractFrontmatter); return those items if present (primary path for gsd-verifier output). 2. Relax the body-section heading regex to accept underscore separators and parenthetical suffixes (e.g. '## human_verification (action required)'). Regression test: tests/bug-2788-audit-uat-frontmatter.test.cjs * fix(#2788): address CodeRabbit — trim whitespace entries, support hyphenated headings, bash fence
176 lines
5.5 KiB
JavaScript
176 lines
5.5 KiB
JavaScript
/**
|
|
* Regression test for bug #2788
|
|
*
|
|
* `gsd-sdk query audit-uat` returned total_items: 0 for VERIFICATION.md
|
|
* files where human-needed items were encoded in the frontmatter
|
|
* `human_verification:` YAML array (the format written by gsd-verifier),
|
|
* or where the body section heading used `## human_verification` (underscore)
|
|
* instead of `## Human Verification` (space).
|
|
*
|
|
* Root cause:
|
|
* 1. parseVerificationItems only searched the body for "## Human Verification"
|
|
* (space, case-insensitive) — never read frontmatter.
|
|
* 2. The body-section regex did not accept underscore in the heading name.
|
|
*
|
|
* Fix: parseVerificationItems now reads the frontmatter human_verification:
|
|
* array first (via extractFrontmatter). Falls back to body-section scan
|
|
* with a relaxed regex that accepts underscore and parenthetical suffixes.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const { describe, test, beforeEach, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { execFileSync } = require('node:child_process');
|
|
const { createTempProject, cleanup } = require('./helpers.cjs');
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..');
|
|
const SDK_CLI = path.join(REPO_ROOT, 'sdk', 'dist', 'cli.js');
|
|
|
|
function runAuditUat(projectDir) {
|
|
const argv = ['query', 'audit-uat', '--project-dir', projectDir];
|
|
let stdout = '';
|
|
let exitCode = 0;
|
|
try {
|
|
stdout = execFileSync(process.execPath, [SDK_CLI, ...argv], {
|
|
encoding: 'utf-8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
env: { ...process.env, GSD_SESSION_KEY: '' },
|
|
});
|
|
} catch (err) {
|
|
exitCode = err.status ?? 1;
|
|
stdout = err.stdout?.toString() ?? '';
|
|
}
|
|
let json = null;
|
|
try { json = JSON.parse(stdout.trim()); } catch { /* ok */ }
|
|
return { exitCode, json };
|
|
}
|
|
|
|
/** Set up a project with a ROADMAP.md milestone and a phase VERIFICATION.md */
|
|
function setupProject(tmpDir, verificationContent) {
|
|
const planningDir = path.join(tmpDir, '.planning');
|
|
const phaseDir = path.join(planningDir, 'phases', '03-invoicing');
|
|
fs.mkdirSync(phaseDir, { recursive: true });
|
|
|
|
// Write a minimal ROADMAP.md so getMilestonePhaseFilter works
|
|
fs.writeFileSync(
|
|
path.join(planningDir, 'ROADMAP.md'),
|
|
[
|
|
'# Roadmap',
|
|
'',
|
|
'## v1.0 — MVP',
|
|
'',
|
|
'### Phase 3: Invoicing',
|
|
].join('\n')
|
|
);
|
|
|
|
// Write STATE.md with current milestone
|
|
fs.writeFileSync(
|
|
path.join(planningDir, 'STATE.md'),
|
|
[
|
|
'---',
|
|
'version: "v1.0"',
|
|
'---',
|
|
'# State',
|
|
].join('\n')
|
|
);
|
|
|
|
fs.writeFileSync(
|
|
path.join(phaseDir, '03-invoicing-VERIFICATION.md'),
|
|
verificationContent
|
|
);
|
|
}
|
|
|
|
describe('bug-2788: audit-uat reads frontmatter human_verification array', () => {
|
|
let tmpDir;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = createTempProject('gsd-test-2788-');
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup(tmpDir);
|
|
});
|
|
|
|
test('frontmatter human_verification: array items are reported', () => {
|
|
// This is the format gsd-verifier writes; before fix total_items was 0
|
|
const content = [
|
|
'---',
|
|
'phase: 03-invoicing',
|
|
'status: human_needed',
|
|
'human_verification:',
|
|
' - test: "Manual frontend smoke — /invoices, /invoices/new"',
|
|
' expected: "All four routes render correctly"',
|
|
' why_human: "Visual rendering cannot be verified by static code inspection"',
|
|
' - test: "Run integration test suite against a real Postgres"',
|
|
' expected: "105 currently-skipped tests pass green"',
|
|
' why_human: "Docker not installed locally"',
|
|
'---',
|
|
'',
|
|
'# Phase 3 Verification Report',
|
|
'',
|
|
'No body Human Verification section here.',
|
|
].join('\n');
|
|
|
|
setupProject(tmpDir, content);
|
|
const result = runAuditUat(tmpDir);
|
|
|
|
assert.strictEqual(result.exitCode, 0, 'should exit 0');
|
|
assert.ok(result.json !== null, 'should emit JSON');
|
|
assert.ok(
|
|
result.json.summary.total_items >= 2,
|
|
`total_items should be >= 2, got ${result.json.summary.total_items}`
|
|
);
|
|
});
|
|
|
|
test('body ## human_verification (underscore) heading is parsed', () => {
|
|
const content = [
|
|
'---',
|
|
'phase: 03-invoicing',
|
|
'status: human_needed',
|
|
'---',
|
|
'',
|
|
'# Phase 3 Verification Report',
|
|
'',
|
|
'## human_verification (action required by Sammy)',
|
|
'',
|
|
'- Manual frontend smoke — /invoices, /invoices/new: verify all four routes render',
|
|
'- Run integration test suite against a real Postgres database',
|
|
].join('\n');
|
|
|
|
setupProject(tmpDir, content);
|
|
const result = runAuditUat(tmpDir);
|
|
|
|
assert.strictEqual(result.exitCode, 0, 'should exit 0');
|
|
assert.ok(result.json !== null, 'should emit JSON');
|
|
assert.ok(
|
|
result.json.summary.total_items >= 2,
|
|
`total_items should be >= 2, got ${result.json.summary.total_items}`
|
|
);
|
|
});
|
|
|
|
test('body ## Human Verification (space) heading still works', () => {
|
|
const content = [
|
|
'---',
|
|
'phase: 03-invoicing',
|
|
'status: human_needed',
|
|
'---',
|
|
'',
|
|
'# Phase 3 Verification Report',
|
|
'',
|
|
'## Human Verification',
|
|
'',
|
|
'- Manual frontend smoke test — verify all four routes render correctly or describe what breaks',
|
|
'- Run integration test suite: 105 tests must pass green',
|
|
].join('\n');
|
|
|
|
setupProject(tmpDir, content);
|
|
const result = runAuditUat(tmpDir);
|
|
|
|
assert.strictEqual(result.exitCode, 0, 'should exit 0');
|
|
assert.ok(result.json?.summary.total_items >= 2, `total_items should be >= 2`);
|
|
});
|
|
});
|