mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 12:10:22 +00:00
test(infra): share sync temp dir helper in approval tests
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { withTempDirSync } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
resolveApprovalRequestAccountId,
|
||||
@@ -16,14 +16,6 @@ import {
|
||||
import type { ExecApprovalRequest } from "./exec-approvals.js";
|
||||
import type { PluginApprovalRequest } from "./plugin-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: {
|
||||
@@ -34,12 +26,6 @@ const baseRequest: ExecApprovalRequest = {
|
||||
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<string, Partial<SessionEntry>>,
|
||||
@@ -96,53 +82,55 @@ 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",
|
||||
},
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
},
|
||||
});
|
||||
|
||||
const requests = [
|
||||
buildRequest({ sessionKey: " " }),
|
||||
buildRequest({ sessionKey: "agent:main:missing" }),
|
||||
baseRequest,
|
||||
] satisfies ExecApprovalRequest[];
|
||||
|
||||
for (const request of requests) {
|
||||
expect(expectResolvedSessionTarget(cfg, request)).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
const requests = [
|
||||
buildRequest({ sessionKey: " " }),
|
||||
buildRequest({ sessionKey: "agent:main:missing" }),
|
||||
baseRequest,
|
||||
] satisfies ExecApprovalRequest[];
|
||||
|
||||
for (const request of requests) {
|
||||
expect(expectResolvedSessionTarget(cfg, 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,
|
||||
lastChannel: "slack",
|
||||
lastTo: "U1",
|
||||
},
|
||||
});
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
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,
|
||||
expect(
|
||||
resolveExecApprovalSessionTarget({
|
||||
cfg,
|
||||
request: baseRequest,
|
||||
turnSourceChannel: " whatsapp ",
|
||||
turnSourceTo: " +15555550123 ",
|
||||
turnSourceAccountId: " work ",
|
||||
turnSourceThreadId: "1739201675.123",
|
||||
}),
|
||||
).toEqual({
|
||||
channel: "whatsapp",
|
||||
to: "+15555550123",
|
||||
accountId: "work",
|
||||
threadId: 1739201675,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,10 +182,11 @@ describe("exec approval session target", () => {
|
||||
] satisfies PlaceholderStoreCase[])(
|
||||
"$name",
|
||||
({ relativeStoreDir, entries, request, expected }) => {
|
||||
const tmpDir = createTempDir();
|
||||
const cfg = writeStoreFile(path.join(tmpDir, relativeStoreDir, "sessions.json"), entries);
|
||||
cfg.session = { store: path.join(tmpDir, "{agentId}", "sessions.json") };
|
||||
expect(expectResolvedSessionTarget(cfg, request)).toEqual(expected);
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const cfg = writeStoreFile(path.join(tmpDir, relativeStoreDir, "sessions.json"), entries);
|
||||
cfg.session = { store: path.join(tmpDir, "{agentId}", "sessions.json") };
|
||||
expect(expectResolvedSessionTarget(cfg, request)).toEqual(expected);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -247,146 +236,153 @@ describe("exec approval session target", () => {
|
||||
});
|
||||
|
||||
it("falls back to the stored session binding when turn source uses another channel", () => {
|
||||
const tmpDir = createTempDir();
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:matrix:channel:!ops:example.org": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
origin: {
|
||||
provider: "matrix",
|
||||
accountId: "ops",
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:matrix:channel:!ops:example.org": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
origin: {
|
||||
provider: "matrix",
|
||||
accountId: "ops",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
lastAccountId: "work",
|
||||
},
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
lastAccountId: "work",
|
||||
},
|
||||
});
|
||||
const request = buildRequest({
|
||||
sessionKey: "agent:main:matrix:channel:!ops:example.org",
|
||||
turnSourceChannel: "discord",
|
||||
turnSourceTo: "channel:D123",
|
||||
turnSourceAccountId: "work",
|
||||
});
|
||||
});
|
||||
const request = buildRequest({
|
||||
sessionKey: "agent:main:matrix:channel:!ops:example.org",
|
||||
turnSourceChannel: "discord",
|
||||
turnSourceTo: "channel:D123",
|
||||
turnSourceAccountId: "work",
|
||||
});
|
||||
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request, channel: "matrix" })).toBeNull();
|
||||
expect(resolveApprovalRequestChannelAccountId({ cfg, request, channel: "matrix" })).toBe("ops");
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request, channel: "matrix" })).toBeNull();
|
||||
expect(resolveApprovalRequestChannelAccountId({ cfg, request, channel: "matrix" })).toBe(
|
||||
"ops",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the session-bound account when no turn-source account is present", () => {
|
||||
const tmpDir = createTempDir();
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U1",
|
||||
lastAccountId: "ops",
|
||||
},
|
||||
});
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U1",
|
||||
lastAccountId: "ops",
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request: baseRequest, channel: "slack" })).toBe(
|
||||
"ops",
|
||||
);
|
||||
expect(
|
||||
doesApprovalRequestMatchChannelAccount({
|
||||
cfg,
|
||||
request: baseRequest,
|
||||
channel: "slack",
|
||||
accountId: "ops",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request: baseRequest, channel: "slack" })).toBe(
|
||||
"ops",
|
||||
);
|
||||
expect(
|
||||
doesApprovalRequestMatchChannelAccount({
|
||||
cfg,
|
||||
request: baseRequest,
|
||||
channel: "slack",
|
||||
accountId: "ops",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers explicit turn-source accounts over stale session account bindings", () => {
|
||||
const tmpDir = createTempDir();
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U1",
|
||||
lastAccountId: "ops",
|
||||
},
|
||||
});
|
||||
const request = buildRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceAccountId: "work",
|
||||
});
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "user:U1",
|
||||
lastAccountId: "ops",
|
||||
},
|
||||
});
|
||||
const request = buildRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceAccountId: "work",
|
||||
});
|
||||
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request, channel: "slack" })).toBe("work");
|
||||
expect(
|
||||
doesApprovalRequestMatchChannelAccount({
|
||||
cfg,
|
||||
request,
|
||||
channel: "slack",
|
||||
accountId: "work",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(resolveApprovalRequestAccountId({ cfg, request, channel: "slack" })).toBe("work");
|
||||
expect(
|
||||
doesApprovalRequestMatchChannelAccount({
|
||||
cfg,
|
||||
request,
|
||||
channel: "slack",
|
||||
accountId: "work",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("reconciles plugin-request turn source and session origin targets through the shared helper", () => {
|
||||
const tmpDir = createTempDir();
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
},
|
||||
});
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
},
|
||||
});
|
||||
|
||||
const target = resolveApprovalRequestOriginTarget({
|
||||
cfg,
|
||||
request: buildPluginRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceTo: "channel:C123",
|
||||
}),
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
resolveTurnSourceTarget: (request) =>
|
||||
request.request.turnSourceChannel === "slack" && request.request.turnSourceTo
|
||||
? { to: request.request.turnSourceTo }
|
||||
: null,
|
||||
resolveSessionTarget: (sessionTarget) => ({ to: sessionTarget.to }),
|
||||
targetsMatch: (a, b) => a.to === b.to,
|
||||
});
|
||||
const target = resolveApprovalRequestOriginTarget({
|
||||
cfg,
|
||||
request: buildPluginRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceTo: "channel:C123",
|
||||
}),
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
resolveTurnSourceTarget: (request) =>
|
||||
request.request.turnSourceChannel === "slack" && request.request.turnSourceTo
|
||||
? { to: request.request.turnSourceTo }
|
||||
: null,
|
||||
resolveSessionTarget: (sessionTarget) => ({ to: sessionTarget.to }),
|
||||
targetsMatch: (a, b) => a.to === b.to,
|
||||
});
|
||||
|
||||
expect(target).toEqual({ to: "channel:C123" });
|
||||
expect(target).toEqual({ to: "channel:C123" });
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when explicit turn source conflicts with the session-bound origin target", () => {
|
||||
const tmpDir = createTempDir();
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
},
|
||||
});
|
||||
withTempDirSync({ prefix: "openclaw-exec-approval-session-target-" }, (tmpDir) => {
|
||||
const storePath = path.join(tmpDir, "sessions.json");
|
||||
const cfg = writeStoreFile(storePath, {
|
||||
"agent:main:main": {
|
||||
sessionId: "main",
|
||||
updatedAt: 1,
|
||||
lastChannel: "slack",
|
||||
lastTo: "channel:C123",
|
||||
},
|
||||
});
|
||||
|
||||
const target = resolveApprovalRequestOriginTarget({
|
||||
cfg,
|
||||
request: buildPluginRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceTo: "channel:C999",
|
||||
}),
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
resolveTurnSourceTarget: (request) =>
|
||||
request.request.turnSourceChannel === "slack" && request.request.turnSourceTo
|
||||
? { to: request.request.turnSourceTo }
|
||||
: null,
|
||||
resolveSessionTarget: (sessionTarget) => ({ to: sessionTarget.to }),
|
||||
targetsMatch: (a, b) => a.to === b.to,
|
||||
});
|
||||
const target = resolveApprovalRequestOriginTarget({
|
||||
cfg,
|
||||
request: buildPluginRequest({
|
||||
turnSourceChannel: "slack",
|
||||
turnSourceTo: "channel:C999",
|
||||
}),
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
resolveTurnSourceTarget: (request) =>
|
||||
request.request.turnSourceChannel === "slack" && request.request.turnSourceTo
|
||||
? { to: request.request.turnSourceTo }
|
||||
: null,
|
||||
resolveSessionTarget: (sessionTarget) => ({ to: sessionTarget.to }),
|
||||
targetsMatch: (a, b) => a.to === b.to,
|
||||
});
|
||||
|
||||
expect(target).toBeNull();
|
||||
expect(target).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to a legacy origin target when no turn-source or session target exists", () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -26,3 +27,28 @@ export async function withTempDir<T>(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function withTempDirSync<T>(
|
||||
options: {
|
||||
prefix: string;
|
||||
parentDir?: string;
|
||||
subdir?: string;
|
||||
},
|
||||
run: (dir: string) => T,
|
||||
): T {
|
||||
const base = fsSync.mkdtempSync(path.join(options.parentDir ?? os.tmpdir(), options.prefix));
|
||||
const dir = options.subdir ? path.join(base, options.subdir) : base;
|
||||
if (options.subdir) {
|
||||
fsSync.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
try {
|
||||
return run(dir);
|
||||
} finally {
|
||||
fsSync.rmSync(base, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 20,
|
||||
retryDelay: 25,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user