Files
proffesor-for-testing__agen…/tests/unit/scripts/validate-sfdipot-assessment.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

383 lines
13 KiB
TypeScript

/**
* SFDIPOT Assessment Validator Unit Tests
*
* Tests the validation script that enforces quality gates on
* Product Factors assessment HTML output.
*/
import { describe, it, expect } from 'vitest';
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
import { execSync } from 'child_process';
// ============================================================================
// Test Helpers
// ============================================================================
const TEST_DIR = join(__dirname, '../../../.test-tmp/sfdipot-validation');
const SCRIPT_PATH = 'scripts/validate-sfdipot-assessment.ts';
/**
* Generate a test assessment HTML file
*
* The validation script checks:
* - Gate 5: automation-human class count vs total
* - Gate 6: TC-XXXX-XXXXXXXX pattern count
* - Gate 7: <td>Verify pattern count
* - Gate 10a: "Why Human Essential:" text count
* - Gate 10b: <td>Explore X; assess Y pattern
*/
function generateTestAssessment(options: {
testCount: number;
p0Percent?: number;
p1Percent?: number;
p2Percent?: number;
p3Percent?: number;
humanPercent?: number;
verifyPatterns?: number;
humanWithReason?: number;
humanWithExplore?: number;
}): string {
const {
testCount,
p0Percent = 10,
p1Percent = 30,
p2Percent = 40,
p3Percent = 20,
humanPercent = 15,
verifyPatterns = 0,
} = options;
const p0Count = Math.floor(testCount * (p0Percent / 100));
const p1Count = Math.floor(testCount * (p1Percent / 100));
const p2Count = Math.floor(testCount * (p2Percent / 100));
const p3Count = Math.max(0, testCount - p0Count - p1Count - p2Count - verifyPatterns);
// Human tests distributed across priorities
const humanCount = Math.floor(testCount * (humanPercent / 100));
// Default: all human tests have proper formatting
const reasonCount = options.humanWithReason ?? humanCount;
const exploreCount = options.humanWithExplore ?? humanCount;
const rows: string[] = [];
let humanIndex = 0;
// Helper to generate a test row
const addTest = (testId: string, priority: string, isHuman: boolean, idx: number) => {
const automationType = isHuman ? 'automation-human' : 'automation-automated';
let description: string;
let reason = '';
if (isHuman) {
// Human test - use "Explore X; assess Y" format if within exploreCount
if (humanIndex < exploreCount) {
description = `Explore authentication flow; assess user experience under various scenarios`;
} else {
description = `Manually test edge case scenario ${idx}`;
}
// Add "Why Human Essential:" if within reasonCount
if (humanIndex < reasonCount) {
reason = `<br><strong>Why Human Essential:</strong> Requires human intuition for subjective evaluation`;
}
humanIndex++;
} else {
description = `Send API request ${idx}; confirm response matches expected schema`;
}
rows.push(`<tr class="priority-${priority} ${automationType}"><td>${testId}</td><td>${description}${reason}</td></tr>`);
};
// Distribute human tests across priority levels that have tests
// Calculate how many human tests can fit in each priority
let remainingHuman = humanCount;
const humanP0 = Math.min(p0Count, remainingHuman);
remainingHuman -= humanP0;
const humanP1 = Math.min(p1Count, remainingHuman);
remainingHuman -= humanP1;
const humanP2 = Math.min(p2Count, remainingHuman);
let testIndex = 0;
// Generate P0 tests
for (let i = 0; i < p0Count; i++) {
const isHuman = i < humanP0;
const testId = `TC-AUTH-${String(testIndex++).padStart(8, '0')}`;
addTest(testId, 'p0', isHuman, i);
}
// Generate P1 tests
for (let i = 0; i < p1Count; i++) {
const isHuman = i < humanP1;
const testId = `TC-AUTH-${String(testIndex++).padStart(8, '0')}`;
addTest(testId, 'p1', isHuman, i);
}
// Generate P2 tests
for (let i = 0; i < p2Count; i++) {
const isHuman = i < humanP2;
const testId = `TC-AUTH-${String(testIndex++).padStart(8, '0')}`;
addTest(testId, 'p2', isHuman, i);
}
// Generate P3 tests (never human)
for (let i = 0; i < p3Count; i++) {
const testId = `TC-AUTH-${String(testIndex++).padStart(8, '0')}`;
rows.push(`<tr class="priority-p3 automation-automated"><td>${testId}</td><td>Check minor UI element ${i}</td></tr>`);
}
// Add "Verify" patterns if specified (these fail Gate 7)
for (let i = 0; i < verifyPatterns; i++) {
const testId = `TC-VRFY-${String(i).padStart(8, '0')}`;
rows.push(`<tr class="priority-p2 automation-automated"><td>${testId}</td><td>Verify login works correctly</td></tr>`);
}
return `<!DOCTYPE html>
<html>
<head><title>SFDIPOT Assessment</title></head>
<body>
<h1>Product Factors Assessment</h1>
<table>
<thead><tr><th>Test ID</th><th>Test Idea</th></tr></thead>
<tbody>
${rows.join('\n')}
</tbody>
</table>
</body>
</html>`;
}
/**
* Run the validation script and capture output
*/
function runValidation(filePath: string): { exitCode: number; output: string } {
try {
const output = execSync(`npx tsx ${SCRIPT_PATH} "${filePath}"`, {
cwd: join(__dirname, '../../..'),
encoding: 'utf-8',
timeout: 30000,
});
return { exitCode: 0, output };
} catch (error: unknown) {
const execError = error as { status?: number; stdout?: string; stderr?: string };
return {
exitCode: execError.status ?? 1,
output: (execError.stdout || '') + (execError.stderr || ''),
};
}
}
// ============================================================================
// Test Suite
// ============================================================================
describe('SFDIPOT Assessment Validator', () => {
beforeAll(() => {
mkdirSync(TEST_DIR, { recursive: true });
});
afterAll(() => {
rmSync(TEST_DIR, { recursive: true, force: true });
});
describe('Gate 6: Minimum Test Count', () => {
it('should pass with exactly 50 tests', () => {
const html = generateTestAssessment({ testCount: 50 });
const filePath = join(TEST_DIR, 'gate6-50-tests.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
expect(result.output).toContain('ALL HARD GATES PASSED');
});
it('should pass with more than 50 tests', () => {
const html = generateTestAssessment({ testCount: 100 });
const filePath = join(TEST_DIR, 'gate6-100-tests.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should fail with fewer than 50 tests', () => {
const html = generateTestAssessment({ testCount: 30 });
const filePath = join(TEST_DIR, 'gate6-30-tests.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Gate 6');
expect(result.output).toContain('FAIL');
});
});
describe('Gate 5: Human Tests >= 10%', () => {
it('should pass with exactly 10% human tests', () => {
const html = generateTestAssessment({ testCount: 100, humanPercent: 10 });
const filePath = join(TEST_DIR, 'gate5-10-percent.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should pass with more than 10% human tests', () => {
const html = generateTestAssessment({ testCount: 100, humanPercent: 20 });
const filePath = join(TEST_DIR, 'gate5-20-percent.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should fail with fewer than 10% human tests', () => {
const html = generateTestAssessment({ testCount: 100, humanPercent: 5 });
const filePath = join(TEST_DIR, 'gate5-5-percent.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Gate 5');
expect(result.output).toContain('FAIL');
});
});
describe('Gate 7: No "Verify X" Patterns', () => {
it('should pass with zero "Verify" patterns', () => {
const html = generateTestAssessment({ testCount: 50, verifyPatterns: 0 });
const filePath = join(TEST_DIR, 'gate7-no-verify.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should fail with "Verify" patterns present', () => {
const html = generateTestAssessment({ testCount: 50, verifyPatterns: 5 });
const filePath = join(TEST_DIR, 'gate7-with-verify.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Gate 7');
expect(result.output).toContain('FAIL');
expect(result.output).toContain('Verify');
});
});
describe('Gate 10a: Human Tests Have Reasoning', () => {
it('should pass when 90%+ human tests have "Why Human Essential"', () => {
const humanCount = 15; // 15% of 100
const html = generateTestAssessment({
testCount: 100,
humanPercent: 15,
humanWithReason: Math.ceil(humanCount * 0.9), // 90%+
});
const filePath = join(TEST_DIR, 'gate10a-pass.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should fail when <90% human tests have reasoning', () => {
const humanCount = 20; // 20% of 100
const html = generateTestAssessment({
testCount: 100,
humanPercent: 20,
humanWithReason: Math.floor(humanCount * 0.5), // Only 50%
});
const filePath = join(TEST_DIR, 'gate10a-fail.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Gate 10a');
});
});
describe('Gate 10b: Human Tests Use "Explore/Assess" Format', () => {
it('should pass when 80%+ human tests use "Explore X; assess Y"', () => {
const humanCount = 15;
const html = generateTestAssessment({
testCount: 100,
humanPercent: 15,
humanWithExplore: Math.ceil(humanCount * 0.8), // 80%+
});
const filePath = join(TEST_DIR, 'gate10b-pass.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
});
it('should fail when <80% human tests use explore format', () => {
const humanCount = 20;
const html = generateTestAssessment({
testCount: 100,
humanPercent: 20,
humanWithExplore: Math.floor(humanCount * 0.3), // Only 30%
});
const filePath = join(TEST_DIR, 'gate10b-fail.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Gate 10b');
});
});
describe('Soft Gates (Informational)', () => {
it('should always pass soft gates regardless of priority distribution', () => {
// Extreme priority distribution (all P0)
const html = generateTestAssessment({
testCount: 50,
p0Percent: 100,
p1Percent: 0,
p2Percent: 0,
p3Percent: 0,
});
const filePath = join(TEST_DIR, 'soft-gates-extreme.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
// Should pass (soft gates don't fail)
expect(result.exitCode).toBe(0);
// Should show informational messages
expect(result.output).toContain('INFO');
expect(result.output).toContain('Domain expert should validate');
});
});
describe('Full Validation Pass', () => {
it('should pass all gates with well-formed assessment', () => {
const html = generateTestAssessment({
testCount: 100,
p0Percent: 10,
p1Percent: 30,
p2Percent: 40,
p3Percent: 20,
humanPercent: 15,
verifyPatterns: 0,
});
const filePath = join(TEST_DIR, 'full-pass.html');
writeFileSync(filePath, html);
const result = runValidation(filePath);
expect(result.exitCode).toBe(0);
expect(result.output).toContain('ALL HARD GATES PASSED');
});
});
describe('Error Handling', () => {
it('should fail gracefully for non-existent file', () => {
const result = runValidation('/nonexistent/path.html');
expect(result.exitCode).toBe(1);
});
});
});