Session 25: Fix all data rendering — proxy routes, Tank01 normalizer, box-score bridge, inline streaks (1579 tests)

This commit is contained in:
Kev
2026-06-12 17:58:55 -04:00
parent 433e827103
commit 956cdb863a
15 changed files with 602 additions and 39 deletions
+47 -4
View File
@@ -30,12 +30,26 @@ function mountApp() {
return app;
}
// Session 25 — the REAL Tank01 shape (traced): sportsbooks are top-level
// keys on the game object, alongside non-book keys (awayTeam, gameID…).
const MLB_BODY = {
'20260612_ARI@CIN': {
gameID: '20260612_ARI@CIN',
awayTeam: 'ARI',
homeTeam: 'CIN',
last_updated_e_time: '1718200000',
bet365: { homeTeamML: '-110', awayTeamML: '+100', totalOver: '9.5', totalOverOdds: '-105', totalUnderOdds: '-115', homeTeamRunLine: '-1.5' },
betmgm: { homeTeamML: '-115', awayTeamML: '-105', totalOver: '9' },
caesars: { homeTeamML: '-112', awayTeamML: '-102', totalUnder: '9.5' },
},
};
// Legacy array shape — still supported for backward compatibility.
const MLB_BODY_LEGACY = {
'20260612_ARI@CIN': {
gameID: '20260612_ARI@CIN',
sportsBooks: [
{ sportsBook: 'bet365', odds: { homeTeamMLOdds: '-110', awayTeamMLOdds: '+100', totalOver: '9.5', totalOverOdds: '-105', totalUnderOdds: '-115' } },
{ sportsBook: 'betmgm', odds: { homeTeamMLOdds: '-115', awayTeamMLOdds: '-105', totalOver: '9' } },
{ sportsBook: 'bet365', odds: { homeTeamMLOdds: '-110', awayTeamMLOdds: '+100', totalOver: '9.5' } },
],
},
};
@@ -49,7 +63,7 @@ beforeEach(() => {
});
describe('GET /api/gamelines/:sport', () => {
test('mlb returns book-by-book odds with teams parsed from gameID', async () => {
test('mlb returns book-by-book odds from top-level book keys (real shape)', async () => {
mlbAdapter.getMLBBettingOdds.mockResolvedValue(MLB_BODY);
const res = await request(mountApp()).get('/api/gamelines/mlb');
expect(res.status).toBe(200);
@@ -57,9 +71,38 @@ describe('GET /api/gamelines/:sport', () => {
const game = res.body.games['20260612_ARI@CIN'];
expect(game.homeTeam).toBe('CIN');
expect(game.awayTeam).toBe('ARI');
// Books must be POPULATED (the Session 25 bug: this was {}).
expect(Object.keys(game.books).sort()).toEqual(['bet365', 'betmgm', 'caesars']);
expect(game.books.bet365.homeML).toBe('-110');
expect(game.books.bet365.awayML).toBe('+100');
expect(game.books.bet365.total).toBe('9.5');
expect(game.books.bet365.homeSpread).toBe('-1.5');
expect(game.books.betmgm.homeML).toBe('-115');
expect(game.books.caesars.total).toBe('9.5'); // totalUnder fallback
});
test('non-book keys (awayTeam, homeTeam, gameID) are excluded from books', async () => {
mlbAdapter.getMLBBettingOdds.mockResolvedValue(MLB_BODY);
const res = await request(mountApp()).get('/api/gamelines/mlb');
const books = res.body.games['20260612_ARI@CIN'].books;
expect(books.awayteam).toBeUndefined();
expect(books.hometeam).toBeUndefined();
expect(books.gameid).toBeUndefined();
});
test('missing fields normalize to null, never undefined/crash', async () => {
mlbAdapter.getMLBBettingOdds.mockResolvedValue(MLB_BODY);
const res = await request(mountApp()).get('/api/gamelines/mlb');
const betmgm = res.body.games['20260612_ARI@CIN'].books.betmgm;
expect(betmgm.homeSpread).toBeNull();
expect(betmgm.overOdds).toBeNull();
expect(Object.prototype.hasOwnProperty.call(betmgm, 'homeSpread')).toBe(true);
});
test('legacy sportsBooks array shape still normalizes', async () => {
mlbAdapter.getMLBBettingOdds.mockResolvedValue(MLB_BODY_LEGACY);
const res = await request(mountApp()).get('/api/gamelines/mlb');
expect(res.body.games['20260612_ARI@CIN'].books.bet365.homeML).toBe('-110');
});
test('nba returns empty games object off-season (not an error)', async () => {
@@ -89,7 +132,7 @@ describe('GET /api/gamelines/:sport', () => {
expect(res.status).toBe(404);
});
test('cache works — adapter called once per request (adapter owns TTL)', async () => {
test('adapter called once per request (adapter owns TTL)', async () => {
mlbAdapter.getMLBBettingOdds.mockResolvedValue(MLB_BODY);
const app = mountApp();
await request(app).get('/api/gamelines/mlb');