// Session 64 (A1-S5) — prop viability: lineup confirmation + injury wire + // the NOT-IN grade kill. Real feeds only; absent → no chips, never guessed. const { parseLineups, statusFor } = require('../../src/services/lineupService'); const { parseInjuries, chipFor } = require('../../src/services/injuryService'); const adapter = require('../../web/src/lib/slateAdapter'); // Real statsapi hydrate=lineups shape (verified live 2026-07-11). const SCHEDULE_JSON = { dates: [{ games: [ { gamePk: 823357, teams: { home: { team: { name: 'Milwaukee Brewers' } }, away: { team: { name: 'Pittsburgh Pirates' } } }, lineups: { homePlayers: [ { id: 663968, fullName: 'Jake Mangum' }, { id: 664040, fullName: 'Brandon Lowe' }, ], awayPlayers: [{ id: 1, fullName: 'Bryan Reynolds' }], }, }, { gamePk: 823356, teams: { home: { team: { name: 'Detroit Tigers' } }, away: { team: { name: 'Tampa Bay Rays' } } }, lineups: {} }, ], }], }; describe('lineupService — CONFIRMED / NOT_IN / PROJECTED', () => { const parsed = parseLineups(SCHEDULE_JSON); test('posted lineup → confirmed with the batting slot', () => { expect(parsed.byPlayer['brandon lowe']).toEqual({ status: 'confirmed', slot: 2, team: 'Milwaukee Brewers' }); expect(parsed.postedTeams).toContain('Milwaukee Brewers'); }); test('team posted, player absent → NOT_IN', () => { expect(statusFor('Christian Yelich', 'Milwaukee Brewers', parsed).status).toBe('not_in'); }); test('team not posted → PROJECTED (never guessed dead)', () => { expect(statusFor('Riley Greene', 'Detroit Tigers', parsed).status).toBe('projected'); }); test('accented/variant names resolve through nameKey', () => { expect(statusFor('Brandon Lowé', 'Milwaukee Brewers', parsed).status).toBe('confirmed'); }); }); describe('injuryService — OUT / GTD / PROB chips', () => { test('parses the ESPN feed shape', () => { const byPlayer = parseInjuries({ injuries: [{ displayName: 'Arizona Diamondbacks', injuries: [ { athlete: { displayName: 'Ketel Marte' }, status: 'Out', details: { type: 'Hamstring' } }, { athlete: { displayName: 'Corbin Carroll' }, status: 'Day-To-Day', shortComment: 'wrist' }, ], }], }); expect(byPlayer['ketel marte']).toMatchObject({ status: 'OUT', detail: 'Hamstring' }); expect(byPlayer['corbin carroll'].status).toBe('GTD'); }); test('unknown status text → no chip (never invented)', () => { expect(chipFor('Active')).toBeNull(); expect(chipFor('')).toBeNull(); }); }); describe('slateAdapter — NOT_IN kills the graded props on the strip', () => { const GAME = { home: 'Milwaukee Brewers', away: 'Pittsburgh Pirates' }; const props = [{ player: 'Christian Yelich', stat_type: 'hits', line: 0.5 }]; const gradeIndex = adapter.indexGrades([ { player: 'Christian Yelich', stat_type: 'hits', line: 0.5, direction: 'over', grade: 'A', team: 'Milwaukee Brewers', gradedAt: { line: 0.5, timestamp: 'x' } }, ]); // Fixture keys computed via nameKey — the same folding BOTH real sides use // (hand-written keys miss nickname resolution, e.g. jake→jacob). const { nameKey } = require('../../web/src/lib/playerName'); const viability = { lineups: { byPlayer: { [nameKey('Jake Mangum')]: { status: 'confirmed', slot: 1 } }, postedTeams: ['Milwaukee Brewers'] }, injuries: {}, }; test('graded prop for a NOT_IN player renders dead (grade preserved)', () => { const strips = adapter.buildPlayerStripsFromProps(props, gradeIndex, {}, Date.now(), GAME, viability); expect(strips[0].lineup.status).toBe('not_in'); expect(strips[0].props[0].dead).toBe(true); expect(strips[0].props[0].grade).toBe('A'); // struck through in UI, never deleted }); test('confirmed player carries the slot; no viability feed → no chips', () => { const confirmed = adapter.buildPlayerStripsFromProps( [{ player: 'Jake Mangum', stat_type: 'hits', line: 0.5 }], {}, {}, Date.now(), GAME, viability, ); expect(confirmed[0].lineup).toEqual({ status: 'confirmed', slot: 1 }); const none = adapter.buildPlayerStripsFromProps(props, gradeIndex, {}, Date.now(), GAME, null); expect(none[0].lineup == null).toBe(true); expect(none[0].props[0].dead == null).toBe(true); }); });