93 lines
3.0 KiB
JavaScript
93 lines
3.0 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import { createConnection } from 'node:net';
|
|
import path from 'node:path';
|
|
import process from 'node:process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const serverRoot = path.resolve(import.meta.dirname, '..');
|
|
const launcher = path.join(serverRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
|
|
|
|
/**
|
|
* E2E 只检查开发者已维护的本机 Redis,不安装、启动或停止任何外部服务。
|
|
* 失败信息固定指向项目要求的公开本地端点,避免泄漏可注入的测试地址。
|
|
*/
|
|
export function checkLocalRedis({ host = '127.0.0.1', port = 6379, timeoutMs = 500 } = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = createConnection({ host, port });
|
|
let settled = false;
|
|
|
|
const finish = (error) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
socket.destroy();
|
|
if (error) reject(error);
|
|
else resolve();
|
|
};
|
|
|
|
const unavailable = () =>
|
|
finish(new Error('本机 Redis 127.0.0.1:6379 不可达,请确认外部 Redis 服务启动后重试'));
|
|
|
|
socket.setTimeout(timeoutMs, unavailable);
|
|
socket.once('connect', () => finish());
|
|
socket.once('error', unavailable);
|
|
});
|
|
}
|
|
|
|
export async function runTestApi() {
|
|
await checkLocalRedis();
|
|
|
|
// 浏览器集成测试只启用 API 与 Redis,其他依赖使用公开禁用态夹具;这里不会读取
|
|
// 开发者的 *.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',
|
|
// API 配置要求始终声明本机 TDX 边界;E2E 不依赖 TDX 可达性,页面会验证不可用状态。
|
|
TDX_BASE_URL: 'http://127.0.0.1:17710',
|
|
TDX_TIMEOUT_MS: '5000',
|
|
REDIS_HOST: '127.0.0.1',
|
|
REDIS_PORT: '6379',
|
|
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 (child.exitCode === null) child.kill(signal);
|
|
};
|
|
process.once('SIGINT', () => forwardSignal('SIGINT'));
|
|
process.once('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
child.once('exit', (code, signal) => {
|
|
if (signal) process.kill(process.pid, signal);
|
|
else process.exit(code ?? 1);
|
|
});
|
|
}
|
|
|
|
if (
|
|
process.argv[1] &&
|
|
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))
|
|
) {
|
|
await runTestApi();
|
|
}
|