// Phase 9 §9.4 — vitest scaffold + auth-boundary tests. // // Covers shared/auth/middleware.js. Mocks the Postgres pool with a thin // in-memory fake — no real DB required. import { describe, it, expect, vi, beforeEach } from 'vitest'; import jwt from 'jsonwebtoken'; import { authenticate, requirePermission } from './middleware.js'; const SECRET = 'test-secret-please-do-not-use-in-prod'; function makeFakePool(users, permissions = {}) { const calls = { count: 0 }; return { calls, query: vi.fn(async (sql, params) => { calls.count += 1; if (sql.includes('FROM users WHERE id')) { const user = users[params[0]]; return { rows: user ? [user] : [] }; } if (sql.includes('FROM permissions')) { return { rows: (permissions[params[0]] || []).map((code) => ({ code })) }; } return { rows: [] }; }), }; } function makeReq(authHeader) { return { headers: authHeader ? { authorization: authHeader } : {} }; } function makeRes() { const res = {}; res.status = vi.fn(() => res); res.json = vi.fn(() => res); return res; } describe('authenticate middleware', () => { let activeUser; let inactiveUser; let validToken; beforeEach(() => { activeUser = { id: 1, username: 'alice', email: 'a@x', is_admin: false, is_active: true }; inactiveUser = { id: 2, username: 'bob', email: 'b@x', is_admin: false, is_active: false }; validToken = jwt.sign({ userId: 1 }, SECRET, { expiresIn: '1h' }); }); it('returns 401 when no Authorization header is present', async () => { const pool = makeFakePool({ 1: activeUser }); const mw = authenticate({ pool, secret: SECRET }); const res = makeRes(); const next = vi.fn(); await mw(makeReq(), res, next); expect(res.status).toHaveBeenCalledWith(401); expect(next).not.toHaveBeenCalled(); }); it('returns 401 when Authorization is not Bearer', async () => { const pool = makeFakePool({ 1: activeUser }); const mw = authenticate({ pool, secret: SECRET }); const res = makeRes(); const next = vi.fn(); await mw(makeReq('Basic abc'), res, next); expect(res.status).toHaveBeenCalledWith(401); expect(next).not.toHaveBeenCalled(); }); it('returns 401 when token is malformed', async () => { const pool = makeFakePool({ 1: activeUser }); const mw = authenticate({ pool, secret: SECRET }); const res = makeRes(); const next = vi.fn(); await mw(makeReq('Bearer not-a-jwt'), res, next); expect(res.status).toHaveBeenCalledWith(401); expect(next).not.toHaveBeenCalled(); }); it('returns 403 when the user is inactive', async () => { const inactiveToken = jwt.sign({ userId: 2 }, SECRET, { expiresIn: '1h' }); const pool = makeFakePool({ 2: inactiveUser }); const mw = authenticate({ pool, secret: SECRET }); const res = makeRes(); const next = vi.fn(); await mw(makeReq(`Bearer ${inactiveToken}`), res, next); expect(res.status).toHaveBeenCalledWith(403); expect(next).not.toHaveBeenCalled(); }); it('calls next() and populates req.user for a valid token + active user', async () => { const pool = makeFakePool({ 1: activeUser }, { 1: ['products:read'] }); const mw = authenticate({ pool, secret: SECRET }); const req = makeReq(`Bearer ${validToken}`); const res = makeRes(); const next = vi.fn(); await mw(req, res, next); expect(next).toHaveBeenCalledOnce(); expect(req.user).toBeDefined(); expect(req.user.id).toBe(1); expect(req.user.permissions).toEqual(['products:read']); }); it('caches the user lookup — same token within TTL → one DB hit', async () => { const pool = makeFakePool({ 1: activeUser }, { 1: [] }); const mw = authenticate({ pool, secret: SECRET }); const next = vi.fn(); await mw(makeReq(`Bearer ${validToken}`), makeRes(), next); await mw(makeReq(`Bearer ${validToken}`), makeRes(), next); // Two queries on first hit (user + permissions), zero on the second expect(pool.calls.count).toBe(2); expect(next).toHaveBeenCalledTimes(2); }); it('refetches after TTL expiry', async () => { vi.useFakeTimers(); const pool = makeFakePool({ 1: activeUser }, { 1: [] }); const mw = authenticate({ pool, secret: SECRET }); const next = vi.fn(); await mw(makeReq(`Bearer ${validToken}`), makeRes(), next); expect(pool.calls.count).toBe(2); vi.advanceTimersByTime(61_000); await mw(makeReq(`Bearer ${validToken}`), makeRes(), next); expect(pool.calls.count).toBe(4); vi.useRealTimers(); }); }); describe('requirePermission middleware', () => { it('returns 401 when req.user is missing', () => { const mw = requirePermission('products:write'); const res = makeRes(); const next = vi.fn(); mw({}, res, next); expect(res.status).toHaveBeenCalledWith(401); expect(next).not.toHaveBeenCalled(); }); it('calls next() for admin users regardless of code', () => { const mw = requirePermission('products:write'); const next = vi.fn(); mw({ user: { is_admin: true, permissions: [] } }, makeRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('calls next() when user has the required permission', () => { const mw = requirePermission('products:write'); const next = vi.fn(); mw({ user: { is_admin: false, permissions: ['products:write'] } }, makeRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('returns 403 when user lacks the required permission', () => { const mw = requirePermission('products:write'); const res = makeRes(); const next = vi.fn(); mw({ user: { is_admin: false, permissions: ['products:read'] } }, res, next); expect(res.status).toHaveBeenCalledWith(403); expect(next).not.toHaveBeenCalled(); }); });