Files
vyndr/tests/unit/slateAdapterBestBook.test.js
T
builtbykev 0996320bd1 S3 (a1): affiliate + partner plumbing
Zero out-of-pocket; everything config-flip-ready but DISABLED/organic.

- BOOK IT deep links: web/src/lib/bookLinks.js + affiliateConfig.js
  (all books enabled:false, Impact/Partnerize param shapes documented,
  empty params skipped). Wired into StatStrip BookItTeaser (real anchor
  now) + scan hand-off links. Every book anchor renders
  rel="sponsored noopener noreferrer" (BOOK_LINK_REL).
- Best-price marker: slateAdapter.detectBestBook (only when >=2 books
  post the SAME line and prices differ — absent beats wrong) + subtle
  signal-green dot in StatStrip. Slate.groupByGame threads the grouped
  per-book rows (books[]) onto PropRowProp instead of discarding them.
- Partner refs: ?ref=CODE -> vyndr_ref cookie (90d, first-touch,
  PartnerRefCapture in layout) -> signup metadata partner_ref ->
  internal GET /api/partners/report/:code (requireInternalAuth; honest
  zeros + note until the TODO migration in docs/PARTNERS.md adds
  user_profiles.partner_ref — NOT run). Stripe promo-code convention:
  partner code == promotion code, verbatim.
- Tests: +41 (2398 -> 2439, 209 suites); bookItTeaser + vyndrCoreScreens
  invariants updated to the new (stronger) rel contract. Web build exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:28:18 -04:00

108 lines
4.5 KiB
JavaScript

// A1 Session 3 — best available price detection. Honesty rules: a best
// price only exists when ≥2 books post the SAME line for the side and
// their prices differ. Absent beats wrong.
const { detectBestBook, buildPlayerStripsFromProps } = require('../../web/src/lib/slateAdapter');
describe('detectBestBook', () => {
const rows = [
{ book: 'draftkings', line: 1.5, over_odds: -115, under_odds: -105 },
{ book: 'fanduel', line: 1.5, over_odds: -105, under_odds: -115 },
{ book: 'betmgm', line: 1.5, over_odds: -120, under_odds: 100 },
];
it('picks the best over price at the shared line', () => {
const out = detectBestBook(rows, 'over', 1.5);
expect(out).toEqual({ book: 'fanduel', odds: -105 });
});
it('picks the best under price independently', () => {
const out = detectBestBook(rows, 'under', 1.5);
expect(out).toEqual({ book: 'betmgm', odds: 100 });
});
it('returns null for a single book (a lone price is not "best")', () => {
expect(detectBestBook([rows[0]], 'over', 1.5)).toBeNull();
expect(detectBestBook([], 'over', 1.5)).toBeNull();
expect(detectBestBook(null, 'over', 1.5)).toBeNull();
});
it('returns null when all books post the identical price', () => {
const flat = [
{ book: 'draftkings', line: 1.5, over_odds: -110 },
{ book: 'fanduel', line: 1.5, over_odds: -110 },
];
expect(detectBestBook(flat, 'over', 1.5)).toBeNull();
});
it('returns null when books only post DIFFERENT lines (odds are not comparable)', () => {
const split = [
{ book: 'draftkings', line: 1.5, over_odds: -200 },
{ book: 'fanduel', line: 2.5, over_odds: 120 },
];
expect(detectBestBook(split, 'over', 1.5)).toBeNull();
expect(detectBestBook(split, 'over', 2.5)).toBeNull();
});
it('without a reference line, compares at the modal line', () => {
const mixed = [
{ book: 'draftkings', line: 1.5, over_odds: -115 },
{ book: 'fanduel', line: 1.5, over_odds: -105 },
{ book: 'betmgm', line: 2.5, over_odds: 200 },
];
expect(detectBestBook(mixed, 'over')).toEqual({ book: 'fanduel', odds: -105 });
});
it('ignores rows with missing book / line / side odds', () => {
const dirty = [
{ book: 'draftkings', line: 1.5, over_odds: -115 },
{ book: null, line: 1.5, over_odds: -100 },
{ book: 'fanduel', line: 1.5, over_odds: null },
{ book: 'betmgm', over_odds: -105 },
];
expect(detectBestBook(dirty, 'over', 1.5)).toBeNull();
});
});
describe('buildPlayerStripsFromProps — bestBook attachment', () => {
const books = [
{ book: 'draftkings', line: 1.5, over_odds: -115, under_odds: -105 },
{ book: 'fanduel', line: 1.5, over_odds: -105, under_odds: -115 },
];
it('attaches bestBook + book to a graded prop (graded side)', () => {
const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings', books }];
const gradeIndex = {
'aaron judge|total_bases': {
player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5,
direction: 'over', grade: 'A', gradedAt: { line: 1.5, odds: -115, timestamp: new Date().toISOString() },
},
};
const strips = buildPlayerStripsFromProps(props, gradeIndex, {});
expect(strips).toHaveLength(1);
const p = strips[0].props[0];
expect(p.book).toBe('draftkings');
expect(p.bestBook).toEqual({ book: 'fanduel', odds: -105 });
});
it('attaches bestBook on awaiting props too (still real market data)', () => {
const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'under', book: 'draftkings', books }];
const strips = buildPlayerStripsFromProps(props, {}, {});
const p = strips[0].props[0];
expect(p.awaiting).toBe(true);
expect(p.bestBook).toEqual({ book: 'draftkings', odds: -105 });
});
it('leaves bestBook null when only one book posted the prop', () => {
const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings', books: [books[0]] }];
const strips = buildPlayerStripsFromProps(props, {}, {});
expect(strips[0].props[0].bestBook).toBeNull();
});
it('leaves bestBook null when props carry no books array (legacy shape)', () => {
const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings' }];
const strips = buildPlayerStripsFromProps(props, {}, {});
expect(strips[0].props[0].bestBook).toBeNull();
});
});