import { existsSync, mkdtempSync, readdirSync, realpathSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { basename, dirname, join, resolve, sep } from 'node:path' import { spawnSync } from 'node:child_process' import { fileURLToPath } from 'node:url' const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') const testPrefix = 'yyd-notification-center-test-' const testRoot = mkdtempSync(join(tmpdir(), testPrefix)) const databasePath = join(testRoot, 'notifications.db') const databaseUrl = `file:${databasePath}` const childEnv = { ...process.env, DATABASE_URL: databaseUrl } // Prisma 5.22 的 Windows schema-engine 在部分主机未初始化 Rust 日志时会无细节提前退出。 // 只在 Windows 测试子进程中启用;Linux/Docker 不受影响,也不会记录数据库内容或密钥。 if (process.platform === 'win32' && !childEnv.RUST_LOG) childEnv.RUST_LOG = 'trace' const runNode = (args) => { const result = spawnSync(process.execPath, args, { cwd: repoRoot, env: childEnv, stdio: 'inherit', }) if (result.error) throw result.error return result.status ?? 1 } const removeTestRoot = () => { const resolvedTemp = realpathSync(tmpdir()) const resolvedTestRoot = realpathSync(testRoot) const isTempChild = resolvedTestRoot.startsWith(`${resolvedTemp}${sep}`) if (!isTempChild || !basename(resolvedTestRoot).startsWith(testPrefix)) { throw new Error(`拒绝清理非测试临时目录: ${resolvedTestRoot}`) } rmSync(resolvedTestRoot, { recursive: true, force: true }) if (existsSync(resolvedTestRoot)) throw new Error('测试临时目录清理失败') } let exitCode = 0 try { const prismaCli = resolve(repoRoot, 'node_modules', 'prisma', 'build', 'index.js') const schema = resolve(repoRoot, 'prisma', 'schema.prisma') exitCode = runNode([prismaCli, 'migrate', 'deploy', '--schema', schema]) if (exitCode === 0) { const tsxCli = resolve(repoRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs') const testsDirectory = resolve(repoRoot, 'src', '__tests__') const testFiles = readdirSync(testsDirectory) .filter((name) => name.endsWith('.test.ts')) .sort() .map((name) => resolve(testsDirectory, name)) if (testFiles.length === 0) throw new Error('未找到测试文件') exitCode = runNode([tsxCli, '--test', '--test-concurrency=1', ...testFiles]) } } catch (error) { console.error('[test-runner]', error instanceof Error ? error.message : String(error)) exitCode = 1 } finally { try { removeTestRoot() } catch (error) { console.error('[test-runner-cleanup]', error instanceof Error ? error.message : String(error)) exitCode = 1 } } process.exitCode = exitCode