114 lines
4.7 KiB
JavaScript
114 lines
4.7 KiB
JavaScript
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');
|
|
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/;'));
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
console.log('\n========================================');
|
|
console.log('测试总结');
|
|
console.log('========================================');
|
|
console.log(`通过: ${results.passed}`);
|
|
console.log(`失败: ${results.failed}`);
|
|
|
|
process.exit(results.failed > 0 ? 1 : 0);
|