openclaw/extensions/telegram/src/bot-message-context.acp-bin...

217 lines
6.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const ensureConfiguredBindingRouteReadyMock = vi.hoisted(() => vi.fn());
const resolveConfiguredBindingRouteMock = vi.hoisted(() => vi.fn());
vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
return {
...actual,
ensureConfiguredBindingRouteReady: (...args: unknown[]) =>
ensureConfiguredBindingRouteReadyMock(...args),
resolveConfiguredBindingRoute: (...args: unknown[]) =>
resolveConfiguredBindingRouteMock(...args),
};
});
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
function createConfiguredTelegramBinding() {
return {
spec: {
channel: "telegram",
accountId: "work",
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
agentId: "codex",
mode: "persistent",
},
record: {
bindingId: "config:acp:telegram:work:-1001234567890:topic:42",
targetSessionKey: "agent:codex:acp:binding:telegram:work:abc123",
targetKind: "session",
conversation: {
channel: "telegram",
accountId: "work",
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
},
status: "active",
boundAt: 0,
metadata: {
source: "config",
mode: "persistent",
agentId: "codex",
},
},
} as const;
}
function createConfiguredTelegramRoute() {
const configuredBinding = createConfiguredTelegramBinding();
return {
bindingResolution: {
conversation: {
channel: "telegram",
accountId: "work",
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
},
compiledBinding: {
channel: "telegram",
accountPattern: "work",
binding: {
type: "acp",
agentId: "codex",
match: {
channel: "telegram",
accountId: "work",
peer: {
kind: "group",
id: "-1001234567890:topic:42",
},
},
},
bindingConversationId: "-1001234567890:topic:42",
target: {
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
},
agentId: "codex",
provider: {
compileConfiguredBinding: () => ({
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
}),
matchInboundConversation: () => ({
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
}),
},
targetFactory: {
driverId: "acp",
materialize: () => ({
record: configuredBinding.record,
statefulTarget: {
kind: "stateful",
driverId: "acp",
sessionKey: configuredBinding.record.targetSessionKey,
agentId: configuredBinding.spec.agentId,
},
}),
},
},
match: {
conversationId: "-1001234567890:topic:42",
parentConversationId: "-1001234567890",
},
record: configuredBinding.record,
statefulTarget: {
kind: "stateful",
driverId: "acp",
sessionKey: configuredBinding.record.targetSessionKey,
agentId: configuredBinding.spec.agentId,
},
},
configuredBinding,
boundSessionKey: configuredBinding.record.targetSessionKey,
route: {
agentId: "codex",
accountId: "work",
channel: "telegram",
sessionKey: configuredBinding.record.targetSessionKey,
mainSessionKey: "agent:codex:main",
matchedBy: "binding.channel",
lastRoutePolicy: "bound",
},
} as const;
}
describe("buildTelegramMessageContext ACP configured bindings", () => {
beforeEach(() => {
ensureConfiguredBindingRouteReadyMock.mockReset();
resolveConfiguredBindingRouteMock.mockReset();
resolveConfiguredBindingRouteMock.mockReturnValue(createConfiguredTelegramRoute());
ensureConfiguredBindingRouteReadyMock.mockResolvedValue({ ok: true });
});
it("treats configured topic bindings as explicit route matches on non-default accounts", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
});
expect(ctx).not.toBeNull();
expect(ctx?.route.accountId).toBe("work");
expect(ctx?.route.matchedBy).toBe("binding.channel");
expect(ctx?.route.sessionKey).toBe("agent:codex:acp:binding:telegram:work:abc123");
expect(ensureConfiguredBindingRouteReadyMock).toHaveBeenCalledTimes(1);
});
it("skips ACP session initialization when topic access is denied", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
resolveTelegramGroupConfig: () => ({
groupConfig: { requireMention: false },
topicConfig: { enabled: false },
}),
});
expect(ctx).toBeNull();
expect(resolveConfiguredBindingRouteMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredBindingRouteReadyMock).not.toHaveBeenCalled();
});
it("defers ACP session initialization for unauthorized control commands", async () => {
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "/new",
},
cfg: {
channels: {
telegram: {},
},
commands: {
useAccessGroups: true,
},
},
});
expect(ctx).toBeNull();
expect(resolveConfiguredBindingRouteMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredBindingRouteReadyMock).not.toHaveBeenCalled();
});
it("drops inbound processing when configured ACP binding initialization fails", async () => {
ensureConfiguredBindingRouteReadyMock.mockResolvedValue({
ok: false,
error: "gateway unavailable",
});
const ctx = await buildTelegramMessageContextForTest({
accountId: "work",
message: {
chat: { id: -1001234567890, type: "supergroup", title: "OpenClaw", is_forum: true },
message_thread_id: 42,
text: "hello",
},
});
expect(ctx).toBeNull();
expect(resolveConfiguredBindingRouteMock).toHaveBeenCalledTimes(1);
expect(ensureConfiguredBindingRouteReadyMock).toHaveBeenCalledTimes(1);
});
});