refactor: share cron and zalo monitor test helpers

This commit is contained in:
Peter Steinberger 2026-03-13 18:15:04 +00:00
parent 99b274592d
commit 41c9e3ead0
2 changed files with 128 additions and 229 deletions

View File

@ -32,6 +32,41 @@ async function waitForPollingLoopStart(): Promise<void> {
await vi.waitFor(() => expect(getUpdatesMock).toHaveBeenCalledTimes(1));
}
const TEST_ACCOUNT = {
accountId: "default",
config: {},
} as unknown as ResolvedZaloAccount;
const TEST_CONFIG = {} as OpenClawConfig;
function createLifecycleRuntime() {
return {
log: vi.fn<(message: string) => void>(),
error: vi.fn<(message: string) => void>(),
};
}
async function startLifecycleMonitor(
options: {
useWebhook?: boolean;
webhookSecret?: string;
webhookUrl?: string;
} = {},
) {
const { monitorZaloProvider } = await import("./monitor.js");
const abort = new AbortController();
const runtime = createLifecycleRuntime();
const run = monitorZaloProvider({
token: "test-token",
account: TEST_ACCOUNT,
config: TEST_CONFIG,
runtime,
abortSignal: abort.signal,
...options,
});
return { abort, runtime, run };
}
describe("monitorZaloProvider lifecycle", () => {
afterEach(() => {
vi.clearAllMocks();
@ -39,26 +74,9 @@ describe("monitorZaloProvider lifecycle", () => {
});
it("stays alive in polling mode until abort", async () => {
const { monitorZaloProvider } = await import("./monitor.js");
const abort = new AbortController();
const runtime = {
log: vi.fn<(message: string) => void>(),
error: vi.fn<(message: string) => void>(),
};
const account = {
accountId: "default",
config: {},
} as unknown as ResolvedZaloAccount;
const config = {} as OpenClawConfig;
let settled = false;
const run = monitorZaloProvider({
token: "test-token",
account,
config,
runtime,
abortSignal: abort.signal,
}).then(() => {
const { abort, runtime, run } = await startLifecycleMonitor();
const monitoredRun = run.then(() => {
settled = true;
});
@ -70,7 +88,7 @@ describe("monitorZaloProvider lifecycle", () => {
expect(settled).toBe(false);
abort.abort();
await run;
await monitoredRun;
expect(settled).toBe(true);
expect(runtime.log).toHaveBeenCalledWith(
@ -84,25 +102,7 @@ describe("monitorZaloProvider lifecycle", () => {
result: { url: "https://example.com/hooks/zalo" },
});
const { monitorZaloProvider } = await import("./monitor.js");
const abort = new AbortController();
const runtime = {
log: vi.fn<(message: string) => void>(),
error: vi.fn<(message: string) => void>(),
};
const account = {
accountId: "default",
config: {},
} as unknown as ResolvedZaloAccount;
const config = {} as OpenClawConfig;
const run = monitorZaloProvider({
token: "test-token",
account,
config,
runtime,
abortSignal: abort.signal,
});
const { abort, runtime, run } = await startLifecycleMonitor();
await waitForPollingLoopStart();
@ -120,25 +120,7 @@ describe("monitorZaloProvider lifecycle", () => {
const { ZaloApiError } = await import("./api.js");
getWebhookInfoMock.mockRejectedValueOnce(new ZaloApiError("Not Found", 404, "Not Found"));
const { monitorZaloProvider } = await import("./monitor.js");
const abort = new AbortController();
const runtime = {
log: vi.fn<(message: string) => void>(),
error: vi.fn<(message: string) => void>(),
};
const account = {
accountId: "default",
config: {},
} as unknown as ResolvedZaloAccount;
const config = {} as OpenClawConfig;
const run = monitorZaloProvider({
token: "test-token",
account,
config,
runtime,
abortSignal: abort.signal,
});
const { abort, runtime, run } = await startLifecycleMonitor();
await waitForPollingLoopStart();
@ -165,29 +147,13 @@ describe("monitorZaloProvider lifecycle", () => {
}),
);
const { monitorZaloProvider } = await import("./monitor.js");
const abort = new AbortController();
const runtime = {
log: vi.fn<(message: string) => void>(),
error: vi.fn<(message: string) => void>(),
};
const account = {
accountId: "default",
config: {},
} as unknown as ResolvedZaloAccount;
const config = {} as OpenClawConfig;
let settled = false;
const run = monitorZaloProvider({
token: "test-token",
account,
config,
runtime,
abortSignal: abort.signal,
const { abort, runtime, run } = await startLifecycleMonitor({
useWebhook: true,
webhookUrl: "https://example.com/hooks/zalo",
webhookSecret: "supersecret", // pragma: allowlist secret
}).then(() => {
});
const monitoredRun = run.then(() => {
settled = true;
});
@ -202,7 +168,7 @@ describe("monitorZaloProvider lifecycle", () => {
expect(registry.httpRoutes).toHaveLength(1);
resolveDeleteWebhook?.();
await run;
await monitoredRun;
expect(settled).toBe(true);
expect(registry.httpRoutes).toHaveLength(0);

View File

@ -14,169 +14,102 @@ import {
const runCronIsolatedAgentTurn = await loadRunCronIsolatedAgentTurn();
const OPENAI_GPT4_MODEL = "openai/gpt-4";
function mockSuccessfulModelFallback() {
runWithModelFallbackMock.mockImplementation(async ({ provider, model, run }) => {
await run(provider, model);
return {
result: {
payloads: [{ text: "ok" }],
meta: { agentMeta: { usage: { input: 10, output: 20 } } },
},
provider,
model,
attempts: [],
};
});
}
async function runFastModeCase(params: {
configFastMode: boolean;
expectedFastMode: boolean;
message: string;
sessionFastMode?: boolean;
}) {
const baseSession = makeCronSession();
resolveCronSessionMock.mockReturnValue(
params.sessionFastMode === undefined
? baseSession
: makeCronSession({
sessionEntry: {
...baseSession.sessionEntry,
fastMode: params.sessionFastMode,
},
}),
);
mockSuccessfulModelFallback();
const result = await runCronIsolatedAgentTurn(
makeIsolatedAgentTurnParams({
cfg: {
agents: {
defaults: {
models: {
[OPENAI_GPT4_MODEL]: {
params: {
fastMode: params.configFastMode,
},
},
},
},
},
},
job: makeIsolatedAgentTurnJob({
payload: {
kind: "agentTurn",
message: params.message,
model: OPENAI_GPT4_MODEL,
},
}),
}),
);
expect(result.status).toBe("ok");
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0][0]).toMatchObject({
provider: "openai",
model: "gpt-4",
fastMode: params.expectedFastMode,
});
}
describe("runCronIsolatedAgentTurn — fast mode", () => {
setupRunCronIsolatedAgentTurnSuite();
it("passes config-driven fast mode into embedded cron runs", async () => {
const cronSession = makeCronSession();
resolveCronSessionMock.mockReturnValue(cronSession);
runWithModelFallbackMock.mockImplementation(async ({ provider, model, run }) => {
await run(provider, model);
return {
result: {
payloads: [{ text: "ok" }],
meta: { agentMeta: { usage: { input: 10, output: 20 } } },
},
provider,
model,
attempts: [],
};
});
const result = await runCronIsolatedAgentTurn(
makeIsolatedAgentTurnParams({
cfg: {
agents: {
defaults: {
models: {
"openai/gpt-4": {
params: {
fastMode: true,
},
},
},
},
},
},
job: makeIsolatedAgentTurnJob({
payload: {
kind: "agentTurn",
await runFastModeCase({
configFastMode: true,
expectedFastMode: true,
message: "test fast mode",
model: "openai/gpt-4",
},
}),
}),
);
expect(result.status).toBe("ok");
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0][0]).toMatchObject({
provider: "openai",
model: "gpt-4",
fastMode: true,
});
});
it("honors session fastMode=false over config fastMode=true", async () => {
const cronSession = makeCronSession({
sessionEntry: {
...makeCronSession().sessionEntry,
fastMode: false,
},
});
resolveCronSessionMock.mockReturnValue(cronSession);
runWithModelFallbackMock.mockImplementation(async ({ provider, model, run }) => {
await run(provider, model);
return {
result: {
payloads: [{ text: "ok" }],
meta: { agentMeta: { usage: { input: 10, output: 20 } } },
},
provider,
model,
attempts: [],
};
});
const result = await runCronIsolatedAgentTurn(
makeIsolatedAgentTurnParams({
cfg: {
agents: {
defaults: {
models: {
"openai/gpt-4": {
params: {
fastMode: true,
},
},
},
},
},
},
job: makeIsolatedAgentTurnJob({
payload: {
kind: "agentTurn",
await runFastModeCase({
configFastMode: true,
expectedFastMode: false,
message: "test fast mode override",
model: "openai/gpt-4",
},
}),
}),
);
expect(result.status).toBe("ok");
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0][0]).toMatchObject({
provider: "openai",
model: "gpt-4",
fastMode: false,
sessionFastMode: false,
});
});
it("honors session fastMode=true over config fastMode=false", async () => {
const cronSession = makeCronSession({
sessionEntry: {
...makeCronSession().sessionEntry,
fastMode: true,
},
});
resolveCronSessionMock.mockReturnValue(cronSession);
runWithModelFallbackMock.mockImplementation(async ({ provider, model, run }) => {
await run(provider, model);
return {
result: {
payloads: [{ text: "ok" }],
meta: { agentMeta: { usage: { input: 10, output: 20 } } },
},
provider,
model,
attempts: [],
};
});
const result = await runCronIsolatedAgentTurn(
makeIsolatedAgentTurnParams({
cfg: {
agents: {
defaults: {
models: {
"openai/gpt-4": {
params: {
fastMode: false,
},
},
},
},
},
},
job: makeIsolatedAgentTurnJob({
payload: {
kind: "agentTurn",
await runFastModeCase({
configFastMode: false,
expectedFastMode: true,
message: "test fast mode session override",
model: "openai/gpt-4",
},
}),
}),
);
expect(result.status).toBe("ok");
expect(runEmbeddedPiAgentMock).toHaveBeenCalledOnce();
expect(runEmbeddedPiAgentMock.mock.calls[0][0]).toMatchObject({
provider: "openai",
model: "gpt-4",
fastMode: true,
sessionFastMode: true,
});
});
});