// A1 Session 3 — BOOK IT deep-link builder. Organic by default; the // affiliate layer only fires when a book is explicitly enabled with // real params. Every anchor renders rel="sponsored noopener noreferrer". const fs = require('fs'); const path = require('path'); const { buildBookLink, resolveBookId, SUPPORTED_BOOKS, BOOK_LINK_REL } = require('../../web/src/lib/bookLinks'); const { AFFILIATE_CONFIG } = require('../../web/src/lib/affiliateConfig'); const WEB = path.join(__dirname, '..', '..', 'web', 'src'); const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8'); describe('buildBookLink — organic by default', () => { it('builds a clean organic link for every supported book (default config)', () => { for (const b of SUPPORTED_BOOKS) { const out = buildBookLink({ book: b.id, player: 'Aaron Judge', sport: 'mlb' }); expect(out).not.toBeNull(); expect(out.tracking).toBe(false); expect(out.url).toContain(`https://${b.host}/`); expect(out.url).toContain('search=Aaron+Judge'); // no affiliate params leak into an organic link expect(out.url).not.toMatch(/irclickid|sharedid|btag|afid|siteid/); } }); it('ships EVERY book disabled in the checked-in config', () => { for (const b of SUPPORTED_BOOKS) { expect(AFFILIATE_CONFIG[b.id]).toBeDefined(); expect(AFFILIATE_CONFIG[b.id].enabled).toBe(false); } }); it('resolves book aliases (DK / DraftKings / draftkings)', () => { expect(resolveBookId('DK')).toBe('draftkings'); expect(resolveBookId('DraftKings')).toBe('draftkings'); expect(resolveBookId('fanduel')).toBe('fanduel'); expect(resolveBookId('MGM')).toBe('betmgm'); expect(resolveBookId('CZR')).toBe('caesars'); expect(resolveBookId('BR')).toBe('betrivers'); }); it('returns null for unknown books and missing player (absent beats wrong)', () => { expect(buildBookLink({ book: 'bovada', player: 'Aaron Judge' })).toBeNull(); expect(buildBookLink({ book: 'prizepicks', player: 'Aaron Judge' })).toBeNull(); expect(buildBookLink({ book: 'draftkings', player: '' })).toBeNull(); expect(buildBookLink({})).toBeNull(); }); it('betrivers routes by two-letter state subdomain, else www', () => { const mi = buildBookLink({ book: 'betrivers', player: 'Judge', state: 'MI' }); expect(mi.url).toContain('https://mi.betrivers.com/'); const bad = buildBookLink({ book: 'betrivers', player: 'Judge', state: 'Michigan' }); expect(bad.url).toContain('https://www.betrivers.com/'); const none = buildBookLink({ book: 'betrivers', player: 'Judge' }); expect(none.url).toContain('https://www.betrivers.com/'); }); }); describe('buildBookLink — affiliate layer (config-flip)', () => { const enabled = { draftkings: { enabled: true, params: { irclickid: 'IRC123', sharedid: 'vyndr', wpsrc: 'vyndr-slate' } }, betmgm: { enabled: true, params: { btag: 'BT-42', afid: '' } }, caesars: { enabled: false, params: { btag: 'SHOULD-NOT-APPEAR' } }, }; it('appends Impact-style params and reports tracking when enabled', () => { const out = buildBookLink({ book: 'draftkings', player: 'Aaron Judge' }, enabled); expect(out.tracking).toBe(true); expect(out.url).toContain('irclickid=IRC123'); expect(out.url).toContain('sharedid=vyndr'); expect(out.url).toContain('wpsrc=vyndr-slate'); expect(out.url).toContain('search=Aaron+Judge'); }); it('skips empty param values (never emits a fabricated id)', () => { const out = buildBookLink({ book: 'betmgm', player: 'Judge' }, enabled); expect(out.tracking).toBe(true); expect(out.url).toContain('btag=BT-42'); expect(out.url).not.toContain('afid='); }); it('enabled with ONLY empty params stays organic (tracking false)', () => { const out = buildBookLink({ book: 'fanduel', player: 'Judge' }, { fanduel: { enabled: true, params: { irclickid: ' ' } } }); expect(out.tracking).toBe(false); expect(out.url).not.toContain('irclickid'); }); it('disabled book with params configured stays organic', () => { const out = buildBookLink({ book: 'caesars', player: 'Judge' }, enabled); expect(out.tracking).toBe(false); expect(out.url).not.toContain('SHOULD-NOT-APPEAR'); }); }); describe('rel="sponsored noopener noreferrer" on every book anchor', () => { it('exports the exact rel contract', () => { expect(BOOK_LINK_REL).toBe('sponsored noopener noreferrer'); }); it('StatStrip BOOK IT anchor uses buildBookLink + BOOK_LINK_REL', () => { const src = read('components/vyndr/StatStrip.tsx'); expect(src).toContain('buildBookLink'); expect(src).toContain('rel={BOOK_LINK_REL}'); expect(src).toContain('BOOK IT ⟶'); }); it('scan page hand-off anchors use buildBookLink + BOOK_LINK_REL', () => { const src = read('app/scan/page.tsx'); expect(src).toContain('SUPPORTED_BOOKS'); expect(src).toContain('rel={BOOK_LINK_REL}'); // the old hardcoded organic builder is gone expect(src).not.toContain('const deepLink ='); }); });