import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { validate, validateMultiple } from '../../src/api/middleware/validation.middleware'; // Mock Express request, response, and next const mockRequest = (data: any = {}): Partial => ({ body: data.body || {}, query: data.query || {}, params: data.params || {} }); const mockResponse = (): Partial => { const res: Partial = {}; res.status = jest.fn().mockReturnValue(res); res.json = jest.fn().mockReturnValue(res); return res; }; const mockNext: NextFunction = jest.fn(); describe('Validation Middleware', () => { afterEach(() => { jest.clearAllMocks(); }); describe('validate', () => { const testSchema = z.object({ name: z.string().min(1), age: z.number().min(0).max(150) }); it('should pass validation with valid data', async () => { const req = mockRequest({ body: { name: 'John', age: 30 } }) as Request; const res = mockResponse() as Response; const middleware = validate(testSchema, 'body'); await middleware(req, res, mockNext); expect(mockNext).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); expect(req.body).toEqual({ name: 'John', age: 30 }); }); it('should fail validation with invalid data', async () => { const req = mockRequest({ body: { name: '', age: -5 } }) as Request; const res = mockResponse() as Response; const middleware = validate(testSchema, 'body'); await middleware(req, res, mockNext); expect(mockNext).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ success: false, error: 'Validation failed', details: expect.arrayContaining([ expect.objectContaining({ field: expect.any(String), message: expect.any(String) }) ]) }) ); }); it('should validate query parameters', async () => { const querySchema = z.object({ page: z.coerce.number().min(1), limit: z.coerce.number().min(1).max(100) }); const req = mockRequest({ query: { page: '2', limit: '50' } }) as Request; const res = mockResponse() as Response; const middleware = validate(querySchema, 'query'); await middleware(req, res, mockNext); expect(mockNext).toHaveBeenCalled(); expect(req.query).toEqual({ page: 2, limit: 50 }); }); it('should validate params', async () => { const paramsSchema = z.object({ id: z.string().uuid() }); const req = mockRequest({ params: { id: '123e4567-e89b-12d3-a456-426614174000' } }) as Request; const res = mockResponse() as Response; const middleware = validate(paramsSchema, 'params'); await middleware(req, res, mockNext); expect(mockNext).toHaveBeenCalled(); }); }); describe('validateMultiple', () => { const schemas = { body: z.object({ title: z.string().min(1) }), query: z.object({ page: z.coerce.number().min(1) }), params: z.object({ id: z.string().uuid() }) }; it('should validate multiple sources successfully', async () => { const req = mockRequest({ body: { title: 'Test' }, query: { page: '1' }, params: { id: '123e4567-e89b-12d3-a456-426614174000' } }) as Request; const res = mockResponse() as Response; const middleware = validateMultiple(schemas); await middleware(req, res, mockNext); expect(mockNext).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); expect(req.body).toEqual({ title: 'Test' }); expect(req.query).toEqual({ page: 1 }); }); it('should collect errors from multiple sources', async () => { const req = mockRequest({ body: { title: '' }, // Invalid query: { page: '0' }, // Invalid params: { id: 'not-a-uuid' } // Invalid }) as Request; const res = mockResponse() as Response; const middleware = validateMultiple(schemas); await middleware(req, res, mockNext); expect(mockNext).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ success: false, error: 'Validation failed', details: expect.arrayContaining([ expect.objectContaining({ source: 'body' }), expect.objectContaining({ source: 'query' }), expect.objectContaining({ source: 'params' }) ]) }) ); }); it('should handle partial schemas', async () => { const partialSchemas = { body: z.object({ name: z.string() }) // No query or params schemas }; const req = mockRequest({ body: { name: 'Test' }, query: { anything: 'goes' }, params: { whatever: 'works' } }) as Request; const res = mockResponse() as Response; const middleware = validateMultiple(partialSchemas); await middleware(req, res, mockNext); expect(mockNext).toHaveBeenCalled(); expect(req.body).toEqual({ name: 'Test' }); // Query and params should remain unchanged expect(req.query).toEqual({ anything: 'goes' }); expect(req.params).toEqual({ whatever: 'works' }); }); }); });