/** * Unit tests for src/middleware/auth.ts */ import crypto from 'crypto'; import { Request, Response, NextFunction } from 'express'; import { v4 as uuidv4 } from 'uuid'; import { signToken } from '../../../src/utils/jwt'; import { ITokenPayload } from '../../../src/types/index'; import { AuthenticationError } from '../../../src/utils/errors'; // Generate test RSA keys const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, }); // Mock environment and Redis before importing auth middleware jest.mock('../../../src/cache/redis', () => ({ getRedisClient: jest.fn().mockResolvedValue({ get: jest.fn().mockResolvedValue(null), // Not revoked by default }), })); // We need to set env vars before importing the middleware process.env['JWT_PUBLIC_KEY'] = publicKey; // Import after setting env import { authMiddleware } from '../../../src/middleware/auth'; import { getRedisClient } from '../../../src/cache/redis'; const mockGetRedisClient = getRedisClient as jest.Mock; function makeTestToken(overrides: Partial = {}): string { const payload: Omit = { sub: uuidv4(), client_id: uuidv4(), scope: 'agents:read', jti: uuidv4(), ...overrides, }; return signToken(payload, privateKey); } function makeReq(authHeader?: string): Partial { return { headers: authHeader ? { authorization: authHeader } : {}, ip: '127.0.0.1', }; } describe('authMiddleware', () => { let next: jest.MockedFunction; beforeEach(() => { next = jest.fn(); mockGetRedisClient.mockResolvedValue({ get: jest.fn().mockResolvedValue(null), }); }); it('should call next() and set req.user for a valid token', async () => { const token = makeTestToken(); const req = makeReq(`Bearer ${token}`) as Request; const res = {} as Response; await authMiddleware(req, res, next); expect(next).toHaveBeenCalledWith(); expect(req.user).toBeDefined(); expect(req.user?.sub).toBeTruthy(); }); it('should call next(AuthenticationError) when Authorization header is missing', async () => { const req = makeReq() as Request; const res = {} as Response; await authMiddleware(req, res, next); expect(next).toHaveBeenCalledWith(expect.any(AuthenticationError)); }); it('should call next(AuthenticationError) when header does not start with Bearer', async () => { const req = makeReq('Basic dXNlcjpwYXNz') as Request; const res = {} as Response; await authMiddleware(req, res, next); expect(next).toHaveBeenCalledWith(expect.any(AuthenticationError)); }); it('should call next(AuthenticationError) for an invalid JWT', async () => { const req = makeReq('Bearer invalid.jwt.token') as Request; const res = {} as Response; await authMiddleware(req, res, next); expect(next).toHaveBeenCalledWith(expect.any(AuthenticationError)); }); it('should call next(AuthenticationError) for a revoked token', async () => { mockGetRedisClient.mockResolvedValue({ get: jest.fn().mockResolvedValue('1'), // Token is revoked }); const token = makeTestToken(); const req = makeReq(`Bearer ${token}`) as Request; const res = {} as Response; await authMiddleware(req, res, next); expect(next).toHaveBeenCalledWith(expect.any(AuthenticationError)); }); });