112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
import { readFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fail, pass, repositoryRoot } from './lib.mjs';
|
|
|
|
function parseCsv(text) {
|
|
const rows = [];
|
|
let row = [];
|
|
let field = '';
|
|
let quoted = false;
|
|
for (let index = 0; index < text.length; index += 1) {
|
|
const character = text[index];
|
|
if (quoted) {
|
|
if (character === '"' && text[index + 1] === '"') {
|
|
field += '"';
|
|
index += 1;
|
|
} else if (character === '"') {
|
|
quoted = false;
|
|
} else {
|
|
field += character;
|
|
}
|
|
} else if (character === '"') {
|
|
quoted = true;
|
|
} else if (character === ',') {
|
|
row.push(field);
|
|
field = '';
|
|
} else if (character === '\n') {
|
|
row.push(field.replace(/\r$/, ''));
|
|
rows.push(row);
|
|
row = [];
|
|
field = '';
|
|
} else {
|
|
field += character;
|
|
}
|
|
}
|
|
if (quoted) throw new Error('Unterminated CSV quote.');
|
|
if (field || row.length) {
|
|
row.push(field.replace(/\r$/, ''));
|
|
rows.push(row);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
const failures = [];
|
|
for (const file of [
|
|
'docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv',
|
|
'docs/quality/PHASE_14_MANUAL_QA_RESULTS_v1.0.csv',
|
|
]) {
|
|
const rows = parseCsv(await readFile(path.join(repositoryRoot, file), 'utf8'));
|
|
const expected = rows[0]?.length ?? 0;
|
|
for (const [index, row] of rows.entries()) {
|
|
if (row.length !== expected) {
|
|
failures.push(`${file}:${index + 1} has ${row.length} columns; expected ${expected}.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const contentType = await readFile(
|
|
path.join(repositoryRoot, 'src/lib/security/content-type.ts'),
|
|
'utf8',
|
|
);
|
|
const route = await readFile(path.join(repositoryRoot, 'src/app/api/demo/route.ts'), 'utf8');
|
|
if (!contentType.includes("=== 'application/json'")) {
|
|
failures.push('Exact JSON media-type comparison is missing.');
|
|
}
|
|
if (!route.includes('isApplicationJson(contentType)')) {
|
|
failures.push('Demo API does not use the exact JSON media-type helper.');
|
|
}
|
|
if (route.includes("startsWith('application/json')")) {
|
|
failures.push('Demo API still accepts look-alike JSON media types by prefix.');
|
|
}
|
|
|
|
const componentVisual = await readFile(
|
|
path.join(repositoryRoot, 'tests/visual/component-system.visual.spec.ts'),
|
|
'utf8',
|
|
);
|
|
const integrationVisual = await readFile(
|
|
path.join(repositoryRoot, 'tests/visual/integrations.visual.spec.ts'),
|
|
'utf8',
|
|
);
|
|
if (!componentVisual.includes("hpc.theme.preference")) {
|
|
failures.push('Component visual test does not use the canonical theme storage key.');
|
|
}
|
|
if (componentVisual.includes('rdg-theme-preference')) {
|
|
failures.push('Component visual test still uses the stale theme storage key.');
|
|
}
|
|
for (const [name, text] of [
|
|
['component-system.visual.spec.ts', componentVisual],
|
|
['integrations.visual.spec.ts', integrationVisual],
|
|
]) {
|
|
if (!text.includes("project.name !== 'chromium'")) {
|
|
failures.push(`${name} is not restricted to canonical Chromium snapshots.`);
|
|
}
|
|
}
|
|
|
|
const workflow = await readFile(path.join(repositoryRoot, '.github/workflows/ci.yml'), 'utf8');
|
|
for (const command of [
|
|
'pnpm test:integration',
|
|
'pnpm test:security',
|
|
'pnpm test:privacy',
|
|
'pnpm test:browser',
|
|
'pnpm test:a11y',
|
|
'pnpm test:visual',
|
|
]) {
|
|
if (!workflow.includes(command)) failures.push(`CI omits required command: ${command}`);
|
|
}
|
|
|
|
if (failures.length) fail(failures);
|
|
else
|
|
pass(
|
|
'Phase 15 evidence CSVs, exact media-type control, deterministic visual setup, and CI test gates are present.',
|
|
);
|