opencrates 3.0.1

Enterprise-grade AI-powered Rust development companion with comprehensive automation, monitoring, and deployment capabilities
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('error_rate');
const responseTime = new Trend('response_time');
const requestCount = new Counter('request_count');

// Test configuration
export const options = {
  scenarios: {
    // Health check scenario - constant load
    health_check: {
      executor: 'constant-vus',
      vus: 5,
      duration: '1m',
      exec: 'healthCheck',
      tags: { test_type: 'health' },
    },
    
    // API load test - ramping load
    api_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 20 },   // Ramp up
        { duration: '5m', target: 20 },   // Steady state
        { duration: '2m', target: 50 },   // Scale up
        { duration: '5m', target: 50 },   // Steady state
        { duration: '2m', target: 0 },    // Ramp down
      ],
      exec: 'apiLoad',
      tags: { test_type: 'api_load' },
    },
    
    // Stress test - high load
    stress_test: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 50 },
        { duration: '3m', target: 100 },
        { duration: '1m', target: 200 },
        { duration: '2m', target: 200 },
        { duration: '1m', target: 0 },
      ],
      exec: 'stressTest',
      tags: { test_type: 'stress' },
    },
    
    // Spike test - sudden load increase
    spike_test: {
      executor: 'ramping-vus',
      startVUs: 10,
      stages: [
        { duration: '30s', target: 10 },
        { duration: '30s', target: 500 },  // Spike
        { duration: '1m', target: 500 },
        { duration: '30s', target: 10 },
      ],
      exec: 'spikeTest',
      tags: { test_type: 'spike' },
    },
    
    // Soak test - prolonged load
    soak_test: {
      executor: 'constant-vus',
      vus: 20,
      duration: '10m',
      exec: 'soakTest',
      tags: { test_type: 'soak' },
    },
  },
  
  // Thresholds for performance criteria
  thresholds: {
    // HTTP request duration should be below 500ms for 95% of requests
    http_req_duration: ['p(95)<500'],
    
    // HTTP request failed rate should be below 1%
    http_req_failed: ['rate<0.01'],
    
    // Custom metrics thresholds
    error_rate: ['rate<0.02'],
    response_time: ['p(95)<1000'],
    
    // Scenario-specific thresholds
    'http_req_duration{test_type:health}': ['p(95)<100'],
    'http_req_duration{test_type:api_load}': ['p(95)<500'],
    'http_req_duration{test_type:stress}': ['p(95)<1000'],
    'http_req_duration{test_type:spike}': ['p(95)<2000'],
    'http_req_duration{test_type:soak}': ['p(95)<500'],
  },
};

// Base URL configuration
const BASE_URL = __ENV.BASE_URL || 'http://opencrates:8080';
const API_URL = `${BASE_URL}/api`;

// Test data
const testData = {
  crateSpecs: [
    {
      name: 'test-crate-1',
      description: 'Load test crate 1',
      version: '0.1.0',
      features: ['default', 'serde'],
    },
    {
      name: 'test-crate-2', 
      description: 'Load test crate 2',
      version: '0.1.0',
      features: ['async', 'tokio'],
    },
  ],
  searchQueries: [
    'web framework',
    'async runtime',
    'serialization',
    'cli tool',
    'database',
  ],
};

// Authentication token (if needed)
const authToken = __ENV.AUTH_TOKEN || '';

// Default headers
const headers = {
  'Content-Type': 'application/json',
  ...(authToken && { 'Authorization': `Bearer ${authToken}` }),
};

// Health check scenario
export function healthCheck() {
  const response = http.get(`${BASE_URL}/health`);
  
  check(response, {
    'Health check status is 200': (r) => r.status === 200,
    'Health check response time < 100ms': (r) => r.timings.duration < 100,
  });
  
  requestCount.add(1);
  responseTime.add(response.timings.duration);
  errorRate.add(response.status !== 200);
  
  sleep(1);
}

// API load test scenario
export function apiLoad() {
  const group = Math.floor(Math.random() * 10);
  
  switch (group) {
    case 0:
    case 1:
    case 2:
      // GET requests (30%)
      testGetEndpoints();
      break;
    case 3:
    case 4:
      // Search requests (20%)
      testSearchApi();
      break;
    case 5:
    case 6:
      // Generate requests (20%)
      testGenerateApi();
      break;
    case 7:
    case 8:
      // Analyze requests (20%)
      testAnalyzeApi();
      break;
    case 9:
      // Config requests (10%)
      testConfigApi();
      break;
  }
  
  sleep(Math.random() * 2 + 1); // 1-3 seconds
}

// Stress test scenario
export function stressTest() {
  // Mixed workload with shorter sleep times
  const actions = [
    testGetEndpoints,
    testSearchApi,
    testGenerateApi,
    testAnalyzeApi,
  ];
  
  const action = actions[Math.floor(Math.random() * actions.length)];
  action();
  
  sleep(Math.random() * 0.5 + 0.1); // 0.1-0.6 seconds
}

// Spike test scenario
export function spikeTest() {
  // Focus on most intensive operations
  if (Math.random() < 0.6) {
    testGenerateApi();
  } else {
    testAnalyzeApi();
  }
  
  sleep(Math.random() * 0.3 + 0.1); // 0.1-0.4 seconds
}

// Soak test scenario
export function soakTest() {
  // Realistic user behavior with longer sessions
  const session = Math.floor(Math.random() * 5);
  
  switch (session) {
    case 0:
      // User browsing session
      testGetEndpoints();
      sleep(2);
      testSearchApi();
      sleep(1);
      testGetEndpoints();
      break;
      
    case 1:
      // Crate generation session
      testConfigApi();
      sleep(1);
      testGenerateApi();
      sleep(3);
      testAnalyzeApi();
      break;
      
    default:
      // General API usage
      apiLoad();
  }
  
  sleep(Math.random() * 3 + 2); // 2-5 seconds
}

// Test functions for different API endpoints
function testGetEndpoints() {
  const endpoints = [
    '/health',
    '/status',
    '/config',
    '/metrics',
    '/api/crates',
    '/api/templates',
  ];
  
  const endpoint = endpoints[Math.floor(Math.random() * endpoints.length)];
  const response = http.get(`${BASE_URL}${endpoint}`, { headers });
  
  check(response, {
    [`GET ${endpoint} status is 200`]: (r) => r.status === 200,
    [`GET ${endpoint} response time < 200ms`]: (r) => r.timings.duration < 200,
  });
  
  trackMetrics(response);
}

function testSearchApi() {
  const query = testData.searchQueries[Math.floor(Math.random() * testData.searchQueries.length)];
  const response = http.get(`${API_URL}/search?q=${encodeURIComponent(query)}&limit=10`, { headers });
  
  check(response, {
    'Search API status is 200': (r) => r.status === 200,
    'Search API response time < 500ms': (r) => r.timings.duration < 500,
    'Search API returns results': (r) => r.json('results') !== undefined,
  });
  
  trackMetrics(response);
}

function testGenerateApi() {
  const spec = testData.crateSpecs[Math.floor(Math.random() * testData.crateSpecs.length)];
  const payload = {
    ...spec,
    name: `${spec.name}-${Date.now()}-${Math.floor(Math.random() * 1000)}`,
  };
  
  const response = http.post(`${API_URL}/generate`, JSON.stringify(payload), { headers });
  
  check(response, {
    'Generate API status is 200 or 202': (r) => r.status === 200 || r.status === 202,
    'Generate API response time < 2000ms': (r) => r.timings.duration < 2000,
    'Generate API returns job ID': (r) => r.json('job_id') !== undefined,
  });
  
  trackMetrics(response);
}

function testAnalyzeApi() {
  const payload = {
    path: '/tmp/test-project',
    options: {
      include_dependencies: true,
      include_vulnerabilities: false,
    },
  };
  
  const response = http.post(`${API_URL}/analyze`, JSON.stringify(payload), { headers });
  
  check(response, {
    'Analyze API status is 200 or 202': (r) => r.status === 200 || r.status === 202,
    'Analyze API response time < 1500ms': (r) => r.timings.duration < 1500,
  });
  
  trackMetrics(response);
}

function testConfigApi() {
  const response = http.get(`${API_URL}/config`, { headers });
  
  check(response, {
    'Config API status is 200': (r) => r.status === 200,
    'Config API response time < 100ms': (r) => r.timings.duration < 100,
    'Config API returns config': (r) => r.json('openai') !== undefined,
  });
  
  trackMetrics(response);
}

// Utility function to track custom metrics
function trackMetrics(response) {
  requestCount.add(1);
  responseTime.add(response.timings.duration);
  errorRate.add(response.status >= 400);
}

// Setup function (runs once before all scenarios)
export function setup() {
  console.log('Starting OpenCrates load test...');
  console.log(`Base URL: ${BASE_URL}`);
  console.log(`Test environment: ${__ENV.TEST_ENV || 'development'}`);
  
  // Verify API is accessible
  const response = http.get(`${BASE_URL}/health`);
  if (response.status !== 200) {
    throw new Error(`API health check failed: ${response.status}`);
  }
  
  return { timestamp: new Date().toISOString() };
}

// Teardown function (runs once after all scenarios)
export function teardown(data) {
  console.log(`Load test completed at: ${new Date().toISOString()}`);
  console.log(`Test started at: ${data.timestamp}`);
}

// Export default function for simple single-scenario tests
export default function() {
  apiLoad();
}