const fs = require('fs'); const path = require('path'); const vm = require('vm'); let passed = 0; let failed = 0; function assert(condition, testName, detail = '') { if (condition) { console.log(` [PASS] ${testName}`); passed++; } else { console.error(` [FAIL] ${testName} - ${detail}`); failed++; } } console.log("========================================================"); console.log(" 身份等级 (shopro/commission/level) 页面与权限 TDD 测试"); console.log("========================================================\n"); // 1. 验证 Level.php 控制器存在与安全性 const levelPhpPath = path.resolve(__dirname, '../application/admin/controller/shopro/commission/Level.php'); assert(fs.existsSync(levelPhpPath), 'Level.php 控制器文件存在'); const levelPhpContent = fs.readFileSync(levelPhpPath, 'utf-8'); // 1.1 验证无临时 $noNeedLogin 遗留(后台安全保障) assert(!levelPhpContent.includes('protected $noNeedLogin'), '无临时 $noNeedLogin 遗留(必须验证登录态)'); // 1.2 验证 $noNeedRight 包含 index 和 select(允许登录管理员正常进入列表) const hasNoNeedRightIndex = /protected\s+\$noNeedRight\s*=\s*\[.*'index'.*\];/s.test(levelPhpContent); const hasNoNeedRightSelect = /protected\s+\$noNeedRight\s*=\s*\[.*'select'.*\];/s.test(levelPhpContent); assert(hasNoNeedRightIndex && hasNoNeedRightSelect, '$noNeedRight 必须包含 index 和 select', '缺少 index 或 select'); // 1.3 验证移除所有临时调试探针代码 assert(!levelPhpContent.includes('test_admin'), '已彻底清除 test_admin 模拟登录调试代码'); assert(!levelPhpContent.includes('read_err_log'), '已彻底清除 read_err_log 日志探测代码'); assert(!levelPhpContent.includes('INIT_ERROR'), '已彻底清除临时 INIT_ERROR 诊断代码'); assert(!levelPhpContent.includes('INDEX_ERROR'), '已彻底清除临时 INDEX_ERROR 诊断代码'); // 1.4 验证核心方法完整性 const requiredMethods = ['_initialize', 'index', 'add', 'edit', 'detail', 'delete', 'select']; requiredMethods.forEach(method => { assert(levelPhpContent.includes(`public function ${method}`), `包含动作方法: ${method}`); }); // 2. 验证前端 JS (level.js) 语法与逻辑完整性 const levelJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/level.js'); assert(fs.existsSync(levelJsPath), 'level.js 前端脚本存在'); const levelJsContent = fs.readFileSync(levelJsPath, 'utf-8'); let jsSyntaxOk = false; let jsSyntaxError = ''; try { new vm.Script(levelJsContent, { filename: 'level.js' }); jsSyntaxOk = true; } catch (e) { jsSyntaxError = e.message; } assert(jsSyntaxOk, 'level.js 语法无 SyntaxError', jsSyntaxError); // 2.1 模拟执行并检查 Controller 结构 const dummyDefine = function (deps, factory) { const $ = () => ({}); const Backend = { api: {} }; const Table = {}; const Form = {}; return factory($, undefined, Backend, Table, Form); }; let moduleLoaded = false; let controllerObj = null; try { const sandbox = { define: dummyDefine, console: console, location: { search: '?ref=addtabs' }, window: {}, Fast: { api: { ajax: () => {}, open: () => {}, fixurl: (u) => u } }, Vue: { ref: (v) => ({ value: v }), reactive: (o) => o, onMounted: (cb) => cb(), }, }; vm.createContext(sandbox); controllerObj = vm.runInContext(levelJsContent, sandbox); moduleLoaded = !!(controllerObj && controllerObj.index); } catch (e) { console.error("level.js 模拟加载异常:", e.message); } assert(moduleLoaded, 'level.js 包含 Controller.index 动作'); // 3. 验证视图模板存在且无语法残缺 const levelHtmlPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/level/index.html'); assert(fs.existsSync(levelHtmlPath), 'index.html 视图模板文件存在'); const levelHtmlContent = fs.readFileSync(levelHtmlPath, 'utf-8'); assert(levelHtmlContent.includes('身份等级'), '模板包含身份等级标题'); assert(levelHtmlContent.includes('commission_rules?.commission_1'), '模板具备佣金比例安全可选链展示'); assert(levelHtmlContent.includes('state.makerBonus'), '模板具备创客进阶基数展示'); // 4. 验证 deploy.sh 对 runtime 目录的权限保障 const deployShPath = path.resolve(__dirname, '../scripts/deploy.sh'); assert(fs.existsSync(deployShPath), 'deploy.sh 部署脚本存在'); const deployShContent = fs.readFileSync(deployShPath, 'utf-8'); assert(deployShContent.includes('chown -R ${WEB_USER}:${WEB_USER} "${PROJECT_DIR}/runtime"'), '部署脚本包含 runtime 目录所有权赋予 www 用户'); assert(deployShContent.includes('chmod -R 777 "${PROJECT_DIR}/runtime"') || deployShContent.includes('chmod -R 775 "${PROJECT_DIR}/runtime"'), '部署脚本包含 runtime 目录写权限'); // 5. 验证生产异常模板 think_exception.tpl 干净无调试代码泄露 const exceptionTplPath = path.resolve(__dirname, '../application/common/view/tpl/think_exception.tpl'); assert(fs.existsSync(exceptionTplPath), 'think_exception.tpl 存在'); const exceptionTplContent = fs.readFileSync(exceptionTplPath, 'utf-8'); assert(!exceptionTplContent.includes('$exception instanceof \\Throwable'), 'think_exception.tpl 已恢复干净,无临时注入调试代码'); console.log(`\n测试统计: 通过: ${passed}, 失败: ${failed}`); if (failed > 0) { console.error(" 自动化测试未全部通过,需要继续修复!"); process.exit(1); } else { console.log(" 全部自动化测试通过!"); process.exit(0); }