Files
openclaw/extensions/discord/src/approval-native.test.ts
Ayaan Zaidi 9935ca3b30 fix(approvals): bind native requests to channel accounts (#121673)
Native approval delivery and resolution now stay bound to the originating or explicitly targeted channel account. Unbound requests fail closed across multiple eligible accounts; trusted reviewer-less SDK callers remain compatible.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-11 01:42:41 +05:30

419 lines
12 KiB
TypeScript

// Discord tests cover approval native plugin behavior.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { splitChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
import { clearSessionStoreCacheForTest } from "openclaw/plugin-sdk/session-store-runtime";
import { describe, expect, it } from "vitest";
import { getDiscordApprovalCapability } from "./approval-native.js";
import { shouldHandleDiscordApprovalRequest } from "./approval-shared.js";
const STORE_PATH = path.join(os.tmpdir(), "openclaw-discord-approval-native-test.json");
const NATIVE_APPROVAL_CFG = {
commands: {
ownerAllowFrom: ["discord:555555555"],
},
} as const;
const NATIVE_DELIVERY_CFG = {
...NATIVE_APPROVAL_CFG,
channels: {
discord: {
execApprovals: {
enabled: true,
},
},
},
} as const;
function createDiscordNativeApprovalAdapter() {
return splitChannelApprovalCapability(getDiscordApprovalCapability());
}
function writeStore(store: Record<string, unknown>) {
fs.writeFileSync(STORE_PATH, `${JSON.stringify(store, null, 2)}\n`, "utf8");
clearSessionStoreCacheForTest();
}
describe("createDiscordNativeApprovalAdapter", () => {
it("keeps approval availability enabled when approvers exist but native delivery is off", () => {
const adapter = createDiscordNativeApprovalAdapter();
const cfg = {
...NATIVE_APPROVAL_CFG,
channels: {
discord: {
execApprovals: {
enabled: false,
approvers: ["555555555"],
target: "channel",
},
},
},
} as const;
expect(
adapter.auth?.getActionAvailabilityState?.({
cfg: cfg as never,
accountId: "main",
action: "approve",
}),
).toEqual({ kind: "enabled" });
expect(
adapter.native?.describeDeliveryCapabilities({
cfg: cfg as never,
accountId: "main",
approvalKind: "exec",
request: {
id: "approval-1",
request: {
command: "pwd",
turnSourceChannel: "discord",
turnSourceTo: "channel:123456789",
turnSourceAccountId: "main",
sessionKey: "agent:main:discord:channel:123456789",
},
createdAtMs: 1,
expiresAtMs: 2,
},
}),
).toEqual({
enabled: false,
preferredSurface: "origin",
supportsOriginSurface: true,
supportsApproverDmSurface: true,
notifyOriginWhenDmOnly: true,
});
});
it("honors ownerAllowFrom fallback when gating approval requests", () => {
expect(
shouldHandleDiscordApprovalRequest({
cfg: {
commands: {
ownerAllowFrom: ["discord:123"],
},
} as never,
accountId: "main",
configOverride: { enabled: true } as never,
request: {
id: "approval-1",
request: {
command: "pwd",
turnSourceChannel: "discord",
turnSourceTo: "channel:123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
}),
).toBe(true);
});
it("reports each configured account as a raw candidate for coordinator selection", () => {
const cfg = {
commands: { ownerAllowFrom: ["discord:123"] },
channels: {
discord: {
accounts: {
default: { token: "token-default", execApprovals: { enabled: true } },
ops: { token: "token-ops", execApprovals: { enabled: true } },
},
},
},
} as const;
const request = {
id: "approval-unbound",
request: { command: "pwd", turnSourceChannel: "discord" },
createdAtMs: 1,
expiresAtMs: 2,
} as const;
expect(
shouldHandleDiscordApprovalRequest({ cfg: cfg as never, accountId: "default", request }),
).toBe(true);
expect(
shouldHandleDiscordApprovalRequest({ cfg: cfg as never, accountId: "ops", request }),
).toBe(true);
});
it("describes the correct Discord exec-approval setup path", () => {
const text = getDiscordApprovalCapability().describeExecApprovalSetup?.({
channel: "discord",
channelLabel: "Discord",
});
expect(text).toContain("`channels.discord.execApprovals.approvers`");
expect(text).toContain("`commands.ownerAllowFrom`");
expect(text).not.toContain("`channels.discord.dm.allowFrom`");
});
it("describes the named-account Discord exec-approval setup path", () => {
const text = getDiscordApprovalCapability().describeExecApprovalSetup?.({
channel: "discord",
channelLabel: "Discord",
accountId: "work",
});
expect(text).toContain("`channels.discord.accounts.work.execApprovals.approvers`");
expect(text).toContain("`commands.ownerAllowFrom`");
expect(text).not.toContain("`channels.discord.execApprovals.approvers`");
});
it("normalizes prefixed turn-source channel ids", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
turnSourceChannel: "discord",
turnSourceTo: "channel:123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toEqual({ to: "123456789" });
});
it("falls back to approver DMs for Discord DM sessions with raw turn-source ids", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:dm:123456789",
turnSourceChannel: "discord",
turnSourceTo: "123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toBeNull();
});
it("falls back to approver DMs for canonical Discord direct sessions", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:direct:123456789",
turnSourceChannel: "discord",
turnSourceTo: "123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toBeNull();
});
it("falls back to approver DMs for account-scoped Discord direct sessions", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:default:direct:123456789",
turnSourceChannel: "discord",
turnSourceTo: "123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toBeNull();
});
it("ignores session-store turn targets for Discord DM sessions", async () => {
writeStore({
"agent:main:discord:dm:123456789": {
sessionId: "sess",
updatedAt: Date.now(),
origin: { provider: "discord", to: "123456789", accountId: "main" },
lastChannel: "discord",
lastTo: "123456789",
lastAccountId: "main",
},
});
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: {
...NATIVE_DELIVERY_CFG,
session: { store: STORE_PATH },
} as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:dm:123456789",
turnSourceChannel: "discord",
turnSourceTo: "123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toBeNull();
});
it("accepts raw turn-source ids when a Discord channel session backs them", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:channel:123456789",
turnSourceChannel: "discord",
turnSourceTo: "123456789",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toEqual({ to: "123456789", threadId: undefined });
});
it("falls back to extracting the channel id from the session key", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "default",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:channel:987654321",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toEqual({ to: "987654321", threadId: undefined });
});
it("preserves explicit turn-source thread ids on origin targets", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:channel:123456789:thread:777888999",
turnSourceChannel: "discord",
turnSourceTo: "channel:123456789",
turnSourceThreadId: "777888999",
turnSourceAccountId: "main",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toEqual({ to: "123456789", threadId: "777888999" });
});
it("falls back to extracting thread ids from the session key", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_DELIVERY_CFG as never,
accountId: "default",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
sessionKey: "agent:main:discord:channel:987654321:thread:444555666",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toEqual({ to: "987654321", threadId: "444555666" });
});
it("rejects origin delivery for requests bound to another Discord account", async () => {
const adapter = createDiscordNativeApprovalAdapter();
const target = await adapter.native?.resolveOriginTarget?.({
cfg: NATIVE_APPROVAL_CFG as never,
accountId: "main",
approvalKind: "plugin",
request: {
id: "abc",
request: {
title: "Plugin approval",
description: "Let plugin proceed",
turnSourceChannel: "discord",
turnSourceTo: "channel:123456789",
turnSourceAccountId: "other",
sessionKey: "agent:main:missing",
},
createdAtMs: 1,
expiresAtMs: 2,
},
});
expect(target).toBeNull();
});
});