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
JavaScript
/**
* 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 };