Session 28: Parlay builder, line movement tracker, book comparison — 3 features, zero credits (1623 tests)
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// 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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user