directus-task-management/tests/api/validation.middleware.test.ts

181 lines
5.4 KiB
TypeScript

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<Request> => ({
body: data.body || {},
query: data.query || {},
params: data.params || {}
});
const mockResponse = (): Partial<Response> => {
const res: Partial<Response> = {};
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' });
});
});
});