import type { DB } from "@umt/db"; import { migrateEngineSchema } from "@umt/tasks"; import type { Kysely } from "kysely"; import { vi } from "vitest"; import { ConfigSchema } from "../../src/config.ts"; import { createOutboxPort } from "../../src/context.ts"; import { createApp } from "../../src/http/app.ts"; import { EventBus } from "../../src/http/sse.ts"; import { createCapabilityRegistry } from "../../src/modules/agent/capability-registry.ts"; import { createAuthSettingsModule } from "../../src/modules/auth-settings/index.ts"; import { createSecretStore, createSettingsReader } from "../../src/modules/auth-settings/provided.ts"; import type { ServerContext, ServerModule } from "../../src/modules/types.ts"; import { DEV_KEY, memoryLogger } from "../helpers.ts"; // 真实 PG 的 W2-A 测试夹具:独立业务 schema + 独立引擎 schema;任务引擎用假对象(只测 cancel 透传)。 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 createHarness(opts: { engine?: boolean; extraModules?: ServerModule[] } = {}) { const ts = await createTestSchema(uniqueSchema("w2a_test"), { max: 5 }); const { schema } = ts; const db: Kysely = ts.db; const engineSchema = `${schema}_ow`; if (opts.engine) await withSchemaLock(() => migrateEngineSchema(PG_URL, engineSchema)); const { logger, lines } = memoryLogger(); const bus = new EventBus(100); const config = ConfigSchema.parse({ DATABASE_URL: PG_URL, SECRET_MASTER_KEY: MASTER_KEY, NODE_ENV: "development", DEV_ADMIN_KEY: DEV_KEY, }); const cancel = vi.fn(async (_runId: string, _reason?: string) => true); const ctx = { role: "all", db, config, logger, events: bus, engine: { cancel } as unknown as ServerContext["engine"], outbox: createOutboxPort(), secrets: createSecretStore({ db, masterKey: MASTER_KEY }), settings: createSettingsReader({ db, logger, events: bus }), capabilities: createCapabilityRegistry({ logger }), } as ServerContext; const mod = createAuthSettingsModule({ setupTokenOnStart: false, engineSchema }); const app = createApp({ config, logger, bus, db, role: "all", version: "0.0.0-test", modules: [mod, ...(opts.extraModules ?? [])], ctx, }); return { db, app, ctx, bus, lines, cancel, schema, engineSchema, async close() { await ts.drop(engineSchema); }, }; } export type Harness = Awaited>; /** 极简 cookie jar:记住 Set-Cookie,请求时带上;写方法自动带 CSRF 头 */ export class Browser { cookies = new Map(); constructor( private readonly app: Harness["app"], readonly ip = "203.0.113.10", ) {} absorb(res: Response) { for (const sc of res.headers.getSetCookie()) { const [pair] = sc.split(";"); const i = pair!.indexOf("="); const k = pair!.slice(0, i).trim(); const v = pair!.slice(i + 1).trim(); if (!v || /max-age=0/i.test(sc)) this.cookies.delete(k); else this.cookies.set(k, decodeURIComponent(v)); } } async req( method: string, path: string, body?: unknown, opts: { csrf?: boolean; headers?: Record } = {}, ) { const headers: Record = { "x-real-ip": this.ip, ...opts.headers }; if (this.cookies.size) headers.cookie = [...this.cookies].map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("; "); if (body !== undefined) headers["content-type"] = "application/json"; const csrf = this.cookies.get("umt_csrf"); if (method !== "GET" && opts.csrf !== false && csrf) headers["x-csrf-token"] = csrf; const res = await this.app.request(`/api/v1${path}`, { method, headers, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), }); this.absorb(res); return res; } } /** Bearer 调用 */ export function bearer(app: Harness["app"], key: string) { return (method: string, path: string, body?: unknown) => app.request(`/api/v1${path}`, { method, headers: { authorization: `Bearer ${key}`, ...(body !== undefined ? { "content-type": "application/json" } : {}), }, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), }); }