Session 7d: Audit fixes - rate limiting, error leak, parallel parlays, analyze cache, bundle analyzer
This commit is contained in:
@@ -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-');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user