import { createMockFetch, TNODE_HOME_HTML, TNODE_HOME_NO_TOKEN, TNODE_SEARCH_OK } from "@umt/testkit"; import { describe, expect, it } from "vitest"; import { createSiteRateLimiter } from "../rate-limit.ts"; import { createSpecializedSiteAdapter } from "./adapter.ts"; import { adapterWith, expectSiteError, testDef, withTorrent } from "./testing.ts"; import { parseTnodeToken, tnodeSearchParams, tnodeSpider } from "./tnode.ts"; const def = testDef("TNodeSpider", { domain: "https://tnode.example/" }); describe("TNodeSpider(MP tnode.py)", () => { it("凭据变更后不复用旧CSRF令牌", async () => { let cookie = "session=first"; const mock = createMockFetch({ "GET /": { body: TNODE_HOME_HTML }, "POST /api/torrent/advancedSearch": { body: TNODE_SEARCH_OK }, }); const adapter = createSpecializedSiteAdapter(def, tnodeSpider, { fetch: mock.fetch, secrets: { async resolve() { return cookie; }, }, limiter: createSiteRateLimiter({ minIntervalMs: 0 }), }); await adapter.list(1); await adapter.list(1); cookie = "session=second"; await adapter.list(1); expect(mock.requests.filter((request) => request.url.pathname === "/")).toHaveLength(2); }); it("__get_params / token 解析", () => { expect(tnodeSearchParams("tt0923811", 1)).toMatchObject({ page: 2, size: 100, type: "imdbid", keyword: "tt0923811", }); expect(tnodeSearchParams(null)).toMatchObject({ type: "title", keyword: "", category: [501, 502, 503, 504] }); expect(parseTnodeToken(TNODE_HOME_HTML)).toBe("CSRF-TOKEN-XYZ"); }); it("列表解析:CSRF token 缓存、系数直取、时间戳转 UTC", async () => { const m = createMockFetch({ "GET /": { body: TNODE_HOME_HTML }, "POST /api/torrent/advancedSearch": { body: TNODE_SEARCH_OK }, }); const a = adapterWith(def, tnodeSpider, m.fetch); const page = await a.search("eva"); await a.list(1); expect(m.requests.filter((r) => r.url.pathname === "/")).toHaveLength(1); expect(m.requests[1]!.headers["x-csrf-token"]).toBe("CSRF-TOKEN-XYZ"); const [x, y] = page.items; expect(x).toMatchObject({ siteReleaseId: "3301", promotion: { type: "free", downloadFactor: 0, uploadFactor: 1 }, uploadedAt: "2024-09-20T00:00:00.000Z", imdbId: "tt0923811", detailUrl: "https://tnode.example/torrent/info/3301", hr: "unknown", }); expect(y!.promotion.type).toBe("2x50%"); const t = withTorrent(m.fetch, (u) => u.pathname.startsWith("/api/torrent/download/")); await adapterWith(def, tnodeSpider, t.fetch).downloadTarget("3301"); expect(t.hits[0]!.headers.get("cookie")).toBe("c=COOKIESECRET"); }); it("取不到 token → auth_expired;缺 Cookie → auth_missing;搜索 HTTP 403 → auth_invalid", async () => { const m = createMockFetch({ "GET /": { body: TNODE_HOME_NO_TOKEN } }); await expectSiteError(adapterWith(def, tnodeSpider, m.fetch).list(1), "auth_expired"); await expectSiteError(adapterWith(def, tnodeSpider, m.fetch, {}).list(1), "auth_missing"); const f = createMockFetch({ "GET /": { body: TNODE_HOME_HTML }, "/api/torrent/advancedSearch": { status: 403 } }); await expectSiteError(adapterWith(def, tnodeSpider, f.fetch).list(1), "auth_invalid"); }); });