feat(GOV-006): 建立环境配置与双部署基线
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const repoRoot = new URL('../../', import.meta.url);
|
||||
const processNames = ['api', 'scheduler', 'worker', 'web'];
|
||||
const sensitiveKeyPattern = /(PASSWORD|API_KEY|TOKEN|SECRET|RABBITMQ_URL)$/;
|
||||
|
||||
async function readRepoFile(relativePath) {
|
||||
return readFile(new URL(relativePath, repoRoot), 'utf8');
|
||||
}
|
||||
|
||||
function parseEnvTemplate(content) {
|
||||
const entries = new Map();
|
||||
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const normalizedLine = line.trim();
|
||||
if (!normalizedLine || normalizedLine.startsWith('#')) continue;
|
||||
|
||||
const separator = normalizedLine.indexOf('=');
|
||||
assert.ok(separator > 0, `无效环境变量行:${line}`);
|
||||
entries.set(normalizedLine.slice(0, separator), normalizedLine.slice(separator + 1));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
test('开发和生产环境都提供四类进程模板且键集合一致', async () => {
|
||||
for (const processName of processNames) {
|
||||
const development = parseEnvTemplate(
|
||||
await readRepoFile(`deploy/env/development/${processName}.env.example`),
|
||||
);
|
||||
const production = parseEnvTemplate(
|
||||
await readRepoFile(`deploy/env/production/${processName}.env.example`),
|
||||
);
|
||||
|
||||
assert.deepEqual([...development.keys()].sort(), [...production.keys()].sort());
|
||||
}
|
||||
});
|
||||
|
||||
test('模板不包含敏感值且 Web 只暴露 VITE 变量', async () => {
|
||||
for (const environment of ['development', 'production']) {
|
||||
for (const processName of processNames) {
|
||||
const entries = parseEnvTemplate(
|
||||
await readRepoFile(`deploy/env/${environment}/${processName}.env.example`),
|
||||
);
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
if (sensitiveKeyPattern.test(key)) assert.equal(value, '', `${key} 必须保持空值`);
|
||||
if (processName === 'web') assert.ok(key.startsWith('VITE_'), `Web 禁止暴露 ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('生产模板禁止模拟数据并保持 MySQL 5.7 配置契约', async () => {
|
||||
const worker = parseEnvTemplate(await readRepoFile('deploy/env/production/worker.env.example'));
|
||||
|
||||
assert.equal(worker.get('APP_ENV'), 'production');
|
||||
assert.equal(worker.get('MARKET_DATA_MODE'), 'live');
|
||||
assert.equal(worker.get('MYSQL_ENABLED'), 'false');
|
||||
assert.equal(worker.get('MYSQL_CHARSET'), 'utf8mb4');
|
||||
assert.equal(worker.get('MYSQL_COLLATION'), 'utf8mb4_general_ci');
|
||||
assert.equal(worker.get('RABBITMQ_ENABLED'), 'false');
|
||||
});
|
||||
|
||||
test('本地 Compose 仅启动 Redis 并提供健康检查', async () => {
|
||||
const compose = await readRepoFile('deploy/compose/development.yml');
|
||||
|
||||
assert.match(compose, /services:\s*\n\s+redis:/);
|
||||
assert.match(compose, /healthcheck:/);
|
||||
assert.match(compose, /127\.0\.0\.1:/);
|
||||
assert.doesNotMatch(compose, /^\s{2}(mysql|rabbitmq|api|worker|scheduler|web):/m);
|
||||
});
|
||||
|
||||
test('生产源码部署使用独立 EnvironmentFile 并提供 Nginx 代理', async () => {
|
||||
for (const processName of ['api', 'scheduler', 'worker']) {
|
||||
const unit = await readRepoFile(`deploy/systemd/a-share-${processName}.service.example`);
|
||||
assert.match(unit, new RegExp(`EnvironmentFile=/etc/a-share-analysis/${processName}\\.env`));
|
||||
assert.match(unit, /User=a-share/);
|
||||
assert.match(unit, /WorkingDirectory=\/opt\/a-share-analysis\/current/);
|
||||
}
|
||||
|
||||
const nginx = await readRepoFile('deploy/nginx/a-share-analysis.conf.example');
|
||||
assert.match(nginx, /location \/api\//);
|
||||
assert.match(nginx, /proxy_pass http:\/\/127\.0\.0\.1:3000/);
|
||||
assert.match(nginx, /\/opt\/a-share-analysis\/current\/web\/dist/);
|
||||
});
|
||||
|
||||
test('部署文档覆盖本地、Compose 生产和 Ubuntu 源码生产', async () => {
|
||||
const local = await readRepoFile('docs/deployment/local-development.md');
|
||||
const compose = await readRepoFile('docs/deployment/docker-compose-production.md');
|
||||
const source = await readRepoFile('docs/deployment/ubuntu-source-production.md');
|
||||
|
||||
assert.match(local, /Redis/);
|
||||
assert.match(compose, /Docker Compose/);
|
||||
assert.match(source, /Ubuntu 22\.04 LTS/);
|
||||
assert.match(source, /Ubuntu 24\.04 LTS/);
|
||||
assert.match(source, /Node\.js 22 LTS/);
|
||||
assert.match(source, /pnpm 10/);
|
||||
assert.match(source, /不负责安装/);
|
||||
});
|
||||
Reference in New Issue
Block a user