openclaw/test/feishu-message-read-authority.integration.test.ts
Peter Steinberger 7f0410c2e6
feat(plugin-sdk): join service and account scheduling at retirement (#162669)
* feat(plugin-sdk): bind scheduled work to service lifetimes

* test(voice-call): bind fast-context fixture scheduling

* fix(plugin-sdk): complete rebased service type consumers
2026-10-03 00:46:23 +00:00

716 lines
25 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { Agent, createServer } from "node:https";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createOperationalRunInstanceRef } from "../src/agents/admitted-run-context.js";
import { wrapToolWithGatewayCallerIdentity } from "../src/agents/tools/gateway-caller-context.js";
import { createMessageTool } from "../src/agents/tools/message-tool-execution.js";
import { dispatchChannelMessageAction } from "../src/channels/plugins/message-action-dispatch.js";
import type {
ChannelMessageActionContext,
ChannelMessageActionName,
ChannelPlugin,
} from "../src/channels/plugins/types.public.js";
import {
clearRuntimeConfigSnapshot,
setRuntimeConfigSnapshot,
type OpenClawConfig,
} from "../src/config/config.js";
import { createAgentRuntimeApprovalAuthorityValidator } from "../src/gateway/agent-runtime-approval-authority.js";
import {
mintMessageActionTurnCapability,
revokeMessageActionTurnCapability,
} from "../src/gateway/message-action-turn-capability.js";
import { createAgentRuntimeAuthorityGuard } from "../src/gateway/server-methods/agent-runtime-authority.js";
import type { GatewayClient, GatewayRequestContext } from "../src/gateway/server-methods/types.js";
import {
claimAgentRunDelegatedAuthority,
releaseAgentRunDelegatedAuthority,
validateAgentRunDelegatedAuthority,
} from "../src/infra/agent-run-registry.js";
import { createPluginRegistry } from "../src/plugins/registry.js";
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../src/plugins/runtime.js";
import type { PluginRuntime } from "../src/plugins/runtime/types.js";
import { createPluginRecord } from "../src/plugins/status.test-fixtures.js";
import { loadBundledPluginFacade } from "../src/test-utils/bundled-plugin-public-surface.js";
import { TEST_TLS_CERT_PEM, TEST_TLS_KEY_PEM } from "./helpers/tls-fixture.js";
const transport = vi.hoisted(() => ({ agent: undefined as Agent | undefined }));
vi.mock("openclaw/plugin-sdk/extension-shared", async (original) => {
const actual = await original<typeof import("openclaw/plugin-sdk/extension-shared")>();
return { ...actual, resolveAmbientNodeProxyAgent: async () => transport.agent };
});
const CURRENT = "oc_current";
const ALLOWED = "oc_allowed";
const BLOCKED = "oc_blocked";
const DIRECT = "oc_direct";
const SELF = "ou_current_sender";
const MEMBER = "ou_group_member";
const OTHER = "ou_other_member";
const MESSAGE = "om_allowed";
const AUTH_PATH = "/open-apis/auth/v3/tenant_access_token/internal";
const MESSAGE_PATH = `/open-apis/im/v1/messages/${MESSAGE}`;
const CHAT_PATH = `/open-apis/im/v1/chats/${ALLOWED}`;
const MEMBERS_PATH = `${CHAT_PATH}/members`;
const PINS_PATH = "/open-apis/im/v1/pins";
const USER_PATH = `/open-apis/contact/v3/users/${MEMBER}`;
const PEERS_PATH = "/open-apis/contact/v3/users";
const PEERS_PAGE_TOKEN = "peers/next+%2F=";
type ActionResult = NonNullable<Awaited<ReturnType<typeof dispatchChannelMessageAction>>>;
type ProviderRequest = { method: string; path: string; query: URLSearchParams };
function createOriginatingRun(currentChat = CURRENT) {
const sessionKey = `agent:main:feishu:channel:${currentChat}`;
const operationalRunInstance = createOperationalRunInstanceRef(`feishu-read-${randomUUID()}`);
const delegatedAuthority = claimAgentRunDelegatedAuthority(operationalRunInstance);
const toolContext = {
currentChannelProvider: "feishu",
currentChannelId: currentChat,
currentChatType: currentChat === DIRECT ? ("direct" as const) : ("group" as const),
};
const turnCapability = mintMessageActionTurnCapability({
agentId: "main",
runId: operationalRunInstance.runId,
sessionKey,
requesterAccountId: "default",
requesterSenderId: SELF,
toolContext,
});
const assert = createAgentRuntimeAuthorityGuard(
{
internal: {
agentRuntimeIdentity: {
kind: "agentRuntime",
agentId: "main",
sessionKey,
operationalRunInstance,
delegatedAuthority: { kind: "local", ...delegatedAuthority },
messageActionContext: { expiresAtMs: Date.now() + 60_000, turnCapability },
},
},
} as GatewayClient,
{
validateAgentRuntimeApprovalAuthority: createAgentRuntimeApprovalAuthorityValidator(),
} as GatewayRequestContext,
() => {},
).commitGuard;
if (!assert) {
throw new Error("Expected originating Feishu run authority");
}
assert();
return {
assert,
toolContext,
revokeTurn: () => revokeMessageActionTurnCapability(turnCapability),
releaseClaim: () => releaseAgentRunDelegatedAuthority(delegatedAuthority),
createTool: (config: OpenClawConfig) =>
wrapToolWithGatewayCallerIdentity(
createMessageTool({
agentId: "main",
agentAccountId: "default",
agentSessionKey: sessionKey,
runId: operationalRunInstance.runId,
messageActionTurnCapability: turnCapability,
...toolContext,
config,
getScopedChannelsCommandSecretTargets: () => ({ targetIds: new Set<string>() }),
resolveCommandSecretRefsViaGateway: async ({ config: resolvedConfig }) => ({
resolvedConfig,
diagnostics: [],
targetStatesByPath: {},
hadUnresolvedTargets: false,
}),
}),
{
agentId: "main",
sessionKey,
operationalRunInstance,
receiptAuthority: () => validateAgentRunDelegatedAuthority(delegatedAuthority),
},
),
dispose: () => {
revokeMessageActionTurnCapability(turnCapability);
releaseAgentRunDelegatedAuthority(delegatedAuthority);
},
};
}
let feishuPlugin: ChannelPlugin;
let server: ReturnType<typeof createServer>;
let origin: string;
let appId: string;
let requests: ProviderRequest[] = [];
let pending: Promise<unknown>[] = [];
let runs: ReturnType<typeof createOriginatingRun>[] = [];
let beforeReply: ((request: ProviderRequest) => void) | undefined;
let afterProviderResult: ((result: ActionResult) => void) | undefined;
function track<T>(operation: Promise<T>): Promise<T> {
pending.push(operation);
return operation;
}
function contentRequests() {
return requests.filter((request) => request.path !== AUTH_PATH).map((request) => request.path);
}
function expectResult(result: ActionResult | null, details: Record<string, unknown>) {
expect(result?.details).toMatchObject(details);
const text = result?.content.find((part) => part.type === "text");
if (text?.type !== "text") {
throw new Error("Expected a model-visible Feishu result");
}
expect(JSON.parse(text.text)).toMatchObject(details);
}
beforeAll(async () => {
// TLS trust is fixture-owned; the real SDK can connect only to this loopback host.
transport.agent = new Agent({ rejectUnauthorized: false });
const connect = transport.agent.createConnection.bind(transport.agent);
transport.agent.createConnection = (options, callback) => {
if (options.host !== "127.0.0.1") {
throw new Error("Feishu fixture refused a non-loopback destination");
}
return connect(options, callback);
};
({ feishuPlugin } = await loadBundledPluginFacade<{ feishuPlugin: ChannelPlugin }>({
pluginId: "feishu",
artifactBasename: "api.js",
}));
server = createServer({ key: TEST_TLS_KEY_PEM, cert: TEST_TLS_CERT_PEM }, (request, response) => {
void (async () => {
const chunks: Buffer[] = [];
for await (const chunk of request) {
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
}
const url = new URL(request.url ?? "/", origin);
const recorded = {
method: request.method ?? "",
path: url.pathname,
query: url.searchParams,
};
requests.push(recorded);
beforeReply?.(recorded);
response.setHeader("content-type", "application/json");
if (url.pathname === AUTH_PATH) {
const body = JSON.parse(Buffer.concat(chunks).toString("utf8")) as Record<string, unknown>;
if (
request.method !== "POST" ||
body.app_id !== appId ||
body.app_secret !== "loopback-placeholder"
) {
response.writeHead(401);
response.end(JSON.stringify({ code: 99991663, msg: "invalid fixture application" }));
return;
}
response.end(
JSON.stringify({ code: 0, tenant_access_token: `tat_${appId}`, expire: 7200 }),
);
return;
}
if (request.method !== "GET" || request.headers.authorization !== `Bearer tat_${appId}`) {
response.writeHead(401);
response.end(JSON.stringify({ code: 99991663, msg: "unexpected fixture request" }));
return;
}
let data: unknown;
if (url.pathname === MESSAGE_PATH) {
data = {
items: [
{
message_id: MESSAGE,
chat_id: ALLOWED,
msg_type: "text",
body: { content: JSON.stringify({ text: "allowed context" }) },
},
],
};
} else if (url.pathname === `${MESSAGE_PATH}/reactions`) {
data = {
items: [
{
reaction_id: "reaction-1",
reaction_type: { emoji_type: "THUMBSUP" },
operator: { operator_type: "user", operator_id: MEMBER },
},
],
has_more: false,
};
} else if (url.pathname === PINS_PATH) {
data = { items: [{ message_id: MESSAGE, chat_id: ALLOWED }], has_more: false };
} else if (url.pathname === MEMBERS_PATH) {
const next = url.searchParams.get("page_token") === "members-next";
data = {
items: [{ member_id: next ? MEMBER : OTHER, member_id_type: "open_id" }],
has_more: !next,
...(next ? {} : { page_token: "members-next" }),
};
} else if (url.pathname === "/open-apis/im/v1/chats") {
const next = url.searchParams.get("page_token") === "groups-next";
data = {
items: [{ chat_id: next ? ALLOWED : BLOCKED, name: next ? "Allowed" : "Blocked" }],
has_more: !next,
...(next ? {} : { page_token: "groups-next" }),
};
} else if (url.pathname.startsWith("/open-apis/im/v1/chats/")) {
data = {
name: url.pathname.endsWith(DIRECT) ? "Direct" : "Allowed",
chat_mode: url.pathname.endsWith(DIRECT) ? "p2p" : "group",
chat_type: "private",
};
} else if (url.pathname === PEERS_PATH) {
const next = url.searchParams.get("page_token") === PEERS_PAGE_TOKEN;
data = {
items: [{ open_id: next ? OTHER : SELF, name: next ? "Other" : "Current sender" }],
has_more: !next,
...(next ? {} : { page_token: PEERS_PAGE_TOKEN }),
};
} else if (url.pathname.startsWith("/open-apis/contact/v3/users/")) {
const id = url.pathname.split("/").at(-1);
data = { user: { open_id: id, name: id === SELF ? "Current sender" : "Allowed member" } };
} else {
response.writeHead(404);
response.end(JSON.stringify({ code: 404, msg: "unhandled fixture route" }));
return;
}
response.end(JSON.stringify({ code: 0, data }));
})().catch((error: unknown) => {
response.writeHead(500, { "content-type": "application/json" });
response.end(JSON.stringify({ error: String(error) }));
});
});
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("Expected loopback TCP listener");
}
origin = `https://127.0.0.1:${address.port}`;
});
afterEach(async () => {
await Promise.allSettled(pending);
runs.forEach((run) => run.dispose());
runs = [];
pending = [];
requests = [];
beforeReply = undefined;
afterProviderResult = undefined;
resetPluginRuntimeStateForTest();
clearRuntimeConfigSnapshot();
vi.unstubAllEnvs();
});
afterAll(async () => {
transport.agent?.destroy();
server.closeAllConnections();
await new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
vi.doUnmock("openclaw/plugin-sdk/extension-shared");
vi.resetModules();
});
function createFixture(
options: {
bundled?: boolean;
currentChat?: string;
} = {},
) {
vi.stubEnv("OPENCLAW_PROXY_ACTIVE", "0");
appId = `cli_feishu_${randomUUID()}`;
const stickerSets: Record<string, Record<string, string[]>> = {
[appId]: { file_allowed: ["thumbs up"] },
other_application: { file_other: ["thumbs up"] },
};
const settings = {
enabled: true,
appId,
appSecret: "loopback-placeholder", // pragma: allowlist secret
domain: origin,
groupPolicy: "allowlist" as "allowlist" | "open",
groupAllowFrom: [ALLOWED, BLOCKED],
groups: { [ALLOWED]: { enabled: true }, [BLOCKED]: { enabled: false } },
allowFrom: [SELF],
dms: { [OTHER]: {} },
actions: { reactions: true, sticker: true },
stickerSets,
};
const cfg = { channels: { feishu: settings } } satisfies OpenClawConfig;
const run = createOriginatingRun(options.currentChat);
runs.push(run);
const owner = createPluginRegistry({
logger: { info() {}, warn() {}, error() {}, debug() {} },
runtime: {} as PluginRuntime,
activateGlobalSideEffects: false,
});
// Registrar trust is a fixture; the prerequisite owns installer provenance proof.
const record = createPluginRecord({
id: "feishu",
origin: options.bundled ? "bundled" : "global",
trustedOfficialInstall: !options.bundled,
});
const plugin: ChannelPlugin = {
...feishuPlugin,
actions: {
...feishuPlugin.actions!,
handleAction: async (ctx) => {
const result = await feishuPlugin.actions!.handleAction!(ctx);
// Revoke at the registered handler's return boundary after a real local consumer.
afterProviderResult?.(result);
return result;
},
},
};
owner.registry.plugins.push(record);
owner.createApi(record, { config: cfg, registrationMode: "full" }).registerChannel({ plugin });
setActivePluginRegistry(owner.registry);
const dispatch = (
action: ChannelMessageActionName,
params: Record<string, unknown>,
overrides: Partial<ChannelMessageActionContext> = {},
) =>
track(
dispatchChannelMessageAction({
cfg,
channel: "feishu",
action,
params,
accountId: "default",
requesterAccountId: "default",
requesterSenderId: SELF,
conversationReadOrigin: "delegated",
assertDirectAdapterHandoff: run.assert,
toolContext: run.toolContext,
...overrides,
}),
);
return { cfg, settings, record, run, dispatch };
}
const readCases: Array<{
action: ChannelMessageActionName;
params: Record<string, unknown>;
details: Record<string, unknown>;
paths: string[];
}> = [
{
action: "read",
params: { chatId: ALLOWED, messageId: MESSAGE },
details: { message: { messageId: MESSAGE, chatId: ALLOWED, content: "allowed context" } },
paths: [MESSAGE_PATH, CHAT_PATH],
},
{
action: "reactions",
params: { chatId: ALLOWED, messageId: MESSAGE },
details: {
reactions: [{ reactionId: "reaction-1", emojiType: "THUMBSUP", operatorId: MEMBER }],
},
paths: [MESSAGE_PATH, CHAT_PATH, `${MESSAGE_PATH}/reactions`],
},
{
action: "list-pins",
params: { chatId: ALLOWED },
details: { chatId: ALLOWED, pins: [{ messageId: MESSAGE, chatId: ALLOWED }] },
paths: [CHAT_PATH, PINS_PATH],
},
{
action: "member-info",
params: { chatId: ALLOWED, memberId: MEMBER },
details: { member: { member_id: MEMBER, name: "Allowed member" } },
paths: [CHAT_PATH, MEMBERS_PATH, MEMBERS_PATH, USER_PATH],
},
{
action: "channel-info",
params: { chatId: ALLOWED },
details: { channel: { chat_id: ALLOWED, name: "Allowed" } },
paths: [CHAT_PATH],
},
{
action: "channel-list",
params: {},
details: { groups: [{ kind: "group", id: ALLOWED }], peers: [{ kind: "user", id: SELF }] },
paths: [],
},
{
action: "sticker-search",
params: { query: "thumbs" },
details: { stickers: [{ fileId: "file_allowed", keyword: "thumbs up" }], truncated: false },
paths: [],
},
];
describe.each([false, true])("Feishu provider reads (bundled: %s)", (bundled) => {
it.each(bundled ? readCases.filter(({ action }) => action === "read") : readCases)(
"runs $action through its real consumer",
async ({ action, params, details, paths }) => {
const fixture = createFixture({ bundled });
expectResult(await fixture.dispatch(action, params), details);
expect(contentRequests()).toEqual(paths);
expect(
requests
.filter((request) => request.path !== AUTH_PATH)
.every((request) => request.method === "GET"),
).toBe(true);
},
);
});
it("filters live directory pages before applying the limit", async () => {
const fixture = createFixture();
fixture.settings.groupPolicy = "open";
fixture.settings.allowFrom = ["*"];
expectResult(await fixture.dispatch("channel-list", { limit: 1 }), {
groups: [{ kind: "group", id: ALLOWED, name: "Allowed" }],
peers: [{ kind: "user", id: SELF, name: "Current sender" }],
});
expect(contentRequests().filter((path) => path === "/open-apis/im/v1/chats")).toHaveLength(2);
expect(contentRequests()).toContain("/open-apis/contact/v3/users");
});
it("finds a live peer on later pages through the registered channel-list action", async () => {
const fixture = createFixture();
fixture.settings.allowFrom = ["*"];
expectResult(
await fixture.dispatch("channel-list", { scope: "peers", query: "Other", limit: 1 }),
{ peers: [{ kind: "user", id: OTHER, name: "Other" }] },
);
const pages = requests.filter((request) => request.path === PEERS_PATH);
expect(pages).toHaveLength(2);
expect(pages[0]?.query.get("page_token")).toBeNull();
expect(pages[1]?.query.get("page_token")).toBe(PEERS_PAGE_TOKEN);
expect(contentRequests()).toEqual([PEERS_PATH, PEERS_PATH]);
});
it("stops live peer pagination when the plugin retires between pages", async () => {
const fixture = createFixture();
fixture.settings.allowFrom = ["*"];
beforeReply = (request) => {
if (request.path !== PEERS_PATH) {
return;
}
fixture.record.enabled = false;
};
await expect(
fixture.dispatch("channel-list", { scope: "peers", query: "Other", limit: 1 }),
).rejects.toThrow("no longer active");
expect(contentRequests()).toEqual([PEERS_PATH]);
});
it("cancels live peer pagination through the local message tool", async () => {
const fixture = createFixture();
fixture.settings.allowFrom = ["*"];
setRuntimeConfigSnapshot(fixture.cfg, fixture.cfg);
const tool = fixture.run.createTool(fixture.cfg);
const controller = new AbortController();
beforeReply = (request) => {
if (request.path === PEERS_PATH) {
controller.abort();
}
};
await expect(
track(
tool.execute(
"feishu-peer-directory",
{ action: "channel-list", channel: "feishu", scope: "peers", query: "Other", limit: 1 },
controller.signal,
),
),
).rejects.toMatchObject({ name: "AbortError" });
expect(contentRequests()).toEqual([PEERS_PATH]);
});
it.each([SELF, OTHER])(
"limits a direct-chat profile read to the current sender (%s)",
async (memberId) => {
const fixture = createFixture({ currentChat: DIRECT });
const result = fixture.dispatch("member-info", { chatId: DIRECT, memberId });
if (memberId === SELF) {
expectResult(await result, { member: { member_id: SELF, name: "Current sender" } });
expect(contentRequests()).toEqual([
`/open-apis/im/v1/chats/${DIRECT}`,
`/open-apis/contact/v3/users/${SELF}`,
]);
} else {
await expect(result).rejects.toThrow("limited to the current sender");
expect(contentRequests()).toEqual([`/open-apis/im/v1/chats/${DIRECT}`]);
}
},
);
it.each(["account", "origin", "target", "reactions", "sticker", "catalog"] as const)(
"retains the %s denial before provider I/O",
async (kind) => {
const fixture = createFixture();
let action: ChannelMessageActionName = "read";
let params: Record<string, unknown> = { chatId: ALLOWED, messageId: MESSAGE };
let overrides: Partial<ChannelMessageActionContext> = {};
let error = "current provider and account";
if (kind === "account") {
overrides = { requesterAccountId: "other" };
}
if (kind === "origin") {
overrides = { toolContext: undefined };
}
if (kind === "target") {
params.chatId = BLOCKED;
error = "not allowed";
}
if (kind === "reactions") {
action = "reactions";
fixture.settings.actions.reactions = false;
error = "disabled";
}
if (kind === "sticker" || kind === "catalog") {
action = "sticker-search";
params = { query: "thumbs" };
if (kind === "sticker") {
fixture.settings.actions.sticker = false;
error = "disabled";
} else {
delete fixture.settings.stickerSets[fixture.settings.appId];
error = "this account's appId";
}
}
await expect(fixture.dispatch(action, params, overrides)).rejects.toThrow(error);
expect(requests).toEqual([]);
},
);
it.each([
["plugin", "metadata"],
["plugin", "result"],
["turn", "result"],
["claim", "result"],
] as const)("rejects %s retirement at %s", async (owner, phase) => {
const fixture = createFixture();
beforeReply = (request) => {
if (request.path !== (phase === "metadata" ? CHAT_PATH : PINS_PATH)) {
return;
}
if (owner === "plugin") {
fixture.record.enabled = false;
}
if (owner === "turn") {
fixture.run.revokeTurn();
}
if (owner === "claim") {
fixture.run.releaseClaim();
}
};
await expect(fixture.dispatch("list-pins", { chatId: ALLOWED })).rejects.toThrow(
"no longer active",
);
expect(contentRequests()).toEqual(phase === "metadata" ? [CHAT_PATH] : [CHAT_PATH, PINS_PATH]);
});
it.each(["sticker-search", "channel-list", "member-info"] as const)(
"rejects a retired local %s result",
async (action) => {
const fixture = createFixture({ currentChat: action === "member-info" ? DIRECT : CURRENT });
let observed: ActionResult | undefined;
afterProviderResult = (result) => {
observed = result;
if (action === "sticker-search") {
fixture.run.releaseClaim();
}
if (action === "channel-list") {
fixture.record.enabled = false;
}
if (action === "member-info") {
fixture.run.revokeTurn();
}
};
const params =
action === "sticker-search"
? { query: "thumbs" }
: action === "member-info"
? { chatId: DIRECT }
: {};
await expect(fixture.dispatch(action, params)).rejects.toThrow("no longer active");
if (action === "sticker-search") {
expectResult(observed ?? null, { stickers: [{ fileId: "file_allowed" }] });
}
if (action === "channel-list") {
expectResult(observed ?? null, { groups: [{ id: ALLOWED }], peers: [{ id: SELF }] });
}
if (action === "member-info") {
expectResult(observed ?? null, { members: [{ member_id: SELF }] });
}
expect(contentRequests()).toEqual(
action === "member-info" ? [`/open-apis/im/v1/chats/${DIRECT}`] : [],
);
},
);
it.each(["allowed", "turn", "claim"] as const)(
"uses the normal local message tool (%s)",
async (mode) => {
const fixture = createFixture();
setRuntimeConfigSnapshot(fixture.cfg, fixture.cfg);
const tool = fixture.run.createTool(fixture.cfg);
beforeReply = (request) => {
if (request.path !== CHAT_PATH) {
return;
}
if (mode === "turn") {
fixture.run.revokeTurn();
}
if (mode === "claim") {
fixture.run.releaseClaim();
}
};
const result = track(
tool.execute("feishu-read", {
action: "read",
channel: "feishu",
target: `chat:${ALLOWED}`,
messageId: MESSAGE,
}),
);
if (mode === "allowed") {
expectResult(await result, { message: { messageId: MESSAGE, content: "allowed context" } });
} else {
await expect(result).rejects.toThrow("no longer active");
}
expect(contentRequests()).toEqual([MESSAGE_PATH, CHAT_PATH]);
},
);
it("rejects a configured account disabled after local message-tool creation", async () => {
const fixture = createFixture();
const cfg = {
channels: {
feishu: {
...fixture.settings,
accounts: { default: { enabled: true } },
},
},
} satisfies OpenClawConfig;
setRuntimeConfigSnapshot(cfg, cfg);
const tool = fixture.run.createTool(cfg);
cfg.channels.feishu.accounts.default.enabled = false;
setRuntimeConfigSnapshot(cfg, cfg);
expect(feishuPlugin.config.resolveAccount(cfg, "default")).toMatchObject({
configured: true,
enabled: false,
});
await expect(
track(
tool.execute("feishu-disabled-read", {
action: "read",
channel: "feishu",
accountId: "default",
target: `chat:${ALLOWED}`,
messageId: MESSAGE,
}),
),
).rejects.toThrow('Account "default" for channel feishu is disabled.');
expect(requests).toEqual([]);
});