- Add load testing with Artillery (smoke, standard, peak, stress) - Add functional tests: voting, lottery, admin (18 tests) - Add fault tolerance tests (14 tests) - Add network tests for WebSocket (7 tests) - Add compatibility tests (15 tests) - Add security tests: anti-fraud, auth, data integrity (10 tests) - Fix optional callback handling in socket handlers - Total: 64 test cases, all passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
132 lines
3.7 KiB
JavaScript
132 lines
3.7 KiB
JavaScript
/**
|
|
* Artillery 压力测试处理器
|
|
* 用于生成测试数据和自定义逻辑
|
|
*
|
|
* 注意:使用 CommonJS 格式以兼容 Artillery
|
|
*/
|
|
|
|
// 节目ID列表
|
|
const programIds = [
|
|
'program_1',
|
|
'program_2',
|
|
'program_3',
|
|
'program_4',
|
|
'program_5',
|
|
'program_6',
|
|
'program_7',
|
|
];
|
|
|
|
// 奖项票种列表 (与 shared/constants 一致)
|
|
const ticketTypes = [
|
|
'best_creativity',
|
|
'best_visual',
|
|
'best_atmosphere',
|
|
'best_performance',
|
|
'best_teamwork',
|
|
'best_innovation',
|
|
'most_popular',
|
|
];
|
|
|
|
// 用户计数器 (确保每个虚拟用户有唯一ID)
|
|
let userCounter = 0;
|
|
|
|
/**
|
|
* 生成唯一用户ID
|
|
*/
|
|
function generateUserId(userContext, events, done) {
|
|
userCounter++;
|
|
const timestamp = Date.now();
|
|
userContext.vars.userId = `stress_user_${timestamp}_${userCounter}`;
|
|
userContext.vars.voteIndex = 0; // 初始化投票索引
|
|
userContext.vars.usedTickets = []; // 已使用的票种
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 随机选择一个节目
|
|
*/
|
|
function selectRandomProgram(userContext, events, done) {
|
|
const randomIndex = Math.floor(Math.random() * programIds.length);
|
|
userContext.vars.selectedProgram = programIds[randomIndex];
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 随机选择一个未使用的票种
|
|
* 符合业务逻辑:每种票只能用一次
|
|
*/
|
|
function selectRandomTicketType(userContext, events, done) {
|
|
const usedTickets = userContext.vars.usedTickets || [];
|
|
const availableTickets = ticketTypes.filter(t => !usedTickets.includes(t));
|
|
|
|
if (availableTickets.length === 0) {
|
|
// 所有票已用完,重置 (用于压测场景)
|
|
userContext.vars.usedTickets = [];
|
|
userContext.vars.selectedTicket = ticketTypes[0];
|
|
} else {
|
|
const randomIndex = Math.floor(Math.random() * availableTickets.length);
|
|
userContext.vars.selectedTicket = availableTickets[randomIndex];
|
|
userContext.vars.usedTickets.push(availableTickets[randomIndex]);
|
|
}
|
|
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 顺序选择票种 (确保不重复)
|
|
*/
|
|
function selectNextTicketType(userContext, events, done) {
|
|
const voteIndex = userContext.vars.voteIndex || 0;
|
|
userContext.vars.selectedTicket = ticketTypes[voteIndex % ticketTypes.length];
|
|
userContext.vars.voteIndex = voteIndex + 1;
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 记录投票结果 (用于调试)
|
|
*/
|
|
function logVoteResult(userContext, events, done) {
|
|
const { userId, selectedProgram, selectedTicket } = userContext.vars;
|
|
console.log(`[VOTE] User: ${userId} -> Program: ${selectedProgram} (${selectedTicket})`);
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 生成随机延迟 (模拟真实用户行为)
|
|
*/
|
|
function randomDelay(userContext, events, done) {
|
|
const minDelay = 100; // 最小 100ms
|
|
const maxDelay = 500; // 最大 500ms
|
|
const delay = Math.floor(Math.random() * (maxDelay - minDelay)) + minDelay;
|
|
userContext.vars.thinkTime = delay / 1000; // 转换为秒
|
|
return done();
|
|
}
|
|
|
|
/**
|
|
* 在连接前打印信息 (调试用)
|
|
*/
|
|
function beforeConnect(requestParams, context, ee, next) {
|
|
console.log(`[CONNECT] Connecting user: ${context.vars.userId}`);
|
|
return next();
|
|
}
|
|
|
|
/**
|
|
* 处理响应 (调试用)
|
|
*/
|
|
function afterResponse(requestParams, response, context, ee, next) {
|
|
if (response.body && response.body.error) {
|
|
console.log(`[ERROR] ${context.vars.userId}: ${response.body.error}`);
|
|
}
|
|
return next();
|
|
}
|
|
|
|
// CommonJS exports
|
|
exports.generateUserId = generateUserId;
|
|
exports.selectRandomProgram = selectRandomProgram;
|
|
exports.selectRandomTicketType = selectRandomTicketType;
|
|
exports.selectNextTicketType = selectNextTicketType;
|
|
exports.logVoteResult = logVoteResult;
|
|
exports.randomDelay = randomDelay;
|
|
exports.beforeConnect = beforeConnect;
|
|
exports.afterResponse = afterResponse;
|