// 测试用 PG 隔离工具。 // // 🔴 背景:本仓**已删除 SQLite 后端**(用户指令:「删除sqlite 都用 pg数据库」)⇒ // 需要 DB 的用例**只有** PG 一条通路,连接串从 `DSHS_TEST_DB_URL` 取。 // ⛔ 缺连接串时**显式跳过并打印原因**(`announceSkip`)—— ⛔ 不许静默通过。 // // ⚠️ 用法约定:`DSHS_TEST_DB_URL` 必须指向**专用测试库**(本模块的 `resetPg` 会 // TRUNCATE 该库 `public` 下的全部表)。⛔ 不要指向生产 / 有业务数据的库。 import pg from 'pg' /** 测试连接串(未设 = `undefined`)。 */ export const PG_URL = process.env.DSHS_TEST_DB_URL /** 缺连接串时的**统一跳过原因**(写进 `{ skip }` 并打印出来)。 */ export const PG_SKIP_REASON = 'DSHS_TEST_DB_URL 未设置 ⇒ 本仓已无 SQLite 后端(PG 是唯一通路),本套用例显式跳过' /** 连接串是否可用。 */ export function pgAvailable() { return PG_URL !== undefined && PG_URL !== '' } /** * 打印跳过原因 —— 每个测试文件在缺连接串时**必须**调一次。 * @param {string} file - 测试文件名(照搬,便于在输出里定位)。 */ export function announceSkip(file) { console.log(`# SKIP ${file} — ${PG_SKIP_REASON}`) } /** * 跑一次迁移(幂等),保证后面 `resetPg` 要 TRUNCATE 的表都已存在。 * ⚠️ 必须在任何 `resetPg()` **之前**调(空库上第一次 TRUNCATE 会报表不存在)。 * @param {string} url - 连接串(缺省取 `DSHS_TEST_DB_URL`)。 */ export async function ensureMigrated(url = PG_URL) { const { openPgAdapter } = await import('../lib/db/pg.js') const db = await openPgAdapter(url, 100000) await db.close() } /** * 清空**专用测试库** `public` 下除 `schema_migrations` 之外的全部表。 * * 为什么需要:SQLite 时代每条用例一个 `:memory:` 库(天然隔离);换到 PG 后 * 同一文件里的用例**共用一个库** ⇒ 不清理就会互相污染(例如每个用例都建 * `alice` ⇒ 第二个用例直接撞唯一约束)。 * @param {string} url - 连接串(缺省取 `DSHS_TEST_DB_URL`)。 */ export async function resetPg(url = PG_URL) { const client = new pg.Client({ connectionString: url }) await client.connect() try { const { rows } = await client.query( "SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND tablename <> 'schema_migrations'", ) if (rows.length > 0) { const list = rows.map((r) => `"${r.tablename}"`).join(', ') await client.query(`TRUNCATE ${list} RESTART IDENTITY CASCADE`) } } finally { await client.end() } } /** 报告尾巴:给"跳过"一个可判的读数(调一次,让退出输出里也有证据)。 */ export function reportSkipSummary(file) { console.log(`# ${file}: 0 条真实用例执行(仅一条显式 skip)—— 原因见上`) }