Files
hzdyjs/test/api-format-validation-test.js
T
2025-06-30 20:33:58 +08:00

331 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const axios = require('axios');
const colors = require('colors');
// API格式验证测试脚本
// 用于验证所有接口是否符合统一响应格式规范
// 获取Token
async function getToken() {
try {
const { getToken } = require('./quick-token-getter');
return await getToken();
} catch (error) {
console.log('❌ 获取Token失败'.red);
return null;
}
}
// 验证响应格式
function validateResponseFormat(response, apiName, expectedType) {
const issues = [];
// 检查基础字段
if (typeof response.code !== 'number') {
issues.push('❌ code字段缺失或类型错误(应为number)');
}
if (typeof response.msg !== 'string') {
issues.push('❌ msg字段缺失或类型错误(应为string)');
}
if (!response.hasOwnProperty('data')) {
issues.push('❌ data字段缺失');
}
// 检查成功状态码
if (response.code === 200) {
// 根据期望类型验证字段
switch (expectedType) {
case 'paginated':
// 分页接口直接在顶层包含total和rows字段
if (typeof response.total !== 'number') {
issues.push('❌ 分页接口缺少total字段(应为number)');
}
if (!Array.isArray(response.rows)) {
issues.push('❌ 分页接口缺少rows字段(应为array)');
}
break;
case 'array':
if (!Array.isArray(response.data)) {
issues.push('❌ 列表接口data应为数组');
}
break;
case 'object':
if (
response.data === null ||
typeof response.data !== 'object' ||
Array.isArray(response.data)
) {
issues.push('❌ 单条数据接口data应为对象(非null)');
}
break;
case 'null':
if (response.data !== null) {
issues.push('❌ 操作类接口data应为null');
}
break;
}
}
return issues;
}
// API测试用例
const apiTestCases = [
{
name: '报修工单列表(分页)',
url: '/pm/repairOrder/list',
method: 'GET',
data: { pageNum: 1, pageSize: 10 },
expectedType: 'paginated',
permission: 'pm:repairOrder:list',
},
{
name: '维修分类下拉选项',
url: '/pm/repairCategory/options',
method: 'GET',
data: null,
expectedType: 'array',
permission: 'pm:category:options',
},
{
name: '报修统计数据',
url: '/pm/repairOrder/statistics',
method: 'GET',
data: null,
expectedType: 'object',
permission: 'pm:repairOrder:statistics',
},
{
name: '系统用户信息',
url: '/system/user/getInfo',
method: 'GET',
data: null,
expectedType: 'object',
permission: 'system:user:query',
},
{
name: '系统字典数据',
url: '/system/dict/data/list',
method: 'GET',
data: { pageNum: 1, pageSize: 10 },
expectedType: 'paginated',
permission: 'system:dict:list',
},
];
// 执行API格式验证测试
async function runApiFormatValidation() {
console.log('🔍 API格式验证测试'.rainbow.bold);
console.log('='.repeat(80).rainbow);
console.log('📋 验证规范:统一响应格式 {code, msg, data}');
console.log('🎯 测试目标:确保所有接口符合新的格式规范');
console.log('');
// 获取Token
console.log('🔐 获取认证Token...'.magenta);
const token = await getToken();
if (!token) {
console.log('❌ 无法获取Token,测试终止'.red);
return;
}
console.log('✅ Token获取成功'.green);
console.log('');
const results = [];
let totalTests = 0;
let passedTests = 0;
// 逐个测试API
for (const testCase of apiTestCases) {
totalTests++;
console.log(`📝 测试 ${totalTests}: ${testCase.name}`.cyan.bold);
console.log(` 📍 接口: ${testCase.method} ${testCase.url}`);
console.log(` 🔑 权限: ${testCase.permission}`);
console.log(` 📊 期望格式: ${testCase.expectedType}`);
try {
// 构建请求配置
const config = {
method: testCase.method.toLowerCase(),
url: `http://localhost/dev-api${testCase.url}`,
headers: {
'Content-Type': 'application/json;charset=utf-8',
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
referer: 'http://localhost/login?redirect=%252Findex',
Authorization: `Bearer ${token}`,
},
timeout: 10000,
};
if (testCase.data) {
if (testCase.method === 'GET') {
config.params = testCase.data;
} else {
config.data = testCase.data;
}
}
// 发送请求
const response = await axios(config);
// 验证响应格式
const issues = validateResponseFormat(
response.data,
testCase.name,
testCase.expectedType,
);
if (issues.length === 0) {
console.log(' ✅ 格式验证通过'.green);
passedTests++;
// 显示响应数据统计
if (response.data.code === 200) {
if (testCase.expectedType === 'paginated') {
console.log(
` 📊 数据统计: total=${response.data.total}, rows=${
response.data.rows?.length || 0
}`,
);
} else if (
testCase.expectedType === 'array' &&
Array.isArray(response.data.data)
) {
console.log(` 📊 数据统计: 数组长度=${response.data.data.length}`);
} else if (testCase.expectedType === 'object' && response.data.data) {
const keys = Object.keys(response.data.data);
console.log(` 📊 数据统计: 对象字段=${keys.length}个`);
}
}
} else {
console.log(' ❌ 格式验证失败'.red);
issues.forEach((issue) => {
console.log(` ${issue}`.red);
});
}
results.push({
name: testCase.name,
url: testCase.url,
method: testCase.method,
success: issues.length === 0,
issues: issues,
response: {
code: response.data.code,
msg: response.data.msg,
hasData: response.data.hasOwnProperty('data'),
dataType:
testCase.expectedType === 'paginated'
? 'paginated'
: Array.isArray(response.data.data)
? 'array'
: response.data.data === null
? 'null'
: typeof response.data.data,
total: response.data.total,
rowsCount: response.data.rows?.length,
},
});
} catch (error) {
console.log(' ❌ 请求失败'.red);
console.log(` 错误信息: ${error.response?.data?.msg || error.message}`.red);
results.push({
name: testCase.name,
url: testCase.url,
method: testCase.method,
success: false,
issues: [`请求失败: ${error.response?.data?.msg || error.message}`],
response: null,
});
}
console.log('');
}
// 输出测试总结
console.log('📊 测试结果汇总'.rainbow.bold);
console.log('='.repeat(80).rainbow);
console.log(`🎯 总测试数: ${totalTests}`);
console.log(`✅ 通过测试: ${passedTests}`.green);
console.log(`❌ 失败测试: ${totalTests - passedTests}`.red);
console.log(`📈 通过率: ${((passedTests / totalTests) * 100).toFixed(1)}%`);
console.log('');
// 详细结果表格
console.log('📋 详细测试结果'.blue.bold);
console.log('─'.repeat(100));
results.forEach((result, index) => {
const status = result.success ? '✅ 通过'.green : '❌ 失败'.red;
console.log(
`${(index + 1).toString().padStart(2)}. ${result.name.padEnd(
20,
)} ${result.method.padEnd(6)} ${status}`,
);
if (!result.success && result.issues.length > 0) {
result.issues.forEach((issue) => {
console.log(` ${issue}`.red);
});
}
if (result.response) {
console.log(
` 响应: code=${result.response.code}, msg="${result.response.msg}", data类型=${result.response.dataType}`,
);
}
console.log('');
});
// 规范建议
if (passedTests < totalTests) {
console.log('💡 规范建议'.yellow.bold);
console.log('─'.repeat(50));
console.log('1. 检查请求头是否包含Bearer Token格式的Authorization');
console.log('2. 分页接口必须直接返回TableDataInfo格式(total、rows、code、msg)');
console.log('3. 非分页接口必须使用R<T>类型包装返回结果');
console.log('4. 确保包含clientid、Content-Type等必要请求头');
console.log('5. 验证响应格式是否符合新的统一规范');
console.log('');
}
// 成功提示
if (passedTests === totalTests) {
console.log('🎉 恭喜!所有接口都符合统一响应格式规范!'.green.bold);
}
return {
total: totalTests,
passed: passedTests,
failed: totalTests - passedTests,
successRate: ((passedTests / totalTests) * 100).toFixed(1),
};
}
// 主函数
async function main() {
try {
const result = await runApiFormatValidation();
console.log('\n🏁 API格式验证测试完成'.rainbow.bold);
// 返回测试结果供其他脚本使用
return result;
} catch (error) {
console.log('❌ 测试执行失败:'.red, error.message);
process.exit(1);
}
}
// 如果直接运行此脚本
if (require.main === module) {
main().then((result) => {
if (result.failed > 0) {
process.exit(1); // 有失败用例时退出码为1
}
});
}
module.exports = { runApiFormatValidation };