Files
vyndr/tests/integration/oddsSoccer.test.js
T

98 lines
3.4 KiB
JavaScript

const request = require('supertest');
const mockGetOdds = jest.fn();
jest.mock('../../src/services/oddsService', () => {
const actual = jest.requireActual('../../src/services/oddsService');
return {
...actual,
getOdds: (...args) => mockGetOdds(...args),
};
});
const mockRedis = { get: jest.fn(), set: jest.fn(), hset: jest.fn(), hgetall: jest.fn(), expire: jest.fn() };
jest.mock('../../src/utils/redis', () => ({
getRedisClient: () => mockRedis,
cacheGet: async () => null,
cacheSet: async () => true,
cacheDel: async () => true,
isDegraded: () => false,
}));
const { SOCCER_SPORT_KEYS } = require('../../src/services/oddsService');
const app = require('../../src/app');
beforeEach(() => {
mockGetOdds.mockReset();
mockRedis.get.mockResolvedValue(null);
});
describe('SOCCER_SPORT_KEYS export', () => {
test('contains all 9 launch leagues', () => {
expect(SOCCER_SPORT_KEYS).toEqual(expect.arrayContaining([
'soccer_wc',
'soccer_epl',
'soccer_laliga',
'soccer_bundesliga',
'soccer_seriea',
'soccer_ligue1',
'soccer_ucl',
'soccer_mls',
'soccer_ligamx',
]));
expect(SOCCER_SPORT_KEYS).toHaveLength(9);
});
});
describe('GET /api/odds/soccer/:league', () => {
test('valid league reaches getOdds with the prefixed key', async () => {
mockGetOdds.mockResolvedValueOnce({
props: [], updated_at: '2026-06-15T00:00:00Z', source: 'live', quota_remaining: 4000,
});
const res = await request(app).get('/api/odds/soccer/wc').expect(200);
expect(mockGetOdds).toHaveBeenCalledWith('soccer_wc');
expect(res.body.sport).toBe('soccer_wc');
expect(Array.isArray(res.body.props)).toBe(true);
});
test('unknown league returns 400 with valid-list hint', async () => {
const res = await request(app).get('/api/odds/soccer/spaceleague').expect(400);
expect(res.body.error).toMatch(/Unknown soccer league/);
expect(res.body.error).toMatch(/wc/);
expect(mockGetOdds).not.toHaveBeenCalled();
});
test('EPL route works (proves it is not WC-only)', async () => {
mockGetOdds.mockResolvedValueOnce({
props: [{ player: 'X', stat_type: 'goals', line: 0.5 }], updated_at: '2026-06-15T00:00:00Z', source: 'cache',
});
const res = await request(app).get('/api/odds/soccer/epl').expect(200);
expect(mockGetOdds).toHaveBeenCalledWith('soccer_epl');
expect(res.body.sport).toBe('soccer_epl');
});
test('case-insensitive league path', async () => {
mockGetOdds.mockResolvedValueOnce({ props: [], updated_at: 't', source: 'cache' });
await request(app).get('/api/odds/soccer/LIGAMX').expect(200);
expect(mockGetOdds).toHaveBeenCalledWith('soccer_ligamx');
});
test('getOdds throwing surfaces as a status code, not a 500 leak', async () => {
const err = new Error('Odds data temporarily unavailable.');
err.statusCode = 429;
mockGetOdds.mockRejectedValueOnce(err);
const res = await request(app).get('/api/odds/soccer/wc').expect(429);
expect(res.body.error).toMatch(/temporarily unavailable/);
});
});
describe('existing NBA/NCAAB routes still work (no regression)', () => {
test('/api/odds/nba still returns the NBA shape', async () => {
mockGetOdds.mockResolvedValueOnce({
props: [], updated_at: 't', source: 'cache', quota_remaining: 4000,
});
const res = await request(app).get('/api/odds/nba').expect(200);
expect(res.body.sport).toBe('nba');
expect(mockGetOdds).toHaveBeenCalledWith('nba');
});
});