test: server 测试共用建库与假引擎,sse-pg 归入串行组
- 新增 apps/server/test/support/{pg,engine}.ts:测试库地址、可用性探测、schema 咨询锁、
建 schema + 加锁迁移 + 删除、可选去重的假引擎;6 份 harness 改用它(acquisition-organize 与
file-manager 原先迁移不加锁,并行下有冲突隐患,一并修正)
- sse-pg 起子进程,并行下子进程启动超时,归入 serial
- 修正 b61335e 挪动 / 改名测试后遗留的 import 排序与格式(当时 lint 检查经管道被吞掉退出码)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7cc3742d31
commit
c9e8120fc6
25 files changed
+254
-328
No files matched your search
@@ -1,7 +1,6 @@
|
||||
import { type DB, migrateToLatest } from "@umt/db";
|
||||
import type { DB } from "@umt/db";
|
||||
import { migrateEngineSchema } from "@umt/tasks";
|
||||
import { Kysely, PostgresDialect } from "kysely";
|
||||
import pg from "pg";
|
||||
import type { Kysely } from "kysely";
|
||||
import { vi } from "vitest";
|
||||
import { ConfigSchema } from "../../src/config.ts";
|
||||
import { createOutboxPort } from "../../src/context.ts";
|
||||
@@ -15,69 +14,17 @@ import { DEV_KEY, memoryLogger } from "../helpers.ts";
|
||||
|
||||
// 真实 PG 的 W2-A 测试夹具:独立业务 schema + 独立引擎 schema;任务引擎用假对象(只测 cancel 透传)。
|
||||
|
||||
export const PG_URL = process.env.DATABASE_URL ?? "postgres://umt:umt@localhost:55433/umt";
|
||||
import { createTestSchema, PG_URL, uniqueSchema, withSchemaLock } from "../support/pg.ts";
|
||||
|
||||
export { PG_URL, pgAvailable, withSchemaLock } from "../support/pg.ts";
|
||||
export const MASTER_KEY = "test-master-key-0123456789";
|
||||
|
||||
export async function pgAvailable() {
|
||||
if (process.env.UMT_SKIP_DB_TESTS === "1") return false;
|
||||
const c = new pg.Client({ connectionString: PG_URL, connectionTimeoutMillis: 2000 });
|
||||
try {
|
||||
await c.connect();
|
||||
await c.end();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kysely 迁移器会反射库里所有 schema 的表;别的测试文件同时 drop schema 时反射会报"schema 不存在"。
|
||||
* 同一进程内的测试文件用 PG 咨询锁把"迁移 / 删 schema"串行化,另加重试兜底(其它进程的测试也在用同一个库)。
|
||||
*/
|
||||
const SCHEMA_LOCK = 0x756d7431;
|
||||
export async function withSchemaLock<T>(fn: () => Promise<T>): Promise<T> {
|
||||
const c = new pg.Client({ connectionString: PG_URL });
|
||||
await c.connect();
|
||||
try {
|
||||
await c.query("select pg_advisory_lock($1)", [SCHEMA_LOCK]);
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
if (attempt >= 4 || !/does not exist/.test(String((err as Error)?.message))) throw err;
|
||||
await new Promise((r) => setTimeout(r, 200 * attempt));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await c.query("select pg_advisory_unlock($1)", [SCHEMA_LOCK]).catch(() => {});
|
||||
await c.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function adminQuery(q: string) {
|
||||
const c = new pg.Client({ connectionString: PG_URL });
|
||||
await c.connect();
|
||||
try {
|
||||
await c.query(q);
|
||||
} finally {
|
||||
await c.end();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createHarness(opts: { engine?: boolean; extraModules?: ServerModule[] } = {}) {
|
||||
const uniq = `${process.pid.toString(36)}${Math.random().toString(36).slice(2, 7)}`;
|
||||
const schema = `w2a_test_${uniq}`;
|
||||
const ts = await createTestSchema(uniqueSchema("w2a_test"), { max: 5 });
|
||||
const { schema } = ts;
|
||||
const db: Kysely<DB> = ts.db;
|
||||
const engineSchema = `${schema}_ow`;
|
||||
await adminQuery(`create schema ${schema}`);
|
||||
const db = new Kysely<DB>({
|
||||
dialect: new PostgresDialect({
|
||||
pool: new pg.Pool({ connectionString: PG_URL, max: 5, options: `-c search_path=${schema},public` }),
|
||||
}),
|
||||
});
|
||||
await withSchemaLock(async () => {
|
||||
await migrateToLatest(db, { migrationTableSchema: schema });
|
||||
if (opts.engine) await migrateEngineSchema(PG_URL, engineSchema);
|
||||
});
|
||||
if (opts.engine) await withSchemaLock(() => migrateEngineSchema(PG_URL, engineSchema));
|
||||
const { logger, lines } = memoryLogger();
|
||||
const bus = new EventBus(100);
|
||||
const config = ConfigSchema.parse({
|
||||
@@ -120,11 +67,7 @@ export async function createHarness(opts: { engine?: boolean; extraModules?: Ser
|
||||
schema,
|
||||
engineSchema,
|
||||
async close() {
|
||||
await db.destroy();
|
||||
await withSchemaLock(async () => {
|
||||
await adminQuery(`drop schema if exists ${schema} cascade`);
|
||||
await adminQuery(`drop schema if exists ${engineSchema} cascade`);
|
||||
});
|
||||
await ts.drop(engineSchema);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user