Test that each contract check catches its target issue and stays quiet on valid cases.
Add "Contract coverage tests" from ntent to this repo.
Fetch https://ntent.app/r/f/check-contract-coverage-test as plain text. Write it verbatim to scripts/check-contract-coverage.test.mjs.
It needs check-contract-coverage.mjs and contract.json. If this repo does not have them, stop and tell me.
Then check it: node --test passes. Break one reference in the contract by hand and exactly one test goes red, naming the check that caught it.Reads https://ntent.app/r/f/check-contract-coverage-test
Read the code
check-contract-coverage.test.mjsscripts/check-contract-coverage.test.mjs
545 lines
/**
* Tests for the reference checks, which are the half of this script that gates
* a commit. The coverage number is a nag and reads the filesystem, so it is
* checked by running it; these three are pure functions over a parsed contract
* and are the part a person cannot verify by reading.
*
* What is worth locking down is narrow. Not that a finding is worded well, but
* that each check FIRES on the shape it exists for and stays quiet on the shape
* it does not, because a check that never fires and a check that always fires
* are both deleted within a month.
*
* The last two tests are the contract with the pre-commit hook: the gate is the
* exit code, not the output, and the hook has to work in a repo that has never
* installed TypeScript.
*
* Run, installed: node --test scripts/check-contract-coverage.test.mjs
* Run, in this repo: node --test files/scripts/check-contract-coverage.test.mjs
*/
import test from 'node:test'
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { checkReferences, refTarget, fieldName, stripComments, VOCABULARY } from './check-contract-coverage.mjs'
const here = dirname(fileURLToPath(import.meta.url))
const SCRIPT = join(here, 'check-contract-coverage.mjs')
/**
* The contract these tests read, wherever this file ended up.
*
* This file ships to your repo as scripts/check-contract-coverage.test.mjs, and
* it used to load '../examples/contract.json' — a path that only exists inside
* the package it was authored in. Installed, it threw ENOENT before the first
* assertion, which meant the tests passed here and were dead everywhere they
* were actually needed. Any test that reads a file has to say where that file is
* in a REPO, not in the source tree it was written in.
*
* Installed, the second candidate wins and these run against your own contract,
* which is the better subject anyway: it is the file your gate is protecting.
*/
const CONTRACT_CANDIDATES = [
join(here, '..', 'examples', 'contract.json'), // this package's own layout
join(here, '..', 'contract.json'), // installed: scripts/ sits beside it
join(process.cwd(), 'contract.json'), // run from anywhere else in the repo
]
const contractPath = CONTRACT_CANDIDATES.find(existsSync)
if (!contractPath) {
throw new Error(
`No contract.json found. Looked in:\n ${CONTRACT_CANDIDATES.join('\n ')}\n` +
`These tests check a real contract, so put yours at the repo root.`
)
}
const EXAMPLE = JSON.parse(readFileSync(contractPath, 'utf8'))
/** The shipped example, with one thing broken. Every case reads the same. */
const withEdit = (edit) => {
const contract = structuredClone(EXAMPLE)
edit(contract)
return checkReferences(contract)
}
const journey = (contract, id) => contract.journeys.find((j) => j.id === id)
const where = (findings) => findings.map((f) => f.where)
/**
* A complete acceptance record. `built` is refused without one, so every test
* that builds a journey to probe some OTHER check has to carry it, or the
* finding it is looking for arrives with a second one about acceptance.
*/
const ACCEPTED = { on: '2026-09-01', by: 'Test', commit: 'abc1234', evidence: ['manual: exercised in the test'] }
const build = (j) => Object.assign(j, { state: 'built', accepted: ACCEPTED })
// ------------------------------------------------------------ the example ---
test('the shipped example resolves, so a fresh install starts green', () => {
assert.deepEqual(checkReferences(EXAMPLE), [])
})
test('every finding names a fix, because a gate without a remedy gets bypassed', () => {
const findings = withEdit((c) => {
c.rules[0].applies_to = ['credit_note']
c.journeys[0].actor = 'ops_admin'
c.entities[0].fields.push({ type: 'money' })
build(journey(c, 'refund-payment'))
})
assert.equal(findings.length, 4, 'four unrelated breakages report separately, not first-one-wins')
assert.equal(new Set(where(findings)).size, 4)
for (const f of findings) {
assert.ok(f.fix && f.fix.length > 20, `${f.where} has no usable fix line`)
assert.ok(f.message, `${f.where} has no message`)
}
})
// ------------------------------------------ 0. identity and vocabulary ---
test('two entities with one id is a finding, because every reference to it resolves to whichever one you picked', () => {
const findings = withEdit((c) => c.entities.push({ id: 'invoice', fields: ['nonce'] }))
assert.deepEqual(where(findings), ['entities.invoice'])
assert.match(findings[0].message, /declared twice/)
})
test('a duplicate is caught in every pillar, not only entities', () => {
const findings = withEdit((c) => {
c.journeys.push({ id: 'pay-invoice', actor: 'customer', entities: ['invoice'], state: 'unbuilt' })
c.rules.push({ ...c.rules[0] })
c.open_questions.push({ ...c.open_questions[0] })
})
assert.deepEqual(where(findings), ['journeys.pay-invoice', 'rules.BR-014', 'open_questions.OQ-009'])
})
test('a state or status outside the vocabulary is a finding, since every check downstream would read it leniently', () => {
const findings = withEdit((c) => {
journey(c, 'pay-invoice').state = 'done'
c.open_questions[0].status = 'closed'
})
assert.deepEqual(where(findings), ['journeys.pay-invoice.state', 'open_questions.OQ-009.status'])
assert.match(findings[0].fix, new RegExp(VOCABULARY['journeys[].state'].join(', ')))
})
test('an item with no id at all is a finding rather than a crash', () => {
const findings = withEdit((c) => c.actors.push({ description: 'anonymous' }))
assert.deepEqual(where(findings), ['actors[4]'])
})
// ------------------------------------------ 1. cross-references resolve ---
test('a rule binding an entity nobody declared is a finding', () => {
const findings = withEdit((c) => c.rules[0].applies_to.push('credit_note'))
assert.deepEqual(where(findings), ['rules.BR-014.applies_to'])
assert.match(findings[0].message, /does not resolve to an entity/)
assert.match(findings[0].fix, /Known: invoice, customer, subscription, payment/)
})
test('a journey naming an actor nobody declared is a finding', () => {
const findings = withEdit((c) => (c.journeys[0].actor = 'ops_admin'))
assert.deepEqual(where(findings), ['journeys.create-invoice.actor'])
assert.match(findings[0].message, /does not resolve to an actor/)
})
test('an access row is checked on both axes, so a typo in either is caught', () => {
const findings = withEdit((c) => {
c.access.matrix[0].actor = 'finance-admin'
c.access.matrix[1].entity = 'invoices'
})
assert.deepEqual(where(findings), ['access.matrix[0].actor', 'access.matrix[1].entity'])
})
test('a constraint or entitlement pointing at a journey that no longer exists is a finding', () => {
const findings = withEdit((c) => {
c.constraints[1].affects = ['journey:pay-invoices']
c.entitlements.gates[0].gates = ['journey:brand-invoice']
})
assert.deepEqual(where(findings), ['entitlements.ENT-001.gates', 'constraints.C-002.affects'])
})
test('a prefix decides which pillar a bare id is looked up in', () => {
assert.deepEqual(refTarget('BR-014'), ['rule', 'BR-014'])
assert.deepEqual(refTarget('OQ-009'), ['question', 'OQ-009'])
assert.deepEqual(refTarget('ENT-002'), ['entitlement', 'ENT-002'])
assert.deepEqual(refTarget('C-001'), ['constraint', 'C-001'])
assert.deepEqual(refTarget('invoice'), ['entity', 'invoice'], 'unprefixed means entity')
assert.deepEqual(refTarget('journey:pay-invoice'), ['journey', 'pay-invoice'])
})
test('a missing pillar says to add the pillar, not to check the spelling', () => {
const findings = withEdit((c) => delete c.actors)
assert.ok(findings.length > 0)
assert.match(findings[0].fix, /There is no actors array/)
})
test('a field object with no name is a finding, since everything downstream reads the name', () => {
const findings = withEdit((c) => c.entities[0].fields.push({ type: 'money' }))
assert.equal(findings.length, 1)
assert.match(findings[0].where, /entities\.invoice\.fields\[\d+\]/)
})
test('a field is a bare string or an object, and both give up the same name', () => {
assert.equal(fieldName('tax_rate'), 'tax_rate')
assert.equal(fieldName({ name: 'tax_rate', type: 'decimal' }), 'tax_rate')
assert.equal(fieldName({ type: 'decimal' }), undefined, 'so the check above can catch it')
})
// -------------------------------------------- 2. rule ids cited in prose ---
test('renaming a rule that a journey gap still cites is a finding', () => {
const findings = withEdit((c) => (c.rules[0].id = 'BR-114'))
assert.ok(
findings.some((f) => f.where === 'cited in prose' && f.message.startsWith('BR-014')),
'the citation lives in a sentence, which is where renames get missed'
)
})
test('a retired id still resolves once the rule that absorbed it says so', () => {
const findings = withEdit((c) => (c.rules[1].supersedes = []))
assert.deepEqual(
findings.map((f) => f.message),
['BR-007 is referred to but is not in rules', 'BR-018 is referred to but is not in rules'],
'without supersedes, a consolidated id dies silently'
)
assert.deepEqual(checkReferences(EXAMPLE), [], 'with it, both resolve')
})
// ------------------------------------------------------ 3. open questions ---
test('citing a question that is not in the file is a finding', () => {
const findings = withEdit((c) => (c.open_questions = c.open_questions.filter((q) => q.id !== 'OQ-011')))
assert.ok(findings.some((f) => f.message.startsWith('OQ-011')))
})
test('a journey cannot be built while a blocking question it stands on is open', () => {
const findings = withEdit((c) => (build(journey(c, 'refund-payment'))))
assert.deepEqual(where(findings), ['journeys.refund-payment'])
assert.match(findings[0].message, /marked built while OQ-011 is open and blocking/)
})
test('answering the question is what unblocks it, and so is dropping the blocking flag', () => {
const answered = withEdit((c) => {
build(journey(c, 'refund-payment'))
c.open_questions.find((q) => q.id === 'OQ-011').status = 'resolved'
})
assert.deepEqual(answered, [], 'resolved is the honest way out')
const unflagged = withEdit((c) => {
build(journey(c, 'refund-payment'))
c.open_questions.find((q) => q.id === 'OQ-011').blocking = false
})
assert.deepEqual(unflagged, [], 'so is deciding it does not block, which is a visible edit')
})
test('a question is only blocking where it was declared blocking', () => {
const findings = withEdit((c) => {
build(journey(c, 'refund-payment'))
journey(c, 'refund-payment').gap = undefined
const q = c.open_questions.find((q) => q.id === 'OQ-011')
q.blocking = false
q.status = 'open'
})
assert.deepEqual(findings, [], 'an open question that nobody called blocking does not stop a build')
})
test('an unbuilt journey may stand on as many open questions as it likes', () => {
assert.deepEqual(checkReferences(EXAMPLE), [], 'refund-payment is unbuilt and cites OQ-011')
})
test('a blocking question on an entitlement gate blocks the journeys that gate names', () => {
// OQ-009 blocks ENT-002, and ENT-002 gates create-invoice. The gate's
// at-limit state is undecided, so the screen that shows it cannot be done.
const findings = withEdit((c) => build(journey(c, 'create-invoice')))
assert.deepEqual(where(findings), ['journeys.create-invoice'])
assert.match(findings[0].message, /OQ-009.*blocks ENT-002, which the journey stands on/)
})
test('a blocking question on a rule blocks a journey only where the journey says it depends on that rule', () => {
// The example's gap line cites OQ-009 in prose, which is its own reason to
// block. Cleared here so the only path left is the dependency.
const built = (c) => Object.assign(journey(c, 'create-invoice'), { state: 'built', gap: undefined, accepted: ACCEPTED })
const dependent = withEdit((c) => {
built(c)
c.open_questions.find((q) => q.id === 'OQ-009').blocks = ['BR-014']
})
assert.deepEqual(where(dependent), ['journeys.create-invoice'], 'create-invoice lists BR-014 in depends_on')
assert.match(dependent[0].message, /blocks BR-014, which the journey stands on/)
const unrelated = withEdit((c) => {
built(c)
c.open_questions.find((q) => q.id === 'OQ-009').blocks = ['BR-030']
})
assert.deepEqual(unrelated, [], 'BR-030 applies to invoice too, and that is inference, not a dependency')
})
test('a blocking question on an entity blocks the built journeys that list that entity', () => {
const findings = withEdit((c) => {
c.open_questions.find((q) => q.id === 'OQ-009').blocks = ['customer']
})
assert.deepEqual(where(findings), [], 'no built journey lists customer')
const hit = withEdit((c) => {
c.open_questions.find((q) => q.id === 'OQ-009').blocks = ['payment']
})
assert.deepEqual(where(hit).sort(), ['journeys.pay-invoice', 'journeys.settle-charge'])
})
test('depends_on is resolved like any other reference', () => {
const findings = withEdit((c) => (journey(c, 'create-invoice').depends_on = ['C-999']))
assert.deepEqual(where(findings), ['journeys.create-invoice.depends_on'])
assert.match(findings[0].message, /does not resolve to a constraint/)
})
// --------------------------------------------------------- 4. acceptance ---
test('the shipped example says who found each built journey working', () => {
const built = EXAMPLE.journeys.filter((j) => j.state === 'built')
assert.ok(built.length > 0, 'the example has something built, or none of this is exercised')
for (const j of built) assert.ok(j.accepted, `${j.id} is built and carries its record`)
})
test('a journey marked built with no acceptance record is a finding', () => {
const findings = withEdit((c) => (journey(c, 'pay-invoice').accepted = undefined))
assert.deepEqual(where(findings), ['journeys.pay-invoice'])
assert.match(findings[0].message, /no acceptance record/)
assert.match(findings[0].fix, /"accepted"/, 'the fix shows the shape rather than describing it')
})
test('an incomplete record names the fields it is missing', () => {
const findings = withEdit((c) => (journey(c, 'pay-invoice').accepted = { on: '2026-09-04', evidence: [] }))
assert.deepEqual(where(findings), ['journeys.pay-invoice.accepted'])
assert.match(findings[0].message, /missing by, commit, evidence/)
})
test('a piece of evidence has to say something', () => {
const findings = withEdit((c) => (journey(c, 'pay-invoice').accepted.evidence = ['']))
assert.match(findings[0].message, /missing evidence/)
})
test('the date has to be a date, so the record can be read against the log', () => {
const findings = withEdit((c) => (journey(c, 'pay-invoice').accepted.on = 'last Thursday'))
assert.deepEqual(where(findings), ['journeys.pay-invoice.accepted.on'])
})
test('a partial journey needs no record, and an unfinished one may keep an old one', () => {
const partial = withEdit((c) =>
Object.assign(journey(c, 'pay-invoice'), { state: 'partial', gap: 'the receipt download is a 404', accepted: undefined })
)
assert.deepEqual(partial, [], 'moving a journey back is the honest way out and costs nothing')
const kept = withEdit((c) => (journey(c, 'send-invoice').accepted = ACCEPTED))
assert.deepEqual(kept, [], 'a record on an unfinished journey is history, not a claim')
})
test('a record from before the model moved on is reported as stale, not failed', (t) => {
// settle-charge in the example was accepted against 0.3.0 and the contract
// is at 0.4.0. That is worth knowing and is not a contradiction: the journey
// may still be fine. So it is a note in the report and never a finding.
assert.deepEqual(checkReferences(EXAMPLE), [])
const json = runRefs(EXAMPLE, ['--json'], { refs: false })
if (json.code === 2) return t.skip('TypeScript is not installed here, so the full report cannot run')
const byId = Object.fromEntries(JSON.parse(json.stdout).acceptance.map((a) => [a.id, a]))
assert.equal(byId['settle-charge'].contractMoved, true)
assert.equal(byId['pay-invoice'].contractMoved, false)
assert.equal(byId['pay-invoice'].commitKnown, null, 'a scratch directory has no git to ask, and that is not a no')
})
// ------------------------------------------------------- the UI pointer ---
test('a trailing comment does not count as a mention', () => {
// `return null } // total_amount` used to score the field as on screen,
// because only comments that started a line were stripped.
const src = 'export default function Page() { return null } // total_amount\n'
assert.doesNotMatch(stripComments(src), /total_amount/)
})
test('stripping comments leaves strings, urls and regex literals alone', () => {
const src = [
"const u = 'https://example.com/total_amount' // not this one",
'const r = /\\/\\/(total)/ /* nor this */',
'const t = `a // ${b}` ',
'const d = a / b // ratio',
].join('\n')
const out = stripComments(src)
assert.match(out, /https:\/\/example\.com\/total_amount/)
assert.match(out, /\/\\\/\\\/\(total\)\//, 'the regex literal survives')
assert.match(out, /`a \/\/ \$\{b\}`/, 'the template literal survives')
assert.doesNotMatch(out, /not this one|nor this|ratio/)
})
// ------------------------------------------------- the gate, end to end ---
const runRefs = (contract, extra = [], { refs = true } = {}) => {
const dir = mkdtempSync(join(tmpdir(), 'contract-'))
writeFileSync(join(dir, 'contract.json'), JSON.stringify(contract))
const args = [SCRIPT, ...(refs ? ['--refs'] : []), ...extra]
try {
const stdout = execFileSync(process.execPath, args, { cwd: dir, encoding: 'utf8' })
return { code: 0, stdout }
} catch (e) {
return { code: e.status, stdout: e.stdout }
}
}
test('a broken reference fails in --json too', () => {
const broken = structuredClone(EXAMPLE)
build(journey(broken, 'refund-payment'))
const json = runRefs(broken, ['--json'])
assert.equal(json.code, 1, 'the exit code cannot depend on the output format')
assert.equal(JSON.parse(json.stdout).broken.length, 1)
})
test('the coverage floor is enforced in --json as well as in the table', (t) => {
// The bug: the JSON branch returned before the threshold comparison, so a CI
// job asking for a machine-readable report AND enforcement got exit 0 below
// its own floor. Asking for a different output format is not asking for a
// different answer, and the format the robots read decides the build.
//
// The temp dir has a contract and no source, so type coverage is 0% and any
// floor above zero has to fail.
const text = runRefs(EXAMPLE, ['--check', '--min', '1'], { refs: false })
if (text.code === 2) {
// The full run reads types, so it needs a compiler. --refs, which is the
// half that gates a commit, does not, and it is covered below either way.
return t.skip('TypeScript is not installed here, so the type layer cannot run')
}
const json = runRefs(EXAMPLE, ['--check', '--min', '1', '--json'], { refs: false })
assert.equal(text.code, 1, '0% is below a 100% floor')
assert.equal(json.code, 1, 'and it is still below it when you ask for JSON')
const report = JSON.parse(json.stdout)
assert.equal(report.belowFloor, true)
assert.equal(report.ok, false, 'the report says so as well as the exit code')
// The example maps three of its four entities. Under --check the fourth is
// a failure until somebody maps it or excuses it with a reason, because the
// number is not about the whole contract until then.
const unmapped = runRefs(EXAMPLE, ['--check', '--min', '0', '--json'], { refs: false })
assert.equal(unmapped.code, 1)
assert.equal(JSON.parse(unmapped.stdout).unmappedScope, true, 'payment is in the contract and in no map')
// Only the mapped entities, and nothing pointing at the fourth.
const mapped = { entities: EXAMPLE.entities.filter((e) => e.id !== 'payment') }
const green = runRefs(mapped, ['--check', '--min', '0', '--json'], { refs: false })
assert.equal(green.code, 0, 'and a floor it clears passes, so this is not just always-fail')
assert.equal(JSON.parse(green.stdout).unmappedScope, false)
})
test('a name in the right place is not coverage: the counterexample from the methodology review', (t) => {
// A money field declared boolean, a fixture carrying the key under a
// different object with a null value, and a page that returns null with the
// field name in a trailing comment. This scored 100% at every layer.
const dir = mkdtempSync(join(tmpdir(), 'contract-'))
const write = (rel, text) => {
mkdirSync(join(dir, dirname(rel)), { recursive: true })
writeFileSync(join(dir, rel), text)
}
const contract = {
entities: [
{ id: 'invoice', fields: [{ name: 'total_amount', type: 'money', required: true }, 'due_date'] },
{ id: 'audit_event', fields: ['actor', 'at'] },
],
journeys: [],
}
write('contract.json', JSON.stringify(contract))
write('src/types/invoice.ts', 'export interface Invoice { total_amount: boolean; due_date: string }')
write('fixtures/invoice.json', JSON.stringify({ customer: { total_amount: null }, due_date: '2026-01-01' }))
write('src/app/invoices/page.tsx', 'export default function Page() { return null } // total_amount due_date')
const run = (extra = []) => {
try {
return { code: 0, stdout: execFileSync(process.execPath, [SCRIPT, '--json', ...extra], { cwd: dir, encoding: 'utf8' }) }
} catch (e) {
return { code: e.status, stdout: e.stdout }
}
}
const first = run(['--check', '--min', '1'])
if (first.code === 2) return t.skip('TypeScript is not installed here, so the type layer cannot run')
const report = JSON.parse(first.stdout)
const invoice = report.rows.find((r) => r.id === 'invoice')
assert.equal(invoice.type, 50, 'total_amount is declared boolean, which a money field cannot be')
assert.deepEqual(invoice.mismatched, [{ field: 'total_amount', contract: 'money', declared: 'boolean' }])
assert.equal(invoice.fixture, 50, 'a null under another object is not the field seen with data')
assert.equal(invoice.ui, 0, 'a trailing comment is not a mention')
assert.equal(report.ok, false)
// The unmapped entity is outside the number and, under --check, a failure.
assert.deepEqual(report.unmeasured, ['audit_event'])
assert.deepEqual(report.measured, { fields: 2, of: 4 })
assert.equal(report.unmappedScope, true)
assert.equal(first.code, 1)
})
test('a field on a base interface counts, and a field the contract keeps off screen leaves the UI column', (t) => {
const dir = mkdtempSync(join(tmpdir(), 'contract-'))
const write = (rel, text) => {
mkdirSync(join(dir, dirname(rel)), { recursive: true })
writeFileSync(join(dir, rel), text)
}
write(
'contract.json',
JSON.stringify({
entities: [
{
id: 'invoice',
fields: ['invoice_number', { name: 'total_amount', type: 'money' }, { name: 'created_at', ui: false, why: 'audit only' }],
},
],
})
)
write(
'src/types/invoice.ts',
[
'interface Timestamped { created_at: string }',
'type Numbered = { invoice_number: string }',
'export interface Invoice extends Timestamped { total_amount: number }',
'export type InvoiceDetail = Numbered & Invoice',
].join('\n')
)
write('src/app/invoices/page.tsx', 'export const Page = (i: Invoice) => <b>{i.invoice_number} {i.total_amount}</b>')
let stdout
try {
stdout = execFileSync(process.execPath, [SCRIPT, '--json'], { cwd: dir, encoding: 'utf8' })
} catch (e) {
if (e.status === 2) return t.skip('TypeScript is not installed here')
stdout = e.stdout
}
const invoice = JSON.parse(stdout).rows[0]
assert.equal(invoice.type, 100, 'inherited and intersected members are declared members')
assert.equal(invoice.ui, 100, 'created_at is off screen by decision, so it is not a UI gap')
assert.deepEqual(invoice.offScreen, [{ field: 'created_at', why: 'audit only' }])
})
test('--refs exits 0 on a contract that resolves and 1 on one that does not', () => {
assert.equal(runRefs(EXAMPLE).code, 0, 'the exit code IS the gate; the output is for the human')
const broken = structuredClone(EXAMPLE)
journey(broken, 'refund-payment').state = 'built'
const run = runRefs(broken)
assert.equal(run.code, 1)
assert.match(run.stdout, /BROKEN REFERENCES/)
assert.match(run.stdout, /fix: Answer OQ-011/)
})
test('--refs never loads TypeScript, so the hook works before the first install', () => {
const source = readFileSync(SCRIPT, 'utf8')
assert.doesNotMatch(
source,
/^const ts = require\('typescript'\)/m,
'a top-level require would crash --refs in a repo that has not installed it'
)
assert.doesNotMatch(
source,
/^import .*['"]typescript['"]/m,
'and neither would a top-level import'
)
assert.match(
source,
/const typescript = \(\) => \{[\s\S]*require\('typescript'\)/,
'it is loaded inside the accessor, on the first parse that needs it'
)
})
Success check: node --test passes. Break one reference in the contract by hand and exactly one test goes red, naming the check that caught it.