From cb6b835a493aae053f0fa9801c11bb51208b6e60 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 19 Feb 2026 13:59:27 +0000 Subject: [PATCH] test: dedupe heartbeat and action-runner fixtures --- .../heartbeat-runner.ghost-reminder.test.ts | 22 ++--- ...espects-ackmaxchars-heartbeat-acks.test.ts | 97 +++++++++---------- ...ner.sender-prefers-delivery-target.test.ts | 22 ++--- src/infra/heartbeat-visibility.test.ts | 46 ++++----- .../outbound/message-action-runner.test.ts | 20 ++-- 5 files changed, 93 insertions(+), 114 deletions(-) diff --git a/src/infra/heartbeat-runner.ghost-reminder.test.ts b/src/infra/heartbeat-runner.ghost-reminder.test.ts index e0e66dd310..0fa7280a37 100644 --- a/src/infra/heartbeat-runner.ghost-reminder.test.ts +++ b/src/infra/heartbeat-runner.ghost-reminder.test.ts @@ -11,6 +11,7 @@ import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createPluginRuntime } from "../plugins/runtime/index.js"; import { createTestRegistry } from "../test-utils/channel-plugins.js"; import { runHeartbeatOnce } from "./heartbeat-runner.js"; +import { seedSessionStore } from "./heartbeat-runner.test-utils.js"; import { enqueueSystemEvent, resetSystemEventsForTest } from "./system-events.js"; // Avoid pulling optional runtime deps during isolated runs. @@ -50,22 +51,11 @@ describe("Ghost reminder bug (issue #13317)", () => { }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify( - { - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "telegram", - lastProvider: "telegram", - lastTo: "155462274", - }, - }, - null, - 2, - ), - ); + await seedSessionStore(storePath, sessionKey, { + lastChannel: "telegram", + lastProvider: "telegram", + lastTo: "155462274", + }); return { cfg, sessionKey }; }; diff --git a/src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts b/src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts index b2bdaf79be..022e1b4b42 100644 --- a/src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts +++ b/src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts @@ -140,17 +140,50 @@ describe("resolveHeartbeatIntervalMs", () => { return sendTelegram; } + function createWhatsAppHeartbeatConfig(params: { + tmpDir: string; + storePath: string; + heartbeat?: Record; + visibility?: Record; + }): OpenClawConfig { + return createHeartbeatConfig({ + tmpDir: params.tmpDir, + storePath: params.storePath, + heartbeat: { + every: "5m", + target: "whatsapp", + ...params.heartbeat, + }, + channels: { + whatsapp: { + allowFrom: ["*"], + ...(params.visibility ? { heartbeat: params.visibility } : {}), + }, + }, + }); + } + + async function createSeededWhatsAppHeartbeatConfig(params: { + tmpDir: string; + storePath: string; + heartbeat?: Record; + visibility?: Record; + }): Promise { + const cfg = createWhatsAppHeartbeatConfig(params); + await seedMainSession(params.storePath, cfg, { + lastChannel: "whatsapp", + lastProvider: "whatsapp", + lastTo: "+1555", + }); + return cfg; + } + it("respects ackMaxChars for heartbeat acks", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { - const cfg = createHeartbeatConfig({ + const cfg = createWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { - every: "5m", - target: "whatsapp", - ackMaxChars: 0, - }, - channels: { whatsapp: { allowFrom: ["*"] } }, + heartbeat: { ackMaxChars: 0 }, }); await seedMainSession(storePath, cfg, { @@ -173,14 +206,10 @@ describe("resolveHeartbeatIntervalMs", () => { it("sends HEARTBEAT_OK when visibility.showOk is true", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { - const cfg = createHeartbeatConfig({ + const cfg = createWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { - every: "5m", - target: "whatsapp", - }, - channels: { whatsapp: { allowFrom: ["*"], heartbeat: { showOk: true } } }, + visibility: { showOk: true }, }); await seedMainSession(storePath, cfg, { @@ -250,19 +279,10 @@ describe("resolveHeartbeatIntervalMs", () => { it("skips heartbeat LLM calls when visibility disables all output", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { - const cfg = createHeartbeatConfig({ + const cfg = createWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { - every: "5m", - target: "whatsapp", - }, - channels: { - whatsapp: { - allowFrom: ["*"], - heartbeat: { showOk: false, showAlerts: false, useIndicator: false }, - }, - }, + visibility: { showOk: false, showAlerts: false, useIndicator: false }, }); await seedMainSession(storePath, cfg, { @@ -286,20 +306,9 @@ describe("resolveHeartbeatIntervalMs", () => { it("skips delivery for markup-wrapped HEARTBEAT_OK", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { - const cfg = createHeartbeatConfig({ + const cfg = await createSeededWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { - every: "5m", - target: "whatsapp", - }, - channels: { whatsapp: { allowFrom: ["*"] } }, - }); - - await seedMainSession(storePath, cfg, { - lastChannel: "whatsapp", - lastProvider: "whatsapp", - lastTo: "+1555", }); replySpy.mockResolvedValue({ text: "HEARTBEAT_OK" }); @@ -318,14 +327,9 @@ describe("resolveHeartbeatIntervalMs", () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { const originalUpdatedAt = 1000; const bumpedUpdatedAt = 2000; - const cfg = createHeartbeatConfig({ + const cfg = createWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { - every: "5m", - target: "whatsapp", - }, - channels: { whatsapp: { allowFrom: ["*"] } }, }); const sessionKey = await seedMainSession(storePath, cfg, { @@ -363,16 +367,9 @@ describe("resolveHeartbeatIntervalMs", () => { it("skips WhatsApp delivery when not linked or running", async () => { await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => { - const cfg = createHeartbeatConfig({ + const cfg = await createSeededWhatsAppHeartbeatConfig({ tmpDir, storePath, - heartbeat: { every: "5m", target: "whatsapp" }, - channels: { whatsapp: { allowFrom: ["*"] } }, - }); - await seedMainSession(storePath, cfg, { - lastChannel: "whatsapp", - lastProvider: "whatsapp", - lastTo: "+1555", }); replySpy.mockResolvedValue({ text: "Heartbeat alert" }); diff --git a/src/infra/heartbeat-runner.sender-prefers-delivery-target.test.ts b/src/infra/heartbeat-runner.sender-prefers-delivery-target.test.ts index 6c13476cd3..625d11e01d 100644 --- a/src/infra/heartbeat-runner.sender-prefers-delivery-target.test.ts +++ b/src/infra/heartbeat-runner.sender-prefers-delivery-target.test.ts @@ -7,6 +7,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolveMainSessionKey } from "../config/sessions.js"; import { runHeartbeatOnce } from "./heartbeat-runner.js"; import { installHeartbeatRunnerTestRuntime } from "./heartbeat-runner.test-harness.js"; +import { seedSessionStore } from "./heartbeat-runner.test-utils.js"; // Avoid pulling optional runtime deps during isolated runs. vi.mock("jiti", () => ({ createJiti: () => () => ({}) })); @@ -35,22 +36,11 @@ describe("runHeartbeatOnce", () => { }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify( - { - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "telegram", - lastProvider: "telegram", - lastTo: "1644620762", - }, - }, - null, - 2, - ), - ); + await seedSessionStore(storePath, sessionKey, { + lastChannel: "telegram", + lastProvider: "telegram", + lastTo: "1644620762", + }); replySpy.mockImplementation(async (ctx) => { expect(ctx.To).toBe("C0A9P2N8QHY"); diff --git a/src/infra/heartbeat-visibility.test.ts b/src/infra/heartbeat-visibility.test.ts index f48e37ad68..7350ffea9f 100644 --- a/src/infra/heartbeat-visibility.test.ts +++ b/src/infra/heartbeat-visibility.test.ts @@ -3,6 +3,20 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js"; describe("resolveHeartbeatVisibility", () => { + function createChannelDefaultsHeartbeatConfig(heartbeat: { + showOk?: boolean; + showAlerts?: boolean; + useIndicator?: boolean; + }): OpenClawConfig { + return { + channels: { + defaults: { + heartbeat, + }, + }, + } as OpenClawConfig; + } + function createTelegramAccountHeartbeatConfig(): OpenClawConfig { return { channels: { @@ -34,17 +48,11 @@ describe("resolveHeartbeatVisibility", () => { }); it("uses channel defaults when provided", () => { - const cfg = { - channels: { - defaults: { - heartbeat: { - showOk: true, - showAlerts: false, - useIndicator: false, - }, - }, - }, - } as OpenClawConfig; + const cfg = createChannelDefaultsHeartbeatConfig({ + showOk: true, + showAlerts: false, + useIndicator: false, + }); const result = resolveHeartbeatVisibility({ cfg, channel: "telegram" }); @@ -236,17 +244,11 @@ describe("resolveHeartbeatVisibility", () => { }); it("webchat uses channel defaults only (no per-channel config)", () => { - const cfg = { - channels: { - defaults: { - heartbeat: { - showOk: true, - showAlerts: false, - useIndicator: false, - }, - }, - }, - } as OpenClawConfig; + const cfg = createChannelDefaultsHeartbeatConfig({ + showOk: true, + showAlerts: false, + useIndicator: false, + }); const result = resolveHeartbeatVisibility({ cfg, channel: "webchat" }); diff --git a/src/infra/outbound/message-action-runner.test.ts b/src/infra/outbound/message-action-runner.test.ts index af62420d44..4c219d4249 100644 --- a/src/infra/outbound/message-action-runner.test.ts +++ b/src/infra/outbound/message-action-runner.test.ts @@ -78,6 +78,14 @@ const runDrySend = (params: { action: "send", }); +function createAlwaysConfiguredPluginConfig(account: Record = { enabled: true }) { + return { + listAccountIds: () => ["default"], + resolveAccount: () => account, + isConfigured: () => true, + }; +} + describe("runMessageAction context isolation", () => { beforeEach(async () => { const { createPluginRuntime } = await import("../../plugins/runtime/index.js"); @@ -680,11 +688,7 @@ describe("runMessageAction card-only send behavior", () => { blurb: "Card-only send test plugin.", }, capabilities: { chatTypes: ["direct"] }, - config: { - listAccountIds: () => ["default"], - resolveAccount: () => ({ enabled: true }), - isConfigured: () => true, - }, + config: createAlwaysConfiguredPluginConfig(), actions: { listActions: () => ["send"], supportsAction: ({ action }) => action === "send", @@ -764,11 +768,7 @@ describe("runMessageAction components parsing", () => { blurb: "Discord components send test plugin.", }, capabilities: { chatTypes: ["direct"] }, - config: { - listAccountIds: () => ["default"], - resolveAccount: () => ({}), - isConfigured: () => true, - }, + config: createAlwaysConfiguredPluginConfig({}), actions: { listActions: () => ["send"], supportsAction: ({ action }) => action === "send",