test(docs): 补齐隔离验收与功能实施记录
This commit is contained in:
1 parent
57069a9a75
commit
ead66f0b93
14 files changed
+899
-40
No files matched your search
@@ -0,0 +1,113 @@
|
||||
/** 旧回归也必须隔离:临时库、带前缀的临时教师与班级,绝不回退真实实例。 */
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import net from 'node:net';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'),
|
||||
dir = path.join(root, '.cache', 'v14-verification', `e2e-${process.pid}-${Date.now()}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const env = {
|
||||
...process.env,
|
||||
DATABASE_PROVIDER: 'sqlite',
|
||||
DATABASE_URL: `file:${path.join(dir, 'test.db').replaceAll('\\', '/')}`,
|
||||
TZ: 'Asia/Shanghai',
|
||||
RATE_LIMIT_ENABLED: 'false',
|
||||
LOG_LEVEL: 'warn',
|
||||
TERM_START_DATE: '2026-09-07',
|
||||
};
|
||||
const require = createRequire(path.join(root, 'packages/server/package.json')),
|
||||
{ createClient } = require('@libsql/client');
|
||||
const db = createClient({ url: env.DATABASE_URL });
|
||||
await db.execute('PRAGMA user_version=0');
|
||||
db.close();
|
||||
function run(args, logName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, args, {
|
||||
cwd: root,
|
||||
env,
|
||||
windowsHide: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const log = fs.createWriteStream(path.join(dir, logName));
|
||||
for (const stream of [child.stdout, child.stderr])
|
||||
stream.on('data', (chunk) => {
|
||||
log.write(chunk);
|
||||
process.stdout.write(chunk);
|
||||
});
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code) => {
|
||||
log.end();
|
||||
if (code === 0) resolve();
|
||||
else reject(new Error(`${logName} 退出码 ${code},日志 ${dir}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
const seedFile = path.join(root, 'packages/server/prisma', `.verification-seed-${process.pid}.ts`),
|
||||
testFile = path.join(root, 'packages/server/scripts', `.verification-e2e-${process.pid}.mjs`);
|
||||
const prefix = `temporary${process.pid}_`;
|
||||
function namespace(code) {
|
||||
for (const name of ['admin', 'teacher1', 'teacher2'])
|
||||
code = code.replaceAll(`'${name}'`, `'${prefix}${name}'`);
|
||||
return code;
|
||||
}
|
||||
let server;
|
||||
try {
|
||||
await run(
|
||||
[
|
||||
'packages/server/node_modules/prisma/build/index.js',
|
||||
'migrate',
|
||||
'deploy',
|
||||
'--config',
|
||||
'packages/server/prisma.config.ts',
|
||||
],
|
||||
'migrate.log',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
seedFile,
|
||||
namespace(fs.readFileSync(path.join(root, 'packages/server/prisma/seed.ts'), 'utf8')),
|
||||
{ flag: 'wx' },
|
||||
);
|
||||
await run(['packages/server/node_modules/tsx/dist/cli.mjs', seedFile], 'seed.log');
|
||||
const probe = net.createServer();
|
||||
await new Promise((r) => probe.listen(0, '127.0.0.1', r));
|
||||
env.PORT = String(probe.address().port);
|
||||
await new Promise((r) => probe.close(r));
|
||||
env.VERIFY_BASE_URL = `http://127.0.0.1:${env.PORT}`;
|
||||
server = spawn(
|
||||
process.execPath,
|
||||
['--import', './packages/server/node_modules/tsx/dist/loader.mjs', 'scripts/verification-server.mts'],
|
||||
{ cwd: root, env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] },
|
||||
);
|
||||
const log = fs.createWriteStream(path.join(dir, 'server.log'));
|
||||
server.stdout.pipe(log);
|
||||
server.stderr.pipe(log);
|
||||
server.once('exit', (code, signal) => console.log(`临时服务退出:code=${code} signal=${signal}`));
|
||||
let ready = false;
|
||||
const startupDeadline = Date.now() + 60_000;
|
||||
while (Date.now() < startupDeadline) {
|
||||
if (server.exitCode !== null) throw new Error('临时服务启动失败');
|
||||
try {
|
||||
ready = (await fetch(`${env.VERIFY_BASE_URL}/healthz`, { signal: AbortSignal.timeout(1500) })).ok;
|
||||
} catch {
|
||||
/* 等待进程启动 */
|
||||
}
|
||||
if (ready) break;
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
if (!ready) throw new Error('临时服务未就绪');
|
||||
fs.writeFileSync(
|
||||
testFile,
|
||||
namespace(fs.readFileSync(path.join(root, 'packages/server/scripts/verify-e2e.mjs'), 'utf8')),
|
||||
{ flag: 'wx' },
|
||||
);
|
||||
await run([testFile], 'e2e.log');
|
||||
} finally {
|
||||
if (server && server.exitCode === null) {
|
||||
server.kill();
|
||||
await new Promise((r) => server.once('exit', r));
|
||||
}
|
||||
for (const file of [seedFile, testFile]) if (fs.existsSync(file)) fs.unlinkSync(file);
|
||||
}
|
||||
console.log(`隔离端到端日志:${dir}`);
|
||||
Reference in new issue
Block a user