process.env.ENGINE2_ENABLED = 'true'; const mockAnalyze = jest.fn(); jest.mock('../../src/services/adapters/openRouterAdapter', () => ({ configured: () => true, analyze: (...args) => mockAnalyze(...args), getUsage: () => ({ requestsToday: 0, requestsRemaining: 1000 }), })); const mockUpdates = []; jest.mock('../../src/utils/supabase', () => ({ getSupabaseServiceClient: () => ({ from() { const proxy = { update(patch) { mockUpdates.push(patch); return { eq() { return Promise.resolve({ error: null }); }, }; }, }; return proxy; }, }), })); const engine2 = require('../../src/services/intelligence/engine2'); beforeEach(() => { engine2.clearQueue(); mockAnalyze.mockReset(); mockUpdates.length = 0; }); function sampleContext(overrides = {}) { return { player_name: 'Jalen Brunson', team: 'NYK', sport: 'nba', direction: 'over', line: 26.5, stat_type: 'points', home_team: 'NYK', away_team: 'BOS', game_date: '2026-06-08', engine1_grade: 'A-', engine1_factors: ['l5_avg', 'opp_rank_stat', 'home_away'], features: { l5_avg: 30.4, l20_avg: 26.1, home_away: 1.0 }, trap: { composite: 0.18, recommendation: 'proceed', signals: {} }, consistency: { consistency: 'reliable', cv: 0.18, score: 0.7 }, recentGames: [ { date: '2026-06-05', value: 32, opponent: 'BOS', home: true }, { date: '2026-06-03', value: 28, opponent: 'MIA', home: false }, ], ...overrides, }; } describe('engine2 — eligibility', () => { test('queues A/B tier grades', () => { engine2.queueAnalysis('g1', sampleContext({ engine1_grade: 'A-' })); engine2.queueAnalysis('g2', sampleContext({ engine1_grade: 'B+' })); expect(engine2.getQueueSize()).toBe(2); }); test('SKIPS C/D/F grades', () => { engine2.queueAnalysis('c1', sampleContext({ engine1_grade: 'C' })); engine2.queueAnalysis('d1', sampleContext({ engine1_grade: 'D' })); engine2.queueAnalysis('f1', sampleContext({ engine1_grade: 'F' })); expect(engine2.getQueueSize()).toBe(0); }); test('respects ENGINE2_ENABLED=false', () => { process.env.ENGINE2_ENABLED = 'false'; jest.resetModules(); const fresh = require('../../src/services/intelligence/engine2'); fresh.queueAnalysis('g3', sampleContext({ engine1_grade: 'A' })); expect(fresh.getQueueSize()).toBe(0); process.env.ENGINE2_ENABLED = 'true'; }); }); describe('engine2 — prompt construction', () => { test('buildPrompt includes player, features, traps, and recent games', () => { const prompt = engine2.__internals.buildPrompt(sampleContext()); expect(prompt).toContain('Jalen Brunson'); expect(prompt).toContain('PROP: over 26.5 points'); expect(prompt).toContain('l5_avg'); expect(prompt).toContain('Trap composite: 0.18'); expect(prompt).toContain('cv=0.18'); }); test('prompt never contains the literal string "VYNDR"', () => { const prompt = engine2.__internals.buildPrompt(sampleContext()); expect(prompt).not.toMatch(/VYNDR/); }); }); describe('engine2 — response parsing', () => { test('parseResponse handles raw JSON', () => { const r = engine2.__internals.parseResponse('{"grade":"A","confidence":0.7,"narrative":"..."}'); expect(r.grade).toBe('A'); }); test('parseResponse handles markdown fenced JSON', () => { const r = engine2.__internals.parseResponse('Here you go:\n```json\n{"grade":"B"}\n```'); expect(r.grade).toBe('B'); }); test('parseResponse extracts JSON from chatty preamble', () => { const r = engine2.__internals.parseResponse('Sure! Here is the analysis: {"grade":"B+","confidence":0.6}'); expect(r.grade).toBe('B+'); }); test('parseResponse returns null when no JSON found', () => { expect(engine2.__internals.parseResponse('no json here')).toBeNull(); expect(engine2.__internals.parseResponse(null)).toBeNull(); }); }); describe('engine2 — validation', () => { test('rejects unknown grade values', () => { expect(engine2.__internals.validateAnalysis({ grade: 'AAA', confidence: 0.7, narrative: 'x', })).toBeNull(); }); test('rejects out-of-range confidence', () => { expect(engine2.__internals.validateAnalysis({ grade: 'A', confidence: 1.5, narrative: 'x', })).toBeNull(); }); test('rejects empty narrative', () => { expect(engine2.__internals.validateAnalysis({ grade: 'A', confidence: 0.7, narrative: '', })).toBeNull(); }); test('truncates narrative + concern + key_factor to caps', () => { const out = engine2.__internals.validateAnalysis({ grade: 'A', confidence: 0.7, narrative: 'x'.repeat(800), trap_concern: 'y'.repeat(500), key_factor: 'z'.repeat(300), agrees_with_engine1: true, }); expect(out.narrative.length).toBe(500); expect(out.trap_concern.length).toBe(300); expect(out.key_factor.length).toBe(200); }); test('passes explicit null grade through', () => { const out = engine2.__internals.validateAnalysis({ grade: null, reason: 'not enough data' }); expect(out).toEqual({ grade: null, reason: 'not enough data' }); }); }); describe('engine2 — processQueue', () => { test('processes queued analysis, persists to grade_history', async () => { mockAnalyze.mockResolvedValue({ response: JSON.stringify({ grade: 'A', confidence: 0.78, agrees_with_engine1: true, narrative: 'Brunson trending up + weak D matchup + rested.', trap_concern: null, key_factor: 'opp_rank_stat', }), modelUsed: 'deepseek/deepseek-chat', latencyMs: 4200, }); engine2.queueAnalysis('grade-1', sampleContext()); const summary = await engine2.processQueue(); expect(summary).toMatchObject({ processed: 1, succeeded: 1, failed: 0 }); expect(mockUpdates).toHaveLength(1); expect(mockUpdates[0].engine2_grade).toBe('A'); expect(mockUpdates[0].engine2_confidence).toBe(0.78); expect(mockUpdates[0].engine2_model).toBe('deepseek/deepseek-chat'); }); test('records failures without crashing', async () => { mockAnalyze.mockResolvedValue(null); // adapter unavailable engine2.queueAnalysis('grade-2', sampleContext()); const summary = await engine2.processQueue(); expect(summary).toMatchObject({ processed: 1, succeeded: 0, failed: 1 }); }); test('respects BATCH_SIZE — leaves remaining items for next call', async () => { mockAnalyze.mockResolvedValue({ response: JSON.stringify({ grade: 'A', confidence: 0.7, agrees_with_engine1: true, narrative: 'x' }), modelUsed: 'deepseek/deepseek-chat', latencyMs: 100, }); // Queue more than BATCH_SIZE items. for (let i = 0; i < engine2.__internals.BATCH_SIZE + 3; i += 1) { engine2.queueAnalysis(`g-${i}`, sampleContext()); } const summary = await engine2.processQueue(); expect(summary.processed).toBe(engine2.__internals.BATCH_SIZE); expect(summary.remaining).toBe(3); }); });