import { createHash } from "node:crypto"; import { createQbClient } from "@umt/adapters"; import type { FakeScript } from "@umt/agent"; import { makeTorrent } from "@umt/testkit"; import { createAcquisitionOrganizeModule } from "../../../src/modules/acquisition-organize/index.ts"; import { createAgentModule } from "../../../src/modules/agent/index.ts"; import type { AgentModuleOptions } from "../../../src/modules/agent/runner.ts"; import { authSettingsModule } from "../../../src/modules/auth-settings/index.ts"; import { createBrowserModule } from "../../../src/modules/browser/index.ts"; import type { BrowserModuleOptions } from "../../../src/modules/browser/service.ts"; import { createSchedulesModule } from "../../../src/modules/schedules/index.ts"; import { createSourcesIdentityModule } from "../../../src/modules/sources-identity/index.ts"; import type { ServerModule } from "../../../src/modules/types.ts"; import { createWebSearchModule, type WebSearchModuleOptions } from "../../../src/modules/websearch/index.ts"; // e2e 模块装配:真实四个模块,外部依赖指向 testkit mock(qB 账号 admin/adminadmin、固定磁盘用量、按标题生成的种子)。 // harness(API/worker 同进程)与崩溃恢复用的 worker 子进程共用这一份,保证两边行为一致。 export const TORRENT_SIZE = 300_000; /** 按发布标题确定性地生成种子(同标题 → 同 infohash,父子进程一致) */ export function torrentFor(title: string) { const seed = createHash("sha256").update(title).digest().readUInt32BE(0); return makeTorrent({ name: `${title}.mkv`, length: TORRENT_SIZE, pieceSeed: seed }); } export interface E2EModuleOptions { sessionDir: string; fakeScripts?: FakeScript[]; /** 下载同步周期(毫秒) */ syncIntervalMs?: number; /** 网页搜索的 fetch / DNS 注入(E13);缺省不联网也能注册能力 */ webSearch?: WebSearchModuleOptions; /** 浏览器模块注入(E14 视觉自动化:证据目录、动作后等待) */ browser?: BrowserModuleOptions; /** 智能体模块的额外参数(E16:替换 / 捕获系统提示) */ agent?: Pick; } export function buildModules(o: E2EModuleOptions): ServerModule[] { return [ authSettingsModule, // TMDB 不注入:走设置 metadata.tmdbApiKeySecretRef / tmdbBaseUrl(W2 收口 b) createSourcesIdentityModule({ schedulerIntervalMs: 3_600_000 }), createAcquisitionOrganizeModule({ downloader: async (inst) => createQbClient({ instanceId: inst.id, baseUrl: inst.base_url, username: "admin", password: "adminadmin", lookup: { attempts: 5, intervalMs: 50 }, }), torrentSource: async ({ release }) => ({ kind: "file", bytes: torrentFor(release.title).bytes }), diskUsage: async (path) => ({ status: "ok", path, totalBytes: 1e12, freeBytes: 6e11, availableBytes: 6e11, usedBytes: 4e11, usedRatio: 0.4, }), probe: async () => ({ status: "unknown", reason: "ffprobe_unavailable" }), options: { backgroundLoops: true, syncIntervalMs: o.syncIntervalMs ?? 300, minVideoBytes: 100_000, verifyHash: "sha256", reconcileIntervalMs: 0, }, }), createAgentModule({ sessionDir: o.sessionDir, ...(o.fakeScripts ? { fakeScripts: o.fakeScripts } : {}), goalPollSec: 1, ...(o.agent ?? {}), }), // 浏览器能力(browser.*)随首批能力一起注册;浏览器服务由用例自己登记(E14 指向 mock camofox) createBrowserModule(o.browser ?? {}), // 网页搜索(web.search / web.fetch);e2e 注入 mock 搜索服务与网页 createWebSearchModule(o.webSearch ?? {}), // 定时任务调度器(驱动上面模块登记的周期任务;须在末尾) createSchedulesModule({ builtinChecks: false, scheduler: { tickMs: 100, lockRetryMs: 200 } }), ]; }