- Remove old review reports (keep latest only) - Move docs/ to deploy/docs-backup/ - Move performance-testing/ to deploy/ - Clean up test output files - Organize root directory
319 lines
8.6 KiB
JavaScript
319 lines
8.6 KiB
JavaScript
// 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
|
|
|
|
================================================================================
|
|
测试完成
|
|
================================================================================
|
|
`;
|
|
}
|