Harden verify-work checkpoint rendering

This commit is contained in:
3metaJun
2026-03-24 01:16:27 +08:00
committed by Tom Boucher
parent f43e0237f2
commit aaaa8e96fe
7 changed files with 249 additions and 15 deletions

View File

@@ -136,6 +136,12 @@ describe('dispatcher error paths', () => {
assert.ok(result.error.includes('Unknown todo subcommand'), `Expected "Unknown todo subcommand" in stderr, got: ${result.error}`);
});
test('uat unknown subcommand errors', () => {
const result = runGsdTools('uat bogus', tmpDir);
assert.strictEqual(result.success, false, 'Should exit non-zero');
assert.ok(result.error.includes('Unknown uat subcommand'), `Expected "Unknown uat subcommand" in stderr, got: ${result.error}`);
});
// Unknown subcommand: init
test('init unknown workflow errors', () => {
const result = runGsdTools('init bogus', tmpDir);

View File

@@ -14,6 +14,7 @@ const {
requireSafePath,
scanForInjection,
sanitizeForPrompt,
sanitizeForDisplay,
safeJsonParse,
validatePhaseNumber,
validateFieldName,
@@ -244,6 +245,19 @@ describe('sanitizeForPrompt', () => {
});
});
describe('sanitizeForDisplay', () => {
test('removes protocol leak lines', () => {
const input = 'Visible line\nuser to=all:final code something bad\nAnother line';
const result = sanitizeForDisplay(input);
assert.equal(result, 'Visible line\nAnother line');
});
test('keeps normal user-facing copy intact', () => {
const input = 'Type `pass` or describe what\\\'s wrong.';
assert.equal(sanitizeForDisplay(input), input);
});
});
// ─── Shell Safety ───────────────────────────────────────────────────────────
describe('validateShellArg', () => {

View File

@@ -2,6 +2,8 @@
* GSD Tools Tests - UAT Audit
*/
'use strict';
const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
@@ -324,3 +326,83 @@ All checks passed.
assert.strictEqual(output.summary.total_files, 0);
});
});
describe('uat render-checkpoint', () => {
let tmpDir;
let uatPath;
beforeEach(() => {
tmpDir = createTempProject();
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-test-phase');
fs.mkdirSync(phaseDir, { recursive: true });
uatPath = path.join(phaseDir, '01-UAT.md');
});
afterEach(() => {
cleanup(tmpDir);
});
test('renders the current checkpoint as raw output', () => {
fs.writeFileSync(uatPath, `---
status: testing
phase: 01-test-phase
---
## Current Test
number: 2
name: Submit form validation
expected: |
Empty submit keeps controls visible.
Validation error copy is shown.
awaiting: user response
`);
const result = runGsdTools(['uat', 'render-checkpoint', '--file', '.planning/phases/01-test-phase/01-UAT.md', '--raw'], tmpDir);
assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`);
assert.ok(result.output.includes('**Test 2: Submit form validation**'));
assert.ok(result.output.includes('Empty submit keeps controls visible.'));
assert.ok(result.output.includes("Type `pass` or describe what's wrong."));
});
test('strips protocol leak lines from current test copy', () => {
fs.writeFileSync(uatPath, `---
status: testing
phase: 01-test-phase
---
## Current Test
number: 6
name: Locale copy
expected: |
English strings render correctly.
user to=all:final code 彩票平台招商 pass
Chinese strings render correctly.
awaiting: user response
`);
const result = runGsdTools(['uat', 'render-checkpoint', '--file', '.planning/phases/01-test-phase/01-UAT.md', '--raw'], tmpDir);
assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`);
assert.ok(!result.output.includes('user to=all:final code'));
assert.ok(!result.output.includes('彩票平台'));
assert.ok(result.output.includes('English strings render correctly.'));
assert.ok(result.output.includes('Chinese strings render correctly.'));
});
test('fails when testing is already complete', () => {
fs.writeFileSync(uatPath, `---
status: complete
phase: 01-test-phase
---
## Current Test
[testing complete]
`);
const result = runGsdTools(['uat', 'render-checkpoint', '--file', '.planning/phases/01-test-phase/01-UAT.md'], tmpDir);
assert.strictEqual(result.success, false, 'Should fail when no current test exists');
assert.ok(result.error.includes('already complete'));
});
});