← back to Designer Wallcoverings

DW-Agents/__tests__/helpers/test-server.ts

233 lines

/**
 * Test Server Helper
 * Utilities for setting up and managing test servers
 */

import axios, { AxiosInstance } from 'axios';

export interface TestServerConfig {
  baseURL: string;
  port: string;
  timeout?: number;
  retries?: number;
}

export class TestServer {
  private client: AxiosInstance;
  private config: TestServerConfig;

  constructor(config: TestServerConfig) {
    this.config = {
      timeout: 5000,
      retries: 3,
      ...config
    };

    this.client = axios.create({
      baseURL: `${config.baseURL}:${config.port}`,
      timeout: this.config.timeout,
      validateStatus: () => true // Don't throw on non-2xx
    });
  }

  /**
   * Check if server is healthy
   */
  async isHealthy(): Promise<boolean> {
    try {
      const response = await this.client.get('/api/health');
      return response.status === 200;
    } catch {
      return false;
    }
  }

  /**
   * Wait for server to be ready
   */
  async waitUntilReady(timeout = 30000): Promise<void> {
    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      if (await this.isHealthy()) {
        return;
      }
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    throw new Error(`Server not ready after ${timeout}ms`);
  }

  /**
   * Make a GET request
   */
  async get(path: string, config?: any) {
    return await this.client.get(path, config);
  }

  /**
   * Make a POST request
   */
  async post(path: string, data?: any, config?: any) {
    return await this.client.post(path, data, config);
  }

  /**
   * Make a PUT request
   */
  async put(path: string, data?: any, config?: any) {
    return await this.client.put(path, data, config);
  }

  /**
   * Make a DELETE request
   */
  async delete(path: string, config?: any) {
    return await this.client.delete(path, config);
  }

  /**
   * Make a request with retry logic
   */
  async requestWithRetry(
    method: string,
    path: string,
    data?: any,
    retries = this.config.retries
  ): Promise<any> {
    for (let i = 0; i < retries!; i++) {
      try {
        const response = await this.client.request({
          method,
          url: path,
          data
        });

        if (response.status >= 200 && response.status < 300) {
          return response;
        }

        if (i === retries! - 1) {
          return response;
        }
      } catch (error) {
        if (i === retries! - 1) {
          throw error;
        }
      }

      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }

  /**
   * Get server metrics
   */
  async getMetrics(): Promise<{
    uptime: number;
    responseTime: number;
    requestCount: number;
  }> {
    const start = Date.now();
    const response = await this.client.get('/api/health');
    const responseTime = Date.now() - start;

    return {
      uptime: response.data.uptime || 0,
      responseTime,
      requestCount: 1
    };
  }

  /**
   * Measure response time
   */
  async measureResponseTime(path: string, iterations = 10): Promise<{
    min: number;
    max: number;
    avg: number;
    p95: number;
  }> {
    const times: number[] = [];

    for (let i = 0; i < iterations; i++) {
      const start = Date.now();
      await this.client.get(path);
      times.push(Date.now() - start);
    }

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

    return {
      min: times[0],
      max: times[times.length - 1],
      avg: times.reduce((a, b) => a + b, 0) / times.length,
      p95: times[Math.floor(times.length * 0.95)]
    };
  }

  /**
   * Test server under load
   */
  async loadTest(config: {
    path: string;
    concurrent: number;
    duration: number;
  }): Promise<{
    totalRequests: number;
    successful: number;
    failed: number;
    avgResponseTime: number;
  }> {
    const { path, concurrent, duration } = config;
    const startTime = Date.now();
    const results: { success: boolean; time: number }[] = [];

    const workers = Array(concurrent).fill(null).map(async () => {
      while (Date.now() - startTime < duration) {
        const reqStart = Date.now();
        try {
          const response = await this.client.get(path);
          results.push({
            success: response.status === 200,
            time: Date.now() - reqStart
          });
        } catch {
          results.push({
            success: false,
            time: Date.now() - reqStart
          });
        }
        await new Promise(resolve => setTimeout(resolve, 100));
      }
    });

    await Promise.all(workers);

    const successful = results.filter(r => r.success).length;
    const failed = results.length - successful;
    const avgResponseTime = results.reduce((sum, r) => sum + r.time, 0) / results.length;

    return {
      totalRequests: results.length,
      successful,
      failed,
      avgResponseTime
    };
  }
}

/**
 * Create a test server instance
 */
export function createTestServer(config?: Partial<TestServerConfig>): TestServer {
  const defaultConfig: TestServerConfig = {
    baseURL: process.env.BASE_URL || 'http://localhost',
    port: process.env.MASTER_HUB_PORT || '9800',
    ...config
  };

  return new TestServer(defaultConfig);
}