import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { parseEnv } from 'node:util'; import { spawnSync } from 'node:child_process'; import pg from 'pg'; // 只使用本机开发库凭据创建独立短期库;绝不在 doodle_dev 中清空或写入测试用户。 const serverRoot = fileURLToPath(new URL('../', import.meta.url)); const local = parseEnv( readFileSync(new URL('../.env', import.meta.url), 'utf8'), ); const url = new URL(local.DATABASE_URL); if ( !['127.0.0.1', 'localhost'].includes(url.hostname) || url.pathname !== '/doodle_dev' ) throw new Error('数据库集成测试只允许本机 doodle_dev 实例创建隔离库'); const database = `doodle_auth_test_${randomUUID().replaceAll('-', '')}`; const admin = new pg.Pool({ connectionString: url.toString(), max: 1, connectionTimeoutMillis: 3000, }); const testUrl = new URL(url); testUrl.pathname = `/${database}`; const env = { ...process.env, DATABASE_URL: testUrl.toString(), TEST_DATABASE_URL: testUrl.toString(), }; let created = false; function run(args) { const result = spawnSync('pnpm', args, { cwd: serverRoot, env, encoding: 'utf8', timeout: 120_000, }); for (const output of [result.stdout, result.stderr]) if (output) process.stdout.write( output .replaceAll(testUrl.toString(), '[test-database-url]') .replaceAll(decodeURIComponent(url.password), '[redacted]'), ); if (result.status !== 0) throw new Error('数据库测试子任务失败'); } try { await admin.query( `CREATE DATABASE "${database}" WITH TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C'`, ); created = true; run(['exec', 'prisma', 'migrate', 'deploy']); run(['exec', 'tsc', '-p', 'tsconfig.test.json']); run(['exec', 'node', '--test', 'dist-test/test/auth-database.test.js']); run(['exec', 'node', '--test', 'dist-test/test/category-database.test.js']); run([ 'exec', 'node', '--test', 'dist-test/test/worksheet-import-database.test.js', ]); run([ 'exec', 'node', '--test', 'dist-test/test/worksheet-database.test.js', ]); run([ 'exec', 'node', '--test', 'dist-test/test/content-publication-database.test.js', ]); } finally { if (created) { await admin.query(`DROP DATABASE "${database}"`); console.log('临时数据库已删除,开发库业务数据未改动。'); } await admin.end(); }