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 { authSettingsModule } from "../../../src/modules/auth-settings/index.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"; // 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; } 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, }), // 定时任务调度器(驱动上面模块登记的周期任务;须在末尾) createSchedulesModule({ builtinChecks: false, scheduler: { tickMs: 100, lockRetryMs: 200 } }), ]; }