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:
rootandClaude Opus 5.5 committed 2026-10-02 17:40:43 +08:00
1 parent 7cc3742d31
commit c9e8120fc6
25 files changed
+254 -328

No files matched your search

+10 -69
View File
@@ -1,36 +1,24 @@
import { randomUUID } from "node:crypto";
import { createTmdbClient } from "@umt/adapters";
import { DEFAULT_SETTINGS, type GlobalSettings, type SettingsKey } from "@umt/contracts";
import { type DB, type Kysely, migrateToLatest, sql } from "@umt/db";
import type { TaskEngine } from "@umt/tasks";
import { Kysely as K, PostgresDialect } from "kysely";
import pg from "pg";
import type { DB, Kysely } from "@umt/db";
import { createOutboxPort } from "../../src/context.ts";
import { createApp } from "../../src/http/app.ts";
import { EventBus } from "../../src/http/sse.ts";
import { createSourcesIdentityModule } from "../../src/modules/sources-identity/index.ts";
import type { RuntimeOverrides } from "../../src/modules/sources-identity/runtime.ts";
import type { SecretStore, ServerContext, SettingsReader } from "../../src/modules/types.ts";
import { withSchemaLock } from "../auth-settings/harness.ts";
import { DEV_KEY, memoryLogger, memoryStore } from "../helpers.ts";
import { fakeEngine as baseFakeEngine } from "../support/engine.ts";
import { createTestSchema, uniqueSchema } from "../support/pg.ts";
export const READ_KEY = "w2b-read-key";
export const EXEC_KEY = "w2b-exec-key";
// W2-B 集成测试夹具:独立 PG schema + 真实迁移 + fake 引擎/凭证 + 模块运行时。
export const PG_URL = process.env.DATABASE_URL ?? "postgres://umt:umt@localhost:55433/umt";
export async function pgAvailable(): Promise<boolean> {
const c = new pg.Client({ connectionString: PG_URL, connectionTimeoutMillis: 2000 });
try {
await c.connect();
await c.end();
return true;
} catch {
return false;
}
}
export type { Enqueued } from "../support/engine.ts";
export { PG_URL, pgAvailable } from "../support/pg.ts";
/** 内存凭证库(W2-A 的 SecretStore 实现前);记录明文以便断言"明文不入业务表" */
export function fakeSecrets(db: () => Kysely<DB>): SecretStore & { plain: Map<string, string> } {
@@ -75,41 +63,8 @@ export function plainSettings(db: () => Kysely<DB>): SettingsReader {
return { all, get: async <K extends SettingsKey>(k: K) => (await all())[k] } as SettingsReader;
}
export interface Enqueued {
task: string;
input: unknown;
key: string;
}
export function fakeEngine(): TaskEngine & { enqueued: Enqueued[] } {
const enqueued: Enqueued[] = [];
const keys = new Set<string>();
return {
enqueued,
async enqueue(task, input, opts) {
const dedup = keys.has(opts.idempotencyKey);
if (!dedup) {
keys.add(opts.idempotencyKey);
enqueued.push({ task, input, key: opts.idempotencyKey });
}
return { runId: randomUUID(), deduplicated: dedup };
},
async signal() {
return { delivered: 0 };
},
async cancel() {
return false;
},
async get() {
return null;
},
async list() {
return [];
},
async startWorker() {},
async stop() {},
};
}
/** 按幂等键去重的假引擎(贴近真实引擎) */
export const fakeEngine = () => baseFakeEngine({ dedupe: true });
export interface Harness {
db: Kysely<DB>;
@@ -130,20 +85,8 @@ export interface HarnessOptions extends RuntimeOverrides {
}
export async function createHarness(name: string, opts: HarnessOptions = {}): Promise<Harness> {
const schema = `w2b_${name}_${process.pid.toString(36)}${Math.random().toString(36).slice(2, 7)}`;
const admin = new K<unknown>({
dialect: new PostgresDialect({ pool: new pg.Pool({ connectionString: PG_URL, max: 1 }) }),
});
await sql.raw(`create schema ${schema}`).execute(admin);
// 与 @umt/db createDb 相同的 int8 解析(createDb 导入即注册全局解析器)
await import("@umt/db");
const db = new K<DB>({
dialect: new PostgresDialect({
pool: new pg.Pool({ connectionString: PG_URL, max: 8, options: `-c search_path=${schema},public` }),
}),
}) as unknown as Kysely<DB>;
// 与其他测试进程共用 PG:迁移内省会看到别处正在删除的 schema,用同一把咨询锁排队并重试
await withSchemaLock(() => migrateToLatest(db, { migrationTableSchema: schema }));
const ts = await createTestSchema(uniqueSchema(`w2b_${name}`), { max: 8 });
const db = ts.db as unknown as Kysely<DB>;
const { logger } = memoryLogger();
const engine = fakeEngine();
@@ -207,9 +150,7 @@ export async function createHarness(name: string, opts: HarnessOptions = {}): Pr
return (await q.execute()) as never;
},
async close() {
await db.destroy();
await withSchemaLock(() => sql.raw(`drop schema if exists ${schema} cascade`).execute(admin));
await admin.destroy();
await ts.drop();
},
};
}