Session 32: Grades pipeline + NFL/NHL wiring + rate limiting + audit cleanup (1718 tests)
- gradeSlateService writes grades:{sport} cache (closes content pipeline →
dataLevel full); fire-and-forget from oddsService.recordDownstream, gated
by shouldGradeSlate (off in test, GRADE_SLATE_ON_FETCH override)
- NFL/NHL wired: oddsService SPORT_KEYS/SPORT_MARKETS (correct the-odds-api
keys americanfootball_nfl/icehockey_nhl), proplineAdapter MARKETS, NHL
MARKET_MAP keys to avoid silent-zero
- rate limiting mounted on 8 public cached routers (odds/parlay 30/min,
rest 60/min)
- jsonlLogger writes to temp under test (no more dirtied tracked artifact);
5MB pipeline test given 20s timeout
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
// Unit: NFL + NHL sport-key wiring (Session 32). Closes the silent-zero
|
||||
// trap before NFL season — sport keys, per-sport markets, PropLine markets,
|
||||
// and end-to-end MARKET_MAP normalization.
|
||||
|
||||
const oddsService = require('../../src/services/oddsService');
|
||||
const propline = require('../../src/services/adapters/proplineAdapter');
|
||||
const { normalizeProps } = require('../../src/utils/oddsNormalizer');
|
||||
|
||||
describe('oddsService sport keys', () => {
|
||||
test('nfl/nhl map to the correct the-odds-api keys', () => {
|
||||
// the-odds-api uses full-name prefixes (basketball_nba, baseball_mlb);
|
||||
// NFL/NHL follow the same convention — NOT football_nfl/hockey_nhl.
|
||||
expect(oddsService.SPORT_KEYS.nfl).toBe('americanfootball_nfl');
|
||||
expect(oddsService.SPORT_KEYS.nhl).toBe('icehockey_nhl');
|
||||
});
|
||||
|
||||
test('getMarketsForSport(nfl) requests NFL markets + spreads (not the NBA fallback)', () => {
|
||||
const markets = oddsService.getMarketsForSport('nfl');
|
||||
expect(markets).toContain('player_pass_yds');
|
||||
expect(markets).toContain('player_anytime_td');
|
||||
expect(markets).toContain('spreads');
|
||||
expect(markets).not.toContain('player_points'); // would mean nba fallback
|
||||
});
|
||||
|
||||
test('getMarketsForSport(nhl) requests NHL markets', () => {
|
||||
const markets = oddsService.getMarketsForSport('nhl');
|
||||
expect(markets).toContain('player_shots_on_goal');
|
||||
expect(markets).toContain('goalie_saves');
|
||||
expect(markets).not.toContain('player_points');
|
||||
});
|
||||
});
|
||||
|
||||
describe('proplineAdapter NFL/NHL markets', () => {
|
||||
test('nfl/nhl markets are populated and sport keys resolve', () => {
|
||||
const { MARKETS, SPORT_KEYS } = propline.__internals;
|
||||
expect(MARKETS.nfl.length).toBeGreaterThan(0);
|
||||
expect(MARKETS.nhl.length).toBeGreaterThan(0);
|
||||
expect(MARKETS.nfl).toContain('player_pass_yds');
|
||||
expect(MARKETS.nhl).toContain('player_shots_on_goal');
|
||||
expect(SPORT_KEYS.nfl).toBe('football_nfl');
|
||||
expect(SPORT_KEYS.nhl).toBe('hockey_nhl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MARKET_MAP end-to-end normalization', () => {
|
||||
function eventWith(marketKey, player, point) {
|
||||
return [{
|
||||
home_team: 'Kansas City Chiefs',
|
||||
away_team: 'Buffalo Bills',
|
||||
commence_time: '2026-09-10T00:00:00Z',
|
||||
bookmakers: [{
|
||||
key: 'draftkings',
|
||||
markets: [{
|
||||
key: marketKey,
|
||||
last_update: '2026-09-09T20:00:00Z',
|
||||
outcomes: [
|
||||
{ description: player, point, name: 'Over', price: -110 },
|
||||
{ description: player, point, name: 'Under', price: -110 },
|
||||
],
|
||||
}],
|
||||
}],
|
||||
}];
|
||||
}
|
||||
|
||||
test('NFL passing-yards prop normalizes to passing_yards', () => {
|
||||
const props = normalizeProps(eventWith('player_pass_yds', 'Patrick Mahomes', 274.5));
|
||||
expect(props).toHaveLength(1);
|
||||
expect(props[0].stat_type).toBe('passing_yards');
|
||||
expect(props[0].player).toBe('Patrick Mahomes');
|
||||
expect(props[0].line).toBe(274.5);
|
||||
});
|
||||
|
||||
test('NFL anytime-TD prop normalizes (does not drop to zero)', () => {
|
||||
const props = normalizeProps(eventWith('player_anytime_td', 'Travis Kelce', 0.5));
|
||||
expect(props).toHaveLength(1);
|
||||
expect(props[0].stat_type).toBe('anytime_td');
|
||||
});
|
||||
|
||||
test('NHL shots-on-goal + goalie-saves normalize', () => {
|
||||
expect(normalizeProps(eventWith('player_shots_on_goal', 'Connor McDavid', 3.5))[0].stat_type).toBe('shots_on_goal');
|
||||
expect(normalizeProps(eventWith('goalie_saves', 'Igor Shesterkin', 28.5))[0].stat_type).toBe('saves');
|
||||
});
|
||||
|
||||
test('off-season / empty response normalizes gracefully (no crash)', () => {
|
||||
expect(normalizeProps([])).toEqual([]);
|
||||
// Unknown market key is skipped, not crashed on.
|
||||
expect(normalizeProps(eventWith('player_unknown_stat', 'Nobody', 1.5))).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user