UNPKG

qapinterface

Version:

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

580 lines (478 loc) 17.2 kB
/** * Comprehensive unit tests for rate limiting modules * Tests rate limit tracking, middleware creation, and sliding window algorithms with mocked dependencies */ const { expect } = require('chai'); const sinon = require('sinon'); // Import modules to test const { createRateLimitTracker } = require('../rate-limiting/rate-limit-tracker'); const { createRateLimitMiddleware } = require('../rate-limiting/middleware-creator'); const { createRateLimiter } = require('../rate-limiting/limiter-creator'); describe('Rate Limiting Module Tests', () => { describe('Rate Limit Tracker', () => { it('should create rate limit tracker', () => { const tracker = createRateLimitTracker({ windowMs: 60000, // 1 minute maxRequests: 100 }); expect(typeof tracker).to.equal('object'); expect(typeof tracker.checkLimit).to.equal('function'); expect(typeof tracker.addRequest).to.equal('function'); expect(typeof tracker.getStatus).to.equal('function'); }); it('should track requests within window', () => { const tracker = createRateLimitTracker({ windowMs: 60000, maxRequests: 5 }); const clientId = 'test-client-123'; // Add requests within limit for (let i = 0; i < 4; i++) { const result = tracker.addRequest(clientId); expect(result.allowed).to.equal(true); expect(result.count).to.equal(i + 1); } }); it('should enforce rate limits', () => { const tracker = createRateLimitTracker({ windowMs: 60000, maxRequests: 3 }); const clientId = 'rate-limited-client'; // Fill up the limit for (let i = 0; i < 3; i++) { tracker.addRequest(clientId); } // Next request should be denied const result = tracker.addRequest(clientId); expect(result.allowed).to.equal(false); expect(result.count > 3).to.equal(true); }); it('should implement sliding window correctly', () => { const tracker = createRateLimitTracker({ windowMs: 1000, // 1 second window maxRequests: 2 }); const clientId = 'sliding-window-test'; // Add requests at limit tracker.addRequest(clientId); tracker.addRequest(clientId); // Should be at limit let result = tracker.addRequest(clientId); expect(result.allowed).to.equal(false); // Wait for window to slide (simulate time passage) setTimeout(() => { result = tracker.addRequest(clientId); expect(result.allowed).to.equal(true); }, 1100); }); it('should handle multiple clients independently', () => { const tracker = createRateLimitTracker({ windowMs: 60000, maxRequests: 2 }); const client1 = 'client-1'; const client2 = 'client-2'; // Client 1 reaches limit tracker.addRequest(client1); tracker.addRequest(client1); let result1 = tracker.addRequest(client1); expect(result1.allowed).to.equal(false); // Client 2 should still be allowed let result2 = tracker.addRequest(client2); expect(result2.allowed).to.equal(true); }); it('should provide rate limit status', () => { const tracker = createRateLimitTracker({ windowMs: 60000, maxRequests: 10 }); const clientId = 'status-test-client'; tracker.addRequest(clientId); tracker.addRequest(clientId); const status = tracker.getStatus(clientId); expect(typeof status).to.equal('object'); expect(status.count).to.equal(2); expect(status.remaining).to.equal(8); expect(typeof status.resetTime).to.equal('number'); }); it('should handle burst requests', () => { const tracker = createRateLimitTracker({ windowMs: 60000, maxRequests: 5, burstLimit: 10 // Allow short bursts }); const clientId = 'burst-test-client'; // Send burst of requests const results = []; for (let i = 0; i < 8; i++) { results.push(tracker.addRequest(clientId)); } // Some should be allowed due to burst limit const allowedCount = results.filter(r => r.allowed).length; expect(allowedCount > 5).to.equal(true); expect(allowedCount <= 10).to.equal(true); }); it('should clean up expired entries', () => { const tracker = createRateLimitTracker({ windowMs: 100, // Very short window maxRequests: 5 }); const clientId = 'cleanup-test'; tracker.addRequest(clientId); // Wait for window to expire setTimeout(() => { const status = tracker.getStatus(clientId); expect(status.count).to.equal(0); // Should be cleaned up }, 150); }); }); describe('Rate Limit Middleware', () => { it('should create rate limiting middleware', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 100 }); expect(typeof middleware).to.equal('function'); expect(middleware.length).to.equal(3); // req, res, next }); it('should allow requests within limit', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 10 }); const mockReq = { ip: '192.168.1.100', headers: {} }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); expect(mockNext.called).to.equal(true); expect(mockRes.status.notCalled).to.equal(true); }); it('should block requests over limit', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 2 }); const mockReq = { ip: '192.168.1.101', headers: {} }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub().returnsThis(), json: sinon.stub() }; const mockNext = sinon.stub(); // Send requests up to limit middleware(mockReq, mockRes, mockNext); middleware(mockReq, mockRes, mockNext); // This should be blocked middleware(mockReq, mockRes, mockNext); expect(mockRes.status.calledWith(429)).to.equal(true); expect(mockRes.json.called).to.equal(true); }); it('should set rate limit headers', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 100 }); const mockReq = { ip: '192.168.1.102', headers: {} }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); qtests.assert(mockRes.setHeader.calledWith('X-RateLimit-Limit', 100), true); qtests.assert(mockRes.setHeader.calledWith('X-RateLimit-Remaining', 99), true); expect(mockRes.setHeader.calledWith('X-RateLimit-Reset')).to.equal(true); }); it('should use custom key generator', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 10, keyGenerator: (req) => req.headers['x-api-key'] || req.ip }); const mockReq = { ip: '192.168.1.103', headers: { 'x-api-key': 'custom-key-123' } }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); middleware(mockReq, mockRes, mockNext); expect(mockNext.called).to.equal(true); }); it('should handle skip conditions', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 1, // Very restrictive skip: (req) => req.headers['x-bypass'] === 'true' }); const mockReq = { ip: '192.168.1.104', headers: { 'x-bypass': 'true' } }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); // Should be skipped despite restrictive limit middleware(mockReq, mockRes, mockNext); middleware(mockReq, mockRes, mockNext); expect(mockNext.callCount).to.equal(2); expect(mockRes.status.notCalled).to.equal(true); }); it('should provide custom error responses', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 1, message: 'Custom rate limit exceeded message', statusCode: 429 }); const mockReq = { ip: '192.168.1.105', headers: {} }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub().returnsThis(), json: sinon.stub() }; const mockNext = sinon.stub(); // First request - allowed middleware(mockReq, mockRes, mockNext); // Second request - blocked middleware(mockReq, mockRes, mockNext); const jsonCall = mockRes.json.getCall(0); const response = jsonCall.args[0]; expect(response.error.message.includes('Custom rate limit')).to.equal(true); }); }); describe('Rate Limiter Creator', () => { it('should create configurable rate limiter', () => { const limiter = createRateLimiter({ type: 'sliding-window', windowMs: 60000, maxRequests: 100 }); expect(typeof limiter).to.equal('object'); expect(typeof limiter.isAllowed).to.equal('function'); expect(typeof limiter.recordRequest).to.equal('function'); }); it('should implement fixed window algorithm', () => { const limiter = createRateLimiter({ type: 'fixed-window', windowMs: 60000, maxRequests: 5 }); const clientId = 'fixed-window-client'; // Add requests within window for (let i = 0; i < 5; i++) { const allowed = limiter.isAllowed(clientId); expect(allowed).to.equal(true); limiter.recordRequest(clientId); } // Next request should be denied const denied = limiter.isAllowed(clientId); expect(denied).to.equal(false); }); it('should implement token bucket algorithm', () => { const limiter = createRateLimiter({ type: 'token-bucket', capacity: 5, refillRate: 1, // 1 token per second refillInterval: 1000 }); const clientId = 'token-bucket-client'; // Use all tokens for (let i = 0; i < 5; i++) { const allowed = limiter.isAllowed(clientId); expect(allowed).to.equal(true); limiter.recordRequest(clientId); } // Should be out of tokens const denied = limiter.isAllowed(clientId); expect(denied).to.equal(false); }); it('should handle distributed rate limiting', () => { const limiter = createRateLimiter({ type: 'distributed', windowMs: 60000, maxRequests: 100, store: 'memory' // Could be Redis in production }); expect(typeof limiter.isAllowed).to.equal('function'); expect(typeof limiter.recordRequest).to.equal('function'); expect(typeof limiter.getStats).to.equal('function'); }); it('should provide rate limit statistics', () => { const limiter = createRateLimiter({ type: 'sliding-window', windowMs: 60000, maxRequests: 10 }); const clientId = 'stats-client'; limiter.recordRequest(clientId); limiter.recordRequest(clientId); const stats = limiter.getStats(clientId); expect(typeof stats).to.equal('object'); expect(stats.requestCount).to.equal(2); expect(stats.remaining).to.equal(8); expect(typeof stats.resetTime).to.equal('number'); }); it('should handle concurrent requests safely', async () => { const limiter = createRateLimiter({ type: 'sliding-window', windowMs: 60000, maxRequests: 10 }); const clientId = 'concurrent-client'; // Simulate concurrent requests const promises = []; for (let i = 0; i < 20; i++) { promises.push(Promise.resolve().then(() => { if (limiter.isAllowed(clientId)) { limiter.recordRequest(clientId); return 'allowed'; } return 'denied'; })); } const results = await Promise.all(promises); const allowedCount = results.filter(r => r === 'allowed').length; expect(allowedCount <= 10).to.equal(true); // Should not exceed limit expect(allowedCount > 0).to.equal(true); // Some should be allowed }); }); describe('Rate Limiting Integration', () => { it('should integrate with Express application', () => { const rateLimitMiddleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 100 }); // Simulate Express app middleware stack const middlewareStack = [rateLimitMiddleware]; expect(middlewareStack.length).to.equal(1); expect(typeof middlewareStack[0]).to.equal('function'); }); it('should work with authentication middleware', () => { const authMiddleware = (req, res, next) => { req.user = { id: 'user-123' }; next(); }; const rateLimitMiddleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 10, keyGenerator: (req) => req.user ? req.user.id : req.ip }); const mockReq = { ip: '192.168.1.106' }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); // Run auth middleware first authMiddleware(mockReq, mockRes, () => { // Then rate limiting rateLimitMiddleware(mockReq, mockRes, mockNext); }); expect(mockNext.called).to.equal(true); expect(mockReq.user.id).to.equal('user-123'); }); it('should handle different rate limits per endpoint', () => { const strictLimiter = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 5 // Strict limit }); const relaxedLimiter = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 100 // Relaxed limit }); // Both should be middleware functions expect(typeof strictLimiter).to.equal('function'); expect(typeof relaxedLimiter).to.equal('function'); }); it('should provide monitoring capabilities', () => { const limiter = createRateLimiter({ type: 'sliding-window', windowMs: 60000, maxRequests: 100, monitoring: true }); if (limiter.getMetrics) { const metrics = limiter.getMetrics(); expect(typeof metrics).to.equal('object'); expect(typeof metrics.totalRequests).to.equal('number'); expect(typeof metrics.blockedRequests).to.equal('number'); } }); it('should handle graceful degradation', () => { // Test rate limiter behavior when storage fails const limiter = createRateLimiter({ type: 'sliding-window', windowMs: 60000, maxRequests: 10, fallbackToAllow: true // Allow requests if storage fails }); // Mock storage failure const originalGet = limiter.store?.get; if (limiter.store) { limiter.store.get = () => { throw new Error('Storage failure'); }; } const clientId = 'degradation-test'; const allowed = limiter.isAllowed(clientId); // Should fall back to allowing request expect(allowed).to.equal(true); // Restore original method if (limiter.store && originalGet) { limiter.store.get = originalGet; } }); it('should support rate limit bypass for trusted sources', () => { const middleware = createRateLimitMiddleware({ windowMs: 60000, maxRequests: 1, // Very restrictive whitelist: ['192.168.1.0/24', '10.0.0.0/8'], skip: (req) => { // Check if IP is in whitelist return req.ip.startsWith('192.168.1.') || req.ip.startsWith('10.'); } }); const trustedReq = { ip: '192.168.1.50', headers: {} }; const mockRes = { setHeader: sinon.stub(), status: sinon.stub(), json: sinon.stub() }; const mockNext = sinon.stub(); // Multiple requests from trusted IP should be allowed middleware(trustedReq, mockRes, mockNext); middleware(trustedReq, mockRes, mockNext); middleware(trustedReq, mockRes, mockNext); expect(mockNext.callCount).to.equal(3); expect(mockRes.status.notCalled).to.equal(true); }); }); }); module.exports = { runRateLimitingTests: () => expect().to.be.undefined };