const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); 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(' 商品详情 pages/goods/index 展示缺陷全链路验证规范'); console.log('========================================================\n'); const rootDir = path.resolve(__dirname, '..'); // 1. 本地前端代码检查 console.log('一、本地前端代码检查:'); const indexVuePath = path.join(rootDir, 'uniapp/pages/goods/index.vue'); const indexVueContent = fs.readFileSync(indexVuePath, 'utf8'); assert(indexVueContent.includes('goodsInfo: null,'), 'index.vue 中 goodsInfo 初始值明确声明为 null (完美呼应缺省判断)'); assert(indexVueContent.includes('state.skeletonLoading = false;') && indexVueContent.includes('finally {'), 'index.vue getGoodsDetail 包含 try...finally 确保骨架屏无论成功失败均解除死锁'); assert(indexVueContent.includes('sheep.$helper.toast('), 'index.vue 参数缺失时采用合法 sheep.$helper.toast 代替未定义 toast'); assert(indexVueContent.includes('sheep.$router.back('), 'index.vue 参数缺失时采用合法 sheep.$router.back 代替未定义 $router'); assert(indexVueContent.includes('options?.id || options?.goods_id'), 'index.vue 支持 id 与 goods_id 双参数兼容解析'); const cardVuePath = path.join(rootDir, 'uniapp/pages/goods/components/detail/detail-content-card.vue'); const cardVueContent = fs.readFileSync(cardVuePath, 'utf8'); assert(cardVueContent.includes(':domain="sheep.$url.cdn()"'), 'detail-content-card.vue mp-html 明确绑定 :domain 提供微信小程序 CDN 图片前缀补全'); assert(cardVueContent.includes('暂无商品图文详情'), 'detail-content-card.vue 包含内容为空时的优雅兜底提示'); // 2. 本地后端代码检查 console.log('\n二、本地后端控制器检查:'); const goodsPhpPath = path.join(rootDir, 'addons/shopro/controller/goods/Goods.php'); const goodsPhpContent = fs.readFileSync(goodsPhpPath, 'utf8'); assert(goodsPhpContent.includes('cdnurl(\'\', true)'), 'Goods.php detail 接口包含获取 CDN 完整域名逻辑'); assert(goodsPhpContent.includes('preg_replace') && goodsPhpContent.includes('uploads'), 'Goods.php 自动将富文本内容中 /uploads/ 补齐为完整 CDN 绝对路径'); // 3. 构建产物检查 console.log('\n三、小程序编译构建产物检查:'); const buildCardJsPath = path.join(rootDir, 'uniapp/unpackage/dist/build/mp-weixin/pages/goods/components/detail/detail-content-card.js'); const buildCardJsContent = fs.readFileSync(buildCardJsPath, 'utf8'); assert(buildCardJsContent.includes('domain:t.sheep.$url.cdn()'), 'build mp-weixin detail-content-card.js 已生效 domain 属性'); const buildIndexJsPath = path.join(rootDir, 'uniapp/unpackage/dist/build/mp-weixin/pages/goods/index.js'); const buildIndexJsContent = fs.readFileSync(buildIndexJsPath, 'utf8'); assert(buildIndexJsContent.includes('goodsInfo:null') && buildIndexJsContent.includes('finally{n.skeletonLoading=!1}'), 'build mp-weixin index.js 已生效 goodsInfo:null 与骨架屏 finally 解锁'); // 4. 线上 API 模拟测试 console.log('\n四、线上 API 模拟测试(正常场景、业务失败场景、参数错误场景):'); try { const remoteScript = `#!/bin/bash curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H 'platform: WechatMiniProgram' 'https://henglian.gynhc.com/addons/shopro/goods.goods/detail?id=35' > /tmp/api_test_goods_35.json curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H 'platform: WechatMiniProgram' 'https://henglian.gynhc.com/addons/shopro/goods.goods/detail?id=99999' > /tmp/api_test_goods_notfound.json curl -s -k --resolve henglian.gynhc.com:443:127.0.0.1 -H 'platform: WechatMiniProgram' 'https://henglian.gynhc.com/addons/shopro/goods.goods/detail' > /tmp/api_test_goods_empty.json `; fs.writeFileSync('/tmp/test_remote_goods.sh', remoteScript); execSync("sshpass -p Bjsslxz1 scp -o StrictHostKeyChecking=no /tmp/test_remote_goods.sh root@118.184.172.94:/tmp/test_remote_goods.sh"); execSync("sshpass -p Bjsslxz1 ssh -o StrictHostKeyChecking=no root@118.184.172.94 'bash /tmp/test_remote_goods.sh'"); const fetchCmd = "sshpass -p Bjsslxz1 ssh -o StrictHostKeyChecking=no root@118.184.172.94 \"cat /tmp/api_test_goods_35.json && echo '===SEP===' && cat /tmp/api_test_goods_notfound.json && echo '===SEP===' && cat /tmp/api_test_goods_empty.json\""; const fetched = execSync(fetchCmd, { timeout: 20000 }).toString(); const parts = fetched.split('===SEP===').map(p => p.trim()); const okRes = JSON.parse(parts[0]); const notFoundRes = JSON.parse(parts[1]); const emptyRes = JSON.parse(parts[2]); assert(okRes.code === 1, '正常场景: 商品 35 详情接口请求成功 (code === 1)'); assert(okRes.data && okRes.data.id === 35, '正常场景: 返回有效商品数据 ID 35'); assert(okRes.data.title === '贵州飞天盛世-窖藏', '正常场景: 商品标题正确回显'); assert(okRes.data.content && okRes.data.content.includes('https://henglian.gynhc.com/uploads/'), '正常场景: 富文本图片已自动补全完整 CDN 绝对路径'); assert(notFoundRes.code === 0, '业务失败场景: 不存在的商品返回 code === 0'); assert(notFoundRes.msg === 'No Results were found' || notFoundRes.msg.includes('未找到') || notFoundRes.msg.includes('不存在'), '业务失败场景: 规范返回未找到商品消息'); assert(emptyRes.code === 0, '参数错误场景: 缺少商品 ID 时返回 code === 0'); } catch (err) { console.error('线上测试发生异常:', err.message); } console.log('\n========================================================'); console.log(` 全部测试完成:通过 ${passed} 项,失败 ${failed} 项`); console.log('========================================================\n'); if (failed > 0) { process.exit(1); }