Session 9: api-football + FootApi + Tank01 adapters, grace period middleware, cookie consent, /pricing page, OOM fix documented (1240 tests)
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
const mockAxiosGet = jest.fn();
|
||||
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args) }));
|
||||
|
||||
const mockCacheStore = new Map();
|
||||
const mockCacheTtls = new Map();
|
||||
jest.mock('../../src/utils/redis', () => ({
|
||||
cacheGet: async (k) => (mockCacheStore.has(k) ? mockCacheStore.get(k) : null),
|
||||
cacheSet: async (k, v, ttl) => { mockCacheStore.set(k, v); mockCacheTtls.set(k, ttl); return true; },
|
||||
cacheDel: async (k) => { mockCacheStore.delete(k); return true; },
|
||||
isDegraded: () => false,
|
||||
}));
|
||||
|
||||
const adapter = require('../../src/services/adapters/tank01MlbAdapter');
|
||||
|
||||
beforeEach(() => {
|
||||
mockAxiosGet.mockReset();
|
||||
mockCacheStore.clear();
|
||||
mockCacheTtls.clear();
|
||||
});
|
||||
|
||||
describe('tank01MlbAdapter', () => {
|
||||
describe('graceful degradation (no RAPID_API_KEY)', () => {
|
||||
const original = process.env.RAPID_API_KEY;
|
||||
beforeAll(() => { delete process.env.RAPID_API_KEY; });
|
||||
afterAll(() => { if (original !== undefined) process.env.RAPID_API_KEY = original; });
|
||||
|
||||
test('hasApiKey false', () => { expect(adapter.hasApiKey()).toBe(false); });
|
||||
|
||||
test('all endpoints return null without touching axios', async () => {
|
||||
expect(await adapter.getMLBBoxScore('20260611_ATL_NYM')).toBeNull();
|
||||
expect(await adapter.getMLBBatterVsPitcher('B1', 'P1')).toBeNull();
|
||||
expect(await adapter.getMLBDailyScoreboard('20260611')).toBeNull();
|
||||
expect(mockAxiosGet).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with key configured', () => {
|
||||
beforeAll(() => { process.env.RAPID_API_KEY = 'test-rapid-key'; });
|
||||
|
||||
test('RapidAPI host header is the MLB-specific Tank01 host', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({ data: { body: [] } });
|
||||
await adapter.getMLBDailyScoreboard('20260611');
|
||||
const [url, opts] = mockAxiosGet.mock.calls[0];
|
||||
expect(url).toMatch(/^https:\/\/tank01-mlb-live-in-game-real-time-statistics\.p\.rapidapi\.com/);
|
||||
expect(opts.headers['x-rapidapi-host']).toBe('tank01-mlb-live-in-game-real-time-statistics.p.rapidapi.com');
|
||||
});
|
||||
|
||||
test('TANK01_MLB_HOST override is honored', async () => {
|
||||
const original = process.env.TANK01_MLB_HOST;
|
||||
process.env.TANK01_MLB_HOST = 'alt-mlb.rapidapi.com';
|
||||
mockAxiosGet.mockResolvedValueOnce({ data: { body: [] } });
|
||||
await adapter.getMLBDailyScoreboard('20260611');
|
||||
const [url] = mockAxiosGet.mock.calls[0];
|
||||
expect(url).toMatch(/^https:\/\/alt-mlb\.rapidapi\.com/);
|
||||
if (original !== undefined) process.env.TANK01_MLB_HOST = original;
|
||||
else delete process.env.TANK01_MLB_HOST;
|
||||
});
|
||||
|
||||
test('getMLBBoxScore tags batters and pitchers with role', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({
|
||||
data: {
|
||||
body: {
|
||||
gameStatus: 'Final',
|
||||
playerStats: {
|
||||
batting: { 'B1': { longName: 'Ronald Acuña', teamAbv: 'ATL' } },
|
||||
pitching: { 'P1': { longName: 'Spencer Strider', teamAbv: 'ATL' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const stats = await adapter.getMLBBoxScore('GAME-1');
|
||||
expect(stats.find((s) => s.role === 'batter').name).toBe('Ronald Acuña');
|
||||
expect(stats.find((s) => s.role === 'pitcher').name).toBe('Spencer Strider');
|
||||
// Final → 24h TTL.
|
||||
expect(mockCacheTtls.get('tank01:mlb:boxscore:GAME-1')).toBe(adapter.__internals.TTL.boxScoreFinal);
|
||||
});
|
||||
|
||||
test('In-progress game keeps 5-min TTL on the box score', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({
|
||||
data: { body: { gameStatus: 'InProgress', playerStats: { batting: {}, pitching: {} } } },
|
||||
});
|
||||
await adapter.getMLBBoxScore('GAME-2');
|
||||
expect(mockCacheTtls.get('tank01:mlb:boxscore:GAME-2')).toBe(adapter.__internals.TTL.boxScoreLive);
|
||||
});
|
||||
|
||||
test('getMLBBatterVsPitcher projects single-object payload', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({
|
||||
data: {
|
||||
body: { batterID: 'B1', pitcherID: 'P1', PA: 18, AB: 16, H: 5, HR: 1, RBI: 3, SO: 4, AVG: '.313', OPS: '.857' },
|
||||
},
|
||||
});
|
||||
const bvp = await adapter.getMLBBatterVsPitcher('B1', 'P1');
|
||||
expect(bvp).toMatchObject({
|
||||
batterId: 'B1', pitcherId: 'P1',
|
||||
plateAppearances: 18, atBats: 16, hits: 5, homeRuns: 1, rbi: 3, strikeouts: 4,
|
||||
avg: '.313', ops: '.857',
|
||||
});
|
||||
});
|
||||
|
||||
test('getMLBBatterVsPitcher handles array of matchups', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({
|
||||
data: {
|
||||
body: [
|
||||
{ batterID: 'B1', pitcherID: 'P1', PA: 12, H: 3, SO: 5 },
|
||||
{ batterID: 'B1', pitcherID: 'P1', PA: 6, H: 2, SO: 1 },
|
||||
],
|
||||
},
|
||||
});
|
||||
const bvp = await adapter.getMLBBatterVsPitcher('B1', 'P1');
|
||||
expect(Array.isArray(bvp)).toBe(true);
|
||||
expect(bvp).toHaveLength(2);
|
||||
expect(bvp[0].plateAppearances).toBe(12);
|
||||
});
|
||||
|
||||
test('null IDs return null without touching axios', async () => {
|
||||
expect(await adapter.getMLBBoxScore(null)).toBeNull();
|
||||
expect(await adapter.getMLBBatterVsPitcher(null, 'P1')).toBeNull();
|
||||
expect(await adapter.getMLBBatterVsPitcher('B1', null)).toBeNull();
|
||||
expect(await adapter.getMLBDailyScoreboard(null)).toBeNull();
|
||||
expect(mockAxiosGet).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('getMLBDailyScoreboard projects both array + map shapes', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({
|
||||
data: { body: { 'GAME-X': { gameID: 'GAME-X', home: 'NYY', away: 'BOS', homePts: 5, awayPts: 4, gameStatus: 'Final' } } },
|
||||
});
|
||||
const games = await adapter.getMLBDailyScoreboard('20260611');
|
||||
expect(games).toHaveLength(1);
|
||||
expect(games[0]).toMatchObject({ gameId: 'GAME-X', homeTeam: 'NYY', awayTeam: 'BOS', homeScore: 5, awayScore: 4 });
|
||||
});
|
||||
|
||||
test('cache hit on repeat call', async () => {
|
||||
mockAxiosGet.mockResolvedValueOnce({ data: { body: { batterID: 'B', PA: 1 } } });
|
||||
await adapter.getMLBBatterVsPitcher('B', 'P');
|
||||
await adapter.getMLBBatterVsPitcher('B', 'P');
|
||||
expect(mockAxiosGet).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('axios throw → stale fallback', async () => {
|
||||
mockCacheStore.set('tank01:mlb:scoreboard:20260611:stale', { body: [{ gameID: 'STALE' }] });
|
||||
mockAxiosGet.mockRejectedValueOnce(new Error('upstream 500'));
|
||||
const games = await adapter.getMLBDailyScoreboard('20260611');
|
||||
expect(games[0].gameId).toBe('STALE');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user