Files

319 lines
8.6 KiB
JavaScript
Raw Permalink Normal View History

// Sub2API API Key Management Performance Test
// API Key 管理接口性能测试
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { config, getBaseUrl, getAuthToken } from '../common/utils.js';
import { httpGet, httpPost, randomSleep, generateTestAPIKeyName } from '../common/utils.js';
// ============= 自定义指标 =============
const apiKeyListDuration = new Trend('apikey_list_duration');
const apiKeyCreateDuration = new Trend('apikey_create_duration');
const apiKeyUpdateDuration = new Trend('apikey_update_duration');
const apiKeyDeleteDuration = new Trend('apikey_delete_duration');
const apiKeyErrorRate = new Rate('apikey_errors');
// ============= 测试配置 =============
export const options = {
scenarios: {
apikey_load: {
executor: 'ramping-vus',
startVUs: 5,
stages: [
{ duration: '1m', target: 20 }, // 预热
{ duration: '3m', target: 50 }, // 正常负载
{ duration: '2m', target: 100 }, // 峰值负载
{ duration: '1m', target: 0 }, // 冷却
],
},
},
thresholds: {
'apikey_list_duration': ['p(95)<500', 'p(99)<1000'],
'apikey_create_duration': ['p(95)<1000', 'p(99)<2000'],
'apikey_errors': ['rate<0.01'],
},
};
// ============= 辅助函数 =============
/**
* 获取认证头
*/
function getHeaders() {
return {
'Authorization': `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json',
};
}
/**
* 创建测试用 API Key
*/
function createTestAPIKey(name) {
const res = http.post(
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({ name: name || generateTestAPIKeyName() }),
{ headers: getHeaders(), tags: { name: 'apikey_create' } }
);
apiKeyCreateDuration.add(res.timings.duration);
return res;
}
/**
* 列出 API Keys
*/
function listAPIKeys(params = {}) {
const url = `${getBaseUrl()}/api/v1/keys${params.page ? `?page=${params.page}` : ''}`;
const res = http.get(url, { headers: getHeaders(), tags: { name: 'apikey_list' } });
apiKeyListDuration.add(res.timings.duration);
return res;
}
/**
* 获取单个 API Key 详情
*/
function getAPIKey(id) {
const res = http.get(`${getBaseUrl()}/api/v1/keys/${id}`, {
headers: getHeaders(),
tags: { name: 'apikey_get' },
});
return res;
}
/**
* 更新 API Key
*/
function updateAPIKey(id, updates) {
const res = http.patch(`${getBaseUrl()}/api/v1/keys/${id}`, JSON.stringify(updates), {
headers: getHeaders(),
tags: { name: 'apikey_update' },
});
apiKeyUpdateDuration.add(res.timings.duration);
return res;
}
/**
* 删除 API Key
*/
function deleteAPIKey(id) {
const res = http.delete(`${getBaseUrl()}/api/v1/keys/${id}`, {
headers: getHeaders(),
tags: { name: 'apikey_delete' },
});
apiKeyDeleteDuration.add(res.timings.duration);
return res;
}
// ============= 测试场景 =============
/**
* 测试 CRUD 完整流程
*/
function testCRUDFlow() {
// 1. 创建
const createRes = createTestAPIKey(`perf-crud-${Date.now()}`);
check(createRes, {
'Create: status is 201': (r) => r.status === 201,
'Create: has key data': (r) => r.json('id') !== undefined,
});
apiKeyErrorRate.add(createRes.status !== 201);
if (createRes.status !== 201) return null;
const keyId = createRes.json('id');
// 2. 读取
const getRes = getAPIKey(keyId);
check(getRes, {
'Get: status is 200': (r) => r.status === 200,
'Get: id matches': (r) => r.json('id') === keyId,
});
// 3. 更新
const updateRes = updateAPIKey(keyId, { name: `updated-${Date.now()}` });
check(updateRes, {
'Update: status is 200': (r) => r.status === 200,
'Update: name changed': (r) => r.json('name').includes('updated'),
});
apiKeyErrorRate.add(updateRes.status !== 200);
// 4. 删除
const deleteRes = deleteAPIKey(keyId);
check(deleteRes, {
'Delete: status is 204': (r) => r.status === 204,
});
apiKeyErrorRate.add(deleteRes.status !== 204);
return keyId;
}
/**
* 测试列表查询
*/
function testListOperations() {
// 测试分页
const page1 = listAPIKeys({ page: 1 });
check(page1, {
'List Page 1: status is 200': (r) => r.status === 200,
'List Page 1: has data': (r) => Array.isArray(r.json('data')),
});
apiKeyErrorRate.add(page1.status !== 200);
// 测试搜索
const searchRes = http.get(`${getBaseUrl()}/api/v1/keys?search=perf`, {
headers: getHeaders(),
tags: { name: 'apikey_search' },
});
check(searchRes, {
'Search: status is 200': (r) => r.status === 200,
});
// 随机睡眠模拟用户浏览
randomSleep(0.1, 0.5);
}
/**
* 测试并发创建
*/
function testConcurrentCreate() {
// 批量创建请求
const batch = [];
const count = 5;
for (let i = 0; i < count; i++) {
batch.push([
'POST',
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({ name: `batch-${Date.now()}-${i}` }),
{
headers: getHeaders(),
tags: { name: 'apikey_batch_create' },
},
]);
}
const responses = http.batch(batch);
let successCount = 0;
for (const res of responses) {
if (res.status === 201) successCount++;
apiKeyErrorRate.add(res.status !== 201);
}
check(responses[0], {
'Batch Create: at least 80% success': () => successCount / count >= 0.8,
});
return successCount;
}
// ============= 主测试函数 =============
export default function () {
const vuId = __VU;
const iteration = __ITER__;
group('API Key Management', () => {
// 每个 VU 的操作模式
const mode = vuId % 4;
switch (mode) {
case 0:
// 读为主:列表 + 单个查询
listAPIKeys();
randomSleep(0.2, 0.5);
// 获取一个 key 进行查询
const listRes = listAPIKeys();
if (listRes.status === 200 && listRes.json('data').length > 0) {
const keyId = listRes.json('data')[0].id;
getAPIKey(keyId);
}
break;
case 1:
// 写为主:创建 + 更新
testCRUDFlow();
break;
case 2:
// 混合操作
listAPIKeys();
randomSleep(0.1, 0.3);
testListOperations();
break;
case 3:
// 批量操作
if (iteration % 10 === 0) {
testConcurrentCreate();
} else {
listAPIKeys();
}
break;
}
});
// 全局思考时间
randomSleep(0.5, 2);
}
// ============= 测试报告 =============
export function handleSummary(data) {
return {
'apikey-performance-report.json': JSON.stringify(data, null, 2),
'apikey-performance-summary.txt': generateSummary(data),
};
}
function generateSummary(data) {
const metrics = data.metrics;
return `
================================================================================
Sub2API API Key Management 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${metrics?.requests_total?.values?.count || 0}
错误率: ${((metrics?.apikey_errors?.values?.rate || 0) * 100).toFixed(2)}%
--------------------------------------------------------------------------------
操作类型性能
--------------------------------------------------------------------------------
列表操作:
平均响应: ${metrics?.apikey_list_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_list_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${metrics?.apikey_list_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
创建操作:
平均响应: ${metrics?.apikey_create_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_create_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${metrics?.apikey_create_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
更新操作:
平均响应: ${metrics?.apikey_update_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_update_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
删除操作:
平均响应: ${metrics?.apikey_delete_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_delete_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`;
}