


























@@ -53,6 +53,11 @@ function createFakeTaskRecord(overrides?: Partial<TaskRecord>): TaskRecord {
5353};
5454}
555556+function findWarningPayload(message: string): Record<string, unknown> | undefined {
57+const payload = mockLogWarn.mock.calls.find(([entry]) => entry === message)?.[1];
58+return payload && typeof payload === "object" ? (payload as Record<string, unknown>) : undefined;
59+}
60+5661describe("detached-task-runtime", () => {
5762afterEach(() => {
5863resetDetachedTaskLifecycleRuntimeForTests();
@@ -125,30 +130,36 @@ describe("detached-task-runtime", () => {
125130taskId: runningTask.taskId,
126131});
127132128-expect(fakeRuntime.createQueuedTaskRun).toHaveBeenCalledWith(
129-expect.objectContaining({ runId: "run-queued", task: "Queue task" }),
130-);
131-expect(fakeRuntime.createRunningTaskRun).toHaveBeenCalledWith(
132-expect.objectContaining({ runId: "run-running", task: "Run task" }),
133-);
134-expect(fakeRuntime.startTaskRunByRunId).toHaveBeenCalledWith(
135-expect.objectContaining({ runId: "run-running", startedAt: 10 }),
136-);
137-expect(fakeRuntime.recordTaskRunProgressByRunId).toHaveBeenCalledWith(
138-expect.objectContaining({ runId: "run-running", lastEventAt: 20 }),
139-);
140-expect(fakeRuntime.finalizeTaskRunByRunId).toHaveBeenCalledWith(
141-expect.objectContaining({ runId: "run-running", status: "succeeded", endedAt: 25 }),
142-);
143-expect(fakeRuntime.completeTaskRunByRunId).toHaveBeenCalledWith(
144-expect.objectContaining({ runId: "run-running", endedAt: 30 }),
145-);
146-expect(fakeRuntime.failTaskRunByRunId).toHaveBeenCalledWith(
147-expect.objectContaining({ runId: "run-running", endedAt: 40 }),
148-);
149-expect(fakeRuntime.setDetachedTaskDeliveryStatusByRunId).toHaveBeenCalledWith(
150-expect.objectContaining({ runId: "run-running", deliveryStatus: "delivered" }),
151-);
133+const queuedArgs = vi.mocked(fakeRuntime.createQueuedTaskRun).mock.calls[0]?.[0];
134+expect(queuedArgs?.runId).toBe("run-queued");
135+expect(queuedArgs?.task).toBe("Queue task");
136+const runningArgs = vi.mocked(fakeRuntime.createRunningTaskRun).mock.calls[0]?.[0];
137+expect(runningArgs?.runId).toBe("run-running");
138+expect(runningArgs?.task).toBe("Run task");
139+const startArgs = vi.mocked(fakeRuntime.startTaskRunByRunId).mock.calls[0]?.[0];
140+expect(startArgs?.runId).toBe("run-running");
141+expect(startArgs?.startedAt).toBe(10);
142+const progressArgs = vi.mocked(fakeRuntime.recordTaskRunProgressByRunId).mock.calls[0]?.[0];
143+expect(progressArgs?.runId).toBe("run-running");
144+expect(progressArgs?.lastEventAt).toBe(20);
145+const finalizeMock = fakeRuntime.finalizeTaskRunByRunId;
146+if (!finalizeMock) {
147+throw new Error("Expected fake runtime finalizer");
148+}
149+const finalizeArgs = vi.mocked(finalizeMock).mock.calls[0]?.[0];
150+expect(finalizeArgs?.runId).toBe("run-running");
151+expect(finalizeArgs?.status).toBe("succeeded");
152+expect(finalizeArgs?.endedAt).toBe(25);
153+const completeArgs = vi.mocked(fakeRuntime.completeTaskRunByRunId).mock.calls[0]?.[0];
154+expect(completeArgs?.runId).toBe("run-running");
155+expect(completeArgs?.endedAt).toBe(30);
156+const failArgs = vi.mocked(fakeRuntime.failTaskRunByRunId).mock.calls[0]?.[0];
157+expect(failArgs?.runId).toBe("run-running");
158+expect(failArgs?.endedAt).toBe(40);
159+const deliveryArgs = vi.mocked(fakeRuntime.setDetachedTaskDeliveryStatusByRunId).mock
160+.calls[0]?.[0];
161+expect(deliveryArgs?.runId).toBe("run-running");
162+expect(deliveryArgs?.deliveryStatus).toBe("delivered");
152163expect(fakeRuntime.cancelDetachedTaskRunById).toHaveBeenCalledWith({
153164cfg: {} as never,
154165taskId: runningTask.taskId,
@@ -165,17 +176,18 @@ describe("detached-task-runtime", () => {
165176166177registerDetachedTaskRuntime("tests/detached-runtime", runtime);
167178168-expect(getDetachedTaskLifecycleRuntimeRegistration()).toMatchObject({
169-pluginId: "tests/detached-runtime",
170- runtime,
171-});
179+const registration = getDetachedTaskLifecycleRuntimeRegistration();
180+expect(registration?.pluginId).toBe("tests/detached-runtime");
181+expect(registration?.runtime).toBe(runtime);
172182expect(getDetachedTaskLifecycleRuntime()).toBe(runtime);
173183});
174184175185it("falls back to legacy complete and fail hooks when a runtime has no finalizer", () => {
176186const defaultRuntime = getDetachedTaskLifecycleRuntime();
177-const completeTaskRunByRunIdSpy = vi.fn(() => []);
178-const failTaskRunByRunIdSpy = vi.fn(() => []);
187+const completeTaskRunByRunIdSpy = vi.fn(
188+(_params: Parameters<typeof completeTaskRunByRunId>[0]) => [],
189+);
190+const failTaskRunByRunIdSpy = vi.fn((_params: Parameters<typeof failTaskRunByRunId>[0]) => []);
179191const legacyRuntime = {
180192 ...defaultRuntime,
181193completeTaskRunByRunId: completeTaskRunByRunIdSpy,
@@ -188,12 +200,16 @@ describe("detached-task-runtime", () => {
188200finalizeTaskRunByRunId({ runId: "legacy-ok", status: "succeeded", endedAt: 10 });
189201finalizeTaskRunByRunId({ runId: "legacy-timeout", status: "timed_out", endedAt: 20 });
190202191-expect(completeTaskRunByRunIdSpy).toHaveBeenCalledWith(
192-expect.objectContaining({ runId: "legacy-ok", status: "succeeded", endedAt: 10 }),
193-);
194-expect(failTaskRunByRunIdSpy).toHaveBeenCalledWith(
195-expect.objectContaining({ runId: "legacy-timeout", status: "timed_out", endedAt: 20 }),
196-);
203+const completeArgs = completeTaskRunByRunIdSpy.mock.calls[0]?.[0] as
204+| Parameters<typeof finalizeTaskRunByRunId>[0]
205+| undefined;
206+expect(completeArgs?.runId).toBe("legacy-ok");
207+expect(completeArgs?.status).toBe("succeeded");
208+expect(completeArgs?.endedAt).toBe(10);
209+const failArgs = failTaskRunByRunIdSpy.mock.calls[0]?.[0];
210+expect(failArgs?.runId).toBe("legacy-timeout");
211+expect(failArgs?.status).toBe("timed_out");
212+expect(failArgs?.endedAt).toBe(20);
197213});
198214199215describe("tryRecoverTaskBeforeMarkLost", () => {
@@ -253,17 +269,11 @@ describe("detached-task-runtime", () => {
253269now: 1_000,
254270});
255271expect(result).toEqual({ recovered: false });
256-expect(mockLogWarn).toHaveBeenCalledWith(
272+const warningPayload = findWarningPayload(
257273"Detached task recovery hook threw, proceeding with markTaskLost",
258-expect.objectContaining({
259-taskId: "task-throw",
260-runtime: "acp",
261-}),
262274);
263-const warningPayload = mockLogWarn.mock.calls.find(
264-([message]) =>
265-message === "Detached task recovery hook threw, proceeding with markTaskLost",
266-)?.[1] as { elapsedMs?: unknown } | undefined;
275+expect(warningPayload?.taskId).toBe("task-throw");
276+expect(warningPayload?.runtime).toBe("acp");
267277expect(typeof warningPayload?.elapsedMs).toBe("number");
268278if (typeof warningPayload?.elapsedMs !== "number") {
269279throw new Error("Expected detached task recovery warning elapsedMs");
@@ -284,10 +294,11 @@ describe("detached-task-runtime", () => {
284294now: 2_000,
285295});
286296expect(result).toEqual({ recovered: false });
287-expect(mockLogWarn).toHaveBeenCalledWith(
297+const warningPayload = findWarningPayload(
288298"Detached task recovery hook returned invalid result, proceeding with markTaskLost",
289-expect.objectContaining({ taskId: "task-invalid", runtime: "cron" }),
290299);
300+expect(warningPayload?.taskId).toBe("task-invalid");
301+expect(warningPayload?.runtime).toBe("cron");
291302});
292303293304it("logs when the recovery hook is slow", async () => {
@@ -305,10 +316,10 @@ describe("detached-task-runtime", () => {
305316now: 3_000,
306317});
307318expect(result).toEqual({ recovered: true });
308-expect(mockLogWarn).toHaveBeenCalledWith(
309- "Detached task recovery hook was slow",
310- expect.objectContaining({ taskId: "task-slow", runtime: "subagent", elapsedMs: 6_000 }),
311-);
319+const warningPayload = findWarningPayload("Detached task recovery hook was slow");
320+expect(warningPayload?.taskId).toBe("task-slow");
321+expect(warningPayload?.runtime).toBe("subagent");
322+expect(warningPayload?.elapsedMs).toBe(6_000);
312323dateNowSpy.mockRestore();
313324});
314325});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。