Files
vyndr/tests/unit/session41AuditFixes.test.js
T
builtbykev 8bc79f3c38 Session 42: Player Intelligence System — archetypes, stat strips, player profile, enhanced cards (2011 tests)
Built from the Claude Design "VYNDR Player Intelligence" bundle (10 sections).

- Archetypes: src/services/archetypeService.js — 41 archetypes (15 NBA / 5
  WNBA-unique / 15 MLB / 6 soccer), classify -> primary+secondary+blend.
  Frontend visual map web/src/lib/archetypes.js (colors verified == backend).
  ArchetypeBadge (full/ghost/tint + glyphs) + ArchetypeBlend (DNA bar).
- StatStrip (compact/expanded): player name once, horizontal mono stats,
  inline GradeBadge props, onPlayerClick -> profile.
- Stats API: extended src/routes/stats.js with /player/:name, /leaders,
  /game/:id (rate-limited). Aggregation in playerIntelService.js (sanitizes
  name param; grades cache; graceful on cold cache). Next proxies added.
- Player Profile /player/[name]: all 9 design sections, graceful empty states.
- Enhanced GameCard (MLB pitchers + player-grouped StatStrips) + GradeResultCard
  (archetype strip + stat context + VYNDR intelligence, optional/self-hiding via
  gradeAdapter.buildIntelFields). Player-name links wired everywhere.
- Settings page replaces the S41 redirect (account/subscription/notifications/
  display/responsible-play/danger-zone with DELETE-gated delete). LINKS to the
  real /settings/security MFA page — does not replace it. + BookChip.
- Bonus: Stats Explorer /explore (real /api/stats/leaders leaderboard); added
  Explore + Settings to Nav MORE.

Deferred (need data pipelines, Session 43): Team Hub, Offseason Intel, Slate
redesign, Stats Explorer sub-panels.

Backend 1940 -> 2011 tests (+71), 157 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:12:24 -04:00

83 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Session 41 — P0 audit fixes. Frontend assertions read page source as text
// (same pattern as the Phase DH 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 redirects to /blog (THE REPORT link target)', () => {
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 <link>', () => {
expect(layout).toContain("from 'next/font/google'");
expect(layout).toContain('Inter(');
expect(layout).toContain('JetBrains_Mono(');
});
it('removed the runtime Google Fonts stylesheet <link>', () => {
// 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)');
});
});