From 925e03931665deeccdcbf01c14f9bc8f3e33c3b1 Mon Sep 17 00:00:00 2001 From: MaxxxDong <186893345+MaxxxDong@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:06:56 +0800 Subject: [PATCH] test(ci): cover matrix binding contract and smoke deps --- .github/workflows/install-smoke.yml | 2 +- src/channels/plugins/contracts/registry.ts | 101 ++++++++++++++++++++- src/channels/plugins/contracts/suites.ts | 6 +- 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index a8115f1644a..37cf662b2d7 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -85,7 +85,7 @@ jobs: node -e " const Module = require(\"node:module\"); const requireFromMatrix = Module.createRequire(\"/app/extensions/matrix/package.json\"); - requireFromMatrix.resolve(\"@vector-im/matrix-bot-sdk/package.json\"); + requireFromMatrix.resolve(\"matrix-js-sdk/package.json\"); requireFromMatrix.resolve(\"@matrix-org/matrix-sdk-crypto-nodejs/package.json\"); const { spawnSync } = require(\"node:child_process\"); const run = spawnSync(\"openclaw\", [\"plugins\", \"list\", \"--json\"], { encoding: \"utf8\" }); diff --git a/src/channels/plugins/contracts/registry.ts b/src/channels/plugins/contracts/registry.ts index 94892151c7b..d7b7deb2d59 100644 --- a/src/channels/plugins/contracts/registry.ts +++ b/src/channels/plugins/contracts/registry.ts @@ -1,3 +1,6 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; import { expect, vi } from "vitest"; import { __testing as discordThreadBindingTesting, @@ -126,7 +129,7 @@ type DirectoryContractEntry = { type SessionBindingContractEntry = { id: string; expectedCapabilities: SessionBindingCapabilities; - getCapabilities: () => SessionBindingCapabilities; + getCapabilities: () => SessionBindingCapabilities | Promise; bindAndResolve: () => Promise; unbindAndVerify: (binding: SessionBindingRecord) => Promise; cleanup: () => Promise | void; @@ -589,6 +592,50 @@ const baseSessionBindingCfg = { session: { mainKey: "main", scope: "per-sender" }, } satisfies OpenClawConfig; +const matrixSessionBindingAuth = { + accountId: "default", + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + accessToken: "token", +} as const; + +let matrixSessionBindingStateDir: string | null = null; + +async function loadMatrixThreadBindingsModule() { + return await import("../../../../extensions/matrix/src/matrix/thread-bindings.js"); +} + +async function loadMatrixRuntimeModule() { + return await import("../../../../extensions/matrix/src/runtime.js"); +} + +async function ensureMatrixSessionBindingManager() { + const [{ createMatrixThreadBindingManager }, { setMatrixRuntime }] = await Promise.all([ + loadMatrixThreadBindingsModule(), + loadMatrixRuntimeModule(), + ]); + if (!matrixSessionBindingStateDir) { + matrixSessionBindingStateDir = await fs.mkdtemp( + path.join(os.tmpdir(), "matrix-session-binding-contract-"), + ); + } + setMatrixRuntime({ + state: { + resolveStateDir: () => matrixSessionBindingStateDir as string, + }, + } as never); + return await createMatrixThreadBindingManager({ + accountId: "default", + auth: matrixSessionBindingAuth, + client: { + sendMessage: vi.fn(async () => "$matrix-event"), + } as never, + idleTimeoutMs: 24 * 60 * 60 * 1000, + maxAgeMs: 0, + enableSweeper: false, + }); +} + export const sessionBindingContractRegistry: SessionBindingContractEntry[] = [ { id: "discord", @@ -708,6 +755,58 @@ export const sessionBindingContractRegistry: SessionBindingContractEntry[] = [ }); }, }, + { + id: "matrix", + expectedCapabilities: { + adapterAvailable: true, + bindSupported: true, + unbindSupported: true, + placements: ["current", "child"], + }, + getCapabilities: async () => { + await ensureMatrixSessionBindingManager(); + return getSessionBindingService().getCapabilities({ + channel: "matrix", + accountId: "default", + }); + }, + bindAndResolve: async () => { + await ensureMatrixSessionBindingManager(); + const service = getSessionBindingService(); + const binding = await service.bind({ + targetSessionKey: "agent:matrix:subagent:thread-1", + targetKind: "subagent", + conversation: { + channel: "matrix", + accountId: "default", + conversationId: "$matrix-event", + parentConversationId: "!room:example", + }, + placement: "current", + metadata: { + introText: "matrix binding active", + label: "matrix-main", + }, + }); + expectResolvedSessionBinding({ + channel: "matrix", + accountId: "default", + conversationId: "$matrix-event", + targetSessionKey: "agent:matrix:subagent:thread-1", + }); + return binding; + }, + unbindAndVerify: unbindAndExpectClearedSessionBinding, + cleanup: async () => { + const { resetMatrixThreadBindingsForTests } = await loadMatrixThreadBindingsModule(); + resetMatrixThreadBindingsForTests(); + expectClearedSessionBinding({ + channel: "matrix", + accountId: "default", + conversationId: "$matrix-event", + }); + }, + }, { id: "telegram", expectedCapabilities: { diff --git a/src/channels/plugins/contracts/suites.ts b/src/channels/plugins/contracts/suites.ts index 892d4b293f9..c2c85bfb747 100644 --- a/src/channels/plugins/contracts/suites.ts +++ b/src/channels/plugins/contracts/suites.ts @@ -478,14 +478,14 @@ export function installChannelDirectoryContractSuite(params: { } export function installSessionBindingContractSuite(params: { - getCapabilities: () => SessionBindingCapabilities; + getCapabilities: () => SessionBindingCapabilities | Promise; bindAndResolve: () => Promise; unbindAndVerify: (binding: SessionBindingRecord) => Promise; cleanup: () => Promise | void; expectedCapabilities: SessionBindingCapabilities; }) { - it("registers the expected session binding capabilities", () => { - expect(params.getCapabilities()).toEqual(params.expectedCapabilities); + it("registers the expected session binding capabilities", async () => { + await expect(params.getCapabilities()).resolves.toEqual(params.expectedCapabilities); }); it("binds and resolves a session binding through the shared service", async () => {