f0c8b4f29b
- 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>
90 lines
3.6 KiB
JavaScript
90 lines
3.6 KiB
JavaScript
// 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([]);
|
|
});
|
|
});
|