Skip to main content
Glama
load-tests.test.ts21.4 kB
import request from 'supertest'; import express, { Application } from 'express'; import { authRouter } from '../../src/auth/routes'; import { mcpRouter } from '../../src/server/routes'; import { MockFactory, TestDataGenerator, PerformanceTestHelpers } from '../utils/test-helpers'; import { fixtures } from '../utils/fixtures'; import { JWTService } from '../../src/auth/jwt-service'; // Mock dependencies for performance testing jest.mock('../../src/config/config', () => ({ config: fixtures.config.testConfig, })); jest.mock('../../src/database/redis', () => ({ redis: MockFactory.createMockRedis(), })); jest.mock('../../src/database/prisma', () => ({ prisma: MockFactory.createMockPrisma(), })); jest.mock('../../src/utils/logger', () => ({ logger: MockFactory.createMockLogger(), })); describe('Performance Load Tests', () => { let app: Application; let mockRedis: any; let mockPrisma: any; let jwtService: JWTService; let validToken: string; beforeAll(async () => { // Setup Express app app = express(); app.use(express.json()); app.use('/api/auth', authRouter); app.use('/api/mcp', mcpRouter); // Initialize JWT service jwtService = new JWTService(); await jwtService.initialize(); // Generate valid token for authenticated tests const tokenPair = await jwtService.generateTokenPair({ userId: fixtures.users.validUser.id, email: fixtures.users.validUser.email, roles: fixtures.users.validUser.roles, permissions: fixtures.users.validUser.permissions, sessionId: 'load-test-session', mfaVerified: false, }); validToken = tokenPair.accessToken; // Setup mocks mockRedis = require('../../src/database/redis').redis; mockPrisma = require('../../src/database/prisma').prisma; // Configure successful responses for load testing mockRedis.get.mockResolvedValue('valid'); mockRedis.setex.mockResolvedValue('OK'); mockRedis.incr.mockResolvedValue(1); // Under rate limit mockPrisma.user.findUnique.mockResolvedValue(fixtures.users.validUser); }); beforeEach(() => { jest.clearAllMocks(); jest.useRealTimers(); }); describe('Authentication Endpoint Load Tests', () => { it('should handle concurrent login requests under normal load', async () => { const loginPayload = { email: 'user@example.com', password: 'SecurePassword123!', }; const config = fixtures.performance.loadTestConfig; const operation = async () => { const response = await request(app) .post('/api/auth/login') .send(loginPayload); expect(response.status).toBeLessThan(500); return response; }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: config.concurrent, duration: config.duration, errorThreshold: config.errorThreshold, }); // Performance assertions expect(results.totalRequests).toBeGreaterThan(0); expect(results.failed / results.totalRequests).toBeLessThanOrEqual(config.errorThreshold); expect(results.averageResponseTime).toBeLessThan(fixtures.performance.performanceThresholds.responseTime.p50); console.log('Login Load Test Results:', { totalRequests: results.totalRequests, successful: results.successful, failed: results.failed, errorRate: `${((results.failed / results.totalRequests) * 100).toFixed(2)}%`, averageResponseTime: `${results.averageResponseTime.toFixed(2)}ms`, maxResponseTime: `${results.maxResponseTime}ms`, }); }); it('should handle high-frequency token refresh requests', async () => { const refreshPayload = { refreshToken: 'valid-refresh-token', }; // Mock successful refresh mockRedis.get .mockResolvedValue(JSON.stringify({ userId: fixtures.users.validUser.id, sessionId: 'session-123', createdAt: new Date().toISOString(), })) .mockResolvedValue(JSON.stringify({ email: fixtures.users.validUser.email, roles: JSON.stringify(fixtures.users.validUser.roles), permissions: JSON.stringify(fixtures.users.validUser.permissions), mfaVerified: 'false', })); const operation = async () => { const response = await request(app) .post('/api/auth/refresh') .send(refreshPayload); return response; }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 50, duration: 10000, // 10 seconds errorThreshold: 0.02, }); expect(results.averageResponseTime).toBeLessThan(200); // Token refresh should be fast expect(results.failed / results.totalRequests).toBeLessThanOrEqual(0.02); }); it('should maintain performance under authentication failures', async () => { const invalidLoginPayload = { email: 'user@example.com', password: 'WrongPassword123!', }; // Mock authentication failure jest.doMock('argon2', () => ({ verify: jest.fn().mockResolvedValue(false), })); const operation = async () => { const response = await request(app) .post('/api/auth/login') .send(invalidLoginPayload); expect(response.status).toBe(401); return response; }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 25, duration: 5000, }); // Failed authentications should still be processed efficiently expect(results.averageResponseTime).toBeLessThan(500); expect(results.successful).toBe(results.totalRequests); // All should return 401 successfully }); }); describe('API Endpoint Performance Tests', () => { it('should handle concurrent profile requests efficiently', async () => { const operation = async () => { const response = await request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); expect([200, 401]).toContain(response.status); return response; }; const { result, duration } = await PerformanceTestHelpers.measureExecutionTime( () => PerformanceTestHelpers.stressTest(operation, { concurrent: 100, duration: 15000, errorThreshold: 0.01, }) ); expect(result.averageResponseTime).toBeLessThan(100); expect(duration).toBeLessThan(16000); // Should complete within expected timeframe console.log('Profile API Performance:', { requestsPerSecond: Math.round(result.totalRequests / (duration / 1000)), averageResponseTime: `${result.averageResponseTime.toFixed(2)}ms`, p95ResponseTime: `${result.maxResponseTime}ms`, }); }); it('should handle mixed workload scenarios', async () => { const operations = [ // Login requests (30%) ...Array(3).fill(async () => { return request(app) .post('/api/auth/login') .send({ email: `user${Math.random()}@example.com`, password: 'SecurePassword123!', }); }), // Profile requests (50%) ...Array(5).fill(async () => { return request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); }), // Refresh requests (20%) ...Array(2).fill(async () => { return request(app) .post('/api/auth/refresh') .send({ refreshToken: 'valid-refresh-token' }); }), ]; const operation = async () => { const randomOp = operations[Math.floor(Math.random() * operations.length)]; return randomOp(); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 75, duration: 20000, errorThreshold: 0.05, }); expect(results.averageResponseTime).toBeLessThan(300); expect(results.failed / results.totalRequests).toBeLessThanOrEqual(0.05); console.log('Mixed Workload Results:', { totalRequests: results.totalRequests, throughput: `${Math.round(results.totalRequests / 20)} req/sec`, errorRate: `${((results.failed / results.totalRequests) * 100).toFixed(2)}%`, }); }); }); describe('Memory and Resource Usage Tests', () => { it('should not leak memory under sustained load', async () => { const initialMemory = process.memoryUsage(); const operation = async () => { return request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); }; const { result, memoryDelta } = await PerformanceTestHelpers.measureMemoryUsage( () => PerformanceTestHelpers.stressTest(operation, { concurrent: 50, duration: 10000, }) ); const finalMemory = process.memoryUsage(); const heapGrowth = finalMemory.heapUsed - initialMemory.heapUsed; const heapGrowthPerRequest = heapGrowth / result.totalRequests; // Memory growth should be reasonable expect(heapGrowthPerRequest).toBeLessThan(1024 * 10); // Less than 10KB per request expect(heapGrowth).toBeLessThan(1024 * 1024 * 50); // Less than 50MB total growth console.log('Memory Usage:', { totalRequests: result.totalRequests, heapGrowth: `${Math.round(heapGrowth / 1024)}KB`, heapGrowthPerRequest: `${Math.round(heapGrowthPerRequest)}B`, }); // Force garbage collection if available if (global.gc) { global.gc(); } }); it('should handle large request payloads efficiently', async () => { const largePayload = { email: 'user@example.com', password: 'SecurePassword123!', metadata: 'x'.repeat(1024 * 100), // 100KB of data }; const operation = async () => { return request(app) .post('/api/auth/login') .send(largePayload); }; const { result, duration } = await PerformanceTestHelpers.measureExecutionTime( () => Promise.all(Array(10).fill(null).map(operation)) ); const averageTime = duration / 10; // Large payloads should still be processed reasonably quickly expect(averageTime).toBeLessThan(1000); // Less than 1 second per large request expect(result.every(r => r.status < 500)).toBe(true); // No server errors }); }); describe('Rate Limiting Performance Tests', () => { it('should efficiently enforce rate limits under high load', async () => { // Configure rate limit to be hit quickly mockRedis.incr.mockImplementation((key) => { const count = Math.floor(Math.random() * 150) + 1; // Random between 1-150 return Promise.resolve(count); }); const operation = async () => { return request(app) .post('/api/auth/login') .send({ email: 'user@example.com', password: 'SecurePassword123!', }); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 100, duration: 5000, errorThreshold: 0.5, // Higher threshold since rate limiting is expected }); // Rate limiting should not significantly impact performance expect(results.averageResponseTime).toBeLessThan(200); const rateLimitedResponses = results.errors.filter( error => error.message.includes('429') ).length; console.log('Rate Limiting Performance:', { totalRequests: results.totalRequests, rateLimitedRequests: rateLimitedResponses, rateLimitPercentage: `${((rateLimitedResponses / results.totalRequests) * 100).toFixed(2)}%`, }); }); it('should handle rate limit recovery efficiently', async () => { let requestCount = 0; mockRedis.incr.mockImplementation(() => { requestCount++; // Simulate rate limit recovery return Promise.resolve(requestCount > 50 ? 1 : 101); }); mockRedis.ttl.mockResolvedValue(1); // Short TTL for quick recovery const operation = async () => { return request(app) .post('/api/auth/login') .send({ email: 'user@example.com', password: 'SecurePassword123!', }); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 20, duration: 10000, errorThreshold: 0.6, }); // Should recover from rate limiting expect(results.successful).toBeGreaterThan(results.failed); }); }); describe('Database Connection Performance', () => { it('should handle database connection pooling under load', async () => { const operation = async () => { mockPrisma.user.findUnique.mockResolvedValue(fixtures.users.validUser); const response = await request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); expect(mockPrisma.user.findUnique).toHaveBeenCalled(); return response; }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 50, duration: 8000, errorThreshold: 0.02, }); // Database operations should be efficient expect(results.averageResponseTime).toBeLessThan(150); expect(results.failed / results.totalRequests).toBeLessThanOrEqual(0.02); // Should not overwhelm the mock database expect(mockPrisma.user.findUnique).toHaveBeenCalledTimes(results.totalRequests); }); it('should handle Redis operations under concurrent load', async () => { let redisOperations = 0; mockRedis.get.mockImplementation(() => { redisOperations++; return Promise.resolve('valid'); }); const operation = async () => { return request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 75, duration: 6000, errorThreshold: 0.01, }); expect(results.averageResponseTime).toBeLessThan(100); expect(redisOperations).toBeGreaterThan(0); expect(redisOperations).toBeLessThanOrEqual(results.totalRequests * 2); // Allow for multiple Redis calls per request console.log('Redis Performance:', { totalRedisOps: redisOperations, opsPerRequest: (redisOperations / results.totalRequests).toFixed(2), }); }); }); describe('Error Handling Performance', () => { it('should handle error scenarios efficiently', async () => { // Force various error conditions const errorOperations = [ async () => { mockPrisma.user.findUnique.mockRejectedValueOnce(new Error('DB Error')); return request(app).get('/api/auth/profile').set('Authorization', `Bearer ${validToken}`); }, async () => { return request(app).get('/api/auth/profile').set('Authorization', 'Bearer invalid-token'); }, async () => { return request(app).post('/api/auth/login').send({ email: 'invalid', password: 'short' }); }, ]; const operation = async () => { const randomErrorOp = errorOperations[Math.floor(Math.random() * errorOperations.length)]; return randomErrorOp(); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: 30, duration: 5000, errorThreshold: 1.0, // All requests expected to error }); // Error handling should still be fast expect(results.averageResponseTime).toBeLessThan(300); expect(results.totalRequests).toBeGreaterThan(0); console.log('Error Handling Performance:', { totalRequests: results.totalRequests, averageErrorResponseTime: `${results.averageResponseTime.toFixed(2)}ms`, }); }); }); describe('Stress Test Scenarios', () => { it('should survive extreme concurrent load', async () => { const extremeConfig = fixtures.performance.stressTestConfig; const operation = async () => { return request(app) .post('/api/auth/login') .send({ email: `user${Math.random()}@example.com`, password: 'SecurePassword123!', }); }; const results = await PerformanceTestHelpers.stressTest(operation, { concurrent: extremeConfig.concurrent, duration: extremeConfig.duration, errorThreshold: extremeConfig.errorThreshold, }); // Should handle extreme load gracefully expect(results.failed / results.totalRequests).toBeLessThanOrEqual(extremeConfig.errorThreshold); expect(results.averageResponseTime).toBeLessThan(2000); // Still reasonable response times console.log('Stress Test Results:', { totalRequests: results.totalRequests, successful: results.successful, failed: results.failed, errorRate: `${((results.failed / results.totalRequests) * 100).toFixed(2)}%`, averageResponseTime: `${results.averageResponseTime.toFixed(2)}ms`, maxResponseTime: `${results.maxResponseTime}ms`, requestsPerSecond: Math.round(results.totalRequests / (extremeConfig.duration / 1000)), }); }); it('should handle spike load patterns', async () => { const spikeConfig = fixtures.performance.spikeTestConfig; // Simulate spike pattern: low load -> high load -> low load const phases = [ { concurrent: spikeConfig.baseLoad, duration: 5000 }, { concurrent: spikeConfig.spikeLoad, duration: spikeConfig.spikeDuration }, { concurrent: spikeConfig.baseLoad, duration: 5000 }, ]; const operation = async () => { return request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); }; const phaseResults = []; for (const phase of phases) { const result = await PerformanceTestHelpers.stressTest(operation, { concurrent: phase.concurrent, duration: phase.duration, errorThreshold: 0.1, }); phaseResults.push(result); } // Should handle spike gracefully const spikePhase = phaseResults[1]; expect(spikePhase.failed / spikePhase.totalRequests).toBeLessThanOrEqual(0.1); expect(spikePhase.averageResponseTime).toBeLessThan(1000); console.log('Spike Test Results:', { basePhase: { requests: phaseResults[0].totalRequests, avgTime: `${phaseResults[0].averageResponseTime.toFixed(2)}ms`, }, spikePhase: { requests: spikePhase.totalRequests, avgTime: `${spikePhase.averageResponseTime.toFixed(2)}ms`, errorRate: `${((spikePhase.failed / spikePhase.totalRequests) * 100).toFixed(2)}%`, }, recoveryPhase: { requests: phaseResults[2].totalRequests, avgTime: `${phaseResults[2].averageResponseTime.toFixed(2)}ms`, }, }); }); }); describe('Performance Regression Tests', () => { it('should maintain baseline performance metrics', async () => { const baselineMetrics = { loginResponseTime: 150, // ms profileResponseTime: 100, // ms throughput: 100, // requests/second minimum }; // Test login performance const loginOp = async () => { return request(app) .post('/api/auth/login') .send({ email: 'user@example.com', password: 'SecurePassword123!', }); }; const loginResults = await PerformanceTestHelpers.stressTest(loginOp, { concurrent: 20, duration: 5000, }); // Test profile performance const profileOp = async () => { return request(app) .get('/api/auth/profile') .set('Authorization', `Bearer ${validToken}`); }; const profileResults = await PerformanceTestHelpers.stressTest(profileOp, { concurrent: 30, duration: 5000, }); // Assert against baselines expect(loginResults.averageResponseTime).toBeLessThanOrEqual(baselineMetrics.loginResponseTime); expect(profileResults.averageResponseTime).toBeLessThanOrEqual(baselineMetrics.profileResponseTime); const totalThroughput = (loginResults.totalRequests + profileResults.totalRequests) / 10; // 10 seconds total expect(totalThroughput).toBeGreaterThanOrEqual(baselineMetrics.throughput); console.log('Baseline Performance Check:', { loginAvgTime: `${loginResults.averageResponseTime.toFixed(2)}ms`, profileAvgTime: `${profileResults.averageResponseTime.toFixed(2)}ms`, totalThroughput: `${totalThroughput.toFixed(2)} req/sec`, passed: { login: loginResults.averageResponseTime <= baselineMetrics.loginResponseTime, profile: profileResults.averageResponseTime <= baselineMetrics.profileResponseTime, throughput: totalThroughput >= baselineMetrics.throughput, }, }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/perfecxion-ai/secure-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server