// Session 58 (Phase 1) — /api/ledger/mine (auth-scoped) + /api/ledger/model // (public record + aggregate). Supabase + auth are mocked; the routes' // scoping and honest-aggregate contracts are what's under test. const express = require('express'); const request = require('supertest'); // requireAuth stub: Authorization present → user u1; else 401. jest.mock('../../src/middleware/auth', () => ({ requireAuth: (req, res, next) => { if (!req.headers.authorization) return res.status(401).json({ error: 'auth required' }); req.user = { id: 'u1', tier: 'analyst' }; return next(); }, })); // Supabase service client stub — records filters so we can assert scoping. const mockCaptured = { filters: [], rows: [] }; function mockChain() { const b = { _filters: [], select() { return b; }, eq(col, val) { b._filters.push(['eq', col, val]); return b; }, is(col, val) { b._filters.push(['is', col, val]); return b; }, not(col, op, val) { b._filters.push(['not', col, op, val]); return b; }, gte(col, val) { b._filters.push(['gte', col, val]); return b; }, ilike(col, val) { b._filters.push(['ilike', col, val]); return b; }, order() { return b; }, limit() { mockCaptured.filters.push(b._filters); return Promise.resolve({ data: mockCaptured.rows, error: null, count: 0 }); }, then(resolve, reject) { mockCaptured.filters.push(b._filters); return Promise.resolve({ data: mockCaptured.rows, error: null, count: 0 }).then(resolve, reject); }, }; return b; } jest.mock('../../src/utils/supabase', () => ({ getSupabaseServiceClient: () => ({ from: () => mockChain() }), })); process.env.SUPABASE_URL = 'https://test.supabase.co'; process.env.SUPABASE_SERVICE_ROLE_KEY = 'test-key'; function mountApp() { delete require.cache[require.resolve('../../src/routes/ledger')]; const routes = require('../../src/routes/ledger'); const app = express(); app.use('/api/ledger', routes); return app; } beforeEach(() => { mockCaptured.filters.length = 0; mockCaptured.rows.length = 0; }); describe('GET /api/ledger/mine', () => { test('401 without auth', async () => { const res = await request(mountApp()).get('/api/ledger/mine'); expect(res.status).toBe(401); }); test('scopes rows to the authenticated user', async () => { mockCaptured.rows.push({ id: 'r1', player_name: 'Judge', user_id: 'u1' }); const res = await request(mountApp()) .get('/api/ledger/mine?sport=mlb&tier=A') .set('Authorization', 'Bearer token'); expect(res.status).toBe(200); expect(res.body.entries).toHaveLength(1); const filters = mockCaptured.filters[0]; expect(filters).toContainEqual(['eq', 'user_id', 'u1']); expect(filters).toContainEqual(['eq', 'sport', 'mlb']); expect(filters).toContainEqual(['ilike', 'grade', 'A%']); }); }); describe('GET /api/ledger/model', () => { test('public — returns the user_id-null record + an aggregate with the n<20 rule', async () => { const res = await request(mountApp()).get('/api/ledger/model'); expect(res.status).toBe(200); expect(res.body.min_sample).toBe(20); expect(res.body.aggregate).toBeTruthy(); expect(res.body.aggregate.hit_pct).toBeNull(); // 0 settles → no percentage // The entries query must be scoped to the PUBLIC record. const entriesFilters = mockCaptured.filters.find((f) => f.some((x) => x[0] === 'is' && x[1] === 'user_id')); expect(entriesFilters).toBeTruthy(); }); });