'use strict'; /** * GET /api/live/:sport (A1 Session 11) — route wiring over the cache-aside * service. The service's cache behavior is unit-tested with injected deps * (liveTrackingService.test.js); here we lock the route contract: sport * gating BEFORE the service, envelope pass-through, and fail-open 200s. */ const express = require('express'); const request = require('supertest'); jest.mock('../../src/services/liveTrackingService', () => ({ getLiveTracking: jest.fn(), })); const { getLiveTracking } = require('../../src/services/liveTrackingService'); function mountLive() { delete require.cache[require.resolve('../../src/routes/live')]; const liveRoutes = require('../../src/routes/live'); const app = express(); app.use('/api/live', liveRoutes); return app; } beforeEach(() => jest.clearAllMocks()); describe('GET /api/live/:sport', () => { it('returns the service envelope for a wired sport (mlb)', async () => { const envelope = { sport: 'mlb', date: '2026-07-11', hasLive: true, updated_at: 'x', games: [{ id: '824249', progress: { label: '▼8th', fraction: 0.83 }, players: { 'bryce harper': { name: 'Bryce Harper', values: { total_bases: 3 } } } }], }; getLiveTracking.mockResolvedValue(envelope); const res = await request(mountLive()).get('/api/live/mlb'); expect(res.status).toBe(200); expect(res.body).toEqual(envelope); expect(getLiveTracking).toHaveBeenCalledWith('mlb'); expect(res.headers['cache-control']).toContain('max-age=30'); }); it('wnba is wired', async () => { getLiveTracking.mockResolvedValue({ sport: 'wnba', hasLive: false, games: [] }); const res = await request(mountLive()).get('/api/live/wnba'); expect(res.status).toBe(200); expect(getLiveTracking).toHaveBeenCalledWith('wnba'); }); it('an unwired sport short-circuits WITHOUT touching the service (no quota risk)', async () => { const res = await request(mountLive()).get('/api/live/nba'); expect(res.status).toBe(200); expect(res.body).toEqual({ sport: 'nba', hasLive: false, games: [] }); expect(getLiveTracking).not.toHaveBeenCalled(); }); it('a service failure fails OPEN — 200 with an honest empty envelope', async () => { getLiveTracking.mockRejectedValue(new Error('redis down')); const res = await request(mountLive()).get('/api/live/mlb'); expect(res.status).toBe(200); expect(res.body).toEqual({ sport: 'mlb', hasLive: false, games: [] }); }); it('sport param is case-insensitive', async () => { getLiveTracking.mockResolvedValue({ sport: 'mlb', hasLive: false, games: [] }); const res = await request(mountLive()).get('/api/live/MLB'); expect(res.status).toBe(200); expect(getLiveTracking).toHaveBeenCalledWith('mlb'); }); });