101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
type CoverageMetrics = Record<string, { pct: number }>;
|
|
type CoveragePolicy = Readonly<{
|
|
summary: Record<string, number>;
|
|
criticalModules: readonly Readonly<{
|
|
path: string;
|
|
minimum: Record<string, number>;
|
|
}>[];
|
|
}>;
|
|
type CoverageSummary = Record<string, CoverageMetrics>;
|
|
type CoverageResult = Readonly<{
|
|
scope: string;
|
|
metric: string;
|
|
threshold: number;
|
|
received: number | undefined;
|
|
passed: boolean;
|
|
}>;
|
|
|
|
function argumentValue(name: string, fallback: string): string {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 && process.argv[index + 1]
|
|
? process.argv[index + 1]
|
|
: fallback;
|
|
}
|
|
|
|
const policyPath = argumentValue(
|
|
"--policy",
|
|
"config/testing/risk-coverage.json",
|
|
);
|
|
const summaryPath = argumentValue(
|
|
"--summary",
|
|
"artifacts/tests/coverage/coverage-summary.json",
|
|
);
|
|
const artifactPath = argumentValue(
|
|
"--artifact",
|
|
"artifacts/quality/risk-coverage.json",
|
|
);
|
|
const policy = JSON.parse(
|
|
await readFile(policyPath, "utf8"),
|
|
) as CoveragePolicy;
|
|
const summary = JSON.parse(
|
|
await readFile(summaryPath, "utf8"),
|
|
) as CoverageSummary;
|
|
const failures: string[] = [];
|
|
const results: CoverageResult[] = [];
|
|
|
|
function evaluate(
|
|
scope: string,
|
|
actual: CoverageMetrics,
|
|
minimum: Record<string, number>,
|
|
): void {
|
|
for (const [metric, threshold] of Object.entries(minimum)) {
|
|
const received = actual?.[metric]?.pct;
|
|
const passed =
|
|
typeof received === "number" &&
|
|
Number.isFinite(received) &&
|
|
received >= threshold;
|
|
results.push({ scope, metric, threshold, received, passed });
|
|
if (!passed) {
|
|
failures.push(
|
|
`${scope}.${metric} expected >= ${threshold}, received ${String(received)}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
evaluate("total", summary.total, policy.summary);
|
|
for (const modulePolicy of policy.criticalModules) {
|
|
const key = Object.keys(summary).find(
|
|
(candidate) =>
|
|
candidate !== "total" &&
|
|
candidate.replaceAll("\\", "/").endsWith(`/${modulePolicy.path}`),
|
|
);
|
|
if (!key) {
|
|
failures.push(`critical module missing from coverage: ${modulePolicy.path}`);
|
|
continue;
|
|
}
|
|
evaluate(modulePolicy.path, summary[key], modulePolicy.minimum);
|
|
}
|
|
|
|
const artifact = {
|
|
schemaVersion: 1,
|
|
policy: policyPath,
|
|
summary: summaryPath,
|
|
status: failures.length === 0 ? "PASS" : "FAIL",
|
|
results,
|
|
failures,
|
|
};
|
|
await mkdir(path.dirname(artifactPath), { recursive: true });
|
|
await writeFile(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
|
|
if (failures.length > 0) {
|
|
process.stderr.write(`Risk coverage failed:\n- ${failures.join("\n- ")}\n`);
|
|
process.exit(1);
|
|
}
|
|
process.stdout.write(
|
|
`Risk coverage: PASS (${results.length} scoped thresholds)\n`,
|
|
);
|