UNPKG

qapinterface

Version:

Comprehensive API utilities for Node.js applications including authentication, security, request processing, and response handling with zero external dependencies

396 lines (329 loc) 12.9 kB
/** * Comprehensive unit tests for middleware modules * Tests all middleware functionality with mocked Express objects */ const { expect } = require('chai'); const sinon = require('sinon'); // Import modules to test const { createRequestProcessingMiddleware } = require('../middleware/request-processor'); const { securityMiddlewareStack } = require('../middleware/security-stack-creator'); const { handleValidationErrors } = require('../middleware/validation-error-handler'); const { requestLogger } = require('../middleware/request-logger'); const { errorLogger } = require('../middleware/error-logger'); describe('Middleware Module Tests', () => { describe('Request Processing Middleware', () => { it('should create request processing middleware', () => { const middleware = createRequestProcessingMiddleware(); expect(typeof middleware).to.equal('function'); expect(middleware.length).to.equal(3); // req, res, next }); it('should process requests with timing and ID generation', async () => { const middleware = createRequestProcessingMiddleware(); const mockReq = { method: 'GET', url: '/api/test', headers: { 'user-agent': 'test-agent' } }; const mockRes = { locals: {} }; const mockNext = sinon.stub(); await middleware(mockReq, mockRes, mockNext); expect(typeof mockReq.id).to.equal('string'); expect(mockReq.id.length > 0).to.equal(true); expect(typeof mockReq.startTime).to.equal('number'); expect(mockNext.called).to.equal(true); }); it('should handle errors gracefully', async () => { const middleware = createRequestProcessingMiddleware(); const mockReq = null; // Simulate error condition const mockRes = {}; const mockNext = sinon.stub(); try { await middleware(mockReq, mockRes, mockNext); } catch (error) { expect(error instanceof Error).to.equal(true); } }); it('should extract request context', async () => { const middleware = createRequestProcessingMiddleware(); const mockReq = { method: 'POST', url: '/api/users', headers: { 'user-agent': 'Mozilla/5.0', 'x-forwarded-for': '192.168.1.100' }, ip: '192.168.1.100', body: { name: 'John Doe' } }; const mockRes = { locals: {} }; const mockNext = sinon.stub(); await middleware(mockReq, mockRes, mockNext); expect(typeof mockReq.context).to.equal('object'); expect(mockReq.context.method).to.equal('POST'); expect(mockReq.context.ip).to.equal('192.168.1.100'); }); }); describe('Security Middleware Stack', () => { it('should create security middleware stack', () => { const stack = securityMiddlewareStack(); expect(Array.isArray(stack)).to.equal(true); expect(stack.length > 0).to.equal(true); stack.forEach(middleware => { expect(typeof middleware).to.equal('function'); }); }); it('should include helmet middleware', () => { const stack = securityMiddlewareStack(); expect(stack.length >= 1).to.equal(true); // First middleware should be helmet expect(typeof stack[0]).to.equal('function'); }); it('should accept custom security options', () => { const customOptions = { csp: { 'script-src': ["'self'"] }, hsts: { maxAge: 31536000 } }; const stack = securityMiddlewareStack(customOptions); expect(Array.isArray(stack)).to.equal(true); expect(stack.length > 0).to.equal(true); }); it('should include rate limiting middleware', () => { const stack = securityMiddlewareStack(); // Should include multiple security middleware expect(stack.length >= 2).to.equal(true); }); }); describe('Validation Error Handler', () => { it('should handle validation errors', () => { const mockErrors = [ { field: 'email', message: 'Invalid email format' }, { field: 'password', message: 'Password too short' } ]; const mockReq = { validationErrors: mockErrors }; const mockRes = { status: sinon.stub().returnsThis(), json: sinon.stub() }; const mockNext = sinon.stub(); handleValidationErrors(mockReq, mockRes, mockNext); expect(mockRes.status.calledWith(400)).to.equal(true); expect(mockRes.json.called).to.equal(true); expect(mockNext.notCalled).to.equal(true); }); it('should proceed when no validation errors', () => { const mockReq = {}; const mockRes = { status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); handleValidationErrors(mockReq, mockRes, mockNext); expect(mockRes.status.notCalled).to.equal(true); expect(mockRes.json.notCalled).to.equal(true); expect(mockNext.called).to.equal(true); }); it('should handle empty validation errors array', () => { const mockReq = { validationErrors: [] }; const mockRes = { status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); handleValidationErrors(mockReq, mockRes, mockNext); expect(mockNext.called).to.equal(true); }); it('should format validation errors properly', () => { const mockErrors = [ { field: 'username', message: 'Username is required' } ]; const mockReq = { validationErrors: mockErrors }; const mockRes = { status: sinon.stub().returnsThis(), json: sinon.stub() }; const mockNext = sinon.stub(); handleValidationErrors(mockReq, mockRes, mockNext); const jsonCall = mockRes.json.getCall(0); const response = jsonCall.args[0]; expect(response.success).to.equal(false); expect(response.error.message).to.equal('Validation failed'); expect(Array.isArray(response.error.issues)).to.equal(true); }); }); describe('Request Logger Middleware', () => { it('should create request logger middleware', () => { const middleware = requestLogger(); expect(typeof middleware).to.equal('function'); expect(middleware.length).to.equal(3); // req, res, next }); it('should log request details', () => { const originalLog = console.log; let loggedData = ''; console.log = (data) => { loggedData += data; }; const middleware = requestLogger(); const mockReq = { id: 'test-request-123', method: 'GET', url: '/api/test', ip: '127.0.0.1', headers: { 'user-agent': 'test-agent' } }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); console.log = originalLog; expect(loggedData.includes('test-request-123')).to.equal(true); expect(loggedData.includes('GET')).to.equal(true); expect(loggedData.includes('/api/test')).to.equal(true); expect(mockNext.called).to.equal(true); }); it('should handle requests without ID', () => { const originalLog = console.log; let logCalled = false; console.log = () => { logCalled = true; }; const middleware = requestLogger(); const mockReq = { method: 'POST', url: '/api/users', ip: '192.168.1.100' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); console.log = originalLog; expect(logCalled).to.equal(true); expect(mockNext.called).to.equal(true); }); it('should log with timestamp', () => { const originalLog = console.log; let loggedData = ''; console.log = (data) => { loggedData += data; }; const middleware = requestLogger(); const mockReq = { id: 'test-123', method: 'GET', url: '/test' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); console.log = originalLog; // Should include timestamp format expect(loggedData.includes(':')).to.equal(true); expect(loggedData.includes('AM') || loggedData.includes('PM')).to.equal(true); }); }); describe('Error Logger Middleware', () => { it('should create error logger middleware', () => { const middleware = errorLogger(); expect(typeof middleware).to.equal('function'); expect(middleware.length).to.equal(4); // err, req, res, next }); it('should log error details', () => { const originalError = console.error; let loggedData = ''; console.error = (data) => { loggedData += data; }; const middleware = errorLogger(); const mockError = new Error('Test error message'); const mockReq = { id: 'error-request-123', method: 'POST', url: '/api/error' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockError, mockReq, mockRes, mockNext); console.error = originalError; expect(loggedData.includes('Test error message')).to.equal(true); expect(loggedData.includes('error-request-123')).to.equal(true); expect(mockNext.calledWith(mockError)).to.equal(true); }); it('should handle errors without request ID', () => { const originalError = console.error; let logCalled = false; console.error = () => { logCalled = true; }; const middleware = errorLogger(); const mockError = new Error('No ID error'); const mockReq = { method: 'GET', url: '/test' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockError, mockReq, mockRes, mockNext); console.error = originalError; expect(logCalled).to.equal(true); expect(mockNext.called).to.equal(true); }); it('should log stack trace for debugging', () => { const originalError = console.error; let loggedData = ''; console.error = (data) => { loggedData += data; }; const middleware = errorLogger(); const mockError = new Error('Stack trace test'); mockError.stack = 'Error: Stack trace test\n at test.js:1:1'; const mockReq = { id: 'stack-test' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockError, mockReq, mockRes, mockNext); console.error = originalError; expect(loggedData.includes('Stack trace test')).to.equal(true); expect(loggedData.includes('test.js:1:1')).to.equal(true); }); it('should include request context in error logs', () => { const originalError = console.error; let loggedData = ''; console.error = (data) => { loggedData += data; }; const middleware = errorLogger(); const mockError = new Error('Context test'); const mockReq = { id: 'context-test-123', method: 'PUT', url: '/api/update', ip: '10.0.0.1' }; const mockRes = {}; const mockNext = sinon.stub(); middleware(mockError, mockReq, mockRes, mockNext); console.error = originalError; expect(loggedData.includes('PUT')).to.equal(true); expect(loggedData.includes('/api/update')).to.equal(true); expect(loggedData.includes('10.0.0.1')).to.equal(true); }); }); describe('Middleware Integration', () => { it('should work together in middleware stack', async () => { const requestProcessor = createRequestProcessingMiddleware(); const validationHandler = handleValidationErrors; const reqLogger = requestLogger(); const mockReq = { method: 'GET', url: '/api/integration-test', headers: {} }; const mockRes = { locals: {} }; let nextCallCount = 0; const mockNext = () => { nextCallCount++; }; // Process through middleware stack await requestProcessor(mockReq, mockRes, mockNext); validationHandler(mockReq, mockRes, mockNext); reqLogger(mockReq, mockRes, mockNext); expect(nextCallCount).to.equal(3); expect(typeof mockReq.id).to.equal('string'); }); it('should handle errors through middleware chain', () => { const originalError = console.error; let errorLogged = false; console.error = () => { errorLogged = true; }; const errorLoggerMiddleware = errorLogger(); const mockError = new Error('Chain error test'); const mockReq = { id: 'chain-test' }; const mockRes = {}; const mockNext = sinon.stub(); errorLoggerMiddleware(mockError, mockReq, mockRes, mockNext); console.error = originalError; expect(errorLogged).to.equal(true); expect(mockNext.calledWith(mockError)).to.equal(true); }); }); }); module.exports = { runMiddlewareTests: () => expect().to.be.undefined };