mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 12:57:40 +00: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 { createPluginRuntime } from "../plugins/runtime/index.js";
|
||||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||||
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
||||||
|
import { seedSessionStore } from "./heartbeat-runner.test-utils.js";
|
||||||
import { enqueueSystemEvent, resetSystemEventsForTest } from "./system-events.js";
|
import { enqueueSystemEvent, resetSystemEventsForTest } from "./system-events.js";
|
||||||
|
|
||||||
// Avoid pulling optional runtime deps during isolated runs.
|
// Avoid pulling optional runtime deps during isolated runs.
|
||||||
@@ -50,22 +51,11 @@ describe("Ghost reminder bug (issue #13317)", () => {
|
|||||||
};
|
};
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
const sessionKey = resolveMainSessionKey(cfg);
|
||||||
|
|
||||||
await fs.writeFile(
|
await seedSessionStore(storePath, sessionKey, {
|
||||||
storePath,
|
lastChannel: "telegram",
|
||||||
JSON.stringify(
|
lastProvider: "telegram",
|
||||||
{
|
lastTo: "155462274",
|
||||||
[sessionKey]: {
|
});
|
||||||
sessionId: "sid",
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
lastChannel: "telegram",
|
|
||||||
lastProvider: "telegram",
|
|
||||||
lastTo: "155462274",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return { cfg, sessionKey };
|
return { cfg, sessionKey };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -140,17 +140,50 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
return sendTelegram;
|
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 () => {
|
it("respects ackMaxChars for heartbeat acks", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = createWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
storePath,
|
||||||
heartbeat: {
|
heartbeat: { ackMaxChars: 0 },
|
||||||
every: "5m",
|
|
||||||
target: "whatsapp",
|
|
||||||
ackMaxChars: 0,
|
|
||||||
},
|
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await seedMainSession(storePath, cfg, {
|
await seedMainSession(storePath, cfg, {
|
||||||
@@ -173,14 +206,10 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
it("sends HEARTBEAT_OK when visibility.showOk is true", async () => {
|
it("sends HEARTBEAT_OK when visibility.showOk is true", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = createWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
storePath,
|
||||||
heartbeat: {
|
visibility: { showOk: true },
|
||||||
every: "5m",
|
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
channels: { whatsapp: { allowFrom: ["*"], heartbeat: { showOk: true } } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await seedMainSession(storePath, cfg, {
|
await seedMainSession(storePath, cfg, {
|
||||||
@@ -250,19 +279,10 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
it("skips heartbeat LLM calls when visibility disables all output", async () => {
|
it("skips heartbeat LLM calls when visibility disables all output", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = createWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
storePath,
|
||||||
heartbeat: {
|
visibility: { showOk: false, showAlerts: false, useIndicator: false },
|
||||||
every: "5m",
|
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
channels: {
|
|
||||||
whatsapp: {
|
|
||||||
allowFrom: ["*"],
|
|
||||||
heartbeat: { showOk: false, showAlerts: false, useIndicator: false },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await seedMainSession(storePath, cfg, {
|
await seedMainSession(storePath, cfg, {
|
||||||
@@ -286,20 +306,9 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
it("skips delivery for markup-wrapped HEARTBEAT_OK", async () => {
|
it("skips delivery for markup-wrapped HEARTBEAT_OK", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = await createSeededWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
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>" });
|
replySpy.mockResolvedValue({ text: "<b>HEARTBEAT_OK</b>" });
|
||||||
@@ -318,14 +327,9 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const originalUpdatedAt = 1000;
|
const originalUpdatedAt = 1000;
|
||||||
const bumpedUpdatedAt = 2000;
|
const bumpedUpdatedAt = 2000;
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = createWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
storePath,
|
||||||
heartbeat: {
|
|
||||||
every: "5m",
|
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionKey = await seedMainSession(storePath, cfg, {
|
const sessionKey = await seedMainSession(storePath, cfg, {
|
||||||
@@ -363,16 +367,9 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
it("skips WhatsApp delivery when not linked or running", async () => {
|
it("skips WhatsApp delivery when not linked or running", async () => {
|
||||||
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg = createHeartbeatConfig({
|
const cfg = await createSeededWhatsAppHeartbeatConfig({
|
||||||
tmpDir,
|
tmpDir,
|
||||||
storePath,
|
storePath,
|
||||||
heartbeat: { every: "5m", target: "whatsapp" },
|
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
||||||
});
|
|
||||||
await seedMainSession(storePath, cfg, {
|
|
||||||
lastChannel: "whatsapp",
|
|
||||||
lastProvider: "whatsapp",
|
|
||||||
lastTo: "+1555",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
replySpy.mockResolvedValue({ text: "Heartbeat alert" });
|
replySpy.mockResolvedValue({ text: "Heartbeat alert" });
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { OpenClawConfig } from "../config/config.js";
|
|||||||
import { resolveMainSessionKey } from "../config/sessions.js";
|
import { resolveMainSessionKey } from "../config/sessions.js";
|
||||||
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
||||||
import { installHeartbeatRunnerTestRuntime } from "./heartbeat-runner.test-harness.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.
|
// Avoid pulling optional runtime deps during isolated runs.
|
||||||
vi.mock("jiti", () => ({ createJiti: () => () => ({}) }));
|
vi.mock("jiti", () => ({ createJiti: () => () => ({}) }));
|
||||||
@@ -35,22 +36,11 @@ describe("runHeartbeatOnce", () => {
|
|||||||
};
|
};
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
const sessionKey = resolveMainSessionKey(cfg);
|
||||||
|
|
||||||
await fs.writeFile(
|
await seedSessionStore(storePath, sessionKey, {
|
||||||
storePath,
|
lastChannel: "telegram",
|
||||||
JSON.stringify(
|
lastProvider: "telegram",
|
||||||
{
|
lastTo: "1644620762",
|
||||||
[sessionKey]: {
|
});
|
||||||
sessionId: "sid",
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
lastChannel: "telegram",
|
|
||||||
lastProvider: "telegram",
|
|
||||||
lastTo: "1644620762",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
replySpy.mockImplementation(async (ctx) => {
|
replySpy.mockImplementation(async (ctx) => {
|
||||||
expect(ctx.To).toBe("C0A9P2N8QHY");
|
expect(ctx.To).toBe("C0A9P2N8QHY");
|
||||||
|
|||||||
@@ -3,6 +3,20 @@ import type { OpenClawConfig } from "../config/config.js";
|
|||||||
import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js";
|
import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js";
|
||||||
|
|
||||||
describe("resolveHeartbeatVisibility", () => {
|
describe("resolveHeartbeatVisibility", () => {
|
||||||
|
function createChannelDefaultsHeartbeatConfig(heartbeat: {
|
||||||
|
showOk?: boolean;
|
||||||
|
showAlerts?: boolean;
|
||||||
|
useIndicator?: boolean;
|
||||||
|
}): OpenClawConfig {
|
||||||
|
return {
|
||||||
|
channels: {
|
||||||
|
defaults: {
|
||||||
|
heartbeat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig;
|
||||||
|
}
|
||||||
|
|
||||||
function createTelegramAccountHeartbeatConfig(): OpenClawConfig {
|
function createTelegramAccountHeartbeatConfig(): OpenClawConfig {
|
||||||
return {
|
return {
|
||||||
channels: {
|
channels: {
|
||||||
@@ -34,17 +48,11 @@ describe("resolveHeartbeatVisibility", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses channel defaults when provided", () => {
|
it("uses channel defaults when provided", () => {
|
||||||
const cfg = {
|
const cfg = createChannelDefaultsHeartbeatConfig({
|
||||||
channels: {
|
showOk: true,
|
||||||
defaults: {
|
showAlerts: false,
|
||||||
heartbeat: {
|
useIndicator: false,
|
||||||
showOk: true,
|
});
|
||||||
showAlerts: false,
|
|
||||||
useIndicator: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as OpenClawConfig;
|
|
||||||
|
|
||||||
const result = resolveHeartbeatVisibility({ cfg, channel: "telegram" });
|
const result = resolveHeartbeatVisibility({ cfg, channel: "telegram" });
|
||||||
|
|
||||||
@@ -236,17 +244,11 @@ describe("resolveHeartbeatVisibility", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("webchat uses channel defaults only (no per-channel config)", () => {
|
it("webchat uses channel defaults only (no per-channel config)", () => {
|
||||||
const cfg = {
|
const cfg = createChannelDefaultsHeartbeatConfig({
|
||||||
channels: {
|
showOk: true,
|
||||||
defaults: {
|
showAlerts: false,
|
||||||
heartbeat: {
|
useIndicator: false,
|
||||||
showOk: true,
|
});
|
||||||
showAlerts: false,
|
|
||||||
useIndicator: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as OpenClawConfig;
|
|
||||||
|
|
||||||
const result = resolveHeartbeatVisibility({ cfg, channel: "webchat" });
|
const result = resolveHeartbeatVisibility({ cfg, channel: "webchat" });
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ const runDrySend = (params: {
|
|||||||
action: "send",
|
action: "send",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createAlwaysConfiguredPluginConfig(account: Record<string, unknown> = { enabled: true }) {
|
||||||
|
return {
|
||||||
|
listAccountIds: () => ["default"],
|
||||||
|
resolveAccount: () => account,
|
||||||
|
isConfigured: () => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("runMessageAction context isolation", () => {
|
describe("runMessageAction context isolation", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { createPluginRuntime } = await import("../../plugins/runtime/index.js");
|
const { createPluginRuntime } = await import("../../plugins/runtime/index.js");
|
||||||
@@ -680,11 +688,7 @@ describe("runMessageAction card-only send behavior", () => {
|
|||||||
blurb: "Card-only send test plugin.",
|
blurb: "Card-only send test plugin.",
|
||||||
},
|
},
|
||||||
capabilities: { chatTypes: ["direct"] },
|
capabilities: { chatTypes: ["direct"] },
|
||||||
config: {
|
config: createAlwaysConfiguredPluginConfig(),
|
||||||
listAccountIds: () => ["default"],
|
|
||||||
resolveAccount: () => ({ enabled: true }),
|
|
||||||
isConfigured: () => true,
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
listActions: () => ["send"],
|
listActions: () => ["send"],
|
||||||
supportsAction: ({ action }) => action === "send",
|
supportsAction: ({ action }) => action === "send",
|
||||||
@@ -764,11 +768,7 @@ describe("runMessageAction components parsing", () => {
|
|||||||
blurb: "Discord components send test plugin.",
|
blurb: "Discord components send test plugin.",
|
||||||
},
|
},
|
||||||
capabilities: { chatTypes: ["direct"] },
|
capabilities: { chatTypes: ["direct"] },
|
||||||
config: {
|
config: createAlwaysConfiguredPluginConfig({}),
|
||||||
listAccountIds: () => ["default"],
|
|
||||||
resolveAccount: () => ({}),
|
|
||||||
isConfigured: () => true,
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
listActions: () => ["send"],
|
listActions: () => ["send"],
|
||||||
supportsAction: ({ action }) => action === "send",
|
supportsAction: ({ action }) => action === "send",
|
||||||
|
|||||||
Reference in New Issue
Block a user