c1d9ec5bbb
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
338 lines
16 KiB
JavaScript
338 lines
16 KiB
JavaScript
'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.
|
|
});
|
|
});
|