411cb6f196
POST /api/scan/parlay — authenticated parlay analysis: - Supabase JWT auth middleware (auth.getUser verification) - 5 correlation types detected between legs (same_game, same_team, same_player_conflicting, positive_correlation, blowout_cascade) - Overall parlay grading (A/B/C/D) with correlation penalty adjustments - Free tier: 5 scans/month, atomic scan count increment - Scan 5: full analysis + personalized upgrade pitch - Scan 6+: 403 block with upgrade pitch - Pitch personalization from scan history (top stats, grades, tier rec) - DB writes: picks + scan_sessions per scan 30 new tests, 158 total (131 Node.js + 27 Python), all passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
89 lines
3.6 KiB
JavaScript
89 lines
3.6 KiB
JavaScript
const { generateUpgradePitch } = require('../../src/services/upgradePitch');
|
|
|
|
function makeMockSupabase(sessions = [], picks = []) {
|
|
return {
|
|
from: (table) => ({
|
|
select: () => ({
|
|
eq: () => ({
|
|
order: () => ({
|
|
limit: () => Promise.resolve({ data: table === 'scan_sessions' ? sessions : picks }),
|
|
}),
|
|
single: () => Promise.resolve({ data: null }),
|
|
}),
|
|
}),
|
|
}),
|
|
};
|
|
}
|
|
|
|
describe('upgradePitch', () => {
|
|
test('generates pitch with correct scan count', async () => {
|
|
const sessions = [
|
|
{ id: '1', final_grade: 'A', legs: ['a', 'b'], created_at: '2026-03-20' },
|
|
{ id: '2', final_grade: 'B', legs: ['c', 'd'], created_at: '2026-03-19' },
|
|
{ id: '3', final_grade: 'D', legs: ['e'], created_at: '2026-03-18' },
|
|
{ id: '4', final_grade: 'C', legs: ['f', 'g'], created_at: '2026-03-17' },
|
|
];
|
|
const picks = [
|
|
{ stat_type: 'points', direction: 'over', grade: 'A', player: 'Jokic' },
|
|
{ stat_type: 'points', direction: 'over', grade: 'B', player: 'Jokic' },
|
|
{ stat_type: 'rebounds', direction: 'over', grade: 'C', player: 'LeBron' },
|
|
];
|
|
|
|
const supabase = makeMockSupabase(sessions, picks);
|
|
const result = await generateUpgradePitch(supabase, 'user-1', { grade: 'B', legs: [] });
|
|
|
|
expect(result.hook).toContain('5'); // 4 prior + 1 current
|
|
expect(result.insight).toContain('points');
|
|
expect(result.cta).toContain('founder rate');
|
|
expect(result.tier_recommended).toBeDefined();
|
|
expect(result.founder_price).toBeDefined();
|
|
});
|
|
|
|
test('recommends analyst for casual bettors (avg <= 4 legs)', async () => {
|
|
const sessions = [
|
|
{ id: '1', final_grade: 'B', legs: ['a', 'b'], created_at: '2026-03-20' },
|
|
{ id: '2', final_grade: 'B', legs: ['c', 'd'], created_at: '2026-03-19' },
|
|
];
|
|
const picks = [
|
|
{ stat_type: 'points', direction: 'over', grade: 'B', player: 'Jokic' },
|
|
];
|
|
|
|
const supabase = makeMockSupabase(sessions, picks);
|
|
const result = await generateUpgradePitch(supabase, 'user-1', { grade: 'A', legs: [{ stat_type: 'points' }] });
|
|
|
|
expect(result.tier_recommended).toBe('analyst');
|
|
expect(result.founder_price).toBe('$14.99/mo');
|
|
});
|
|
|
|
test('recommends desk for power users (avg > 4 legs)', async () => {
|
|
const sessions = [
|
|
{ id: '1', final_grade: 'B', legs: ['a', 'b', 'c', 'd', 'e'], created_at: '2026-03-20' },
|
|
{ id: '2', final_grade: 'A', legs: ['f', 'g', 'h', 'i', 'j', 'k'], created_at: '2026-03-19' },
|
|
];
|
|
const picks = [
|
|
{ stat_type: 'points', direction: 'over', grade: 'A', player: 'Jokic' },
|
|
{ stat_type: 'points', direction: 'over', grade: 'B', player: 'LeBron' },
|
|
{ stat_type: 'rebounds', direction: 'over', grade: 'A', player: 'Giannis' },
|
|
{ stat_type: 'assists', direction: 'over', grade: 'B', player: 'Trae' },
|
|
{ stat_type: 'points', direction: 'over', grade: 'B', player: 'Luka' },
|
|
];
|
|
|
|
const supabase = makeMockSupabase(sessions, picks);
|
|
const result = await generateUpgradePitch(supabase, 'user-1', {
|
|
grade: 'B',
|
|
legs: [{ stat_type: 'points' }, { stat_type: 'rebounds' }, { stat_type: 'assists' }, { stat_type: 'points' }, { stat_type: 'steals' }],
|
|
});
|
|
|
|
expect(result.tier_recommended).toBe('desk');
|
|
expect(result.founder_price).toBe('$34.99/mo');
|
|
});
|
|
|
|
test('handles no prior scans gracefully', async () => {
|
|
const supabase = makeMockSupabase([], []);
|
|
const result = await generateUpgradePitch(supabase, 'user-1', { grade: 'C', legs: [{ stat_type: 'points' }] });
|
|
|
|
expect(result.hook).toContain('1'); // just the current scan
|
|
expect(result.tier_recommended).toBeDefined();
|
|
});
|
|
});
|