Path coverage: every branch from acquisition to the first grade callback

Audited the corridor rather than trusting the recorder. Nine upstream operations
sit between acquisition success and gradeAndCacheSlate — game binding, per-date
schedule fetch, roster index, attachEventIdentity, book-price capture, team-stat
refresh, hits-factor context, matchup keys — each with its own catch. NONE of
them early-returns, so the corridor always reaches the grader; but seven of them
were SILENT, and two could be misattributed.

COVERAGE WAS INCOMPLETE. Closed:
  * BINDING — bound / unresolved / already-had, and its throw
  * SCHEDULE — dates requested, game count, and its throw. A schedule outage
    previously surfaced as an EVENT IDENTITY error because it lands in that
    catch; it now records SCHEDULE_STAGE_ERROR and rethrows unchanged.
  * ROSTER — indexed players/teams/failed and evidence-date-validity, and its
    throw. Its catch only console.warn'd, so this stage was entirely invisible.
  * DEDUPE per-reason accounting off the filter's OWN branches: invalid fields /
    non-model book / duplicate identity / capped / not examined, plus
    model-book eligibility. Counts reconcile to the input exactly.
  * GRADE BOUNDARY — `reached` is derived from a candidate count and proves
    nothing. GRADE_LOOP_ENTERED, FIRST_GRADEBESTSIDE_STARTED and
    FIRST_ONGRADED_OBSERVED are now separate control-flow facts. No model
    output is recorded; a test greps for p_win/grade/confidence/edge/side.

Terminal states now name the stage: SCHEDULE_STAGE_ERROR, ROSTER_STAGE_ERROR,
BINDING_STAGE_ERROR, EVENT_IDENTITY_STAGE_ERROR, ADMISSION_STAGE_ERROR,
DEDUPE_STAGE_ERROR, IDENTITY_ALL_UNRESOLVED, ALL_REJECTED,
DEDUPE_ALL_NON_MODEL_BOOK, DEDUPE_ALL_INVALID_FIELDS, DEDUPE_EMPTY_OTHER,
READY_FOR_GRADING, GRADE_LOOP_STARTED, FIRST_GRADE_CALLBACK_OBSERVED.

TRACE COMPLETENESS INVARIANT. `reconcilePregrade` — acquisition NONZERO +
CONTINUED with no correlated downstream state is an OBSERVABILITY_GAP, never a
pipeline verdict. This programme has twice read an absence as a conclusion
("MLB exited at acquisition", "all props rejected at admission"); both were
wrong. Now it is a typed state with tests.

dedupeProps takes an OPTIONAL stats object and increments on the branches it
already takes, in the same order — reused, never reimplemented. Without the
object it is byte-identical; a test asserts that.

A PRODUCTION-BREAKING BUG CAUGHT BY THE FULL SUITE: the frozen no-op recorder
did not implement gradeStarted/firstOnGraded, so any caller without a recorder
threw inside the grade loop — and gradeAndCacheSlate's catch turned that into
{written:false,count:0}. Every slate would have graded NOTHING, silently. Fixed,
NO_PREGRADE now covers the full recorder surface, and a test asserts it does.

Exception semantics unchanged throughout: every added catch records and RETHROWS
the identical error. Admission rules, dedupe predicates, MODEL_BOOKS, event
identity, gameBinder, gradeBestSide and its arguments: 0 changed lines.
eventIdentity, oddsService, retentionService, gameBinder, bookRoles,
analyzeViaEngine1, probabilityEstimator, snapshotScheduler and ledgerService:
UNCHANGED. Zero new external calls — the only diff hit is the existing
getScheduleWithPitchers line re-indented into its own try.

Twelve teeth, injections verified present, against a green baseline of 89:
upstream catch silent (1) · missing trace as failure (1) · admission exception
as ALL_REJECTED (2) · non-model-book as duplicate (3) · dedupe-empty as
rejection (1) · falsely says grading started (6) · onGraded unrecorded (1) ·
sport overwrite (2) · intraday overwrite (1) · different attempt id (3) ·
observer adds an external call (1) · store failure changes outcome (2).
Restored byte-identically; teeth 3/4/6 re-run after the NO_PREGRADE fix.

The first teeth pass ran against a baseline the finer states had invalidated;
six superseded assertions were updated first and the run repeated.

388 suites / 5,269 tests pass. web tsc exit 0. Lineage stays OFF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
Kev
2026-08-28 01:21:09 -04:00
parent 8d741e3932
commit c1d9ec5bbb
6 changed files with 523 additions and 32 deletions
+337
View File
@@ -0,0 +1,337 @@
'use strict';
/**
* PATH COVERAGE — acquisition success to the first real onGraded callback.
*
* Every branch in that corridor must explain itself. Twice now this programme
* has read an absence as a conclusion: first "MLB exited at acquisition" (from
* a missing retention record), then "all props rejected at admission" (from an
* empty collector). Both were wrong. So the rule here is that a MISSING trace
* is an OBSERVABILITY_GAP, never a pipeline verdict.
*/
const fs = require('fs');
const path = require('path');
const acq = require('../../src/services/ops/acquisitionTrace');
const snap = require('../../src/services/snapshotService');
const { __internals } = require('../../src/services/gradeSlateService');
const { MODEL_BOOKS } = require('../../src/config/bookRoles');
const ROOT = path.resolve(__dirname, '..', '..');
const BOOK = [...MODEL_BOOKS][0];
jest.setTimeout(20000);
const P = (n, over = {}) => Array.from({ length: n }, (_, i) => ({
player: `P${i}`, stat_type: 'hits', line: 0.5 + i, sport: 'mlb', book: BOOK,
home_team: 'Reds', away_team: 'Cardinals', game_date: '2026-08-28', ...over,
}));
const identityOk = (counts = {}) => ({
buildPlayerTeamIndex: async () => ({ index: {}, stats: { players: 12, teams: 2, failed: 0 } }),
evidenceIsDateValid: () => true,
attachEventIdentity: () => ({ total: 4, canonical: 4, unresolved: 0, unsupported: 0, impossible: 0, reasons: {}, ...counts }),
});
/** Runs the REAL runSnapshot corridor; grading is stubbed unless asked for. */
async function corridor(over = {}) {
const pg = []; const a = [];
await snap.runSnapshot('mlb', {
getOdds: async () => ({ sport: 'mlb', props: P(4), provider: 'propline', source: 'cache' }),
gradeAndCacheSlate: async () => ({ written: false, count: 0 }),
gameBinder: { attachGameTimes: async () => ({ bound: 4, unresolved: 0, alreadyHad: 0 }) },
mlbAdapter: { getScheduleWithPitchers: async () => [{ gamePk: 1 }, { gamePk: 2 }] },
eventIdentity: identityOk(),
notify: async () => {}, sleep: async () => {}, retryDelayMs: 0,
now: () => '2026-08-28T14:00:00.000Z', scheduledHourUtc: 14, processStartedAt: 'proc-1',
retention: null,
ledger: { recordPipelineGrades: async () => {}, captureClosing: async () => {}, gameDateFor: () => '2026-08-28' },
captureBookPrices: async () => ({}), buildEspnIndex: async () => ({}),
refreshTeamStats: async () => ({ captured: 0 }),
cacheGet: async () => null, cacheSet: async () => {},
persistAcquisitionTrace: async (t) => a.push(t),
persistPregradeTrace: async (t) => pg.push(t),
...over,
});
return { pg: pg[0], acq: a[0] };
}
describe('UPSTREAM MLB STAGES', () => {
test('SCHEDULE FETCH ERROR names the schedule stage, not identity', async () => {
const { pg } = await corridor({ mlbAdapter: { getScheduleWithPitchers: async () => { throw new Error('statsapi 503'); } } });
expect(pg.schedule.threw).toBe(true);
expect(pg.schedule.error).toMatch(/statsapi 503/);
expect(pg.outcome).toBe(acq.PREGRADE_OUTCOME.SCHEDULE_STAGE_ERROR);
expect(pg.outcome).not.toBe(acq.PREGRADE_OUTCOME.EVENT_IDENTITY_STAGE_ERROR);
});
test('SCHEDULE SUCCESS records dates requested and game count', async () => {
const { pg } = await corridor();
expect(pg.schedule.completed).toBe(true);
expect(pg.schedule.dates_requested).toBe(1);
expect(pg.schedule.games).toBe(2);
});
test('ROSTER INDEX ERROR is visible — its catch used to swallow silently', async () => {
const { pg } = await corridor({
eventIdentity: { ...identityOk(), buildPlayerTeamIndex: async () => { throw new Error('roster 500'); } },
});
expect(pg.roster.threw).toBe(true);
expect(pg.roster.error).toMatch(/roster 500/);
expect(pg.outcome).toBe(acq.PREGRADE_OUTCOME.ROSTER_STAGE_ERROR);
});
test('ROSTER SUCCESS records the indexed counts', async () => {
const { pg } = await corridor();
expect(pg.roster.completed).toBe(true);
expect(pg.roster.players).toBe(12);
expect(pg.roster.teams).toBe(2);
expect(pg.roster.evidence_date_valid).toBe(true);
});
test('BINDING ERROR names the binding stage', async () => {
const { pg } = await corridor({ gameBinder: { attachGameTimes: async () => { throw new Error('binder boom'); } } });
expect(pg.binding.threw).toBe(true);
expect(pg.outcome).toBe(acq.PREGRADE_OUTCOME.BINDING_STAGE_ERROR);
});
test('BINDING SUCCESS records bound / unresolved / already-had', async () => {
const { pg } = await corridor({ gameBinder: { attachGameTimes: async () => ({ bound: 3, unresolved: 1, alreadyHad: 0 }) } });
expect(pg.binding).toMatchObject({ completed: true, bound: 3, unresolved: 1, already_had: 0 });
});
test('EVENT IDENTITY ERROR is recorded and the existing fallback is preserved', async () => {
const { pg } = await corridor({
eventIdentity: { ...identityOk(), attachEventIdentity: () => { throw new Error('identity boom'); } },
});
expect(pg.identity.threw).toBe(true);
expect(pg.outcome).toBe(acq.PREGRADE_OUTCOME.EVENT_IDENTITY_STAGE_ERROR);
// The existing catch keeps the slate alive — props stay on legacy identity.
const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8');
expect(src).toMatch(/Identity is additive: a failure leaves props on legacy identity/);
});
test('IDENTITY ALL UNRESOLVED reports truthful status counts', async () => {
const { pg } = await corridor({
eventIdentity: identityOk({ total: 4, canonical: 0, unresolved: 4, impossible: 0, reasons: { no_team_match: 4 } }),
gradeAndCacheSlate: async (sp, props, o) => {
o.pregrade.admission({ started: true, completed: true, threw: false, input_count: 4, admitted_count: 0, rejected_count: 4, rejection_reason_counts: { EVENT_UNRESOLVED: 4 } });
return { written: false, count: 0 };
},
});
expect(pg.identity).toMatchObject({ completed: true, total: 4, canonical: 0, unresolved: 4 });
expect(pg.identity.reasons.no_team_match).toBe(4);
// Names the CAUSE, not just the symptom.
expect(pg.outcome).toBe(acq.PREGRADE_OUTCOME.IDENTITY_ALL_UNRESOLVED);
});
});
describe('DEDUPE IS A FILTER — exact drop accounting off its own branches', () => {
const d = (rows, limit = 100) => { const st = {}; const out = __internals.dedupeProps(rows, limit, st); return { out, st }; };
test('behaviour is byte-identical with and without the stats object', () => {
const rows = [...P(3), ...P(2, { book: 'nope' })];
expect(__internals.dedupeProps(rows, 100).length).toBe(d(rows).out.length);
});
test('ALL dropped by MODEL BOOK', () => {
const { out, st } = d(P(5, { book: 'not_a_model_book' }));
expect(out).toHaveLength(0);
expect(st.dropped_non_model_book).toBe(5);
expect(st.dropped_invalid_fields || 0).toBe(0);
});
test('ALL dropped by MISSING REQUIRED FIELDS', () => {
const { out, st } = d(P(4).map((p) => ({ ...p, player: null })));
expect(out).toHaveLength(0);
expect(st.dropped_invalid_fields).toBe(4);
expect(st.dropped_non_model_book || 0).toBe(0);
});
test('only TRUE duplicates counted as duplicates', () => {
const one = P(1);
const { out, st } = d([...one, ...one, ...one]);
expect(out).toHaveLength(1);
expect(st.duplicate_identity_removed).toBe(2);
expect(st.dropped_non_model_book || 0).toBe(0);
});
test('counts reconcile to the input exactly', () => {
const rows = [...P(2), ...P(2), ...P(1, { book: 'x' }), { player: null }];
const { out, st } = d(rows);
const accounted = out.length
+ (st.dropped_invalid_fields || 0) + (st.dropped_non_model_book || 0)
+ (st.duplicate_identity_removed || 0) + (st.not_examined || 0);
expect(accounted).toBe(rows.length);
expect(st.input_count).toBe(rows.length);
});
test('MODEL-book eligibility is countable for the cohort', () => {
const { st } = d([...P(3), ...P(2, { book: 'nope' })]);
expect(st.model_book_eligible).toBe(3);
expect(st.dropped_non_model_book).toBe(2);
});
test('the cap records not_examined rather than pretending it saw everything', () => {
const { out, st } = d(P(10), 4);
expect(out).toHaveLength(4);
expect(st.capped).toBe(true);
expect(st.not_examined).toBe(6);
});
});
describe('THE GRADING BOUNDARY', () => {
const realGrade = () => require('../../src/services/gradeSlateService').gradeAndCacheSlate;
test('candidates > 0 but loop NOT entered is distinct from loop entered', () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb', propsCount: 4 });
acq.pregradeRecorder(t).gradeLoop({ candidate_count: 4, reached: true });
expect(acq.finishPregrade(t, {}).outcome).toBe(acq.PREGRADE_OUTCOME.READY_FOR_GRADING);
expect(t.grade_loop.first_gradebestside_started).toBeUndefined();
});
test('FIRST gradeBestSide START is proven through the REAL loop', async () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb', propsCount: 3 });
await realGrade()('mlb', P(3), {
pregrade: acq.pregradeRecorder(t), cacheSet: async () => {}, grade: async () => null,
});
expect(t.grade_loop.entered).toBe(true);
expect(t.grade_loop.first_gradebestside_started).toBe(true);
expect(acq.finishPregrade(t, {}).outcome).toBe(acq.PREGRADE_OUTCOME.GRADE_LOOP_STARTED);
});
test('FIRST onGraded is proven separately from gradeBestSide starting', async () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb', propsCount: 3 });
const rec = acq.pregradeRecorder(t);
await realGrade()('mlb', P(3), {
pregrade: rec, cacheSet: async () => {}, grade: async () => null,
onGraded: () => { rec.firstOnGraded(); },
});
expect(t.grade_loop.first_gradebestside_started).toBe(true);
expect(t.grade_loop.first_ongraded_observed).toBe(true);
expect(acq.finishPregrade(t, {}).outcome).toBe(acq.PREGRADE_OUTCOME.FIRST_GRADE_CALLBACK_OBSERVED);
});
test('gradeBestSide starting WITHOUT onGraded is its own state', async () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb', propsCount: 3 });
await realGrade()('mlb', P(3), {
pregrade: acq.pregradeRecorder(t), cacheSet: async () => {}, grade: async () => null,
});
expect(t.grade_loop.first_gradebestside_started).toBe(true);
expect(t.grade_loop.first_ongraded_observed).toBeUndefined();
expect(acq.finishPregrade(t, {}).outcome).not.toBe(acq.PREGRADE_OUTCOME.FIRST_GRADE_CALLBACK_OBSERVED);
});
test('runSnapshot wraps onGraded ONLY when a collector exists', () => {
const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8');
expect(src).toMatch(/onGraded: collector \? \(\(base, sides\) => \{ pgRec\.firstOnGraded\(\); return collector\.onGraded\(base, sides\); \}\) : undefined/);
expect(src).toMatch(/ongraded_hook_attached: !!collector/);
});
test('no model output is recorded — control-flow facts only', () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb' });
const rec = acq.pregradeRecorder(t);
rec.gradeStarted(); rec.firstOnGraded();
const blob = JSON.stringify(t);
for (const forbidden of ['p_win', 'grade', 'confidence', 'projection', 'edge', 'side']) {
expect(blob).not.toMatch(new RegExp(`"${forbidden}"`));
}
});
});
describe('TRACE COMPLETENESS INVARIANT', () => {
const acqOk = { snapshot_attempt_id: 'acq_1', final: acq.FINAL.NONZERO, outcome: acq.OUTCOME.CONTINUED };
test('acquisition CONTINUED + NO downstream trace = OBSERVABILITY_GAP, not failure', () => {
const r = acq.reconcilePregrade(acqOk, null);
expect(r.applicable).toBe(true);
expect(r.complete).toBe(false);
expect(r.outcome).toBe(acq.PREGRADE_OUTCOME.OBSERVABILITY_GAP);
});
test('a mismatched attempt id is also a gap, never a verdict', () => {
const r = acq.reconcilePregrade(acqOk, { snapshot_attempt_id: 'other', outcome: acq.PREGRADE_OUTCOME.ALL_REJECTED });
expect(r.outcome).toBe(acq.PREGRADE_OUTCOME.OBSERVABILITY_GAP);
expect(r.reason).toBe('attempt_id_mismatch');
});
test('an undecided downstream state is a gap, not a conclusion', () => {
for (const o of [undefined, acq.PREGRADE_OUTCOME.INCOMPLETE, acq.PREGRADE_OUTCOME.OTHER_PREGRADING_ERROR]) {
const r = acq.reconcilePregrade(acqOk, { snapshot_attempt_id: 'acq_1', outcome: o });
expect(r.outcome).toBe(acq.PREGRADE_OUTCOME.OBSERVABILITY_GAP);
}
});
test('a decided downstream state reconciles complete', () => {
const r = acq.reconcilePregrade(acqOk, { snapshot_attempt_id: 'acq_1', outcome: acq.PREGRADE_OUTCOME.ALL_REJECTED });
expect(r.complete).toBe(true);
expect(r.outcome).toBe(acq.PREGRADE_OUTCOME.ALL_REJECTED);
});
test('a stubbed/short-circuited grader that records nothing IS a gap', async () => {
// Proves the invariant fires rather than inventing a verdict from silence.
const { pg, acq: a } = await corridor();
expect(acq.reconcilePregrade(a, pg).outcome).toBe(acq.PREGRADE_OUTCOME.OBSERVABILITY_GAP);
});
test('it does not apply when acquisition did not continue', () => {
const r = acq.reconcilePregrade({ snapshot_attempt_id: 'a', final: acq.FINAL.THREW, outcome: acq.OUTCOME.EARLY_RETURN_ODDS_ERROR }, null);
expect(r.applicable).toBe(false);
});
test('a real corridor run produces a decided state and the SAME attempt id', async () => {
// The REAL grader, so admission/dedupe/grade-loop actually record. With a
// stubbed grader nothing records and the reconciliation correctly reports a
// gap — which is the invariant working, not a decided state.
const { pg, acq: a } = await corridor({
gradeAndCacheSlate: require('../../src/services/gradeSlateService').gradeAndCacheSlate,
grade: async () => null,
});
expect(pg.snapshot_attempt_id).toBe(a.snapshot_attempt_id);
const r = acq.reconcilePregrade(a, pg);
expect(r.complete).toBe(true);
expect(r.outcome).not.toBe(acq.PREGRADE_OUTCOME.OBSERVABILITY_GAP);
});
});
describe('THE NO-OP RECORDER MUST IMPLEMENT EVERY METHOD', () => {
// A missing method throws inside the grade loop, and the enclosing catch
// turns that into {written:false,count:0} — a silently empty slate for every
// caller that passes no recorder. This shipped once; it must not again.
test('NO_PREGRADE covers the full recorder surface', async () => {
const real = acq.pregradeRecorder(acq.beginPregrade({ attemptId: 'a', sport: 'mlb' }));
const src = fs.readFileSync(path.join(ROOT, 'src/services/gradeSlateService.js'), 'utf8');
const i = src.indexOf('const NO_PREGRADE');
const block = src.slice(i, src.indexOf('\n});', i) + 4);
for (const m of Object.keys(real)) {
expect(block).toContain(`${m}(`);
}
});
test('grading works with NO recorder passed at all', async () => {
const res = await require('../../src/services/gradeSlateService')
.gradeAndCacheSlate('mlb', P(2), { cacheSet: async () => {}, grade: async () => null });
// Refusals produce no grades, but it must NOT fail with a recorder error.
expect(res.error).toBeUndefined();
});
});
describe('ZERO NEW EXTERNAL CALLS', () => {
test('the observer adds no provider or schedule call', () => {
const snapSrc = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8');
// getScheduleWithPitchers is called exactly where the product already called it.
expect((snapSrc.match(/getScheduleWithPitchers\(/g) || []).length).toBe(2);
expect((snapSrc.match(/buildPlayerTeamIndex\(/g) || []).length).toBe(1);
const traceSrc = fs.readFileSync(path.join(ROOT, 'src/services/ops/acquisitionTrace.js'), 'utf8');
const code = traceSrc.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
.replace(/s\.replace\([\s\S]*?\);/g, '');
for (const f of ['fetch(', 'axios', 'getSchedule', 'getProps', 'getOdds', 'oddsService']) {
expect(code).not.toContain(f);
}
});
test('a trace-store failure never changes the product outcome', async () => {
const { pg } = await corridor({ persistPregradeTrace: async () => { throw new Error('redis down'); } });
expect(pg).toBeUndefined(); // nothing stored…
// …and the run still completed: no throw escaped runSnapshot.
});
});
+23 -11
View File
@@ -48,8 +48,11 @@ describe('THE STAGE MATRIX', () => {
expect(trace.admission.rejected_count).toBe(0);
expect(trace.dedupe.completed).toBe(true);
expect(trace.dedupe.output_count).toBe(5);
expect(trace.grade_loop).toEqual({ candidate_count: 5, reached: true });
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.READY_FOR_GRADING);
expect(trace.grade_loop).toMatchObject({ candidate_count: 5, reached: true });
// The loop now proves it ENTERED, which is strictly stronger than a
// candidate count, so the terminal state advances past READY_FOR_GRADING.
expect(trace.grade_loop.first_gradebestside_started).toBe(true);
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.GRADE_LOOP_STARTED);
});
test('ALL_REJECTED — with EXACT reason accounting', async () => {
@@ -74,7 +77,7 @@ describe('THE STAGE MATRIX', () => {
expect(trace.admission.admitted_count).toBe(4);
expect(trace.admission.rejected_count).toBe(3);
expect(trace.admission.rejection_reason_counts.EVENT_PLAYER_TEAM_CONTRADICTION).toBe(3);
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.READY_FOR_GRADING);
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.GRADE_LOOP_STARTED);
});
test('DEDUPE_EMPTY — admitted > 0 but dedupe filters to zero, NOT ALL_REJECTED', async () => {
@@ -84,7 +87,9 @@ describe('THE STAGE MATRIX', () => {
expect(trace.admission.admitted_count).toBe(5);
expect(trace.dedupe.input_count).toBe(5);
expect(trace.dedupe.output_count).toBe(0);
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.DEDUPE_EMPTY);
// Now names WHICH filter emptied it, not merely that it emptied.
expect(trace.dedupe.dropped_non_model_book).toBe(5);
expect(trace.outcome).toBe(acq.PREGRADE_OUTCOME.DEDUPE_ALL_NON_MODEL_BOOK);
expect(trace.outcome).not.toBe(acq.PREGRADE_OUTCOME.ALL_REJECTED);
});
@@ -126,7 +131,9 @@ describe('THE STAGE MATRIX', () => {
await snap2.runSnapshot('mlb', {
getOdds: async () => ({ sport: 'mlb', props: props(2), provider: 'propline', source: 'cache' }),
gradeAndCacheSlate: async () => ({ written: false, count: 0 }),
eventIdentity: { buildPlayerTeamIndex: async () => { throw new Error('roster down'); },
// Roster succeeds so the IDENTITY throw is the one under test; roster now
// has its own terminal state and would otherwise (correctly) outrank it.
eventIdentity: { buildPlayerTeamIndex: async () => ({ index: {}, stats: { players: 1, teams: 1, failed: 0 } }),
evidenceIsDateValid: () => false,
attachEventIdentity: () => { throw new Error('statsapi shape changed'); } },
mlbAdapter: { getScheduleWithPitchers: async () => [] },
@@ -141,14 +148,14 @@ describe('THE STAGE MATRIX', () => {
expect(pg).toHaveLength(1);
expect(pg[0].identity).toBeTruthy();
expect(pg[0].identity.threw).toBe(true);
expect(pg[0].identity.error).toMatch(/statsapi shape changed|roster down/);
expect(pg[0].outcome).toBe(acq.PREGRADE_OUTCOME.IDENTITY_STAGE_ERROR);
expect(pg[0].identity.error).toMatch(/statsapi shape changed/);
expect(pg[0].outcome).toBe(acq.PREGRADE_OUTCOME.EVENT_IDENTITY_STAGE_ERROR);
});
test('IDENTITY_STAGE_ERROR outranks everything downstream', async () => {
test('EVENT_IDENTITY_STAGE_ERROR outranks everything downstream', async () => {
const t = acq.beginPregrade({ attemptId: 'a', sport: 'mlb', propsCount: 3 });
acq.pregradeRecorder(t).identity({ started: true, completed: false, threw: true, error: 'statsapi down' });
expect(acq.finishPregrade(t, {}).outcome).toBe(acq.PREGRADE_OUTCOME.IDENTITY_STAGE_ERROR);
expect(acq.finishPregrade(t, {}).outcome).toBe(acq.PREGRADE_OUTCOME.EVENT_IDENTITY_STAGE_ERROR);
});
test('an empty collector alone can NEVER be classified', () => {
@@ -225,7 +232,12 @@ describe('THE OBSERVER DOES NOT TOUCH THE PROPS', () => {
test('the recorder default is a frozen no-op, so existing callers are identical', () => {
const src = fs.readFileSync(path.join(ROOT, 'src/services/gradeSlateService.js'), 'utf8');
expect(src).toMatch(/const NO_PREGRADE = Object\.freeze\(\{ identity\(\) \{\}, admission\(\) \{\}, dedupe\(\) \{\}, gradeLoop\(\) \{\} \}\)/);
// Now multi-line and covering the full recorder surface; match the shape,
// not the exact one-line form.
expect(src).toMatch(/const NO_PREGRADE = Object\.freeze\(\{/);
for (const m of ['identity', 'admission', 'dedupe', 'gradeLoop', 'gradeStarted', 'firstOnGraded']) {
expect(src).toMatch(new RegExp(`${m}\\(\\) \\{\\}`));
}
expect(src).toMatch(/const pregrade = opts\.pregrade \|\| NO_PREGRADE;/);
});
});
@@ -309,7 +321,7 @@ describe('THE TRACE IS ACTUALLY PERSISTED BY THE REAL PATH', () => {
expect(pgStored[0].snapshot_attempt_id).toBe(acqStored[0].snapshot_attempt_id);
expect(pgStored[0].input_props_count).toBe(3);
expect(pgStored[0].admission.input_count).toBe(3);
expect(pgStored[0].outcome).toBe(acq.PREGRADE_OUTCOME.READY_FOR_GRADING);
expect(pgStored[0].outcome).toBe(acq.PREGRADE_OUTCOME.GRADE_LOOP_STARTED);
});
});
+3 -1
View File
@@ -106,7 +106,9 @@ describe('CANONICAL EVENT AVAILABILITY BY ROW CLASS', () => {
// wraps it in try/catch to record and rethrow, which changed `const gate =`
// into `gate = `. The invariant is that admission runs on (props, sport).
expect(gs).toMatch(/gate = admitForGrading\(props, sport\)/);
expect(gs).toMatch(/dedupeProps\(gate\.admitted, limit\)/);
// The call gained an optional diagnostic stats object; match the call, not
// its exact arity.
expect(gs).toMatch(/dedupeProps\(gate\.admitted, limit/);
expect(gs).not.toMatch(/dedupeProps\(gate\.rejected/);
});