292 lines
7.8 KiB
JavaScript
292 lines
7.8 KiB
JavaScript
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
|
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const { validateRequest } = require('../../middleware/validation');
|
|
const Joi = require('joi');
|
|
const { mockRequest, mockResponse, mockNext } = require('../setup');
|
|
|
|
describe('Validation Middleware', () => {
|
|
|
|
describe('validateRequest', () => {
|
|
const testSchema = Joi.object({
|
|
name: Joi.string().required(),
|
|
email: Joi.string().email().required(),
|
|
age: Joi.number().integer().min(0).max(120).optional(),
|
|
tags: Joi.array().items(Joi.string()).optional()
|
|
});
|
|
|
|
it('should pass validation with valid data', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
age: 30,
|
|
tags: ['admin', 'user']
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
expect(req.body).to.deep.equal({
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
age: 30,
|
|
tags: ['admin', 'user']
|
|
});
|
|
});
|
|
|
|
it('should fail validation with missing required field', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
email: 'john@example.com'
|
|
// missing name
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data.success).to.be.false;
|
|
expect(res.data.message).to.include('name');
|
|
});
|
|
|
|
it('should fail validation with invalid email', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'invalid-email'
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data.success).to.be.false;
|
|
expect(res.data.message).to.include('email');
|
|
});
|
|
|
|
it('should fail validation with invalid age', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
age: -5
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data.success).to.be.false;
|
|
expect(res.data.message).to.include('age');
|
|
});
|
|
|
|
it('should strip unknown fields', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
unknownField: 'should be removed'
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
expect(req.body).to.not.have.property('unknownField');
|
|
});
|
|
|
|
it('should handle array validation', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
tags: ['valid', 'tags']
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
expect(req.body.tags).to.deep.equal(['valid', 'tags']);
|
|
});
|
|
|
|
it('should fail with invalid array items', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
tags: ['valid', 123, 'invalid-number']
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data.success).to.be.false;
|
|
});
|
|
|
|
it('should handle nested object validation', () => {
|
|
const nestedSchema = Joi.object({
|
|
user: Joi.object({
|
|
name: Joi.string().required(),
|
|
profile: Joi.object({
|
|
age: Joi.number().required(),
|
|
preferences: Joi.array().items(Joi.string())
|
|
}).required()
|
|
}).required()
|
|
});
|
|
|
|
const req = mockRequest({
|
|
body: {
|
|
user: {
|
|
name: 'John',
|
|
profile: {
|
|
age: 30,
|
|
preferences: ['dark-mode', 'notifications']
|
|
}
|
|
}
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(nestedSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should provide detailed error messages', () => {
|
|
const req = mockRequest({
|
|
body: {
|
|
name: '',
|
|
email: 'invalid',
|
|
age: 150
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(testSchema);
|
|
middleware(req, res, next);
|
|
|
|
expect(res.statusCode).to.equal(400);
|
|
expect(res.data.success).to.be.false;
|
|
expect(res.data.message).to.be.a('string');
|
|
expect(res.data.details).to.be.an('array');
|
|
expect(res.data.details.length).to.be.greaterThan(0);
|
|
});
|
|
|
|
it('should handle alternative schemas', () => {
|
|
const altSchema = Joi.alternatives().try(
|
|
Joi.object({
|
|
type: Joi.string().valid('user').required(),
|
|
username: Joi.string().required()
|
|
}),
|
|
Joi.object({
|
|
type: Joi.string().valid('device').required(),
|
|
deviceId: Joi.number().required()
|
|
})
|
|
);
|
|
|
|
const req1 = mockRequest({
|
|
body: {
|
|
type: 'user',
|
|
username: 'john'
|
|
}
|
|
});
|
|
const res1 = mockResponse();
|
|
const next1 = mockNext();
|
|
|
|
const middleware1 = validateRequest(altSchema);
|
|
middleware1(req1, res1, next1);
|
|
|
|
expect(next1.errors).to.have.length(0);
|
|
|
|
const req2 = mockRequest({
|
|
body: {
|
|
type: 'device',
|
|
deviceId: 123
|
|
}
|
|
});
|
|
const res2 = mockResponse();
|
|
const next2 = mockNext();
|
|
|
|
const middleware2 = validateRequest(altSchema);
|
|
middleware2(req2, res2, next2);
|
|
|
|
expect(next2.errors).to.have.length(0);
|
|
});
|
|
|
|
it('should handle query parameter validation', () => {
|
|
const querySchema = Joi.object({
|
|
page: Joi.number().integer().min(1).default(1),
|
|
limit: Joi.number().integer().min(1).max(100).default(10),
|
|
search: Joi.string().optional()
|
|
});
|
|
|
|
const req = mockRequest({
|
|
query: {
|
|
page: '2',
|
|
limit: '20',
|
|
search: 'test'
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(querySchema, 'query');
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
expect(req.query.page).to.equal(2); // Should be converted to number
|
|
expect(req.query.limit).to.equal(20);
|
|
});
|
|
|
|
it('should handle params validation', () => {
|
|
const paramsSchema = Joi.object({
|
|
id: Joi.number().integer().positive().required(),
|
|
slug: Joi.string().alphanum().optional()
|
|
});
|
|
|
|
const req = mockRequest({
|
|
params: {
|
|
id: '123',
|
|
slug: 'test-slug'
|
|
}
|
|
});
|
|
const res = mockResponse();
|
|
const next = mockNext();
|
|
|
|
const middleware = validateRequest(paramsSchema, 'params');
|
|
middleware(req, res, next);
|
|
|
|
expect(next.errors).to.have.length(0);
|
|
expect(req.params.id).to.equal(123);
|
|
});
|
|
});
|
|
});
|