Session 7d: Audit fixes - rate limiting, error leak, parallel parlays, analyze cache, bundle analyzer

This commit is contained in:
Kev
2026-06-10 03:12:20 -04:00
parent d954e4d952
commit 6f4a353de9
18 changed files with 913 additions and 72 deletions
+122
View File
@@ -0,0 +1,122 @@
// PERF-2 (Session 7d): proves analyzeProp runs in parallel inside
// scanParlay. We mock analyzeProp to sleep — a sequential loop would
// take N × delay, parallel allSettled takes ~delay.
let mockAnalyzeDelayMs = 100;
let mockAnalyzeCallTimes = [];
let mockAnalyzeRejectIndices = new Set();
jest.mock('../../src/services/propAnalyzer', () => ({
analyzeProp: async (leg) => {
const startedAt = Date.now();
mockAnalyzeCallTimes.push(startedAt);
await new Promise((r) => setTimeout(r, mockAnalyzeDelayMs));
if (mockAnalyzeRejectIndices.has(leg._idx)) {
throw new Error(`forced failure for leg ${leg._idx}`);
}
return {
...leg,
grade: 'A-',
confidence: 0.78,
edge_pct: 5.2,
reasoning: { summary: 'ok', steps: {} },
kill_conditions_triggered: [],
};
},
}));
jest.mock('../../src/services/oddsService', () => ({
getOdds: async () => ({ spreads: [], props: [] }),
}));
jest.mock('../../src/services/correlationEngine', () => ({
detectCorrelations: () => [],
}));
jest.mock('../../src/services/parlayGrader', () => ({
gradeParlayFromLegs: () => ({ grade: 'A-', confidence: 0.7 }),
}));
jest.mock('../../src/services/upgradePitch', () => ({
generateUpgradePitch: async () => null,
}));
function makeSelectChain(arrayRows, singleRow) {
return {
single: () => Promise.resolve({ data: singleRow, error: null }),
then: (resolve) => resolve({ data: arrayRows, error: null }),
};
}
const mockSupabase = {
from() {
return {
insert(rows) {
const isArr = Array.isArray(rows);
const arrayRows = isArr ? rows.map((_, i) => ({ id: `p${i + 1}` })) : [{ id: 'p1' }];
const singleRow = { id: 'sess-1' };
return {
select: () => makeSelectChain(arrayRows, singleRow),
};
},
update() {
const chain = {
eq() { return chain; },
select() { return chain; },
single: () => Promise.resolve({ data: { scan_count: 1 }, error: null }),
};
return chain;
},
};
},
};
jest.mock('../../src/utils/supabase', () => ({
getSupabaseServiceClient: () => mockSupabase,
}));
const { scanParlay } = require('../../src/services/parlayScanService');
beforeEach(() => {
mockAnalyzeCallTimes = [];
mockAnalyzeRejectIndices = new Set();
mockAnalyzeDelayMs = 80;
});
describe('parlayScanService parallel leg resolution (PERF-2)', () => {
test('6 legs resolve in roughly one delay window, not six', async () => {
const user = { id: 'u1', tier: 'desk', scan_count: 0 };
const legs = [0, 1, 2, 3, 4, 5].map((i) => ({
_idx: i, player: `P${i}`, stat_type: 'points', line: 25, direction: 'over',
}));
const start = Date.now();
const out = await scanParlay(user, legs);
const elapsed = Date.now() - start;
expect(out.legs).toHaveLength(6);
// analyzeProp was invoked once per leg.
expect(mockAnalyzeCallTimes.length).toBe(6);
// Every call started within a small window of each other — proves
// the loop didn't await one before issuing the next.
const first = Math.min(...mockAnalyzeCallTimes);
const last = Math.max(...mockAnalyzeCallTimes);
expect(last - first).toBeLessThan(40);
// Sequential 6 × 80ms ≈ 480ms; parallel should land near 80-200ms
// depending on host. Leave generous headroom for slow CI.
expect(elapsed).toBeLessThan(6 * mockAnalyzeDelayMs * 0.7);
});
test('one failed leg does not crash the parlay; the rest succeed', async () => {
const user = { id: 'u2', tier: 'desk', scan_count: 0 };
const legs = [0, 1, 2].map((i) => ({
_idx: i, player: `P${i}`, stat_type: 'points', line: 25, direction: 'over',
}));
mockAnalyzeRejectIndices = new Set([1]);
const out = await scanParlay(user, legs);
expect(out.legs).toHaveLength(3);
// Index 1 is the failed leg — grade should be 'F' from the stub.
expect(out.legs[1].grade).toBe('F');
expect(out.legs[0].grade).toBe('A-');
expect(out.legs[2].grade).toBe('A-');
});
});