test: dedupe heartbeat and action-runner fixtures

This commit is contained in:
Peter Steinberger
2026-02-19 13:59:27 +00:00
parent 26c9b37f5b
commit cb6b835a49
5 changed files with 93 additions and 114 deletions

View File

@@ -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 };
};

View File

@@ -140,17 +140,50 @@ describe("resolveHeartbeatIntervalMs", () => {
return sendTelegram;
}
function createWhatsAppHeartbeatConfig(params: {
tmpDir: string;
storePath: string;
heartbeat?: Record<string, unknown>;
visibility?: Record<string, unknown>;
}): 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<string, unknown>;
visibility?: Record<string, unknown>;
}): Promise<OpenClawConfig> {
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: "<b>HEARTBEAT_OK</b>" });
@@ -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" });

View File

@@ -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");

View File

@@ -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" });

View File

@@ -78,6 +78,14 @@ const runDrySend = (params: {
action: "send",
});
function createAlwaysConfiguredPluginConfig(account: Record<string, unknown> = { 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",