mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 02:50:42 +00:00
test: tighten subagent restart assertions
This commit is contained in:
@@ -78,6 +78,38 @@ function countMatching<T>(items: readonly T[], predicate: (item: T) => boolean)
|
||||
return count;
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`expected ${label}`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function requireSubagentEndedHookCall(runId: string): {
|
||||
event: Record<string, unknown>;
|
||||
ctx: Record<string, unknown>;
|
||||
} {
|
||||
const call = runSubagentEndedHookMock.mock.calls.find((candidate) => {
|
||||
const ctx = candidate[1] as { runId?: string } | undefined;
|
||||
return ctx?.runId === runId;
|
||||
});
|
||||
if (!call) {
|
||||
throw new Error(`expected subagent_ended hook call for ${runId}`);
|
||||
}
|
||||
return {
|
||||
event: requireRecord(call[0], `${runId} subagent_ended event`),
|
||||
ctx: requireRecord(call[1], `${runId} subagent_ended context`),
|
||||
};
|
||||
}
|
||||
|
||||
function requireSessionLifecycleEventCall(label: string): Record<string, unknown> {
|
||||
const call = emitSessionLifecycleEventMock.mock.calls[0];
|
||||
if (!call) {
|
||||
throw new Error(`expected ${label}`);
|
||||
}
|
||||
return requireRecord(call[0], label);
|
||||
}
|
||||
|
||||
const noopContextEngine = {
|
||||
info: { id: "test-context-engine", name: "Test context engine" },
|
||||
ingest: async () => ({ ingested: false }),
|
||||
@@ -296,14 +328,9 @@ describe("subagent registry steer restarts", () => {
|
||||
});
|
||||
expect(matchingCalls).toHaveLength(1);
|
||||
});
|
||||
expect(runSubagentEndedHookMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
runId: "run-new",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
runId: "run-new",
|
||||
}),
|
||||
);
|
||||
const hookCall = requireSubagentEndedHookCall("run-new");
|
||||
expect(hookCall.event.runId).toBe("run-new");
|
||||
expect(hookCall.ctx.runId).toBe("run-new");
|
||||
|
||||
const announce = (announceSpy.mock.calls[0]?.[0] ?? {}) as { childRunId?: string };
|
||||
expect(announce.childRunId).toBe("run-new");
|
||||
@@ -330,17 +357,12 @@ describe("subagent registry steer restarts", () => {
|
||||
await waitForRegistrySideEffect(() => {
|
||||
expect(runSubagentEndedHookMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(runSubagentEndedHookMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
targetSessionKey: "agent:main:subagent:completion-delayed",
|
||||
reason: "subagent-complete",
|
||||
sendFarewell: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
runId: "run-completion-delayed",
|
||||
requesterSessionKey: MAIN_REQUESTER_SESSION_KEY,
|
||||
}),
|
||||
);
|
||||
const hookCall = requireSubagentEndedHookCall("run-completion-delayed");
|
||||
expect(hookCall.event.targetSessionKey).toBe("agent:main:subagent:completion-delayed");
|
||||
expect(hookCall.event.reason).toBe("subagent-complete");
|
||||
expect(hookCall.event.sendFarewell).toBe(true);
|
||||
expect(hookCall.ctx.runId).toBe("run-completion-delayed");
|
||||
expect(hookCall.ctx.requesterSessionKey).toBe(MAIN_REQUESTER_SESSION_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -423,21 +445,13 @@ describe("subagent registry steer restarts", () => {
|
||||
emitLifecycleEnd("run-terminal-state-new");
|
||||
|
||||
await waitForRegistrySideEffect(() => {
|
||||
expect(runSubagentEndedHookMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
runId: "run-terminal-state-new",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
runId: "run-terminal-state-new",
|
||||
}),
|
||||
);
|
||||
const hookCall = requireSubagentEndedHookCall("run-terminal-state-new");
|
||||
expect(hookCall.event.runId).toBe("run-terminal-state-new");
|
||||
expect(hookCall.ctx.runId).toBe("run-terminal-state-new");
|
||||
});
|
||||
expect(emitSessionLifecycleEventMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:main:subagent:terminal-state",
|
||||
reason: "subagent-status",
|
||||
}),
|
||||
);
|
||||
const lifecycleEvent = requireSessionLifecycleEventCall("terminal-state lifecycle event");
|
||||
expect(lifecycleEvent.sessionKey).toBe("agent:main:subagent:terminal-state");
|
||||
expect(lifecycleEvent.reason).toBe("subagent-status");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -532,11 +546,12 @@ describe("subagent registry steer restarts", () => {
|
||||
expect(replaced).toBe(true);
|
||||
|
||||
const run = listMainRuns().find((entry) => entry.runId === "run-wake-new");
|
||||
expect(run).toMatchObject({
|
||||
frozenResultText: undefined,
|
||||
fallbackFrozenResultText: "final summary before wake",
|
||||
fallbackFrozenResultCapturedAt: 1234,
|
||||
});
|
||||
if (!run) {
|
||||
throw new Error("expected wake replacement run");
|
||||
}
|
||||
expect(run.frozenResultText).toBeUndefined();
|
||||
expect(run.fallbackFrozenResultText).toBe("final summary before wake");
|
||||
expect(run.fallbackFrozenResultCapturedAt).toBe(1234);
|
||||
});
|
||||
|
||||
it("restores announce for a finished run when steer replacement dispatch fails", async () => {
|
||||
@@ -579,7 +594,8 @@ describe("subagent registry steer restarts", () => {
|
||||
expect(mod.isSubagentSessionRunActive(childSessionKey)).toBe(false);
|
||||
|
||||
const run = listMainRuns()[0];
|
||||
expect(run?.outcome).toMatchObject({ status: "error", error: "manual kill" });
|
||||
expect(run?.outcome?.status).toBe("error");
|
||||
expect(run?.outcome?.error).toBe("manual kill");
|
||||
expect(run?.outcome?.startedAt).toBeTypeOf("number");
|
||||
expect(run?.outcome?.endedAt).toBeTypeOf("number");
|
||||
expect(run?.outcome?.elapsedMs).toBeTypeOf("number");
|
||||
@@ -588,24 +604,19 @@ describe("subagent registry steer restarts", () => {
|
||||
expect(run?.cleanupHandled).toBe(true);
|
||||
expect(typeof run?.cleanupCompletedAt).toBe("number");
|
||||
await flushAnnounce();
|
||||
expect(runSubagentEndedHookMock).toHaveBeenCalledWith(
|
||||
{
|
||||
targetSessionKey: childSessionKey,
|
||||
targetKind: "subagent",
|
||||
reason: "subagent-killed",
|
||||
sendFarewell: true,
|
||||
accountId: undefined,
|
||||
runId: "run-killed",
|
||||
endedAt: expect.any(Number),
|
||||
outcome: "killed",
|
||||
error: "manual kill",
|
||||
},
|
||||
{
|
||||
runId: "run-killed",
|
||||
childSessionKey,
|
||||
requesterSessionKey: MAIN_REQUESTER_SESSION_KEY,
|
||||
},
|
||||
);
|
||||
const hookCall = requireSubagentEndedHookCall("run-killed");
|
||||
expect(hookCall.event.targetSessionKey).toBe(childSessionKey);
|
||||
expect(hookCall.event.targetKind).toBe("subagent");
|
||||
expect(hookCall.event.reason).toBe("subagent-killed");
|
||||
expect(hookCall.event.sendFarewell).toBe(true);
|
||||
expect(hookCall.event.accountId).toBeUndefined();
|
||||
expect(hookCall.event.runId).toBe("run-killed");
|
||||
expect(typeof hookCall.event.endedAt).toBe("number");
|
||||
expect(hookCall.event.outcome).toBe("killed");
|
||||
expect(hookCall.event.error).toBe("manual kill");
|
||||
expect(hookCall.ctx.runId).toBe("run-killed");
|
||||
expect(hookCall.ctx.childSessionKey).toBe(childSessionKey);
|
||||
expect(hookCall.ctx.requesterSessionKey).toBe(MAIN_REQUESTER_SESSION_KEY);
|
||||
});
|
||||
|
||||
it("treats a child session as inactive when only a stale older row is still unended", () => {
|
||||
|
||||
Reference in New Issue
Block a user