From feed4007feba4e1f9d7287d0d1da87a9af6e21f4 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 4 Apr 2026 02:14:46 +0900 Subject: [PATCH] test(contracts): localize surface registry helpers --- ...directory.registry-backed.contract.test.ts | 2 +- ...aces-only.registry-backed.contract.test.ts | 2 +- ...threading.registry-backed.contract.test.ts | 2 +- .../channels/surface-contract-registry.ts | 113 ++++++++++++++++++ 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 test/helpers/channels/surface-contract-registry.ts diff --git a/src/channels/plugins/contracts/directory.registry-backed.contract.test.ts b/src/channels/plugins/contracts/directory.registry-backed.contract.test.ts index 6f67aeb9358..b203dfb1da0 100644 --- a/src/channels/plugins/contracts/directory.registry-backed.contract.test.ts +++ b/src/channels/plugins/contracts/directory.registry-backed.contract.test.ts @@ -1,6 +1,6 @@ import { describe } from "vitest"; +import { getDirectoryContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js"; import { installChannelDirectoryContractSuite } from "../../../../test/helpers/channels/threading-directory-contract-suites.js"; -import { getDirectoryContractRegistry } from "./registry.js"; for (const entry of getDirectoryContractRegistry()) { describe(`${entry.id} directory contract`, () => { diff --git a/src/channels/plugins/contracts/surfaces-only.registry-backed.contract.test.ts b/src/channels/plugins/contracts/surfaces-only.registry-backed.contract.test.ts index f10e44dfc7b..dc42df3d95b 100644 --- a/src/channels/plugins/contracts/surfaces-only.registry-backed.contract.test.ts +++ b/src/channels/plugins/contracts/surfaces-only.registry-backed.contract.test.ts @@ -1,6 +1,6 @@ import { describe } from "vitest"; +import { getSurfaceContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js"; import { installChannelSurfaceContractSuite } from "../../../../test/helpers/channels/surface-contract-suite.js"; -import { getSurfaceContractRegistry } from "./registry.js"; for (const entry of getSurfaceContractRegistry()) { for (const surface of entry.surfaces) { diff --git a/src/channels/plugins/contracts/threading.registry-backed.contract.test.ts b/src/channels/plugins/contracts/threading.registry-backed.contract.test.ts index db3c26e8502..8dbf89f3d75 100644 --- a/src/channels/plugins/contracts/threading.registry-backed.contract.test.ts +++ b/src/channels/plugins/contracts/threading.registry-backed.contract.test.ts @@ -1,6 +1,6 @@ import { describe } from "vitest"; +import { getThreadingContractRegistry } from "../../../../test/helpers/channels/surface-contract-registry.js"; import { installChannelThreadingContractSuite } from "../../../../test/helpers/channels/threading-directory-contract-suites.js"; -import { getThreadingContractRegistry } from "./registry.js"; for (const entry of getThreadingContractRegistry()) { describe(`${entry.id} threading contract`, () => { diff --git a/test/helpers/channels/surface-contract-registry.ts b/test/helpers/channels/surface-contract-registry.ts new file mode 100644 index 00000000000..ed4d6564ad4 --- /dev/null +++ b/test/helpers/channels/surface-contract-registry.ts @@ -0,0 +1,113 @@ +import { vi } from "vitest"; +import { + listBundledChannelPlugins, + setBundledChannelRuntime, +} from "../../../src/channels/plugins/bundled.js"; +import { + channelPluginSurfaceKeys, + type ChannelPluginSurface, +} from "../../../src/channels/plugins/contracts/manifest.js"; +import type { ChannelPlugin } from "../../../src/channels/plugins/types.js"; +import type { OpenClawConfig } from "../../../src/config/config.js"; +import { + listLineAccountIds, + resolveDefaultLineAccountId, + resolveLineAccount, +} from "../../../src/plugin-sdk/line.js"; + +function buildBundledPluginModuleId(pluginId: string, artifactBasename: string): string { + return ["..", "..", "..", "extensions", pluginId, artifactBasename].join("/"); +} + +type SurfaceContractEntry = { + id: string; + plugin: Pick< + ChannelPlugin, + | "id" + | "actions" + | "setup" + | "status" + | "outbound" + | "messaging" + | "threading" + | "directory" + | "gateway" + >; + surfaces: readonly ChannelPluginSurface[]; +}; + +type ThreadingContractEntry = { + id: string; + plugin: Pick; +}; + +type DirectoryContractEntry = { + id: string; + plugin: Pick; + coverage: "lookups" | "presence"; + cfg?: OpenClawConfig; + accountId?: string; +}; + +const sendMessageMatrixMock = vi.hoisted(() => + vi.fn(async (to: string, _message: string, opts?: { threadId?: string }) => ({ + messageId: opts?.threadId ? "$matrix-thread" : "$matrix-root", + roomId: to.replace(/^room:/, ""), + })), +); + +setBundledChannelRuntime("line", { + channel: { + line: { + listLineAccountIds, + resolveDefaultLineAccountId, + resolveLineAccount: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId?: string }) => + resolveLineAccount({ cfg, accountId }), + }, + }, +} as never); + +vi.mock(buildBundledPluginModuleId("matrix", "runtime-api.js"), async () => { + const matrixRuntimeApiModuleId = buildBundledPluginModuleId("matrix", "runtime-api.js"); + const actual = await vi.importActual(matrixRuntimeApiModuleId); + return { + ...actual, + sendMessageMatrix: sendMessageMatrixMock, + }; +}); + +let surfaceContractRegistryCache: SurfaceContractEntry[] | undefined; +let threadingContractRegistryCache: ThreadingContractEntry[] | undefined; +let directoryContractRegistryCache: DirectoryContractEntry[] | undefined; + +export function getSurfaceContractRegistry(): SurfaceContractEntry[] { + surfaceContractRegistryCache ??= listBundledChannelPlugins().map((plugin) => ({ + id: plugin.id, + plugin, + surfaces: channelPluginSurfaceKeys.filter((surface) => Boolean(plugin[surface])), + })); + return surfaceContractRegistryCache; +} + +export function getThreadingContractRegistry(): ThreadingContractEntry[] { + threadingContractRegistryCache ??= getSurfaceContractRegistry() + .filter((entry) => entry.surfaces.includes("threading")) + .map((entry) => ({ + id: entry.id, + plugin: entry.plugin, + })); + return threadingContractRegistryCache; +} + +const directoryPresenceOnlyIds = new Set(["whatsapp", "zalouser"]); + +export function getDirectoryContractRegistry(): DirectoryContractEntry[] { + directoryContractRegistryCache ??= getSurfaceContractRegistry() + .filter((entry) => entry.surfaces.includes("directory")) + .map((entry) => ({ + id: entry.id, + plugin: entry.plugin, + coverage: directoryPresenceOnlyIds.has(entry.id) ? "presence" : "lookups", + })); + return directoryContractRegistryCache; +}