Files
openclaw/src/channels/plugins/binding-routing.test.ts
2026-04-22 23:16:57 +01:00

115 lines
3.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import {
__testing,
registerSessionBindingAdapter,
type SessionBindingAdapter,
type SessionBindingRecord,
} from "../../infra/outbound/session-binding-service.js";
import type { ResolvedAgentRoute } from "../../routing/resolve-route.js";
import { resolveRuntimeConversationBindingRoute } from "./binding-routing.js";
function createRoute(): ResolvedAgentRoute {
return {
agentId: "main",
channel: "demo",
accountId: "default",
sessionKey: "agent:main:main",
mainSessionKey: "agent:main:main",
lastRoutePolicy: "main",
matchedBy: "default",
};
}
function createBinding(overrides?: Partial<SessionBindingRecord>): SessionBindingRecord {
return {
bindingId: "binding-1",
targetSessionKey: "agent:review:acp:session-1",
targetKind: "session",
conversation: {
channel: "demo",
accountId: "default",
conversationId: "room-1",
},
status: "active",
boundAt: 1,
...overrides,
};
}
function registerAdapter(record: SessionBindingRecord | null): {
resolveByConversation: ReturnType<typeof vi.fn>;
touch: ReturnType<typeof vi.fn>;
} {
const resolveByConversation = vi.fn<SessionBindingAdapter["resolveByConversation"]>(() => record);
const touch = vi.fn<NonNullable<SessionBindingAdapter["touch"]>>();
registerSessionBindingAdapter({
channel: "demo",
accountId: "default",
listBySession: () => [],
resolveByConversation,
touch,
});
return { resolveByConversation, touch };
}
describe("runtime conversation binding route", () => {
beforeEach(() => {
__testing.resetSessionBindingAdaptersForTests();
});
it("rewrites the route to a runtime-bound ACP session and touches the binding", () => {
const binding = createBinding();
const { resolveByConversation, touch } = registerAdapter(binding);
const result = resolveRuntimeConversationBindingRoute({
route: createRoute(),
conversation: {
channel: "demo",
accountId: "default",
conversationId: "room-1",
},
});
expect(resolveByConversation).toHaveBeenCalledWith({
channel: "demo",
accountId: "default",
conversationId: "room-1",
});
expect(touch).toHaveBeenCalledWith("binding-1", undefined);
expect(result.boundSessionKey).toBe("agent:review:acp:session-1");
expect(result.boundAgentId).toBe("review");
expect(result.route).toMatchObject({
agentId: "review",
sessionKey: "agent:review:acp:session-1",
lastRoutePolicy: "session",
matchedBy: "binding.channel",
});
});
it("touches plugin-owned bindings without rewriting the channel route", () => {
const route = createRoute();
const binding = createBinding({
metadata: {
pluginBindingOwner: "plugin",
pluginId: "demo-plugin",
pluginRoot: "/tmp/demo-plugin",
},
});
const { touch } = registerAdapter(binding);
const result = resolveRuntimeConversationBindingRoute({
route,
conversation: {
channel: "demo",
accountId: "default",
conversationId: "room-1",
},
});
expect(touch).toHaveBeenCalledWith("binding-1", undefined);
expect(result.bindingRecord).toBe(binding);
expect(result.boundSessionKey).toBeUndefined();
expect(result.route).toBe(route);
});
});