163 lines
8.2 KiB
JavaScript
163 lines
8.2 KiB
JavaScript
// tests/lib/coordinator-contract.test.mjs
|
|
// SKAL-1·4a — deterministic test of the review-coordinator contract subset.
|
|
//
|
|
// Inline reviewer-JSON fixtures (idiom: tests/validators/brief-validator.test.mjs).
|
|
// Hermetic: no LLM, network, time, or randomness.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import {
|
|
severityRank,
|
|
ingest,
|
|
dedupByTriplet,
|
|
judgeFilter,
|
|
reasonablenessFilter,
|
|
computeVerdict,
|
|
runContract,
|
|
} from '../../lib/review/coordinator-contract.mjs';
|
|
|
|
// ---- Pass 1 — dedup --------------------------------------------------------
|
|
|
|
test('dedupByTriplet — genuine cross-reviewer collapse (identical triplet) → 1, raised_by both', () => {
|
|
// Both reviewers flag the SAME (file,line,rule_key) triplet — this is the
|
|
// real collapse the coordinator performs.
|
|
const findings = [
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', title: 'algo from header', reviewer: 'correctness' },
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', title: 'NG1 violated', reviewer: 'conformance' },
|
|
];
|
|
const out = dedupByTriplet(findings);
|
|
assert.equal(out.length, 1, 'identical triplets must collapse to one');
|
|
assert.deepEqual([...out[0].raised_by].sort(), ['conformance', 'correctness']);
|
|
});
|
|
|
|
test('dedupByTriplet — README #2 two DIFFERENT rule_keys at same file:line do NOT collapse', () => {
|
|
// The bakeoff-rich README marks issue #2 dual-flaggable via SECURITY_INJECTION
|
|
// AND NON_GOAL_VIOLATED. Those are DIFFERENT triplets → distinct defects → kept.
|
|
const findings = [
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', reviewer: 'correctness' },
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'NON_GOAL_VIOLATED', severity: 'BLOCKER', reviewer: 'conformance' },
|
|
];
|
|
assert.equal(dedupByTriplet(findings).length, 2);
|
|
});
|
|
|
|
test('dedupByTriplet — survivor is the highest-severity finding in the group', () => {
|
|
const findings = [
|
|
{ file: 'x.mjs', line: 1, rule_key: 'SECURITY_INJECTION', severity: 'MINOR', reviewer: 'correctness' },
|
|
{ file: 'x.mjs', line: 1, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', reviewer: 'correctness' },
|
|
];
|
|
const out = dedupByTriplet(findings);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].severity, 'BLOCKER');
|
|
});
|
|
|
|
test('dedupByTriplet — severity tie breaks toward the conformance reviewer', () => {
|
|
const findings = [
|
|
{ file: 'x.mjs', line: 1, rule_key: 'NON_GOAL_VIOLATED', severity: 'BLOCKER', title: 'corr', reviewer: 'correctness' },
|
|
{ file: 'x.mjs', line: 1, rule_key: 'NON_GOAL_VIOLATED', severity: 'BLOCKER', title: 'conf', reviewer: 'conformance' },
|
|
];
|
|
const out = dedupByTriplet(findings);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].reviewer, 'conformance');
|
|
});
|
|
|
|
// ---- severity ranking (4-tier, incl. synthetic SUGGESTION) -----------------
|
|
|
|
test('severityRank — orders the full 4-tier SEVERITY_VALUES incl. SUGGESTION', () => {
|
|
assert.ok(severityRank('BLOCKER') < severityRank('MAJOR'));
|
|
assert.ok(severityRank('MAJOR') < severityRank('MINOR'));
|
|
assert.ok(severityRank('MINOR') < severityRank('SUGGESTION'));
|
|
});
|
|
|
|
test('dedupByTriplet — SUGGESTION vs MINOR (synthetic) keeps the MINOR survivor', () => {
|
|
// gold corpus has no SUGGESTION; exercise the 4th tier synthetically.
|
|
const findings = [
|
|
{ file: 'x.mjs', line: 2, rule_key: 'MISSING_ERROR_HANDLING', severity: 'SUGGESTION', reviewer: 'correctness' },
|
|
{ file: 'x.mjs', line: 2, rule_key: 'MISSING_ERROR_HANDLING', severity: 'MINOR', reviewer: 'correctness' },
|
|
];
|
|
const out = dedupByTriplet(findings);
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].severity, 'MINOR');
|
|
});
|
|
|
|
// ---- Pass 4 — verdict thresholds -------------------------------------------
|
|
|
|
test('computeVerdict — BLOCKER>=1 → BLOCK, MAJOR-only → WARN, else ALLOW', () => {
|
|
assert.equal(computeVerdict([{ severity: 'BLOCKER' }, { severity: 'MAJOR' }]).verdict, 'BLOCK');
|
|
assert.equal(computeVerdict([{ severity: 'MAJOR' }, { severity: 'MINOR' }]).verdict, 'WARN');
|
|
assert.equal(computeVerdict([{ severity: 'MINOR' }]).verdict, 'ALLOW');
|
|
assert.equal(computeVerdict([{ severity: 'SUGGESTION' }]).verdict, 'ALLOW');
|
|
assert.equal(computeVerdict([]).verdict, 'ALLOW');
|
|
});
|
|
|
|
test('computeVerdict — counts each severity tier', () => {
|
|
const { counts } = computeVerdict([
|
|
{ severity: 'BLOCKER' }, { severity: 'BLOCKER' }, { severity: 'MAJOR' }, { severity: 'MINOR' },
|
|
]);
|
|
assert.deepEqual(counts, { BLOCKER: 2, MAJOR: 1, MINOR: 1, SUGGESTION: 0 });
|
|
});
|
|
|
|
// ---- Pass 3 — reasonableness -----------------------------------------------
|
|
|
|
test('reasonablenessFilter — drops unknown rule_key + citation-less, corrects severity mismatch', () => {
|
|
const r = reasonablenessFilter([
|
|
{ file: 'x.mjs', line: 1, rule_key: 'NOPE_KEY', severity: 'BLOCKER' }, // unknown → drop
|
|
{ file: '', line: 1, rule_key: 'MISSING_TEST', severity: 'MAJOR' }, // no file → drop
|
|
{ file: 'x.mjs', line: -1, rule_key: 'MISSING_TEST', severity: 'MAJOR' }, // line < 0 → drop
|
|
{ file: 'x.mjs', line: 1, rule_key: 'MISSING_TEST', severity: 'MINOR' }, // catalogue is MAJOR → correct, keep
|
|
]);
|
|
assert.equal(r.kept.length, 1);
|
|
assert.equal(r.dropped.length, 3);
|
|
assert.equal(r.kept[0].severity, 'MAJOR');
|
|
assert.equal(r.kept[0].original_severity, 'MINOR');
|
|
});
|
|
|
|
// ---- Pass 2 — judge --------------------------------------------------------
|
|
|
|
test('judgeFilter — drops over-long title and empty recommended_action', () => {
|
|
const j = judgeFilter([
|
|
{ file: 'x.mjs', line: 1, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 'x'.repeat(101) }, // too long → drop
|
|
{ file: 'x.mjs', line: 2, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 'ok', recommended_action: ' ' }, // empty action → drop
|
|
{ file: 'x.mjs', line: 3, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 'ok' }, // keep (no action field is fine)
|
|
]);
|
|
assert.equal(j.kept.length, 1);
|
|
assert.equal(j.dropped.length, 2);
|
|
});
|
|
|
|
// ---- ingest ----------------------------------------------------------------
|
|
|
|
test('ingest — collects findings from valid payloads, skips invalid ones', () => {
|
|
const { findings, skipped } = ingest([
|
|
{ reviewer: 'correctness', findings: [{ file: 'x.mjs', line: 1, rule_key: 'MISSING_TEST', severity: 'MAJOR' }] },
|
|
{ reviewer: 'bad', findings: [{ line: 1, rule_key: 'MISSING_TEST', severity: 'MAJOR' }] }, // missing file → invalid → skipped
|
|
]);
|
|
assert.equal(findings.length, 1);
|
|
assert.equal(findings[0].reviewer, 'correctness');
|
|
assert.equal(skipped.length, 1);
|
|
});
|
|
|
|
// ---- end-to-end ------------------------------------------------------------
|
|
|
|
test('runContract — two reviewers, dual-flag triplet collapses, verdict BLOCK', () => {
|
|
const result = runContract([
|
|
{ reviewer: 'correctness', findings: [
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', title: 'algo from header' },
|
|
{ file: 'lib/auth/refresh.mjs', line: 0, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 'no concurrent test' },
|
|
] },
|
|
{ reviewer: 'conformance', findings: [
|
|
{ file: 'lib/auth/jwt.mjs', line: 19, rule_key: 'SECURITY_INJECTION', severity: 'BLOCKER', title: 'NG1' }, // collapses with correctness's
|
|
{ file: 'lib/handlers/login.mjs', line: 17, rule_key: 'UNIMPLEMENTED_CRITERION', severity: 'BLOCKER', title: '200 not 401' },
|
|
] },
|
|
]);
|
|
assert.equal(result.verdict, 'BLOCK');
|
|
assert.equal(result.findings.length, 3, '4 raw findings, jwt:19 SECURITY_INJECTION collapses → 3');
|
|
const jwt = result.findings.find((f) => f.file === 'lib/auth/jwt.mjs');
|
|
assert.deepEqual([...jwt.raised_by].sort(), ['conformance', 'correctness']);
|
|
});
|
|
|
|
test('runContract — deterministic: identical input yields identical output', () => {
|
|
const input = [
|
|
{ reviewer: 'correctness', findings: [{ file: 'a.mjs', line: 1, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 't' }] },
|
|
{ reviewer: 'conformance', findings: [{ file: 'b.mjs', line: 2, rule_key: 'UNIMPLEMENTED_CRITERION', severity: 'BLOCKER', title: 'u' }] },
|
|
];
|
|
assert.deepEqual(runContract(input), runContract(input));
|
|
});
|