f1c181de02
- vitest globalSetup:每次运行 CREATE sf_test_<ts> 临时库 → drizzle-orm 程序化迁移器 全量迁移(apps/api/src/db/migrate.ts,drizzle-kit 无法指向运行时建库)→ 设 DATABASE_URL_TEST 让 getDb() 自动回退到临时库;teardown DROP(WITH FORCE)。 有 default 导出时 vitest 不捕获具名 teardown,故用双具名导出。 - db.full-migrate.test.ts:21 表齐 + 四张不可变触发器在库 + sf_test_* 护栏断言 (current_database 不符即红,杜绝静默连开发库)。 - helpers.ts 收敛:rand/rejectionMessage/createSiteFixture/withImmutableCleanup/ withTestDb,Task 6–12 七文件内联版替换为 import(断言、对照组、清场顺序不变)。 - scripts/migrate-test.sh:docker compose up --wait + pnpm test 一键验收。 - 实证:75 测试全绿(72+3);开发库 21 表行数总和 0(零写入);无 sf_test_* 残留。 Co-Authored-By: Claude Code <noreply@anthropic.com>
43 lines
2.1 KiB
TypeScript
43 lines
2.1 KiB
TypeScript
// vitest globalSetup(Task 13):每次测试运行用完好的空临时库,结束即删——
|
||
// 彻底消除"测试碰开发库"与"清场残留"两类问题。
|
||
// 机制:用开发库凭据连维护库(postgres)CREATE DATABASE sf_test_<ts> →
|
||
// 用 drizzle-orm 程序化迁移器对临时库跑 apps/api/drizzle 全量迁移 →
|
||
// 设 process.env.DATABASE_URL_TEST(getDb() 的 DATABASE_URL_TEST ?? DATABASE_URL
|
||
// 回退链让所有测试自动连临时库;worker 由主进程在 setup 之后派生,env 继承生效)。
|
||
// 导出形式注意:vitest 的 globalSetup 加载器在有 default 导出时不捕获具名 teardown
|
||
// (loadGlobalSetupFile 的 if/default 分支只取 default 作 setup),故这里必须
|
||
// 用具名 setup + teardown 双导出、不写 default——teardown 才会被注册并在收尾执行。
|
||
// teardown DROP DATABASE 用 WITH (FORCE) 先断开残余业务连接再删;setup 中途失败也自清。
|
||
import { requireDatabaseUrl } from '../../apps/api/src/db/env.js'
|
||
import { createDatabase, dropDatabase, migrateDatabase } from '../../apps/api/src/db/migrate.js'
|
||
|
||
let created: { adminUrl: string; name: string } | undefined
|
||
|
||
export async function setup(): Promise<void> {
|
||
// 只认 DATABASE_URL(开发库凭据)——即便环境里残留 DATABASE_URL_TEST 也不受影响
|
||
const devUrl = requireDatabaseUrl({ allowTestUrl: false })
|
||
const adminUrl = new URL(devUrl)
|
||
adminUrl.pathname = '/postgres' // CREATE/DROP DATABASE 须在维护库会话里执行
|
||
const name = `sf_test_${Date.now()}`
|
||
const testUrl = new URL(devUrl)
|
||
testUrl.pathname = `/${name}`
|
||
|
||
await createDatabase(adminUrl.toString(), name)
|
||
try {
|
||
await migrateDatabase(testUrl.toString())
|
||
} catch (e) {
|
||
// 迁移半途而废:立即清掉本次临时库再抛出,失败运行也不留残留
|
||
await dropDatabase(adminUrl.toString(), name)
|
||
throw e
|
||
}
|
||
process.env.DATABASE_URL_TEST = testUrl.toString()
|
||
created = { adminUrl: adminUrl.toString(), name }
|
||
}
|
||
|
||
export async function teardown(): Promise<void> {
|
||
if (!created) return
|
||
const { adminUrl, name } = created
|
||
created = undefined
|
||
await dropDatabase(adminUrl, name)
|
||
}
|