Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
process.env.ENGINE2_ENABLED = 'false'; // queue but don't drain in unit tests
|
||||
|
||||
const mockAxiosGet = jest.fn();
|
||||
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args), post: jest.fn() }));
|
||||
|
||||
const mockGetPlayerProps = jest.fn();
|
||||
jest.mock('../../src/services/adapters/sharpApiAdapter', () => ({
|
||||
configured: () => true,
|
||||
getPlayerProps: (...args) => mockGetPlayerProps(...args),
|
||||
getGameOdds: async () => null,
|
||||
getConsensusLine: async () => null,
|
||||
}));
|
||||
|
||||
jest.mock('../../src/services/adapters/openRouterAdapter', () => ({
|
||||
configured: () => false,
|
||||
analyze: async () => null,
|
||||
getUsage: () => ({ requestsToday: 0, requestsRemaining: 1000 }),
|
||||
}));
|
||||
|
||||
// Feature/trap/consistency stubs — orchestrator owns the wiring; we
|
||||
// verify that wiring with deterministic returns rather than re-testing
|
||||
// every sub-service.
|
||||
jest.mock('../../src/services/intelligence/featureCache', () => ({
|
||||
getFeatures: async () => ({
|
||||
features: { l5_avg: 30, l20_avg: 26, home_away: 1.0, opp_rank_stat: 0.85, rest_days: 2 },
|
||||
meta: { features_available: [], features_missing: [] },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../src/services/intelligence/trapDetection', () => ({
|
||||
getTrapScore: async () => ({
|
||||
composite: 0.1,
|
||||
signals: {},
|
||||
active_count: 0,
|
||||
recommendation: 'proceed',
|
||||
}),
|
||||
normalizeName: (n) => String(n || '').toLowerCase(),
|
||||
detectReturningTeammate: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('../../src/services/intelligence/consistencyScore', () => ({
|
||||
getConsistency: async () => ({ consistency: 'reliable', cv: 0.18, score: 0.7, games: 20 }),
|
||||
}));
|
||||
|
||||
jest.mock('../../src/services/intelligence/gameLogService', () => ({
|
||||
getGameLogs: async () => [{ points: 30 }, { points: 28 }],
|
||||
getCareerPlayoffGames: async () => null,
|
||||
getWithWithoutStats: async () => null,
|
||||
}));
|
||||
|
||||
const mockInserts = [];
|
||||
jest.mock('../../src/utils/supabase', () => ({
|
||||
getSupabaseServiceClient: () => ({
|
||||
from() {
|
||||
const proxy = {
|
||||
insert(row) {
|
||||
mockInserts.push(row);
|
||||
const inserted = { id: `gid-${mockInserts.length}` };
|
||||
return {
|
||||
select() { return { single: () => Promise.resolve({ data: inserted, error: null }) }; },
|
||||
};
|
||||
},
|
||||
update() {
|
||||
return { eq() { return Promise.resolve({ error: null }); } };
|
||||
},
|
||||
};
|
||||
return proxy;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const orchestrator = require('../../src/services/intelligence/gradingOrchestrator');
|
||||
const engine2 = require('../../src/services/intelligence/engine2');
|
||||
|
||||
beforeEach(() => {
|
||||
mockAxiosGet.mockReset();
|
||||
mockGetPlayerProps.mockReset();
|
||||
mockInserts.length = 0;
|
||||
engine2.clearQueue();
|
||||
});
|
||||
|
||||
describe('runPipeline', () => {
|
||||
test('empty slate → summary with zeros', async () => {
|
||||
mockAxiosGet.mockResolvedValue({ status: 200, data: { events: [] } });
|
||||
const summary = await orchestrator.runPipeline('nba');
|
||||
expect(summary).toMatchObject({
|
||||
sport: 'nba',
|
||||
games_processed: 0,
|
||||
props_graded: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('one game with three props → 3 grades inserted', async () => {
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
status: 200,
|
||||
data: {
|
||||
events: [{
|
||||
id: 'ev-1',
|
||||
date: '2026-06-08T23:30Z',
|
||||
status: { type: { state: 'pre' } },
|
||||
competitions: [{
|
||||
competitors: [
|
||||
{ homeAway: 'home', id: '1', team: { abbreviation: 'NYK', displayName: 'Knicks' } },
|
||||
{ homeAway: 'away', id: '2', team: { abbreviation: 'BOS', displayName: 'Celtics' } },
|
||||
],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
});
|
||||
mockGetPlayerProps.mockResolvedValue([
|
||||
{ player: 'Jalen Brunson', team: 'NYK', statType: 'points', line: 26.5, direction: 'over' },
|
||||
{ player: 'Jayson Tatum', team: 'BOS', statType: 'points', line: 28.5, direction: 'over' },
|
||||
{ player: 'OG Anunoby', team: 'NYK', statType: 'rebounds', line: 6.5, direction: 'under' },
|
||||
]);
|
||||
|
||||
const summary = await orchestrator.runPipeline('nba', { skipEngine2: true });
|
||||
expect(summary.games_processed).toBe(1);
|
||||
expect(summary.props_graded).toBe(3);
|
||||
expect(mockInserts).toHaveLength(3);
|
||||
});
|
||||
|
||||
test('SharpAPI failure on one game increments errors, other games continue', async () => {
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
status: 200,
|
||||
data: {
|
||||
events: [
|
||||
{ id: 'ev-a', competitions: [{ competitors: [{ homeAway: 'home', team: { abbreviation: 'A' } }, { homeAway: 'away', team: { abbreviation: 'B' } }] }] },
|
||||
{ id: 'ev-b', competitions: [{ competitors: [{ homeAway: 'home', team: { abbreviation: 'C' } }, { homeAway: 'away', team: { abbreviation: 'D' } }] }] },
|
||||
],
|
||||
},
|
||||
});
|
||||
mockGetPlayerProps
|
||||
.mockRejectedValueOnce(new Error('upstream down'))
|
||||
.mockResolvedValueOnce([{ player: 'X', team: 'C', statType: 'points', line: 10, direction: 'over' }]);
|
||||
const summary = await orchestrator.runPipeline('nba', { skipEngine2: true });
|
||||
expect(summary.errors).toBe(1);
|
||||
expect(summary.props_graded).toBe(1);
|
||||
});
|
||||
|
||||
test('A-tier grades queued for engine2; C-tier not queued', async () => {
|
||||
process.env.ENGINE2_ENABLED = 'true';
|
||||
jest.resetModules();
|
||||
// Re-require with fresh ENGINE2_ENABLED.
|
||||
jest.doMock('axios', () => ({ get: (...args) => mockAxiosGet(...args), post: jest.fn() }));
|
||||
jest.doMock('../../src/services/adapters/sharpApiAdapter', () => ({
|
||||
configured: () => true, getPlayerProps: (...args) => mockGetPlayerProps(...args),
|
||||
}));
|
||||
jest.doMock('../../src/services/adapters/openRouterAdapter', () => ({
|
||||
configured: () => false, analyze: async () => null, getUsage: () => ({ requestsToday: 0, requestsRemaining: 1000 }),
|
||||
}));
|
||||
jest.doMock('../../src/services/intelligence/featureCache', () => ({
|
||||
getFeatures: async () => ({ features: { l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2 }, meta: {} }),
|
||||
}));
|
||||
jest.doMock('../../src/services/intelligence/trapDetection', () => ({
|
||||
getTrapScore: async () => ({ composite: 0.05, signals: {} }),
|
||||
normalizeName: (n) => n,
|
||||
}));
|
||||
jest.doMock('../../src/services/intelligence/consistencyScore', () => ({
|
||||
getConsistency: async () => ({ consistency: 'elite', score: 1.0 }),
|
||||
}));
|
||||
jest.doMock('../../src/services/intelligence/gameLogService', () => ({
|
||||
getGameLogs: async () => null, getCareerPlayoffGames: async () => null,
|
||||
}));
|
||||
jest.doMock('../../src/utils/supabase', () => ({
|
||||
getSupabaseServiceClient: () => ({
|
||||
from() {
|
||||
const proxy = {
|
||||
insert(row) {
|
||||
mockInserts.push(row);
|
||||
return { select() { return { single: () => Promise.resolve({ data: { id: `gid-${mockInserts.length}` }, error: null }) }; } };
|
||||
},
|
||||
update() { return { eq() { return Promise.resolve({ error: null }); } }; },
|
||||
};
|
||||
return proxy;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const orch = require('../../src/services/intelligence/gradingOrchestrator');
|
||||
const e2 = require('../../src/services/intelligence/engine2');
|
||||
e2.clearQueue();
|
||||
mockInserts.length = 0;
|
||||
mockAxiosGet.mockResolvedValue({
|
||||
status: 200,
|
||||
data: {
|
||||
events: [{
|
||||
id: 'ev-q',
|
||||
competitions: [{ competitors: [
|
||||
{ homeAway: 'home', team: { abbreviation: 'NYK' } },
|
||||
{ homeAway: 'away', team: { abbreviation: 'BOS' } },
|
||||
] }],
|
||||
}],
|
||||
},
|
||||
});
|
||||
mockGetPlayerProps.mockResolvedValue([
|
||||
{ player: 'A-tier player', team: 'NYK', statType: 'points', line: 25.5, direction: 'over' },
|
||||
]);
|
||||
|
||||
const summary = await orch.runPipeline('nba', { skipEngine2: true });
|
||||
expect(summary.props_graded).toBe(1);
|
||||
// The grade should have been A/A-/A+ given those features, so engine2
|
||||
// queue should have grown.
|
||||
expect(summary.engine2_queued).toBe(1);
|
||||
process.env.ENGINE2_ENABLED = 'false';
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEngineStatus', () => {
|
||||
test('returns adapter configuration and queue size', () => {
|
||||
const status = orchestrator.getEngineStatus();
|
||||
expect(status.adapters_configured).toHaveProperty('sharp_api');
|
||||
expect(status.adapters_configured).toHaveProperty('open_router');
|
||||
expect(typeof status.engine2_queue_size).toBe('number');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user