Session 28: Parlay builder, line movement tracker, book comparison — 3 features, zero credits (1623 tests)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// Integration: parlay / lines / books routes (Session 28).
|
||||
|
||||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
|
||||
// Redis-backed services are mocked at the redis layer.
|
||||
const mockStore = {};
|
||||
const mockScan = jest.fn(async () => ['0', []]);
|
||||
jest.mock('../../src/utils/redis', () => ({
|
||||
cacheGet: jest.fn(async (k) => (k in mockStore ? mockStore[k] : null)),
|
||||
getRedisClient: () => ({ scan: mockScan, lrange: async () => [], rpush: async () => 1, ltrim: async () => 'OK', expire: async () => 1 }),
|
||||
isDegraded: () => false,
|
||||
}));
|
||||
|
||||
function mount(routePath, file) {
|
||||
delete require.cache[require.resolve(file)];
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(routePath, require(file));
|
||||
return app;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
for (const k of Object.keys(mockStore)) delete mockStore[k];
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('POST /api/parlay/calculate', () => {
|
||||
const app = () => mount('/api/parlay', '../../src/routes/parlay');
|
||||
|
||||
test('returns combined odds + grade for valid legs', async () => {
|
||||
const res = await request(app()).post('/api/parlay/calculate').send({
|
||||
legs: [
|
||||
{ player: 'A', stat: 'points', odds: 100, grade: 'A', gameId: 'g1' },
|
||||
{ player: 'B', stat: 'hits', odds: 100, grade: 'A', gameId: 'g2' },
|
||||
],
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.combinedOdds).toBe(300); // 2×2 = 4.0 → +300
|
||||
expect(res.body.combinedGrade).toBeDefined();
|
||||
});
|
||||
|
||||
test('empty legs → 400', async () => {
|
||||
const res = await request(app()).post('/api/parlay/calculate').send({ legs: [] });
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
test('suggestions endpoint returns combos', async () => {
|
||||
const props = [
|
||||
{ player: 'A', stat: 'points', odds: -110, grade: 'A', gameId: 'g1' },
|
||||
{ player: 'B', stat: 'hits', odds: -110, grade: 'A', gameId: 'g2' },
|
||||
{ player: 'C', stat: 'goals', odds: -110, grade: 'B', gameId: 'g3' },
|
||||
];
|
||||
const res = await request(app()).post('/api/parlay/suggestions').send({ props, legs: 3, max: 1 });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.suggestions).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/lines/:sport/movers', () => {
|
||||
const app = () => mount('/api/lines', '../../src/routes/lineMovement');
|
||||
|
||||
test('empty when no snapshots cached', async () => {
|
||||
const res = await request(app()).get('/api/lines/mlb/movers');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.movers).toEqual([]);
|
||||
});
|
||||
|
||||
test('unsupported sport → 404', async () => {
|
||||
const res = await request(app()).get('/api/lines/cricket/movers');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/books/:sport', () => {
|
||||
const app = () => mount('/api/books', '../../src/routes/bookComparison');
|
||||
|
||||
test('returns best lines from cached props', async () => {
|
||||
mockStore[`odds:nba:${new Date().toISOString().split('T')[0]}`] = {
|
||||
props: [
|
||||
{
|
||||
player: 'Wemby', stat_type: 'points',
|
||||
lines: [
|
||||
{ book: 'dk', over_odds: -110 },
|
||||
{ book: 'fd', over_odds: -105 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const res = await request(app()).get('/api/books/nba');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.bestLines).toHaveLength(1);
|
||||
expect(res.body.bestLines[0].bestBook).toBe('fd');
|
||||
});
|
||||
|
||||
test('empty when no cached props', async () => {
|
||||
const res = await request(app()).get('/api/books/nba');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.bestLines).toEqual([]);
|
||||
});
|
||||
|
||||
test('unsupported sport → 404', async () => {
|
||||
const res = await request(app()).get('/api/books/cricket');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user