Files
vyndr/tests/unit/mlbStatsAdapter.test.js
T

128 lines
5.1 KiB
JavaScript

// Unit: MLB Stats API adapter (Session 30). No auth; cached; defensive.
const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({ get: (...a) => mockAxiosGet(...a) }));
const mockStore = new Map();
const mockTtls = new Map();
jest.mock('../../src/utils/redis', () => ({
cacheGet: async (k) => (mockStore.has(k) ? mockStore.get(k) : null),
cacheSet: async (k, v, ttl) => { mockStore.set(k, v); mockTtls.set(k, ttl); return true; },
}));
const adapter = require('../../src/services/adapters/mlbStatsAdapter');
const { TTL } = adapter.__internals;
beforeEach(() => {
mockAxiosGet.mockReset();
mockStore.clear();
mockTtls.clear();
});
describe('mlbStatsAdapter — no auth', () => {
test('sends NO auth headers (free API)', async () => {
mockAxiosGet.mockResolvedValue({ data: { dates: [] } });
await adapter.getScheduleWithPitchers('2026-06-14');
const [, opts] = mockAxiosGet.mock.calls[0];
expect(opts.headers).toBeUndefined();
});
});
describe('mlbStatsAdapter — schedule', () => {
test('returns normalized games with probable pitchers', async () => {
mockAxiosGet.mockResolvedValue({
data: { totalGames: 1, dates: [{ games: [{
gamePk: 777, gameDate: '2026-06-14T17:10:00Z',
status: { abstractGameState: 'Preview' },
venue: { name: 'GABP' },
teams: {
home: { team: { name: 'Reds', id: 17 }, probablePitcher: { id: 1, fullName: 'Hunter Greene' } },
away: { team: { name: 'D-backs', id: 29 }, probablePitcher: { id: 2, fullName: 'Zac Gallen' } },
},
}] }] },
});
const games = await adapter.getScheduleWithPitchers('2026-06-14');
expect(games).toHaveLength(1);
expect(games[0].gamePk).toBe(777);
expect(games[0].home.probablePitcher).toEqual({ id: 1, name: 'Hunter Greene' });
expect(games[0].away.team).toBe('D-backs');
expect(games[0].venue).toBe('GABP');
const url = mockAxiosGet.mock.calls[0][0];
expect(url).toContain('/schedule?sportId=1&date=2026-06-14');
expect(url).toContain('hydrate=probablePitcher');
expect(mockTtls.get('mlbstats:schedule:2026-06-14')).toBe(TTL.schedule);
});
test('missing date → [] without axios', async () => {
expect(await adapter.getScheduleWithPitchers()).toEqual([]);
expect(mockAxiosGet).not.toHaveBeenCalled();
});
test('error → [] (stale fallback empty)', async () => {
mockAxiosGet.mockRejectedValue(new Error('mlb down'));
expect(await adapter.getScheduleWithPitchers('2026-06-14')).toEqual([]);
});
});
describe('mlbStatsAdapter — game log', () => {
test('returns per-game splits', async () => {
mockAxiosGet.mockResolvedValue({
data: { stats: [{ splits: [
{ date: '2026-06-13', opponent: { name: 'Mets' }, isHome: true, stat: { hits: 2, homeRuns: 1 } },
{ date: '2026-06-12', opponent: { name: 'Mets' }, isHome: true, stat: { hits: 0 } },
] }] },
});
const log = await adapter.getPlayerGameLog(592450, 2026, 'hitting');
expect(log).toHaveLength(2);
expect(log[0].stat.hits).toBe(2);
expect(log[0].opponent).toBe('Mets');
const url = mockAxiosGet.mock.calls[0][0];
expect(url).toContain('/people/592450/stats?stats=gameLog&season=2026&group=hitting');
expect(mockTtls.get('mlbstats:gamelog:592450:2026:hitting')).toBe(TTL.gameLog);
});
test('no playerId → []', async () => {
expect(await adapter.getPlayerGameLog()).toEqual([]);
});
});
describe('mlbStatsAdapter — season averages', () => {
test('returns the season stat object', async () => {
mockAxiosGet.mockResolvedValue({ data: { stats: [{ splits: [{ stat: { avg: '.312', obp: '.401', slg: '.589', ops: '.990', homeRuns: 22, rbi: 55 } }] }] } });
const s = await adapter.getSeasonAverages(592450);
expect(s.avg).toBe('.312');
expect(s.slg).toBe('.589');
expect(mockTtls.get('mlbstats:season:592450:2026:hitting')).toBe(TTL.season);
});
test('empty splits → null', async () => {
mockAxiosGet.mockResolvedValue({ data: { stats: [] } });
expect(await adapter.getSeasonAverages(592450)).toBeNull();
});
});
describe('mlbStatsAdapter — batter vs pitcher', () => {
test('returns matchup stat object', async () => {
mockAxiosGet.mockResolvedValue({ data: { stats: [{ splits: [{ stat: { atBats: 14, hits: 5, homeRuns: 2, avg: '.357' } }] }] } });
const bvp = await adapter.getBatterVsPitcher(592450, 12345);
expect(bvp.hits).toBe(5);
expect(bvp.homeRuns).toBe(2);
const url = mockAxiosGet.mock.calls[0][0];
expect(url).toContain('stats=vsPlayer&opposingPlayerId=12345');
expect(mockTtls.get('mlbstats:bvp:592450:12345:hitting')).toBe(TTL.bvp);
});
test('missing ids → null without axios', async () => {
expect(await adapter.getBatterVsPitcher(null, 1)).toBeNull();
expect(await adapter.getBatterVsPitcher(1, null)).toBeNull();
expect(mockAxiosGet).not.toHaveBeenCalled();
});
test('cache hit on repeat call', async () => {
mockAxiosGet.mockResolvedValue({ data: { stats: [{ splits: [{ stat: { hits: 1 } }] }] } });
await adapter.getBatterVsPitcher(1, 2);
await adapter.getBatterVsPitcher(1, 2);
expect(mockAxiosGet).toHaveBeenCalledTimes(1);
});
});