104 lines
3.0 KiB
JavaScript
104 lines
3.0 KiB
JavaScript
import { execFileSync, spawn } from 'node:child_process';
|
|
import { createConnection } from 'node:net';
|
|
import path from 'node:path';
|
|
import process from 'node:process';
|
|
|
|
const serverRoot = path.resolve(import.meta.dirname, '..');
|
|
const launcher = path.join(serverRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
|
|
const redisContainerName = `a-share-analysis-e2e-redis-${process.pid}`;
|
|
let ownsRedisContainer = false;
|
|
let redisPort = '6379';
|
|
|
|
const localRedisAvailable = await new Promise((resolve) => {
|
|
const socket = createConnection({ host: '127.0.0.1', port: 6379 });
|
|
const finish = (available) => {
|
|
socket.destroy();
|
|
resolve(available);
|
|
};
|
|
socket.setTimeout(500, () => finish(false));
|
|
socket.once('connect', () => finish(true));
|
|
socket.once('error', () => finish(false));
|
|
});
|
|
|
|
if (!localRedisAvailable) {
|
|
execFileSync(
|
|
'docker',
|
|
[
|
|
'run',
|
|
'--rm',
|
|
'--detach',
|
|
'--name',
|
|
redisContainerName,
|
|
'--publish',
|
|
'127.0.0.1::6379',
|
|
'redis:7.4.8-alpine3.21',
|
|
],
|
|
{ stdio: ['ignore', 'pipe', 'inherit'] },
|
|
);
|
|
ownsRedisContainer = true;
|
|
redisPort = execFileSync('docker', ['port', redisContainerName, '6379/tcp'], {
|
|
encoding: 'utf8',
|
|
})
|
|
.trim()
|
|
.split(':')
|
|
.at(-1);
|
|
}
|
|
|
|
// 浏览器集成测试只启用 API 与禁用态 MySQL;Redis 故障会形成可验证的安全 503 响应,
|
|
// 所有值均为公开测试夹具,不读取开发者的 *.env.local 或任何生产配置。
|
|
const child = spawn(process.execPath, [launcher, 'apps/api/src/main.ts'], {
|
|
cwd: serverRoot,
|
|
env: {
|
|
PATH: process.env.PATH,
|
|
APP_ENV: 'production',
|
|
LOG_LEVEL: 'error',
|
|
API_HOST: '127.0.0.1',
|
|
API_PORT: '3000',
|
|
REDIS_HOST: '127.0.0.1',
|
|
REDIS_PORT: redisPort,
|
|
REDIS_USERNAME: '',
|
|
REDIS_PASSWORD: '',
|
|
REDIS_DATABASE: '0',
|
|
REDIS_TLS: 'false',
|
|
MYSQL_ENABLED: 'false',
|
|
MYSQL_HOST: '127.0.0.1',
|
|
MYSQL_PORT: '3306',
|
|
MYSQL_DATABASE: 'a_share_analysis_dev',
|
|
MYSQL_USERNAME: '',
|
|
MYSQL_PASSWORD: '',
|
|
MYSQL_CHARSET: 'utf8mb4',
|
|
MYSQL_COLLATION: 'utf8mb4_general_ci',
|
|
MYSQL_TIMEZONE: '+08:00',
|
|
AI_ENABLED: 'false',
|
|
AI_BASE_URL: '',
|
|
AI_API_KEY: '',
|
|
AI_MODEL: '',
|
|
AI_TIMEOUT_MS: '30000',
|
|
},
|
|
stdio: 'inherit',
|
|
});
|
|
|
|
const forwardSignal = (signal) => {
|
|
if (ownsRedisContainer) {
|
|
try {
|
|
execFileSync('docker', ['stop', redisContainerName], { stdio: 'ignore' });
|
|
} catch {
|
|
// Playwright 可能已因启动失败清理容器,重复清理保持幂等。
|
|
}
|
|
}
|
|
if (child.exitCode === null) child.kill(signal);
|
|
};
|
|
process.once('SIGINT', () => forwardSignal('SIGINT'));
|
|
process.once('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
child.once('exit', (code, signal) => {
|
|
if (ownsRedisContainer) {
|
|
try {
|
|
execFileSync('docker', ['stop', redisContainerName], { stdio: 'ignore' });
|
|
} catch {
|
|
// 容器已由信号处理清理时无需再次报告错误。
|
|
}
|
|
}
|
|
if (signal) process.kill(process.pid, signal);
|
|
else process.exit(code ?? 1);
|
|
});
|