From 41c9e3ead089a886b82fe81e88814c3d8304cd60 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 18:15:04 +0000 Subject: [PATCH] refactor: share cron and zalo monitor test helpers --- extensions/zalo/src/monitor.lifecycle.test.ts | 122 ++++----- src/cron/isolated-agent/run.fast-mode.test.ts | 235 +++++++----------- 2 files changed, 128 insertions(+), 229 deletions(-) diff --git a/extensions/zalo/src/monitor.lifecycle.test.ts b/extensions/zalo/src/monitor.lifecycle.test.ts index 6cce789da56..e5fa65e1063 100644 --- a/extensions/zalo/src/monitor.lifecycle.test.ts +++ b/extensions/zalo/src/monitor.lifecycle.test.ts @@ -32,6 +32,41 @@ async function waitForPollingLoopStart(): Promise { 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); diff --git a/src/cron/isolated-agent/run.fast-mode.test.ts b/src/cron/isolated-agent/run.fast-mode.test.ts index 471471e9ecd..abe50ea5554 100644 --- a/src/cron/isolated-agent/run.fast-mode.test.ts +++ b/src/cron/isolated-agent/run.fast-mode.test.ts @@ -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", - 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, + await runFastModeCase({ + configFastMode: true, + expectedFastMode: true, + message: "test fast mode", }); }); 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", - 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, + await runFastModeCase({ + configFastMode: true, + expectedFastMode: false, + message: "test fast mode override", + 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", - 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, + await runFastModeCase({ + configFastMode: false, + expectedFastMode: true, + message: "test fast mode session override", + sessionFastMode: true, }); }); });