Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
process.env.VYNDR_INTERNAL_KEY = 'health-test-key';
|
||||
|
||||
const mockPing = jest.fn();
|
||||
jest.mock('../../src/utils/redis', () => ({
|
||||
getRedisClient: () => ({ ping: mockPing }),
|
||||
isDegraded: () => false,
|
||||
cacheGet: async () => null,
|
||||
cacheSet: async () => true,
|
||||
cacheDel: async () => true,
|
||||
}));
|
||||
|
||||
const mockSupabaseLimit = jest.fn();
|
||||
jest.mock('../../src/utils/supabase', () => ({
|
||||
getSupabaseServiceClient: () => ({
|
||||
from() {
|
||||
const proxy = {
|
||||
select() { return proxy; },
|
||||
limit() { return mockSupabaseLimit(); },
|
||||
};
|
||||
return proxy;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockAxiosGet = jest.fn();
|
||||
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args), post: jest.fn() }));
|
||||
|
||||
// Adapter configured() helpers — fixed boolean returns are enough for the
|
||||
// detailed branch assertions.
|
||||
jest.mock('../../src/services/adapters/sharpApiAdapter', () => ({ configured: () => true }));
|
||||
jest.mock('../../src/services/adapters/propOddsAdapter', () => ({ configured: () => false }));
|
||||
jest.mock('../../src/services/adapters/parlayApiAdapter', () => ({ configured: () => true }));
|
||||
jest.mock('../../src/services/adapters/oddsPapiAdapter', () => ({ configured: () => false }));
|
||||
jest.mock('../../src/services/adapters/cfbdAdapter', () => ({ configured: () => false }));
|
||||
jest.mock('../../src/services/adapters/openRouterAdapter', () => ({ configured: () => true }));
|
||||
|
||||
const app = require('../../src/app');
|
||||
const http = require('http');
|
||||
|
||||
function call(path, headers = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const server = app.listen(0, '127.0.0.1', () => {
|
||||
const port = server.address().port;
|
||||
const req = http.request({ host: '127.0.0.1', port, path, method: 'GET', headers }, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (c) => chunks.push(c));
|
||||
res.on('end', () => {
|
||||
server.close();
|
||||
const raw = Buffer.concat(chunks).toString('utf8');
|
||||
let parsed; try { parsed = JSON.parse(raw); } catch { parsed = raw; }
|
||||
resolve({ status: res.statusCode, body: parsed });
|
||||
});
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockPing.mockReset();
|
||||
mockSupabaseLimit.mockReset();
|
||||
mockAxiosGet.mockReset();
|
||||
});
|
||||
|
||||
describe('GET /api/health', () => {
|
||||
test('public response is minimal when no internal key', async () => {
|
||||
mockPing.mockResolvedValue('PONG');
|
||||
mockSupabaseLimit.mockResolvedValue({ error: null });
|
||||
const res = await call('/api/health');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual({ status: 'healthy' });
|
||||
});
|
||||
|
||||
test('reports 503 when Redis is down', async () => {
|
||||
mockPing.mockRejectedValue(new Error('econnrefused'));
|
||||
mockSupabaseLimit.mockResolvedValue({ error: null });
|
||||
const res = await call('/api/health');
|
||||
expect(res.status).toBe(503);
|
||||
expect(res.body.status).toBe('degraded');
|
||||
});
|
||||
|
||||
test('reports 503 when Supabase errors', async () => {
|
||||
mockPing.mockResolvedValue('PONG');
|
||||
mockSupabaseLimit.mockResolvedValue({ error: { message: 'bad' } });
|
||||
const res = await call('/api/health');
|
||||
expect(res.status).toBe(503);
|
||||
});
|
||||
|
||||
test('detailed branch with valid internal key includes adapters + python', async () => {
|
||||
mockPing.mockResolvedValue('PONG');
|
||||
mockSupabaseLimit.mockResolvedValue({ error: null });
|
||||
mockAxiosGet.mockResolvedValue({ status: 200, data: { status: 'ok' } });
|
||||
const res = await call('/api/health', { 'x-vyndr-internal-key': 'health-test-key' });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.checks.python).toBe('ok');
|
||||
expect(res.body.checks.adapters).toMatchObject({
|
||||
sharpapi: true,
|
||||
propodds: false,
|
||||
openrouter: true,
|
||||
});
|
||||
expect(typeof res.body.uptime).toBe('number');
|
||||
});
|
||||
|
||||
test('wrong internal key falls through to public response', async () => {
|
||||
mockPing.mockResolvedValue('PONG');
|
||||
mockSupabaseLimit.mockResolvedValue({ error: null });
|
||||
const res = await call('/api/health', { 'x-vyndr-internal-key': 'wrong' });
|
||||
expect(res.body).toEqual({ status: 'healthy' });
|
||||
expect(res.body.checks).toBeUndefined();
|
||||
});
|
||||
|
||||
test('python down does not flip overall status (Python is optional)', async () => {
|
||||
mockPing.mockResolvedValue('PONG');
|
||||
mockSupabaseLimit.mockResolvedValue({ error: null });
|
||||
mockAxiosGet.mockRejectedValue(new Error('refused'));
|
||||
const res = await call('/api/health', { 'x-vyndr-internal-key': 'health-test-key' });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.checks.python).toBe('down');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user