mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 22:14:34 +00:00
refactor(test): dedupe heartbeat runner e2e scaffolding
This commit is contained in:
@@ -14,6 +14,72 @@ vi.mock("jiti", () => ({ createJiti: () => () => ({}) }));
|
|||||||
installHeartbeatRunnerTestRuntime();
|
installHeartbeatRunnerTestRuntime();
|
||||||
|
|
||||||
describe("resolveHeartbeatIntervalMs", () => {
|
describe("resolveHeartbeatIntervalMs", () => {
|
||||||
|
function createHeartbeatConfig(params: {
|
||||||
|
tmpDir: string;
|
||||||
|
storePath: string;
|
||||||
|
heartbeat: Record<string, unknown>;
|
||||||
|
channels: Record<string, unknown>;
|
||||||
|
}): OpenClawConfig {
|
||||||
|
return {
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
workspace: params.tmpDir,
|
||||||
|
heartbeat: params.heartbeat as never,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: params.channels as never,
|
||||||
|
session: { store: params.storePath },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedMainSession(
|
||||||
|
storePath: string,
|
||||||
|
cfg: OpenClawConfig,
|
||||||
|
session: {
|
||||||
|
sessionId?: string;
|
||||||
|
updatedAt?: number;
|
||||||
|
lastChannel: string;
|
||||||
|
lastProvider: string;
|
||||||
|
lastTo: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const sessionKey = resolveMainSessionKey(cfg);
|
||||||
|
await seedSessionStore(storePath, sessionKey, session);
|
||||||
|
return sessionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWhatsAppDeps(
|
||||||
|
params: {
|
||||||
|
sendWhatsApp?: ReturnType<typeof vi.fn>;
|
||||||
|
getQueueSize?: () => number;
|
||||||
|
nowMs?: () => number;
|
||||||
|
webAuthExists?: () => Promise<boolean>;
|
||||||
|
hasActiveWebListener?: () => boolean;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...(params.sendWhatsApp ? { sendWhatsApp: params.sendWhatsApp } : {}),
|
||||||
|
getQueueSize: params.getQueueSize ?? (() => 0),
|
||||||
|
nowMs: params.nowMs ?? (() => 0),
|
||||||
|
webAuthExists: params.webAuthExists ?? (async () => true),
|
||||||
|
hasActiveWebListener: params.hasActiveWebListener ?? (() => true),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTelegramDeps(
|
||||||
|
params: {
|
||||||
|
sendTelegram?: ReturnType<typeof vi.fn>;
|
||||||
|
getQueueSize?: () => number;
|
||||||
|
nowMs?: () => number;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...(params.sendTelegram ? { sendTelegram: params.sendTelegram } : {}),
|
||||||
|
getQueueSize: params.getQueueSize ?? (() => 0),
|
||||||
|
nowMs: params.nowMs ?? (() => 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function seedSessionStore(
|
async function seedSessionStore(
|
||||||
storePath: string,
|
storePath: string,
|
||||||
sessionKey: string,
|
sessionKey: string,
|
||||||
@@ -81,23 +147,18 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "whatsapp",
|
||||||
target: "whatsapp",
|
ackMaxChars: 0,
|
||||||
ackMaxChars: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
await seedMainSession(storePath, cfg, {
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
@@ -111,13 +172,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps({ sendWhatsApp }),
|
||||||
sendWhatsApp,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => true,
|
|
||||||
hasActiveWebListener: () => true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendWhatsApp).toHaveBeenCalled();
|
expect(sendWhatsApp).toHaveBeenCalled();
|
||||||
@@ -126,22 +181,17 @@ 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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "whatsapp",
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: { whatsapp: { allowFrom: ["*"], heartbeat: { showOk: true } } },
|
channels: { whatsapp: { allowFrom: ["*"], heartbeat: { showOk: true } } },
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
await seedMainSession(storePath, cfg, {
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
@@ -155,13 +205,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps({ sendWhatsApp }),
|
||||||
sendWhatsApp,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => true,
|
|
||||||
hasActiveWebListener: () => true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendWhatsApp).toHaveBeenCalledTimes(1);
|
expect(sendWhatsApp).toHaveBeenCalledTimes(1);
|
||||||
@@ -171,15 +215,12 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
it("does not deliver HEARTBEAT_OK to telegram when showOk is false", async () => {
|
it("does not deliver HEARTBEAT_OK to telegram when showOk is false", async () => {
|
||||||
await withTempTelegramHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempTelegramHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "telegram",
|
||||||
target: "telegram",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
@@ -188,11 +229,9 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
heartbeat: { showOk: false },
|
heartbeat: { showOk: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
await seedMainSession(storePath, cfg, {
|
||||||
lastChannel: "telegram",
|
lastChannel: "telegram",
|
||||||
lastProvider: "telegram",
|
lastProvider: "telegram",
|
||||||
lastTo: "12345",
|
lastTo: "12345",
|
||||||
@@ -206,11 +245,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeTelegramDeps({ sendTelegram }),
|
||||||
sendTelegram,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendTelegram).not.toHaveBeenCalled();
|
expect(sendTelegram).not.toHaveBeenCalled();
|
||||||
@@ -219,15 +254,12 @@ 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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "whatsapp",
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
@@ -235,11 +267,9 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
heartbeat: { showOk: false, showAlerts: false, useIndicator: false },
|
heartbeat: { showOk: false, showAlerts: false, useIndicator: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
await seedMainSession(storePath, cfg, {
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
@@ -252,13 +282,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
const result = await runHeartbeatOnce({
|
const result = await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps({ sendWhatsApp }),
|
||||||
sendWhatsApp,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => true,
|
|
||||||
hasActiveWebListener: () => true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(replySpy).not.toHaveBeenCalled();
|
expect(replySpy).not.toHaveBeenCalled();
|
||||||
@@ -269,22 +293,17 @@ 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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "whatsapp",
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
await seedMainSession(storePath, cfg, {
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
@@ -298,13 +317,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps({ sendWhatsApp }),
|
||||||
sendWhatsApp,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => true,
|
|
||||||
hasActiveWebListener: () => true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendWhatsApp).not.toHaveBeenCalled();
|
expect(sendWhatsApp).not.toHaveBeenCalled();
|
||||||
@@ -315,22 +328,17 @@ 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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: {
|
||||||
heartbeat: {
|
every: "5m",
|
||||||
every: "5m",
|
target: "whatsapp",
|
||||||
target: "whatsapp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
const sessionKey = await seedMainSession(storePath, cfg, {
|
||||||
updatedAt: originalUpdatedAt,
|
updatedAt: originalUpdatedAt,
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
@@ -352,12 +360,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps(),
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => true,
|
|
||||||
hasActiveWebListener: () => true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const finalStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
|
const finalStore = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record<
|
||||||
@@ -370,19 +373,13 @@ 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: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: { every: "5m", target: "whatsapp" },
|
||||||
heartbeat: { every: "5m", target: "whatsapp" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||||
session: { store: storePath },
|
});
|
||||||
};
|
await seedMainSession(storePath, cfg, {
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
|
||||||
lastChannel: "whatsapp",
|
lastChannel: "whatsapp",
|
||||||
lastProvider: "whatsapp",
|
lastProvider: "whatsapp",
|
||||||
lastTo: "+1555",
|
lastTo: "+1555",
|
||||||
@@ -396,13 +393,11 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
const res = await runHeartbeatOnce({
|
const res = await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeWhatsAppDeps({
|
||||||
sendWhatsApp,
|
sendWhatsApp,
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
webAuthExists: async () => false,
|
webAuthExists: async () => false,
|
||||||
hasActiveWebListener: () => false,
|
hasActiveWebListener: () => false,
|
||||||
},
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.status).toBe("skipped");
|
expect(res.status).toBe("skipped");
|
||||||
@@ -417,19 +412,13 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
expectedAccountId: string | undefined;
|
expectedAccountId: string | undefined;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await withTempTelegramHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
await withTempTelegramHeartbeatSandbox(async ({ tmpDir, storePath, replySpy }) => {
|
||||||
const cfg: OpenClawConfig = {
|
const cfg = createHeartbeatConfig({
|
||||||
agents: {
|
tmpDir,
|
||||||
defaults: {
|
storePath,
|
||||||
workspace: tmpDir,
|
heartbeat: params.heartbeat,
|
||||||
heartbeat: params.heartbeat as never,
|
channels: { telegram: params.telegram },
|
||||||
},
|
});
|
||||||
},
|
await seedMainSession(storePath, cfg, {
|
||||||
channels: { telegram: params.telegram as never },
|
|
||||||
session: { store: storePath },
|
|
||||||
};
|
|
||||||
const sessionKey = resolveMainSessionKey(cfg);
|
|
||||||
|
|
||||||
await seedSessionStore(storePath, sessionKey, {
|
|
||||||
lastChannel: "telegram",
|
lastChannel: "telegram",
|
||||||
lastProvider: "telegram",
|
lastProvider: "telegram",
|
||||||
lastTo: "123456",
|
lastTo: "123456",
|
||||||
@@ -443,11 +432,7 @@ describe("resolveHeartbeatIntervalMs", () => {
|
|||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
cfg,
|
cfg,
|
||||||
deps: {
|
deps: makeTelegramDeps({ sendTelegram }),
|
||||||
sendTelegram,
|
|
||||||
getQueueSize: () => 0,
|
|
||||||
nowMs: () => 0,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendTelegram).toHaveBeenCalledTimes(1);
|
expect(sendTelegram).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user