diff --git a/tests/validate-architectures.test.mjs b/tests/validate-architectures.test.mjs index 4c315e2e..a56398a3 100644 --- a/tests/validate-architectures.test.mjs +++ b/tests/validate-architectures.test.mjs @@ -182,3 +182,25 @@ test('rejects a sourceUrl carrying a password-only userinfo component', () => { assert.equal(result.status, 1); assert.match(result.stderr, /sourceUrl must be an https URL/); }); + +// record.id doubles as the error label, so a record that is missing it falls +// back to ''. Line coverage cannot see that fallback: the rest of +// each `path: record.id || ''` line runs on every malformed record. +// Both fallback sites fire for one id-less record, and the reported label is +// what a maintainer reads to find the offending entry. +test('labels an id-less record in every error it raises', () => { + const result = runScriptWithFixtures( + SCRIPT, + catalogFixture([{ title: 'Acme Platform', organization: 'Acme Corp' }]), + ); + assert.equal(result.status, 1); + assert.match( + result.stderr, + /\[error\] : missing id, title, or organization/, + ); + assert.match( + result.stderr, + /\[error\] : sourceUrl must be an https URL/, + ); + assert.doesNotMatch(result.stderr, /\[error\] undefined:/); +}); diff --git a/tests/validate-case-studies.test.mjs b/tests/validate-case-studies.test.mjs index d68c3186..c6ca9ef5 100644 --- a/tests/validate-case-studies.test.mjs +++ b/tests/validate-case-studies.test.mjs @@ -259,3 +259,33 @@ test('rejects an entry missing industries entirely', () => { assert.equal(result.status, 1); assert.match(result.stderr, /industries must be an array/); }); + +// The error label is `entry.id ?? entry.slug ?? 'unknown'`. Both fallback arms +// sit mid-line, so line coverage reports them as run whenever a malformed +// entry is validated at all; only an entry that actually omits `id` reaches +// the slug arm, and only one omitting `slug` too reaches the literal. +test('falls back to the slug when an entry has no id', () => { + const result = runScriptWithFixtures( + SCRIPT, + fixture({ + ...validData, + caseStudies: [{ ...validEntry, id: undefined }], + }), + ); + assert.equal(result.status, 1); + assert.match(result.stderr, /\[error\] example-org: duplicate or missing id/); + assert.doesNotMatch(result.stderr, /\[error\] unknown:/); +}); + +test('falls back to "unknown" when an entry has neither id nor slug', () => { + const result = runScriptWithFixtures( + SCRIPT, + fixture({ + ...validData, + caseStudies: [{ ...validEntry, id: undefined, slug: undefined }], + }), + ); + assert.equal(result.status, 1); + assert.match(result.stderr, /\[error\] unknown: duplicate or missing id/); + assert.doesNotMatch(result.stderr, /\[error\] undefined:/); +}); diff --git a/tests/validate-launch-metrics.test.mjs b/tests/validate-launch-metrics.test.mjs index 20715c2f..c760721d 100644 --- a/tests/validate-launch-metrics.test.mjs +++ b/tests/validate-launch-metrics.test.mjs @@ -334,3 +334,34 @@ test('rejects a signal sourceUrl carrying a password-only userinfo component', ( assert.equal(result.status, 1); assert.match(result.stderr, /sourceUrl must not carry a userinfo component/); }); + +// `data.signals || []` keeps a file with no signals key from throwing on +// .length, so the count check reports it as a data problem instead of the +// script dying with a TypeError. The `|| []` arm is invisible to line +// coverage because the assignment line runs for every file. +test('reports a missing signals key as a count failure, not a crash', () => { + const withoutSignals = { ...validData }; + delete withoutSignals.signals; + const result = runScriptWithFixtures(SCRIPT, fixture(withoutSignals)); + assert.equal(result.status, 1); + assert.match(result.stderr, /signals must define 3-5 signals, got 0/); + assert.doesNotMatch(result.stderr, /TypeError/); +}); + +// A signal with no id has no natural label, so every error it raises is +// filed under '(missing id)' rather than the empty string `signal.id` would +// otherwise contribute. +test('labels an id-less signal "(missing id)" in every error it raises', () => { + const anonymous = { ...validSignal }; + delete anonymous.id; + const result = runScriptWithFixtures( + SCRIPT, + fixture({ ...validData, signals: [...validData.signals, anonymous] }), + ); + assert.equal(result.status, 1); + assert.match( + result.stderr, + /\[error\] \(missing id\): duplicate or missing signal id/, + ); + assert.doesNotMatch(result.stderr, /\[error\] undefined:/); +}); diff --git a/tests/validate-metrics.test.mjs b/tests/validate-metrics.test.mjs index 84ef9504..52afd042 100644 --- a/tests/validate-metrics.test.mjs +++ b/tests/validate-metrics.test.mjs @@ -398,3 +398,16 @@ test('accepts a valid breakdown', () => { ); assert.equal(result.status, 0, result.stderr); }); + +// `data.metrics || []` lets the per-metric loop skip a file whose collector +// never wrote a metrics array, so the failure reported is the one that caused +// it rather than an iteration TypeError. The `|| []` arm sits mid-line, so +// line coverage reports it as run on every valid file. +test('reports a metrics-less file by its real failure, not an iteration crash', () => { + const truncated = { ...validData, generated: false }; + delete truncated.metrics; + const result = runScriptWithFixtures(SCRIPT, metricsFixture(truncated)); + assert.equal(result.status, 1); + assert.match(result.stderr, /generated must be true/); + assert.doesNotMatch(result.stderr, /TypeError/); +});