From 5f0e97b22ae9c4dc2fb9c48da9c08eeb6fa326af Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 20:39:40 +0000 Subject: [PATCH] test: extract exec approval session target coverage --- src/infra/exec-approval-forwarder.test.ts | 55 ----- .../exec-approval-session-target.test.ts | 189 ++++++++++++++++++ 2 files changed, 189 insertions(+), 55 deletions(-) create mode 100644 src/infra/exec-approval-session-target.test.ts diff --git a/src/infra/exec-approval-forwarder.test.ts b/src/infra/exec-approval-forwarder.test.ts index d1d72aecd24..d29856c3088 100644 --- a/src/infra/exec-approval-forwarder.test.ts +++ b/src/infra/exec-approval-forwarder.test.ts @@ -1,6 +1,3 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { telegramOutbound } from "../channels/plugins/outbound/telegram.js"; import type { OpenClawConfig } from "../config/config.js"; @@ -380,58 +377,6 @@ describe("exec approval forwarder", () => { }); }); - it("prefers turn-source routing over stale session last route", async () => { - vi.useFakeTimers(); - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-exec-approval-forwarder-test-")); - try { - const storePath = path.join(tmpDir, "sessions.json"); - fs.writeFileSync( - storePath, - JSON.stringify({ - "agent:main:main": { - updatedAt: 1, - channel: "slack", - to: "U1", - lastChannel: "slack", - lastTo: "U1", - }, - }), - "utf-8", - ); - - const cfg = { - session: { store: storePath }, - approvals: { exec: { enabled: true, mode: "session" } }, - } as OpenClawConfig; - - const { deliver, forwarder } = createForwarder({ cfg }); - await expect( - forwarder.handleRequested({ - ...baseRequest, - request: { - ...baseRequest.request, - turnSourceChannel: "whatsapp", - turnSourceTo: "+15555550123", - turnSourceAccountId: "work", - turnSourceThreadId: "1739201675.123", - }, - }), - ).resolves.toBe(true); - - expect(deliver).toHaveBeenCalledTimes(1); - expect(deliver).toHaveBeenCalledWith( - expect.objectContaining({ - channel: "whatsapp", - to: "+15555550123", - accountId: "work", - threadId: 1739201675, - }), - ); - } finally { - fs.rmSync(tmpDir, { recursive: true, force: true }); - } - }); - it("can forward resolved notices without pending cache when request payload is present", async () => { vi.useFakeTimers(); const cfg = { diff --git a/src/infra/exec-approval-session-target.test.ts b/src/infra/exec-approval-session-target.test.ts new file mode 100644 index 00000000000..234614b017c --- /dev/null +++ b/src/infra/exec-approval-session-target.test.ts @@ -0,0 +1,189 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; +import type { SessionEntry } from "../config/sessions.js"; +import { resolveExecApprovalSessionTarget } from "./exec-approval-session-target.js"; +import type { ExecApprovalRequest } from "./exec-approvals.js"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +const baseRequest: ExecApprovalRequest = { + id: "req-1", + request: { + command: "echo hello", + sessionKey: "agent:main:main", + }, + createdAtMs: 1000, + expiresAtMs: 6000, +}; + +function createTempDir(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-exec-approval-session-target-")); + tempDirs.push(dir); + return dir; +} + +function writeStoreFile( + storePath: string, + entries: Record>, +): OpenClawConfig { + fs.mkdirSync(path.dirname(storePath), { recursive: true }); + fs.writeFileSync(storePath, JSON.stringify(entries), "utf-8"); + return { + session: { store: storePath }, + } as OpenClawConfig; +} + +describe("exec approval session target", () => { + it("returns null for blank session keys, missing entries, and unresolved targets", () => { + const tmpDir = createTempDir(); + const storePath = path.join(tmpDir, "sessions.json"); + const cfg = writeStoreFile(storePath, { + "agent:main:main": { + sessionId: "main", + updatedAt: 1, + lastChannel: "slack", + }, + }); + + const cases = [ + { + request: { + ...baseRequest, + request: { + ...baseRequest.request, + sessionKey: " ", + }, + }, + }, + { + request: { + ...baseRequest, + request: { + ...baseRequest.request, + sessionKey: "agent:main:missing", + }, + }, + }, + { + request: baseRequest, + }, + ]; + + for (const testCase of cases) { + expect( + resolveExecApprovalSessionTarget({ + cfg, + request: testCase.request, + }), + ).toBeNull(); + } + }); + + it("prefers turn-source routing over stale session delivery state", () => { + const tmpDir = createTempDir(); + const storePath = path.join(tmpDir, "sessions.json"); + const cfg = writeStoreFile(storePath, { + "agent:main:main": { + sessionId: "main", + updatedAt: 1, + channel: "slack", + to: "U1", + lastChannel: "slack", + lastTo: "U1", + }, + }); + + expect( + resolveExecApprovalSessionTarget({ + cfg, + request: baseRequest, + turnSourceChannel: " whatsapp ", + turnSourceTo: " +15555550123 ", + turnSourceAccountId: " work ", + turnSourceThreadId: "1739201675.123", + }), + ).toEqual({ + channel: "whatsapp", + to: "+15555550123", + accountId: "work", + threadId: 1739201675, + }); + }); + + it("uses the parsed session-key agent id for store-path placeholders", () => { + const tmpDir = createTempDir(); + const storePath = path.join(tmpDir, "{agentId}", "sessions.json"); + const cfg = writeStoreFile(path.join(tmpDir, "helper", "sessions.json"), { + "agent:helper:main": { + sessionId: "main", + updatedAt: 1, + lastChannel: "discord", + lastTo: "channel:123", + lastAccountId: " Work ", + lastThreadId: "55", + }, + }); + cfg.session = { store: storePath }; + + expect( + resolveExecApprovalSessionTarget({ + cfg, + request: { + ...baseRequest, + request: { + ...baseRequest.request, + sessionKey: "agent:helper:main", + }, + }, + }), + ).toEqual({ + channel: "discord", + to: "channel:123", + accountId: "work", + threadId: 55, + }); + }); + + it("falls back to request agent id for legacy session keys", () => { + const tmpDir = createTempDir(); + const storePath = path.join(tmpDir, "{agentId}", "sessions.json"); + const cfg = writeStoreFile(path.join(tmpDir, "worker-1", "sessions.json"), { + "legacy-main": { + sessionId: "legacy-main", + updatedAt: 1, + lastChannel: "telegram", + lastTo: "-100123", + lastThreadId: 77, + }, + }); + cfg.session = { store: storePath }; + + expect( + resolveExecApprovalSessionTarget({ + cfg, + request: { + ...baseRequest, + request: { + ...baseRequest.request, + agentId: "Worker 1", + sessionKey: "legacy-main", + }, + }, + }), + ).toEqual({ + channel: "telegram", + to: "-100123", + accountId: undefined, + threadId: 77, + }); + }); +});