import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { delegateScript } from "./fixtures/scripts.ts"; import { completeDownload, createE2E, type E2E, pgAvailable, runRow, seedBase, seedMovieTarget, waitFor, } from "./harness.ts"; // E3 关闭聊天不影响后台:只发一次 POST /agent/runs(HTTP 请求里不跑模型,立即返回 queued),之后不再有任何 API 请求 // 也不订阅 SSE;持久任务独立推进:模型会话 → 提交 → (外部下载器完成)→ 核验入库 → run succeeded。 const hasPg = await pgAvailable(); describe.skipIf(!hasPg)("E3 关闭聊天不影响后台", () => { let e: E2E; let target: Awaited>; beforeAll(async () => { e = await createE2E("e3", { fakeScripts: [delegateScript] }); const base = await seedBase(e); target = await seedMovieTarget(e, base.siteId); await e.db .insertInto("automation_grant") .values({ action: "download.submit", scope: JSON.stringify({}), granted_by: "user:e2e" }) .execute(); }, 120_000); afterAll(async () => { await e?.close(); }, 60_000); it("创建后不再请求,任务系统独立推进到 completed", async () => { const t0 = Date.now(); const r = await e.api("POST", "/agent/runs", { goal: "把《沙丘2》1080p 收进来", mode: "execute" }); // HTTP 不驱动模型:立即返回 queued(模型会话在 worker 的持久任务里跑) expect(r.status).toBe(201); expect(r.body.data.status).toBe("queued"); expect(Date.now() - t0).toBeLessThan(5_000); const runId = r.body.data.id as string; const task = await e.db .selectFrom("task") .selectAll() .where("id", "=", r.body.data.taskId) .executeTakeFirstOrThrow(); expect(task).toMatchObject({ kind: "agentRun", subject_type: "agent_run", subject_id: runId }); expect(task.engine_run_id).toBeTruthy(); // "聊天已关闭":从这里开始只有外部世界(下载器)在变化 await completeDownload(e, "Dune.Part.Two.2024.1080p.BluRay.x264-GRP"); const run = await waitFor( async () => { const x = await runRow(e, runId); return x.status === "succeeded" ? x : null; }, 120_000, "run succeeded", ); expect(run.finished_at).not.toBeNull(); expect(run.summary).toContain("已入库 1 个"); // 业务表先落终态,引擎在最后一步返回后才把 run 标 completed:给这个窗口留时间(负载高时会有几百毫秒) const engineRun = await waitFor( async () => { const r = await e.booted.ctx.engine.get(task.engine_run_id!); return r?.status === "completed" ? r : null; }, 15_000, "engine run completed", ); expect(engineRun.status).toBe("completed"); const t = await e.db.selectFrom("task").select("status").where("id", "=", task.id).executeTakeFirstOrThrow(); expect(t.status).toBe("succeeded"); const slot = await e.db .selectFrom("acquisition_slot") .select("status") .where("id", "=", target.slotId) .executeTakeFirstOrThrow(); expect(slot.status).toBe("satisfied"); }, 180_000); });