test: stabilize active memory timeout mocks

This commit is contained in:
Peter Steinberger
2026-05-06 06:18:54 +01:00
parent f505c84285
commit 6c743021d7

View File

@@ -125,6 +125,20 @@ describe("active-memory plugin", () => {
"utf8",
);
};
const waitForAbort = async (abortSignal?: AbortSignal): Promise<never> => {
if (abortSignal?.aborted) {
throw (abortSignal.reason as unknown) ?? new Error("Operation aborted");
}
return await new Promise<never>((_resolve, reject) => {
abortSignal?.addEventListener(
"abort",
() => {
reject((abortSignal.reason as unknown) ?? new Error("Operation aborted"));
},
{ once: true },
);
});
};
const makeMemoryToolAllowlistError = (
reason: string,
sources = "runtime toolsAllow: memory_recall, memory_search, memory_get",
@@ -145,6 +159,7 @@ describe("active-memory plugin", () => {
beforeEach(async () => {
vi.clearAllMocks();
runEmbeddedPiAgent.mockReset();
stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-active-memory-test-"));
configFile = {
plugins: {
@@ -171,6 +186,9 @@ describe("active-memory plugin", () => {
},
},
};
for (const key of Object.keys(hoisted.sessionStore)) {
delete hoisted.sessionStore[key];
}
hoisted.sessionStore["agent:main:main"] = {
sessionId: "s-main",
updatedAt: 0,
@@ -1547,26 +1565,28 @@ describe("active-memory plugin", () => {
const sessionKey = "agent:main:transcript-debug";
hoisted.sessionStore[sessionKey] = { sessionId: "s-main", updatedAt: 0 };
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
const lines = [
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: { debug: { backend: "qmd", hits: 3 } },
},
}),
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: {},
},
}),
];
await fs.writeFile(params.sessionFile, `${lines.join("\n")}\n`, "utf8");
return { payloads: [{ text: "wings are fine." }] };
});
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
const lines = [
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: { debug: { backend: "qmd", hits: 3 } },
},
}),
JSON.stringify({
message: {
role: "toolResult",
toolName: "memory_search",
details: {},
},
}),
];
await fs.writeFile(params.sessionFile, `${lines.join("\n")}\n`, "utf8");
return { payloads: [{ text: "wings are fine." }] };
},
);
await hooks.before_prompt_build(
{ prompt: "debug transcript bug", messages: [] },
@@ -1807,27 +1827,29 @@ describe("active-memory plugin", () => {
sessionId: "s-timeout-partial",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(
params.sessionFile,
[
{ type: "message", message: { role: "user", content: "ignore this user text" } },
{
type: "message",
message: { role: "assistant", content: "alpha beta gamma delta" },
},
{
type: "message",
message: {
role: "assistant",
content: [{ type: "text", text: "epsilon zeta eta theta" }],
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
await writeTranscriptJsonl(
params.sessionFile,
[
{ type: "message", message: { role: "user", content: "ignore this user text" } },
{
type: "message",
message: { role: "assistant", content: "alpha beta gamma delta" },
},
},
],
"\n{",
);
return await new Promise<never>(() => {});
});
{
type: "message",
message: {
role: "assistant",
content: [{ type: "text", text: "epsilon zeta eta theta" }],
},
},
],
"\n{",
);
return await waitForAbort(params.abortSignal);
},
);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? timeout partial", messages: [] },
@@ -1879,15 +1901,7 @@ describe("active-memory plugin", () => {
message: { role: "assistant", content: "temporary partial recall summary" },
},
]);
await new Promise<never>((_resolve, reject) => {
params.abortSignal?.addEventListener(
"abort",
() => {
reject(params.abortSignal?.reason ?? new Error("Operation aborted"));
},
{ once: true },
);
});
await waitForAbort(params.abortSignal);
},
);
@@ -1899,7 +1913,9 @@ describe("active-memory plugin", () => {
expect(result).toEqual({
prependContext: expect.stringContaining("temporary partial recall summary"),
});
await expect(fs.access(tempSessionFile)).rejects.toThrow();
await vi.waitFor(async () => {
await expect(fs.access(tempSessionFile)).rejects.toThrow();
});
expect(getActiveMemoryLines(sessionKey)).toEqual(
expect.arrayContaining([
expect.stringContaining("🧩 Active Memory: status=timeout_partial"),
@@ -1925,10 +1941,12 @@ describe("active-memory plugin", () => {
sessionId: "s-timeout-empty-transcript",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await fs.writeFile(params.sessionFile, "", "utf8");
return await new Promise<never>(() => {});
});
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
await fs.writeFile(params.sessionFile, "", "utf8");
return await waitForAbort(params.abortSignal);
},
);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? empty timeout transcript", messages: [] },
@@ -1956,7 +1974,9 @@ describe("active-memory plugin", () => {
sessionId: "s-timeout-missing-transcript",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async () => await new Promise<never>(() => {}));
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { abortSignal?: AbortSignal }) => await waitForAbort(params.abortSignal),
);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? missing timeout transcript", messages: [] },
@@ -1983,18 +2003,20 @@ describe("active-memory plugin", () => {
sessionId: "s-timeout-boilerplate-transcript",
updatedAt: 0,
};
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
type: "message",
message: {
role: "assistant",
content: "LLM request timed out after 15000 ms.",
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
type: "message",
message: {
role: "assistant",
content: "LLM request timed out after 15000 ms.",
},
},
},
]);
await new Promise<never>(() => {});
});
]);
await waitForAbort(params.abortSignal);
},
);
const result = await hooks.before_prompt_build(
{ prompt: "what wings should i order? timeout boilerplate", messages: [] },
@@ -2494,18 +2516,20 @@ describe("active-memory plugin", () => {
plugin.register(api as unknown as OpenClawPluginApi);
const sessionKey = "agent:main:terminal-zero-hit";
hoisted.sessionStore[sessionKey] = { sessionId: "s-terminal-zero-hit", updatedAt: 0 };
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
message: {
role: "toolResult",
toolName: "memory_search",
details: { results: [], debug: { backend: "qmd", hits: 0, searchMs: 8 } },
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
message: {
role: "toolResult",
toolName: "memory_search",
details: { results: [], debug: { backend: "qmd", hits: 0, searchMs: 8 } },
},
},
},
]);
await new Promise<never>(() => {});
});
]);
await waitForAbort(params.abortSignal);
},
);
const result = await hooks.before_prompt_build(
{ prompt: "what food do i usually order? zero hit", messages: [] },
@@ -2579,23 +2603,25 @@ describe("active-memory plugin", () => {
plugin.register(api as unknown as OpenClawPluginApi);
const sessionKey = "agent:main:terminal-unavailable";
hoisted.sessionStore[sessionKey] = { sessionId: "s-terminal-unavailable", updatedAt: 0 };
runEmbeddedPiAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
message: {
role: "toolResult",
toolName: "memory_search",
details: {
disabled: true,
warning: "Memory search is unavailable due to an embedding/provider error.",
action: "Check the embedding provider configuration, then retry memory_search.",
error: "embedding request failed",
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { sessionFile: string; abortSignal?: AbortSignal }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
message: {
role: "toolResult",
toolName: "memory_search",
details: {
disabled: true,
warning: "Memory search is unavailable due to an embedding/provider error.",
action: "Check the embedding provider configuration, then retry memory_search.",
error: "embedding request failed",
},
},
},
},
]);
await new Promise<never>(() => {});
});
]);
await waitForAbort(params.abortSignal);
},
);
const result = await hooks.before_prompt_build(
{ prompt: "what food do i usually order? unavailable", messages: [] },
@@ -3591,7 +3617,9 @@ describe("active-memory plugin", () => {
circuitBreakerCooldownMs: 60_000,
};
plugin.register(api as unknown as OpenClawPluginApi);
runEmbeddedPiAgent.mockImplementation(async () => await new Promise<never>(() => {}));
runEmbeddedPiAgent.mockImplementation(
async (params: { abortSignal?: AbortSignal }) => await waitForAbort(params.abortSignal),
);
// First two calls should actually attempt the subagent (and timeout).
await hooks.before_prompt_build(
@@ -3646,7 +3674,9 @@ describe("active-memory plugin", () => {
plugin.register(api as unknown as OpenClawPluginApi);
// First call: timeout (trips the breaker with max=1).
runEmbeddedPiAgent.mockImplementationOnce(async () => await new Promise<never>(() => {}));
runEmbeddedPiAgent.mockImplementationOnce(
async (params: { abortSignal?: AbortSignal }) => await waitForAbort(params.abortSignal),
);
await hooks.before_prompt_build(
{ prompt: "cb reset test timeout", messages: [] },
{