Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+203
View File
@@ -0,0 +1,203 @@
const request = require('supertest');
const express = require('express');
const statsRoutes = require('../../src/routes/stats');
const propsRoutes = require('../../src/routes/props');
// Mock supabase
jest.mock('../../src/utils/supabase', () => ({
getSupabaseServiceClient: () => mockSupabase,
}));
// Mock auth middleware
jest.mock('../../src/middleware/auth', () => ({
requireAuth: (req, res, next) => {
req.user = req._mockUser || { id: '123', tier: 'analyst' };
next();
},
}));
let mockSupabase;
beforeEach(() => {
mockSupabase = {
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
order: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
not: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
single: jest.fn(),
};
});
function buildStatsApp() {
const app = express();
app.use(express.json());
app.use('/stats', statsRoutes);
return app;
}
function buildPropsApp(mockUser) {
const app = express();
app.use(express.json());
// Inject mock user before routes
app.use((req, res, next) => {
if (mockUser) req._mockUser = mockUser;
next();
});
app.use('/props', propsRoutes);
return app;
}
describe('GET /stats/parlays-graded', () => {
it('returns count from scan_sessions', async () => {
// Mock the select to return count via head:true pattern
// supabase .from().select('*', { count: 'exact', head: true }) returns { count, error }
mockSupabase.select.mockReturnValue({ count: 42, error: null });
const app = buildStatsApp();
const res = await request(app).get('/stats/parlays-graded');
expect(res.status).toBe(200);
expect(res.body).toEqual({ count: 42 });
expect(res.headers['x-vyndr-mission']).toBeDefined();
});
it('returns 503 on supabase error', async () => {
mockSupabase.select.mockReturnValue({ count: null, error: new Error('db down') });
const app = buildStatsApp();
const res = await request(app).get('/stats/parlays-graded');
expect(res.status).toBe(503);
expect(res.headers['x-vyndr-mission']).toBeDefined();
});
});
describe('GET /stats/public', () => {
it('returns all 4 fields', async () => {
// Chain: first call is for count, second for grades, third for kill_conditions
let callCount = 0;
mockSupabase.from.mockImplementation(() => {
callCount++;
return mockSupabase;
});
mockSupabase.select.mockImplementation((fields, opts) => {
if (opts && opts.count === 'exact') {
// parlays count
return { count: 100, error: null };
}
if (fields === 'final_grade') {
// grades
return {
data: [
{ final_grade: 'A' },
{ final_grade: 'B' },
{ final_grade: 'A' },
{ final_grade: 'A' },
],
error: null,
};
}
if (fields === 'kill_conditions') {
// return this for the .not() chain
return mockSupabase;
}
return mockSupabase;
});
mockSupabase.not.mockReturnValue({
data: [
{ kill_conditions: ['back-to-back'] },
{ kill_conditions: ['injury'] },
{ kill_conditions: [] },
],
error: null,
});
const app = buildStatsApp();
const res = await request(app).get('/stats/public');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('parlays_graded');
expect(res.body).toHaveProperty('avg_grade');
expect(res.body).toHaveProperty('kill_conditions_caught');
expect(res.body).toHaveProperty('sports_covered');
expect(res.body.parlays_graded).toBe(100);
expect(res.body.avg_grade).toBe('A');
expect(res.body.kill_conditions_caught).toBe(2);
expect(res.body.sports_covered).toEqual(['NBA', 'MLB']);
expect(res.headers['x-vyndr-mission']).toBeDefined();
});
});
describe('GET /stats/live', () => {
it('returns array of max 3 items', async () => {
const mockPicks = [
{ player: 'LeBron', stat_type: 'points', line: 25.5, direction: 'over', grade: 'A', confidence: 0.85, created_at: '2026-03-28T12:00:00Z' },
{ player: 'Curry', stat_type: 'threes', line: 3.5, direction: 'over', grade: 'B', confidence: 0.72, created_at: '2026-03-28T11:00:00Z' },
{ player: 'Jokic', stat_type: 'rebounds', line: 10.5, direction: 'under', grade: 'A', confidence: 0.9, created_at: '2026-03-28T10:00:00Z' },
];
mockSupabase.limit.mockReturnValue({ data: mockPicks, error: null });
const app = buildStatsApp();
const res = await request(app).get('/stats/live');
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBeLessThanOrEqual(3);
expect(res.body[0]).toHaveProperty('player');
expect(res.body[0]).toHaveProperty('stat');
expect(res.body[0]).toHaveProperty('sport', 'NBA');
expect(res.body[0]).toHaveProperty('graded_at');
expect(res.headers['x-vyndr-mission']).toBeDefined();
});
it('returns 503 on supabase error', async () => {
mockSupabase.limit.mockReturnValue({ data: null, error: new Error('db down') });
const app = buildStatsApp();
const res = await request(app).get('/stats/live');
expect(res.status).toBe(503);
expect(res.headers['x-vyndr-mission']).toBeDefined();
});
});
describe('GET /props/joint-history', () => {
it('blocks free tier with 403', async () => {
const app = buildPropsApp({ id: '123', tier: 'free' });
const res = await request(app)
.get('/props/joint-history')
.query({ player_a: 'LeBron', stat_a: 'points', player_b: 'AD', stat_b: 'rebounds' });
expect(res.status).toBe(403);
expect(res.body.error).toMatch(/Analyst or Desk/);
});
it('returns sample_size 0 when no data', async () => {
let eqCalls = 0;
mockSupabase.eq.mockImplementation(() => {
eqCalls++;
if (eqCalls >= 4) return { data: [], error: null };
return mockSupabase;
});
const app = buildPropsApp({ id: '123', tier: 'analyst' });
const res = await request(app)
.get('/props/joint-history')
.query({ player_a: 'LeBron', stat_a: 'points', player_b: 'AD', stat_b: 'rebounds' });
expect(res.status).toBe(200);
expect(res.body.sample_size).toBe(0);
});
it('returns 400 when missing query params', async () => {
const app = buildPropsApp({ id: '123', tier: 'analyst' });
const res = await request(app).get('/props/joint-history');
expect(res.status).toBe(400);
});
});