Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+110
View File
@@ -0,0 +1,110 @@
const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args) }));
const mockCache = { current: new Map() };
jest.mock('../../src/utils/redis', () => ({
cacheGet: async (k) => mockCache.current.get(k) ?? null,
cacheSet: async (k, v) => { mockCache.current.set(k, v); return true; },
cacheDel: async (k) => { mockCache.current.delete(k); return true; },
}));
jest.mock('../../src/utils/rateLimiter', () => ({
createLimiter: () => ({ waitForToken: async () => true, snapshot: () => ({}) }),
createCircuitBreaker: () => ({ call: async (fn) => fn(), snapshot: () => ({}) }),
}));
const cache = require('../../src/services/intelligence/teamStatsCache');
beforeEach(() => {
mockAxiosGet.mockReset();
mockCache.current.clear();
});
describe('teamStatsCache', () => {
test('refreshTeamStats walks team list and writes cache per team', async () => {
mockAxiosGet
// teams list
.mockResolvedValueOnce({
status: 200,
data: {
sports: [{ leagues: [{ teams: [
{ team: { id: 1, abbreviation: 'NYK', displayName: 'Knicks' } },
{ team: { id: 2, abbreviation: 'BOS', displayName: 'Celtics' } },
] }] }],
},
})
// team 1 stats
.mockResolvedValueOnce({
status: 200,
data: { results: { stats: [{ stats: [
{ name: 'offensiveRating', value: 118.5 },
{ name: 'defensiveRating', value: 110.2 },
{ name: 'pace', value: 100.4 },
] }] } },
})
// team 2 stats
.mockResolvedValueOnce({
status: 200,
data: { results: { stats: [{ stats: [
{ name: 'offensiveRating', value: 120.0 },
{ name: 'defensiveRating', value: 108.0 },
] }] } },
});
const summary = await cache.refreshTeamStats('nba');
expect(summary.captured).toBe(2);
expect(summary.total).toBe(2);
const nyk = await cache.getTeamStats('nba', 'NYK');
expect(nyk).toMatchObject({ offensive_rating: 118.5, defensive_rating: 110.2, pace: 100.4 });
const bos = await cache.getTeamStats('nba', 'BOS');
expect(bos).toMatchObject({ offensive_rating: 120.0, defensive_rating: 108.0 });
});
test('getOpponentRank returns the normalized 0..1 rank baked at refresh time', async () => {
// The normalized value is set during refreshTeamStats; reads use it
// directly. A solo-team cache entry without the field returns null.
mockCache.current.set('team_stats:nba:NYK', {
defensive_rating: 110.2,
defensive_rank_normalized: 0.45,
});
expect(await cache.getOpponentRank('nba', 'NYK', 'points')).toBe(0.45);
});
test('getOpponentRank returns null when cache predates the normalization upgrade', async () => {
mockCache.current.set('team_stats:nba:OLD', { defensive_rating: 110.2 });
expect(await cache.getOpponentRank('nba', 'OLD', 'points')).toBeNull();
});
test('refreshTeamStats normalizes defensive_rank_normalized across the league', async () => {
mockAxiosGet
.mockResolvedValueOnce({
status: 200,
data: {
sports: [{ leagues: [{ teams: [
{ team: { id: 1, abbreviation: 'BEST', displayName: 'Best D' } },
{ team: { id: 2, abbreviation: 'MID', displayName: 'Middle' } },
{ team: { id: 3, abbreviation: 'WORST', displayName: 'Worst D' } },
] }] }],
},
})
.mockResolvedValueOnce({ status: 200, data: { results: { stats: [{ stats: [{ name: 'defensiveRating', value: 105 }] }] } } })
.mockResolvedValueOnce({ status: 200, data: { results: { stats: [{ stats: [{ name: 'defensiveRating', value: 112 }] }] } } })
.mockResolvedValueOnce({ status: 200, data: { results: { stats: [{ stats: [{ name: 'defensiveRating', value: 118 }] }] } } });
await cache.refreshTeamStats('nba');
expect(await cache.getOpponentRank('nba', 'BEST', 'points')).toBe(0);
expect(await cache.getOpponentRank('nba', 'MID', 'points')).toBeCloseTo(0.5, 5);
expect(await cache.getOpponentRank('nba', 'WORST', 'points')).toBe(1);
});
test('getTeamStats returns null when nothing cached', async () => {
expect(await cache.getTeamStats('nba', 'GHOST')).toBeNull();
});
test('refreshTeamStats skips unsupported sport gracefully', async () => {
const summary = await cache.refreshTeamStats('curling');
// listTeams returns [] for unsupported sport, so total = 0.
expect(summary).toMatchObject({ captured: 0, errored: 0, total: 0 });
});
});