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,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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user