// Session 41 — P0 audit fixes. Frontend assertions read page source as text // (same pattern as the Phase D–H suites); backend stat-gate acceptance is // covered in tests/integration/analyze.test.js. const fs = require('fs'); const path = require('path'); const ROOT = path.join(__dirname, '..', '..'); const WEB = path.join(ROOT, 'web', 'src'); const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8'); describe('Session 41 — backend MLB stat_type whitelist', () => { const analyze = fs.readFileSync(path.join(ROOT, 'src', 'routes', 'analyze.js'), 'utf8'); const scan = fs.readFileSync(path.join(ROOT, 'src', 'routes', 'scan.js'), 'utf8'); const mlbStats = ['hits', 'strikeouts', 'total_bases', 'rbi', 'home_runs', 'earned_runs', 'hits_allowed', 'innings_pitched']; it.each(mlbStats)('/api/analyze gate whitelists MLB stat %s', (stat) => { expect(analyze).toContain(`'${stat}'`); }); it.each(mlbStats)('/api/scan (parlay) gate whitelists MLB stat %s', (stat) => { expect(scan).toContain(`'${stat}'`); }); }); describe('Session 41 — broken-route redirects', () => { // Session 42 — /settings is now a real settings page (replaced the S41 // redirect). The 404 it fixed is still fixed; the route just renders content. it('/settings is a real page (no longer a redirect stub)', () => { const src = read('app/settings/page.tsx'); expect(src).not.toContain("redirect('/profile')"); expect(src).toContain('DANGER ZONE'); }); it('/report is now the REAL Report archive, no longer a redirect to /blog', () => { // The S41 redirect existed BECAUSE the surface did not. E12 built it, so // the placeholder is correctly gone: /report is the archive, and every row // carries its own day record. const page = read('app/report/page.tsx'); expect(page).toContain('ReportArchive'); expect(page).not.toContain("redirect('/blog')"); }); it.skip('SUPERSEDED — /report used to redirect to /blog', () => { const src = read('app/report/page.tsx'); expect(src).toContain("redirect('/blog')"); }); it('/settings/security stays the real MFA page (NOT clobbered into a redirect)', () => { // The audit spec wanted this redirected too, but it is a working MFA // enrollment flow — overwriting it would be a security-feature regression. const src = read('app/settings/security/page.tsx'); expect(src).toContain('mfa'); expect(src).not.toContain("redirect('/profile')"); }); }); describe('Session 41 — profile reads tier from useAuth', () => { const src = read('app/profile/page.tsx'); it('destructures tier from useAuth (same source as the nav)', () => { expect(src).toMatch(/tier:\s*authTier/); }); it('derives the displayed tier from the auth session', () => { expect(src).toContain('authTier || profile.tier'); }); }); describe('Session 41 — self-hosted fonts (no Google Fonts CDN)', () => { const layout = read('app/layout.tsx'); const globals = read('app/globals.css'); it('layout uses next/font instead of a runtime ', () => { expect(layout).toContain("from 'next/font/google'"); expect(layout).toContain('Inter('); expect(layout).toContain('JetBrains_Mono('); }); it('removed the runtime Google Fonts stylesheet ', () => { // The historical reference survives in a code comment; what must be gone // is the actual CDN stylesheet href that caused the 503. expect(layout).not.toMatch(/href=["'][^"']*fonts\.googleapis\.com/); expect(layout).not.toContain('rel="stylesheet"'); }); it('globals.css :root maps --sans/--mono onto the next/font variables', () => { expect(globals).toContain('--sans: var(--font-sans)'); expect(globals).toContain('--mono: var(--font-mono)'); }); });