From 51b0f22d82ffd202dfca7814dbd2f343c2383f57 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 10:09:24 +0100 Subject: [PATCH] test: remove heartbeat session store paths --- ...tbeat-runner.returns-default-unset.test.ts | 378 +++++++++--------- 1 file changed, 185 insertions(+), 193 deletions(-) diff --git a/src/infra/heartbeat-runner.returns-default-unset.test.ts b/src/infra/heartbeat-runner.returns-default-unset.test.ts index 6c61c309643..e52540554f0 100644 --- a/src/infra/heartbeat-runner.returns-default-unset.test.ts +++ b/src/infra/heartbeat-runner.returns-default-unset.test.ts @@ -9,8 +9,13 @@ import { resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, resolveMainSessionKey, - resolveStorePath, } from "../config/sessions.js"; +import { + deleteSessionEntry, + listSessionEntries, + upsertSessionEntry, +} from "../config/sessions/store.js"; +import type { SessionEntry } from "../config/sessions/types.js"; import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js"; import { buildAgentPeerSessionKey } from "../routing/session-key.js"; import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js"; @@ -31,6 +36,7 @@ import { enqueueSystemEvent, resetSystemEventsForTest } from "./system-events.js let previousRegistry: ReturnType | null = null; let testRegistry: ReturnType | null = null; +let previousStateDir: string | undefined; let fixtureRoot = ""; let fixtureCount = 0; @@ -160,11 +166,38 @@ function resolveWhatsAppTargetForTest(params: { const createCaseDir = async (prefix: string) => { const dir = path.join(fixtureRoot, `${prefix}-${fixtureCount++}`); await fs.mkdir(dir, { recursive: true }); + process.env.OPENCLAW_STATE_DIR = dir; return dir; }; +type TestSessionRowsTarget = { + agentId: string; + sessionsDir: string; +}; + +function sessionRowsTarget(root: string, agentId = "main"): TestSessionRowsTarget { + return { + agentId, + sessionsDir: path.join(root, "agents", agentId, "sessions"), + }; +} + +async function replaceTestSessionRows( + target: TestSessionRowsTarget, + store: Record, +): Promise { + const { agentId } = target; + for (const { sessionKey } of listSessionEntries({ agentId })) { + deleteSessionEntry({ agentId, sessionKey }); + } + for (const [sessionKey, entry] of Object.entries(store)) { + upsertSessionEntry({ agentId, sessionKey, entry }); + } +} + beforeAll(async () => { previousRegistry = getActivePluginRegistry(); + previousStateDir = process.env.OPENCLAW_STATE_DIR; const whatsappPlugin = createOutboundTestPlugin({ id: "whatsapp", @@ -246,6 +279,11 @@ afterAll(async () => { if (previousRegistry) { setActivePluginRegistry(previousRegistry); } + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } }); describe("resolveHeartbeatIntervalMs", () => { @@ -662,7 +700,7 @@ describe("runHeartbeatOnce", () => { it("uses the last non-empty payload for delivery", async () => { const tmpDir = await createCaseDir("hb-last-payload"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const replySpy = vi.fn(); try { const cfg: OpenClawConfig = { @@ -673,21 +711,18 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); replySpy.mockResolvedValue([{ text: "Let me check..." }, { text: "Final alert" }]); const sendWhatsApp = vi @@ -721,7 +756,7 @@ describe("runHeartbeatOnce", () => { it("uses per-agent heartbeat overrides and session keys", async () => { const tmpDir = await createCaseDir("hb-agent-overrides"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir, "ops"); const replySpy = vi.fn(); try { const cfg: OpenClawConfig = { @@ -739,21 +774,18 @@ describe("runHeartbeatOnce", () => { ], }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveAgentMainSessionKey({ cfg, agentId: "ops" }); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); replySpy.mockResolvedValue([{ text: "Final alert" }]); const sendWhatsApp = vi .fn< @@ -798,7 +830,6 @@ describe("runHeartbeatOnce", () => { it("reuses non-default agent sessionFile from templated stores", async () => { const tmpDir = await createCaseDir("hb-templated-store"); - const storeTemplate = path.join(tmpDir, "agents", "{agentId}", "sessions", "sessions.json"); const replySpy = vi.fn(); const agentId = "ops"; try { @@ -817,28 +848,23 @@ describe("runHeartbeatOnce", () => { ], }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storeTemplate }, + session: {}, }; const sessionKey = resolveAgentMainSessionKey({ cfg, agentId }); - const storePath = resolveStorePath(storeTemplate, { agentId }); - const sessionsDir = path.dirname(storePath); + const target = sessionRowsTarget(tmpDir, agentId); + const { sessionsDir } = target; const sessionId = "sid-ops"; const sessionFile = path.join(sessionsDir, `${sessionId}.jsonl`); - await fs.mkdir(sessionsDir, { recursive: true }); - await fs.writeFile(sessionFile, "", "utf-8"); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId, - sessionFile, - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId, + sessionFile, + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); replySpy.mockResolvedValue([{ text: "Final alert" }]); const sendWhatsApp = vi @@ -912,7 +938,7 @@ describe("runHeartbeatOnce", () => { const replySpy = vi.fn(); try { const tmpDir = await createCaseDir(caseDir); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const cfg: OpenClawConfig = { agents: { defaults: { @@ -924,7 +950,7 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const mainSessionKey = resolveMainSessionKey(cfg); const agentId = resolveAgentIdFromSessionKey(mainSessionKey); @@ -936,23 +962,20 @@ describe("runHeartbeatOnce", () => { }); applyOverride({ cfg, sessionKey: overrideSessionKey }); - await fs.writeFile( - storePath, - JSON.stringify({ - [mainSessionKey]: { - sessionId: "sid-main", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - [overrideSessionKey]: { - sessionId: `sid-${peerKind}`, - updatedAt: Date.now() + 10_000, - lastChannel: "whatsapp", - lastTo: peerId, - }, - }), - ); + await replaceTestSessionRows(target, { + [mainSessionKey]: { + sessionId: "sid-main", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + [overrideSessionKey]: { + sessionId: `sid-${peerKind}`, + updatedAt: Date.now() + 10_000, + lastChannel: "whatsapp", + lastTo: peerId, + }, + }); replySpy.mockClear(); replySpy.mockResolvedValue([{ text: message }]); @@ -1003,7 +1026,7 @@ describe("runHeartbeatOnce", () => { const replySpy = vi.fn(); try { const tmpDir = await createCaseDir("hb-subagent-guard"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const cfg: OpenClawConfig = { agents: { defaults: { @@ -1015,7 +1038,7 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const mainSessionKey = resolveMainSessionKey(cfg); const agentId = resolveAgentIdFromSessionKey(mainSessionKey); @@ -1025,23 +1048,20 @@ describe("runHeartbeatOnce", () => { cfg.agents.defaults.heartbeat.session = subagentKey; } - await fs.writeFile( - storePath, - JSON.stringify({ - [mainSessionKey]: { - sessionId: "sid-main", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - [subagentKey]: { - sessionId: "sid-subagent", - updatedAt: Date.now() + 10_000, - lastChannel: "whatsapp", - lastTo: "99999@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [mainSessionKey]: { + sessionId: "sid-main", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + [subagentKey]: { + sessionId: "sid-subagent", + updatedAt: Date.now() + 10_000, + lastChannel: "whatsapp", + lastTo: "99999@g.us", + }, + }); replySpy.mockClear(); replySpy.mockResolvedValue([{ text: "Main session heartbeat" }]); @@ -1084,7 +1104,7 @@ describe("runHeartbeatOnce", () => { it("suppresses duplicate heartbeat payloads within 24h", async () => { const tmpDir = await createCaseDir("hb-dup-suppress"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const replySpy = vi.fn(); try { const cfg: OpenClawConfig = { @@ -1095,23 +1115,20 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - lastHeartbeatText: "Final alert", - lastHeartbeatSentAt: 0, - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + lastHeartbeatText: "Final alert", + lastHeartbeatSentAt: 0, + }, + }); replySpy.mockResolvedValue([{ text: "Final alert" }]); const sendWhatsApp = vi @@ -1170,7 +1187,7 @@ describe("runHeartbeatOnce", () => { const replySpy = vi.fn(); try { const tmpDir = await createCaseDir(caseDir); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const cfg: OpenClawConfig = { agents: { defaults: { @@ -1183,22 +1200,18 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastProvider: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); replySpy.mockClear(); replySpy.mockResolvedValue(replies); @@ -1234,7 +1247,6 @@ describe("runHeartbeatOnce", () => { it("loads the default agent session from templated stores", async () => { const tmpDir = await createCaseDir("openclaw-hb"); - const storeTemplate = path.join(tmpDir, "agents", "{agentId}", "sessions.json"); const replySpy = vi.fn(); try { const cfg: OpenClawConfig = { @@ -1243,25 +1255,20 @@ describe("runHeartbeatOnce", () => { list: [{ id: "work", default: true }], }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storeTemplate }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); const agentId = resolveAgentIdFromSessionKey(sessionKey); - const storePath = resolveStorePath(storeTemplate, { agentId }); + const target = sessionRowsTarget(tmpDir, agentId); - await fs.mkdir(path.dirname(storePath), { recursive: true }); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastProvider: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); replySpy.mockResolvedValue({ text: "Hello from heartbeat" }); const sendWhatsApp = vi @@ -1309,7 +1316,7 @@ describe("runHeartbeatOnce", () => { replyText?: string; }) { const tmpDir = await createCaseDir("openclaw-hb"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const workspaceDir = path.join(tmpDir, "workspace"); await fs.mkdir(workspaceDir, { recursive: true }); @@ -1373,20 +1380,17 @@ describe("runHeartbeatOnce", () => { }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); if (params.queueCronEvent) { enqueueSystemEvent("Cron: QMD maintenance completed", { sessionKey, @@ -1435,7 +1439,7 @@ describe("runHeartbeatOnce", () => { it("keeps non-task HEARTBEAT.md context while stripping blank-line-separated task blocks", async () => { const tmpDir = await createCaseDir("openclaw-hb-tasks-context"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const workspaceDir = path.join(tmpDir, "workspace"); await fs.mkdir(workspaceDir, { recursive: true }); await fs.writeFile( @@ -1468,19 +1472,16 @@ Some global directive after tasks. }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; - await fs.writeFile( - storePath, - JSON.stringify({ - [resolveMainSessionKey(cfg)]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [resolveMainSessionKey(cfg)]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); const replySpy = vi.fn().mockResolvedValue({ text: "Handled due heartbeat tasks" }); const sendWhatsApp = vi .fn< @@ -1510,7 +1511,7 @@ Some global directive after tasks. it("strips documented unindented task entries while keeping following top-level bullets", async () => { const tmpDir = await createCaseDir("openclaw-hb-unindented-tasks-context"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const workspaceDir = path.join(tmpDir, "workspace"); await fs.mkdir(workspaceDir, { recursive: true }); await fs.writeFile( @@ -1539,19 +1540,16 @@ tasks: }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; - await fs.writeFile( - storePath, - JSON.stringify({ - [resolveMainSessionKey(cfg)]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [resolveMainSessionKey(cfg)]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); const replySpy = vi.fn().mockResolvedValue({ text: "Handled due heartbeat tasks" }); const sendWhatsApp = vi .fn< @@ -1716,7 +1714,7 @@ tasks: it("uses an internal-only cron prompt when heartbeat delivery target is none", async () => { const tmpDir = await createCaseDir("hb-cron-target-none"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const cfg: OpenClawConfig = { agents: { defaults: { @@ -1725,20 +1723,17 @@ tasks: }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); enqueueSystemEvent("Cron: rotate logs", { sessionKey, contextKey: "cron:rotate-logs", @@ -1773,7 +1768,7 @@ tasks: it("uses an internal-only exec prompt when heartbeat delivery target is none", async () => { const tmpDir = await createCaseDir("hb-exec-target-none"); - const storePath = path.join(tmpDir, "sessions.json"); + const target = sessionRowsTarget(tmpDir); const cfg: OpenClawConfig = { agents: { defaults: { @@ -1782,20 +1777,17 @@ tasks: }, }, channels: { whatsapp: { allowFrom: ["*"] } }, - session: { store: storePath }, + session: {}, }; const sessionKey = resolveMainSessionKey(cfg); - await fs.writeFile( - storePath, - JSON.stringify({ - [sessionKey]: { - sessionId: "sid", - updatedAt: Date.now(), - lastChannel: "whatsapp", - lastTo: "120363401234567890@g.us", - }, - }), - ); + await replaceTestSessionRows(target, { + [sessionKey]: { + sessionId: "sid", + updatedAt: Date.now(), + lastChannel: "whatsapp", + lastTo: "120363401234567890@g.us", + }, + }); enqueueSystemEvent("exec finished: backup completed", { sessionKey, contextKey: "exec:backup",