// Unit: book comparison service (Session 28). Pure functions. const { compareProp, bestLines } = require('../../src/services/bookComparisonService'); const prop = { player: 'Wembanyama', stat_type: 'points', lines: [ { book: 'draftkings', line: 28.5, over_odds: -110, under_odds: -110 }, { book: 'fanduel', line: 28.5, over_odds: -105, under_odds: -115 }, // best over { book: 'betmgm', line: 28.5, over_odds: -120, under_odds: -102 }, // best under ], }; describe('bookComparisonService — compareProp', () => { test('identifies the best OVER line (highest payout)', () => { const c = compareProp(prop, 'over'); expect(c.bestBook).toBe('fanduel'); // -105 pays more than -110/-120 expect(c.bestOdds).toBe(-105); expect(c.books.find((b) => b.book === 'fanduel').isBest).toBe(true); expect(c.bookCount).toBe(3); }); test('identifies the best UNDER line', () => { const c = compareProp(prop, 'under'); expect(c.bestBook).toBe('betmgm'); // -102 pays more than -110/-115 expect(c.bestOdds).toBe(-102); }); test('savings is positive (best beats the field average)', () => { const c = compareProp(prop, 'over'); expect(c.savings).toBeGreaterThan(0); }); test('single-book prop still compares (bookCount 1)', () => { const c = compareProp({ player: 'X', stat_type: 'hits', lines: [{ book: 'dk', over_odds: -110 }] }, 'over'); expect(c.bookCount).toBe(1); expect(c.bestBook).toBe('dk'); }); test('no usable lines → null, not crash', () => { expect(compareProp({ player: 'X', stat_type: 'hits', lines: [] }, 'over')).toBeNull(); expect(compareProp({ player: 'X', stat_type: 'hits' }, 'over')).toBeNull(); }); }); describe('bookComparisonService — bestLines', () => { test('drops single-book props and sorts by savings desc', () => { const props = [ prop, { player: 'Solo', stat_type: 'reb', lines: [{ book: 'dk', over_odds: -110 }] }, // 1 book → dropped { player: 'BigEdge', stat_type: 'ast', lines: [ { book: 'dk', over_odds: -200 }, { book: 'fd', over_odds: +120 }, // huge spread → big savings ], }, ]; const lines = bestLines(props, { side: 'over' }); expect(lines.map((l) => l.player)).not.toContain('Solo'); expect(lines[0].player).toBe('BigEdge'); // biggest savings first }); test('non-array input → empty', () => { expect(bestLines(null)).toEqual([]); }); });