Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user