const assert = require('assert'); const fs = require('fs'); const path = require('path'); const projectRoot = path.resolve(__dirname, '..', '..'); const serverPath = path.join(projectRoot, 'backend', 'server.js'); const source = fs.readFileSync(serverPath, 'utf8'); const results = { passed: 0, failed: 0 }; function test(name, fn) { try { fn(); results.passed += 1; console.log(` [PASS] ${name}`); } catch (error) { results.failed += 1; console.error(` [FAIL] ${name}: ${error.message}`); } } console.log('\n========== 架构约束测试 ==========\n'); test('Express async 错误补丁在所有路由之前加载', () => { const patchIndex = source.indexOf("require('express-async-errors')"); const routeIndex = source.search(/\bapp\.(get|post|put|delete|patch)\s*\(/); assert.ok(patchIndex >= 0); assert.ok(routeIndex > patchIndex); }); test('全局错误中间件保持为最后一个 app.use', () => { const errorHandlerIndex = source.indexOf('app.use(expressErrorHandler);'); assert.ok(errorHandlerIndex >= 0); assert.strictEqual(source.lastIndexOf('app.use('), errorHandlerIndex); }); test('抽取的纯函数不在 server.js 保留第二份定义', () => { const extractedFunctions = [ 'normalizeVersion', 'normalizeSha256', 'normalizeVirtualPath', 'buildHttpDownloadUrl', 'parseDownloadTrafficLine', 'isShareIpAllowed', 'resolveClientType', 'parseBooleanLike' ]; for (const functionName of extractedFunctions) { assert.strictEqual(new RegExp(`function\\s+${functionName}\\s*\\(`).test(source), false, functionName); } }); test('旧占位路由已清除且真实系统路由已挂载', () => { assert.strictEqual(fs.existsSync(path.join(projectRoot, 'backend', 'routes', 'health.js')), false); assert.strictEqual(fs.existsSync(path.join(projectRoot, 'backend', 'routes', 'index.js')), false); assert.ok(source.includes("require('./routes/system.routes')")); assert.ok(source.includes("app.use('/api', createSystemRouter")); }); test('package.json 与 package-lock.json 顶层依赖同步', () => { const packageJson = require('../package.json'); const packageLock = require('../package-lock.json'); assert.deepStrictEqual(packageLock.packages[''].dependencies, packageJson.dependencies); }); test('前端 package.json 与 lock 文件依赖同步且没有手工缓存版本号', () => { const frontendPackage = require('../../frontend/package.json'); const frontendLock = require('../../frontend/package-lock.json'); assert.deepStrictEqual(frontendLock.packages[''].dependencies, frontendPackage.dependencies); assert.deepStrictEqual(frontendLock.packages[''].devDependencies, frontendPackage.devDependencies); const frontendDirectory = path.join(projectRoot, 'frontend'); for (const fileName of fs.readdirSync(frontendDirectory).filter((name) => name.endsWith('.html'))) { const html = fs.readFileSync(path.join(frontendDirectory, fileName), 'utf8'); assert.strictEqual(/\?v=\d+/.test(html), false, fileName); } }); test('生产部署统一使用 Vite dist 且不会删除 lock 文件', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); const nginxConfig = fs.readFileSync(path.join(projectRoot, 'nginx', 'nginx.conf'), 'utf8'); const nginxExample = fs.readFileSync(path.join(projectRoot, 'nginx', 'nginx.conf.example'), 'utf8'); assert.ok(installer.includes('npm ci --include=dev')); assert.ok(installer.includes('npm run build')); assert.ok(installer.includes('frontend/dist')); assert.strictEqual(/rm\s+-rf\s+node_modules\s+package-lock\.json/.test(installer), false); assert.ok(nginxConfig.includes('root /usr/share/nginx/html;')); assert.ok(nginxConfig.includes('alias /runtime/downloads/;')); for (const config of [installer, nginxConfig, nginxExample]) { assert.ok(config.includes('proxy_hide_header X-Request-ID;')); assert.ok(config.includes('proxy_hide_header Content-Security-Policy;')); } }); test('桌面安装包由 Gitea Release 发布且不会再次提交到 Git', () => { const gitignore = fs.readFileSync(path.join(projectRoot, '.gitignore'), 'utf8'); const release = JSON.parse(fs.readFileSync( path.join(projectRoot, 'frontend', 'desktop-release.json'), 'utf8' )); const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); const compose = fs.readFileSync(path.join(projectRoot, 'docker-compose.yml'), 'utf8'); assert.ok(gitignore.split(/\r?\n/).includes('/frontend/downloads/*.exe')); assert.strictEqual(gitignore.includes('!/frontend/downloads/'), false); assert.match(release.sha256, /^[a-f0-9]{64}$/); assert.ok(Number.isSafeInteger(release.size) && release.size > 0); assert.strictEqual(release.tag, `v${release.version}`); assert.strictEqual(release.publicPath, `/downloads/${release.asset}`); assert.ok(installer.includes('sync_desktop_release')); assert.ok(compose.includes('desktop-release:')); }); test('Docker 构建上下文排除依赖、环境配置和运行时数据', () => { const dockerIgnore = fs.readFileSync(path.join(projectRoot, 'backend', '.dockerignore'), 'utf8'); for (const requiredEntry of ['node_modules', '.env', 'data/*', 'storage/*']) { assert.ok(dockerIgnore.split(/\r?\n/).includes(requiredEntry), requiredEntry); } const dockerfile = fs.readFileSync(path.join(projectRoot, 'backend', 'Dockerfile'), 'utf8'); assert.ok(dockerfile.includes('AS dependencies')); assert.ok(dockerfile.includes('AS runtime')); assert.ok(dockerfile.includes('npm_config_nodedir=/usr/local')); }); test('install.sh 生成的 Nginx server 块花括号平衡', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); const templates = [...installer.matchAll(/cat > .*?<< EOF\r?\n([\s\S]*?)\r?\nEOF/g)] .map((match) => match[1]) .filter((template) => template.includes('server {')); assert.strictEqual(templates.length, 3); for (const template of templates) { const openingBraces = (template.match(/\{/g) || []).length; const closingBraces = (template.match(/\}/g) || []).length; assert.strictEqual(openingBraces, closingBraces); } }); test('PM2 部署没有启用 cluster 或多实例参数', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); assert.strictEqual(/pm2\s+start[^\n]*(?:\s-i\s|--instances)/.test(installer), false); }); test('install.sh 可在最小化系统完成预检且不会吞掉 NodeSource 下载错误', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); assert.ok(installer.includes("/dev/tcp/git.workyai.cn/443")); assert.strictEqual(installer.includes('ping -c 1 git.workyai.cn'), false); assert.strictEqual(/^\s*clear\s*$/m.test(installer), false); assert.ok(installer.includes('clear_screen')); assert.ok(installer.includes('main "$@"')); assert.ok(installer.includes('/etc/apt/sources.list.d/ubuntu.sources')); for (const dependency of ['ca-certificates', 'iproute2', 'dnsutils', 'procps']) { assert.ok(installer.includes(dependency), dependency); } assert.ok(installer.includes('download_and_run_setup_script')); assert.strictEqual(/curl[^\n]+\|\s*(?:ba)?sh/.test(installer), false); assert.ok(installer.includes('verify_nodejs_installation')); }); test('install.sh 生成可用且受保护的生产环境配置', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); assert.ok(installer.includes('PUBLIC_BASE_URL=${PUBLIC_BASE_URL_VALUE}')); assert.ok(installer.includes('ALLOWED_HOSTS=${ALLOWED_HOSTS_VALUE}')); assert.ok(installer.includes('ALLOWED_ORIGINS=${ALLOWED_ORIGINS_VALUE}')); assert.ok(installer.includes('chmod 600 "${PROJECT_DIR}/backend/.env"')); assert.ok(installer.includes('dotenv_quote')); assert.strictEqual(installer.includes('ALLOWED_ORIGINS_VALUE=""'), false); assert.strictEqual(installer.includes('type_count++'), false); assert.ok(installer.includes('/api/health')); }); test('install.sh 只展示已实现的 SSL 方案且 Certbot 分支可达', () => { const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8'); assert.ok(installer.includes('deploy_certbot || ssl_fallback "1"')); assert.ok(installer.includes('download_and_run_setup_script "https://get.acme.sh"')); assert.strictEqual(installer.includes('证书申请功能开发中'), false); assert.strictEqual(installer.includes('阿里云AccessKey ID'), false); assert.strictEqual(installer.includes('腾讯云SecretId'), false); }); console.log('\n========================================'); console.log('测试总结'); console.log('========================================'); console.log(`通过: ${results.passed}`); console.log(`失败: ${results.failed}`); process.exit(results.failed > 0 ? 1 : 0);