331 lines
11 KiB
JavaScript
331 lines
11 KiB
JavaScript
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 };
|
||
|