import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
const errorRate = new Rate('error_rate');
const responseTime = new Trend('response_time');
const requestCount = new Counter('request_count');
export const options = {
scenarios: {
health_check: {
executor: 'constant-vus',
vus: 5,
duration: '1m',
exec: 'healthCheck',
tags: { test_type: 'health' },
},
api_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 20 }, { duration: '5m', target: 20 }, { duration: '2m', target: 50 }, { duration: '5m', target: 50 }, { duration: '2m', target: 0 }, ],
exec: 'apiLoad',
tags: { test_type: 'api_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: {
executor: 'ramping-vus',
startVUs: 10,
stages: [
{ duration: '30s', target: 10 },
{ duration: '30s', target: 500 }, { duration: '1m', target: 500 },
{ duration: '30s', target: 10 },
],
exec: 'spikeTest',
tags: { test_type: 'spike' },
},
soak_test: {
executor: 'constant-vus',
vus: 20,
duration: '10m',
exec: 'soakTest',
tags: { test_type: 'soak' },
},
},
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
error_rate: ['rate<0.02'],
response_time: ['p(95)<1000'],
'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'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://opencrates:8080';
const API_URL = `${BASE_URL}/api`;
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',
],
};
const authToken = __ENV.AUTH_TOKEN || '';
const headers = {
'Content-Type': 'application/json',
...(authToken && { 'Authorization': `Bearer ${authToken}` }),
};
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);
}
export function apiLoad() {
const group = Math.floor(Math.random() * 10);
switch (group) {
case 0:
case 1:
case 2:
testGetEndpoints();
break;
case 3:
case 4:
testSearchApi();
break;
case 5:
case 6:
testGenerateApi();
break;
case 7:
case 8:
testAnalyzeApi();
break;
case 9:
testConfigApi();
break;
}
sleep(Math.random() * 2 + 1); }
export function stressTest() {
const actions = [
testGetEndpoints,
testSearchApi,
testGenerateApi,
testAnalyzeApi,
];
const action = actions[Math.floor(Math.random() * actions.length)];
action();
sleep(Math.random() * 0.5 + 0.1); }
export function spikeTest() {
if (Math.random() < 0.6) {
testGenerateApi();
} else {
testAnalyzeApi();
}
sleep(Math.random() * 0.3 + 0.1); }
export function soakTest() {
const session = Math.floor(Math.random() * 5);
switch (session) {
case 0:
testGetEndpoints();
sleep(2);
testSearchApi();
sleep(1);
testGetEndpoints();
break;
case 1:
testConfigApi();
sleep(1);
testGenerateApi();
sleep(3);
testAnalyzeApi();
break;
default:
apiLoad();
}
sleep(Math.random() * 3 + 2); }
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);
}
function trackMetrics(response) {
requestCount.add(1);
responseTime.add(response.timings.duration);
errorRate.add(response.status >= 400);
}
export function setup() {
console.log('Starting OpenCrates load test...');
console.log(`Base URL: ${BASE_URL}`);
console.log(`Test environment: ${__ENV.TEST_ENV || 'development'}`);
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() };
}
export function teardown(data) {
console.log(`Load test completed at: ${new Date().toISOString()}`);
console.log(`Test started at: ${data.timestamp}`);
}
export default function() {
apiLoad();
}