Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+205
View File
@@ -0,0 +1,205 @@
process.env.VYNDR_INTERNAL_KEY = 'corr-test-key';
const mockState = {
resolutions: [],
updates: [],
};
jest.mock('../../src/utils/supabase', () => ({
getSupabaseServiceClient: () => ({
from(table) {
const ctx = { table };
const proxy = {
select() { return proxy; },
gte() { return Promise.resolve({ data: mockState.resolutions, error: null }); },
update(patch) {
mockState.updates.push({ table, patch });
return {
eq() { return Promise.resolve({ error: null }); },
in() { return Promise.resolve({ error: null }); },
};
},
};
return proxy;
},
}),
}));
jest.mock('../../src/utils/rateLimiter', () => ({
createLimiter: () => ({ waitForToken: async () => true, snapshot: () => ({}) }),
createCircuitBreaker: () => ({ call: async (fn) => fn(), snapshot: () => ({}) }),
API_BUDGETS: {
espn: { tokensPerInterval: 100, interval: 60_000 },
mlbStats: { tokensPerInterval: 100, interval: 60_000 },
oddsPapi: { tokensPerInterval: 100, interval: 60_000 },
sharpApi: { tokensPerInterval: 100, interval: 60_000 },
openRouter: { tokensPerInterval: 100, interval: 60_000 },
},
}));
const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({
get: (...args) => mockAxiosGet(...args),
post: jest.fn().mockResolvedValue({ status: 200, data: {} }),
}));
jest.mock('../../src/services/distribution/telegram', () => ({
configured: () => false,
postToTelegram: async () => ({ ok: true }),
}));
const express = require('express');
const corrections = require('../../src/routes/corrections');
function makeApp() {
const app = express();
app.use(express.json());
app.use('/api/grading', corrections);
return app;
}
function call(app, body, headers = {}) {
return new Promise((resolve) => {
const http = require('http');
const server = app.listen(0, '127.0.0.1', () => {
const port = server.address().port;
const req = http.request({
host: '127.0.0.1',
port,
path: '/api/grading/correct',
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
}, (res) => {
const chunks = [];
res.on('data', (c) => chunks.push(c));
res.on('end', () => {
server.close();
const raw = Buffer.concat(chunks).toString('utf8');
let parsed; try { parsed = JSON.parse(raw); } catch { parsed = raw; }
resolve({ status: res.statusCode, body: parsed });
});
});
req.write(JSON.stringify(body || {}));
req.end();
});
});
}
beforeEach(() => {
mockState.resolutions = [];
mockState.updates = [];
mockAxiosGet.mockReset();
});
describe('POST /api/grading/correct', () => {
test('rejects without internal key', async () => {
const app = makeApp();
const res = await call(app, { hours: 24 });
expect(res.status).toBe(401);
});
test('no-ops when no resolutions in window', async () => {
mockState.resolutions = [];
const app = makeApp();
const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
expect(res.status).toBe(200);
expect(res.body).toMatchObject({ checked: 0, corrected: 0 });
});
test('detects a flipped result and updates both tables', async () => {
// Stored: hit at actual=10 (line 9.5 over). Box now reports actual=9 → miss.
mockState.resolutions = [{
id: 'res-1', grade_id: 'gh-1', game_id: 'g-correct', sport: 'nba',
player_espn_id: '999', player_name: 'Test Player', stat_type: 'points',
line: 9.5, direction: 'over', actual_value: 10, result: 'hit', margin: 0.5,
}];
// Build a minimal basketball box-score with the player's points now at 9.
mockAxiosGet.mockResolvedValue({
data: {
boxscore: {
players: [
{
statistics: [{
labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
athletes: [{ athlete: { id: 999 }, starter: true, stats: ['33','9','3-7','1-4','2-2','5','4','1','1','0'] }],
}],
},
{ statistics: [{ athletes: [] }] },
],
},
},
});
const app = makeApp();
const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
expect(res.status).toBe(200);
expect(res.body.checked).toBe(1);
expect(res.body.corrected).toBe(1);
expect(res.body.details[0]).toMatchObject({
flipped: true,
old: { actual: 10, result: 'hit' },
new: { actual: 9, result: 'miss' },
});
// Both tables updated (resolution_results + grade_history).
expect(mockState.updates.some((u) => u.table === 'resolution_results')).toBe(true);
expect(mockState.updates.some((u) => u.table === 'grade_history')).toBe(true);
});
test('value change without result flip is a silent update', async () => {
// Stored: hit at actual=12 (line 9.5 over). Box now reports actual=11 → still HIT, just lower margin.
mockState.resolutions = [{
id: 'res-2', grade_id: 'gh-2', game_id: 'g-silent', sport: 'nba',
player_espn_id: '111', player_name: 'Silent', stat_type: 'points',
line: 9.5, direction: 'over', actual_value: 12, result: 'hit', margin: 2.5,
}];
mockAxiosGet.mockResolvedValue({
data: {
boxscore: {
players: [
{
statistics: [{
labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
athletes: [{ athlete: { id: 111 }, starter: true, stats: ['30','11','5-9','1-2','0-0','3','2','0','0','0'] }],
}],
},
{ statistics: [{ athletes: [] }] },
],
},
},
});
const app = makeApp();
const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
expect(res.body.checked).toBe(1);
expect(res.body.corrected).toBe(0);
expect(res.body.details[0]).toMatchObject({ flipped: false, new: { actual: 11, result: 'hit' } });
});
test('no change at all is a no-op (no updates queued)', async () => {
mockState.resolutions = [{
id: 'res-3', grade_id: 'gh-3', game_id: 'g-noop', sport: 'nba',
player_espn_id: '222', player_name: 'NoChange', stat_type: 'points',
line: 9.5, direction: 'over', actual_value: 10, result: 'hit', margin: 0.5,
}];
mockAxiosGet.mockResolvedValue({
data: {
boxscore: {
players: [
{
statistics: [{
labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
athletes: [{ athlete: { id: 222 }, starter: true, stats: ['30','10','5-9','1-2','0-0','3','2','0','0','0'] }],
}],
},
{ statistics: [{ athletes: [] }] },
],
},
},
});
const app = makeApp();
const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
expect(res.body.checked).toBe(1);
expect(res.body.corrected).toBe(0);
expect(res.body.details).toHaveLength(0);
expect(mockState.updates).toHaveLength(0);
});
});