/** 测试辅助(非公开 API):构造适配器、在 mock fetch 前挂二进制 .torrent 路由。 */ import { buildFakeTorrent } from "@umt/testkit"; import { createSiteRateLimiter } from "../rate-limit.ts"; import { type SiteAdapter, type SiteCredentialKind, SiteError, staticSecretResolver } from "../types.ts"; import { createSpecializedSiteAdapter } from "./adapter.ts"; import type { SpecializedSiteDefinition, SpecializedSpider } from "./types.ts"; export function testDef( parser: SpecializedSiteDefinition["parser"], over: Partial = {}, ): SpecializedSiteDefinition { return { id: "demo", name: "Demo", domain: "https://pt.example.com/", parser, ...over, umt: { version: "demo@0.0.0-test", kind: "api", displayName: "Demo", timezone: "+08:00", rateLimit: { minIntervalMs: 0, burst: 10 }, capabilities: { list: true, search: true, detail: false, download: true, rss: false }, ...(over.umt ?? {}), }, }; } export function adapterWith( def: SpecializedSiteDefinition, spider: SpecializedSpider, fetchImpl: typeof globalThis.fetch, secrets: Partial> = { cookie: "c=COOKIESECRET", apiKey: "APIKEYSECRET" }, ): SiteAdapter { return createSpecializedSiteAdapter(def, spider, { fetch: fetchImpl, secrets: staticSecretResolver({ [def.id]: secrets }), limiter: createSiteRateLimiter({ minIntervalMs: 0 }), }); } /** 包一层 fetch:命中 predicate 的请求返回合法 .torrent 字节 */ export function withTorrent( inner: typeof globalThis.fetch, predicate: (url: URL) => boolean, name = "demo", ): { fetch: typeof globalThis.fetch; infoHash: string; hits: Array<{ url: URL; headers: Headers }> } { const t = buildFakeTorrent({ name }); const hits: Array<{ url: URL; headers: Headers }> = []; const f = (async (input: string | URL | Request, init?: RequestInit) => { const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url); if (predicate(url)) { hits.push({ url, headers: new Headers(init?.headers) }); return new Response(t.bytes as Uint8Array, { status: 200, headers: { "content-type": "application/x-bittorrent" }, }); } return inner(input, init); }) as typeof globalThis.fetch; return { fetch: f, infoHash: t.infoHash, hits }; } export async function expectSiteError(p: Promise, kind: string): Promise { try { await p; } catch (e) { if (!(e instanceof SiteError)) throw e; if (e.kind !== kind) throw new Error(`expected SiteError ${kind}, got ${e.kind}: ${e.message}`); return e; } throw new Error(`expected SiteError ${kind}`); }