// 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); }); });