Session 23: All-day intelligence layer — schedule, game lines, streaks, hot lists, stat filtering, ParlayAPI dead (1567 tests)

This commit is contained in:
Kev
2026-06-12 11:16:58 -04:00
parent 6ab49d4c37
commit 0538205fab
32 changed files with 2276 additions and 2 deletions
@@ -0,0 +1,90 @@
// Integration: /api/streaks/:sport and /api/hotlist/:sport (Session 23).
// rosterLogs is mocked so we exercise route → engine without Redis.
const express = require('express');
const request = require('supertest');
jest.mock('../../src/services/rosterLogs', () => ({
loadRosterLogs: jest.fn(),
}));
const { loadRosterLogs } = require('../../src/services/rosterLogs');
function mountStreaks() {
delete require.cache[require.resolve('../../src/routes/streaks')];
const app = express();
app.use('/api/streaks', require('../../src/routes/streaks'));
return app;
}
function mountHotlist() {
delete require.cache[require.resolve('../../src/routes/hotlist')];
const app = express();
app.use('/api/hotlist', require('../../src/routes/hotlist'));
return app;
}
beforeEach(() => jest.clearAllMocks());
describe('GET /api/streaks/:sport', () => {
test('returns computed streaks from cached logs', async () => {
loadRosterLogs.mockResolvedValue([
{ name: 'Wemby', team: 'SA', games: [{ points: 30 }, { points: 28 }, { points: 26 }] },
]);
const res = await request(mountStreaks()).get('/api/streaks/nba');
expect(res.status).toBe(200);
expect(res.body.source).toBe('computed');
expect(res.body.streaks[0].player).toBe('Wemby');
expect(res.body.streaks[0].currentStreak).toBe(3);
});
test('stat filter narrows the response', async () => {
loadRosterLogs.mockResolvedValue([
{ name: 'A', games: [{ points: 30 }, { points: 30 }] },
{ name: 'B', games: [{ assists: 9 }, { assists: 8 }] },
]);
const res = await request(mountStreaks()).get('/api/streaks/nba?stat=points');
expect(res.body.stat).toBe('points');
expect(res.body.streaks.every((s) => s.category === 'points')).toBe(true);
});
test('empty roster → empty streaks, not an error', async () => {
loadRosterLogs.mockResolvedValue([]);
const res = await request(mountStreaks()).get('/api/streaks/mlb');
expect(res.status).toBe(200);
expect(res.body.streaks).toEqual([]);
});
test('loader throwing → 200 with empty streaks (platform never down)', async () => {
loadRosterLogs.mockRejectedValue(new Error('redis exploded'));
const res = await request(mountStreaks()).get('/api/streaks/nba');
expect(res.status).toBe(200);
expect(res.body.streaks).toEqual([]);
});
test('unsupported sport → 404', async () => {
const res = await request(mountStreaks()).get('/api/streaks/cricket');
expect(res.status).toBe(404);
});
});
describe('GET /api/hotlist/:sport', () => {
test('returns ranked hot players', async () => {
loadRosterLogs.mockResolvedValue([
{ name: 'Riser', seasonAvg: { points: 18 }, games: [{ points: 28 }, { points: 30 }] },
]);
const res = await request(mountHotlist()).get('/api/hotlist/nba?stat=points');
expect(res.status).toBe(200);
expect(res.body.players[0].name).toBe('Riser');
expect(res.body.players[0].rank).toBe(1);
});
test('empty roster → empty players', async () => {
loadRosterLogs.mockResolvedValue([]);
const res = await request(mountHotlist()).get('/api/hotlist/mlb');
expect(res.body.players).toEqual([]);
});
test('unsupported sport → 404', async () => {
const res = await request(mountHotlist()).get('/api/hotlist/nfl');
expect(res.status).toBe(404);
});
});