← back to Designer Wallcoverings

DW-Agents/__tests__/performance/load-test.test.ts

279 lines

/**
 * Performance and Load Tests
 * Tests system performance under various load conditions
 */

import axios from 'axios';

const BASE_URL = process.env.BASE_URL || 'http://localhost';
const MASTER_HUB_PORT = process.env.MASTER_HUB_PORT || '9800';
const API_BASE = `${BASE_URL}:${MASTER_HUB_PORT}`;

describe('Performance Tests', () => {

  describe('Response Time Tests', () => {
    it('should respond to health check within 100ms under normal load', async () => {
      const iterations = 10;
      const times: number[] = [];

      for (let i = 0; i < iterations; i++) {
        const start = Date.now();
        await axios.get(`${API_BASE}/api/health`);
        times.push(Date.now() - start);
      }

      const avg = times.reduce((a, b) => a + b, 0) / times.length;
      expect(avg).toBeLessThan(100);
    });

    it('should maintain consistent response times', async () => {
      const iterations = 50;
      const times: number[] = [];

      for (let i = 0; i < iterations; i++) {
        const start = Date.now();
        await axios.get(`${API_BASE}/api/health`);
        times.push(Date.now() - start);
        await new Promise(resolve => setTimeout(resolve, 50));
      }

      const avg = times.reduce((a, b) => a + b, 0) / times.length;
      const max = Math.max(...times);
      const min = Math.min(...times);

      // Standard deviation should be low (consistent performance)
      const variance = times.reduce((sum, time) => sum + Math.pow(time - avg, 2), 0) / times.length;
      const stdDev = Math.sqrt(variance);

      expect(stdDev).toBeLessThan(avg); // Std dev should be less than average
      expect(max).toBeLessThan(min * 5); // Max shouldn't be more than 5x min
    });
  });

  describe('Throughput Tests', () => {
    it('should handle 100 requests per second', async () => {
      const duration = 5000; // 5 seconds
      const targetRPS = 100;
      const expectedRequests = (duration / 1000) * targetRPS;

      const startTime = Date.now();
      const promises: Promise<any>[] = [];
      let completed = 0;

      const interval = setInterval(() => {
        for (let i = 0; i < 10; i++) { // Batch of 10
          promises.push(
            axios.get(`${API_BASE}/api/health`, { validateStatus: () => true })
              .then(() => completed++)
              .catch(() => completed++)
          );
        }
      }, 100); // Every 100ms

      // Wait for test duration
      await new Promise(resolve => setTimeout(resolve, duration));
      clearInterval(interval);

      // Wait for all requests to complete
      await Promise.all(promises);

      const totalTime = Date.now() - startTime;
      const actualRPS = (completed / totalTime) * 1000;

      console.log(`Throughput test: ${actualRPS.toFixed(2)} req/s (target: ${targetRPS})`);

      expect(completed).toBeGreaterThan(expectedRequests * 0.8); // At least 80% success
    }, 10000);

    it('should handle burst traffic', async () => {
      // Send 200 requests as fast as possible
      const burstSize = 200;
      const startTime = Date.now();

      const promises = Array(burstSize).fill(null).map(() =>
        axios.get(`${API_BASE}/api/health`, { validateStatus: () => true })
      );

      const responses = await Promise.all(promises);
      const duration = Date.now() - startTime;

      const successful = responses.filter(r => r.status === 200).length;
      const successRate = (successful / burstSize) * 100;

      console.log(`Burst test: ${successful}/${burstSize} successful in ${duration}ms (${successRate.toFixed(1)}%)`);

      expect(successRate).toBeGreaterThan(90); // 90% success rate
      expect(duration).toBeLessThan(5000); // Complete within 5 seconds
    }, 10000);
  });

  describe('Concurrency Tests', () => {
    it('should handle 50 concurrent connections', async () => {
      const concurrentRequests = 50;

      const promises = Array(concurrentRequests).fill(null).map(() =>
        axios.get(`${API_BASE}/api/health`, {
          timeout: 5000,
          validateStatus: () => true
        })
      );

      const responses = await Promise.all(promises);

      const successful = responses.filter(r => r.status === 200).length;
      const successRate = (successful / concurrentRequests) * 100;

      expect(successRate).toBeGreaterThan(95); // 95% success rate
    });

    it('should handle sustained concurrent load', async () => {
      const duration = 10000; // 10 seconds
      const concurrency = 20;
      const startTime = Date.now();

      const results: boolean[] = [];
      const workers = Array(concurrency).fill(null).map(async () => {
        while (Date.now() - startTime < duration) {
          try {
            const response = await axios.get(`${API_BASE}/api/health`, {
              timeout: 2000
            });
            results.push(response.status === 200);
          } catch {
            results.push(false);
          }
          await new Promise(resolve => setTimeout(resolve, 100));
        }
      });

      await Promise.all(workers);

      const successRate = (results.filter(r => r).length / results.length) * 100;

      console.log(`Sustained load test: ${successRate.toFixed(1)}% success over ${duration}ms`);

      expect(successRate).toBeGreaterThan(95);
    }, 15000);
  });

  describe('Stress Tests', () => {
    it('should recover from overload', async () => {
      // Phase 1: Overload (500 requests rapid fire)
      const overloadPromises = Array(500).fill(null).map(() =>
        axios.get(`${API_BASE}/api/health`, {
          validateStatus: () => true,
          timeout: 5000
        }).catch(() => ({ status: 0 }))
      );

      await Promise.all(overloadPromises);

      // Phase 2: Wait for recovery
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Phase 3: Test normal operation
      const recoveryPromises = Array(10).fill(null).map(() =>
        axios.get(`${API_BASE}/api/health`)
      );

      const recoveryResponses = await Promise.all(recoveryPromises);

      // Should fully recover
      recoveryResponses.forEach(response => {
        expect(response.status).toBe(200);
      });
    }, 20000);

    it('should maintain performance under sustained load', async () => {
      const testDuration = 30000; // 30 seconds
      const requestInterval = 100; // 10 req/sec
      const startTime = Date.now();

      const responseTimes: number[] = [];

      while (Date.now() - startTime < testDuration) {
        const reqStart = Date.now();
        try {
          await axios.get(`${API_BASE}/api/health`, { timeout: 3000 });
          responseTimes.push(Date.now() - reqStart);
        } catch {
          responseTimes.push(3000); // Timeout
        }

        await new Promise(resolve => setTimeout(resolve, requestInterval));
      }

      // Calculate performance degradation
      const firstQuarter = responseTimes.slice(0, Math.floor(responseTimes.length / 4));
      const lastQuarter = responseTimes.slice(-Math.floor(responseTimes.length / 4));

      const firstAvg = firstQuarter.reduce((a, b) => a + b, 0) / firstQuarter.length;
      const lastAvg = lastQuarter.reduce((a, b) => a + b, 0) / lastQuarter.length;

      const degradation = ((lastAvg - firstAvg) / firstAvg) * 100;

      console.log(`Performance degradation: ${degradation.toFixed(2)}%`);

      // Performance shouldn't degrade more than 50%
      expect(degradation).toBeLessThan(50);
    }, 35000);
  });

  describe('Resource Utilization', () => {
    it('should not leak memory under repeated requests', async () => {
      // Make 1000 requests and track response consistency
      const iterations = 1000;
      let errors = 0;

      for (let i = 0; i < iterations; i++) {
        try {
          const response = await axios.get(`${API_BASE}/api/health`, {
            timeout: 2000
          });
          expect(response.status).toBe(200);
        } catch {
          errors++;
        }

        // Allow GC every 100 requests
        if (i % 100 === 0) {
          await new Promise(resolve => setTimeout(resolve, 100));
        }
      }

      const errorRate = (errors / iterations) * 100;
      expect(errorRate).toBeLessThan(5); // Less than 5% errors
    }, 60000);
  });

  describe('Latency Distribution', () => {
    it('should have acceptable p50, p95, and p99 latencies', async () => {
      const iterations = 200;
      const times: number[] = [];

      for (let i = 0; i < iterations; i++) {
        const start = Date.now();
        await axios.get(`${API_BASE}/api/health`);
        times.push(Date.now() - start);
        await new Promise(resolve => setTimeout(resolve, 25));
      }

      times.sort((a, b) => a - b);

      const p50 = times[Math.floor(iterations * 0.50)];
      const p95 = times[Math.floor(iterations * 0.95)];
      const p99 = times[Math.floor(iterations * 0.99)];

      console.log(`Latency distribution:
        P50: ${p50}ms
        P95: ${p95}ms
        P99: ${p99}ms
      `);

      expect(p50).toBeLessThan(100);
      expect(p95).toBeLessThan(300);
      expect(p99).toBeLessThan(500);
    }, 15000);
  });
});