mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
test: dedupe heartbeat and action-runner fixtures
This commit is contained in:
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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" });
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user