import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { snapshotSite } from "../../src/modules/sources-identity/service/snapshot.ts"; import { createHarness, type Harness, pgAvailable } from "./harness.ts"; // 快照总截止时间:站点请求卡住且不理会取消信号(过人机验证、浏览器代抓这类等待)时, // 快照到点按失败收尾、写完 site_snapshot,任务不会一直停在运行中(2026-10-07 OurBits 卡了 10 小时)。 const up = await pgAvailable(); // biome-ignore lint/suspicious/noExplicitAny: 响应体断言 const json = async (r: Response): Promise => { const body = await r.json(); if (!r.ok) throw new Error(`${r.status} ${JSON.stringify(body)}`); return body.data; }; describe.skipIf(!up)("快照截止时间", () => { let h: Harness; let siteId: string; // 永不返回,也不理会 signal const hang = (() => new Promise(() => {})) as typeof fetch; beforeAll(async () => { h = await createHarness("snapdeadline", { fetch: hang }); const s = await json( await h.req("/sites", { body: { key: "audiences", name: "观众", adapter: "nexusphp", baseUrl: "https://aud.test/", rateLimit: { minIntervalMs: 0, concurrency: 10 }, }, }), ); siteId = s.id; await json( await h.req(`/sites/${siteId}/credentials`, { method: "PUT", body: { kind: "cookie", cookie: "c_secure_uid=1; c_secure_pass=abc" }, }), ); }); afterAll(async () => { await h?.close(); }); it("请求卡住 → 到点失败,快照记录收尾", async () => { const t0 = Date.now(); const r = await snapshotSite(h.rt, { siteId, kind: "periodic" }, { deadlineMs: 300 }); expect(Date.now() - t0).toBeLessThan(5_000); expect(r.status).toBe("failed"); expect(r.error).toContain("没完成"); const snap = await h.db .selectFrom("site_snapshot") .selectAll() .where("id", "=", r.snapshotId!) .executeTakeFirstOrThrow(); expect(snap.status).toBe("failed"); expect(snap.finished_at).not.toBeNull(); }); it("旧的「运行中」记录在下一次快照开始时按中断收尾,刚开始的不动", async () => { const mk = (ago: number) => h.db .insertInto("site_snapshot") .values({ site_id: siteId, kind: "periodic", filters: "{}", definition_version: "t", started_at: new Date(Date.now() - ago), }) .returning("id") .executeTakeFirstOrThrow(); const stale = await mk(3_600_000); const fresh = await mk(100); await snapshotSite(h.rt, { siteId, kind: "periodic" }, { deadlineMs: 300 }); const get = (id: string) => h.db.selectFrom("site_snapshot").selectAll().where("id", "=", id).executeTakeFirstOrThrow(); expect(await get(stale.id)).toMatchObject({ status: "failed" }); expect((await get(stale.id)).error).toMatchObject({ message: "上次快照中断,没有收尾" }); expect((await get(fresh.id)).status).toBe("running"); }); });