- server 的 src/modules/*/__tests__ 挪到 test/<模块>/;e2e 去掉 eN- 前缀, w5a2 / v2 / search2 / *-dh1 / *-md1 / *-md2、web 的 agui3 / search2、domain 的 id2 / id3 改为按行为命名 - vitest 分三组:unit、pg(真实 PG + 假引擎,文件并行,最多 6 个)、serial(真实引擎 / worker / e2e,串行); pg 组用例超时放宽到 60s(迁移类用例并行时排队等 schema 锁)。server 测试 615s → 约 350s - CLAUDE.md 补测试约定;待办补 harness 去重与 continuous-series 偶发超时(疑似 Slot 评估竞争) - ADR-004 补充:维持 Deno,记录实际只用到 --allow-run 白名单 Co-Authored-By: Claude Opus 5.5 <[email protected]>
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
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<ReturnType<typeof seedMovieTarget>>;
|
|
|
|
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);
|
|
});
|