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类型包装返回结果'); 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 };