import { beforeEach, describe, expect, it, vi } from "vitest"; import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js"; import type { RuntimeEnv } from "../runtime-api.js"; import { matrixPlugin } from "./channel.js"; import { resolveMatrixAccount } from "./matrix/accounts.js"; import { resolveMatrixConfigForAccount } from "./matrix/client/config.js"; import { installMatrixTestRuntime } from "./test-runtime.js"; import type { CoreConfig } from "./types.js"; describe("matrix directory", () => { const runtimeEnv: RuntimeEnv = createRuntimeEnv(); beforeEach(() => { installMatrixTestRuntime(); }); it("lists peers and groups from config", async () => { const cfg = { channels: { matrix: { dm: { allowFrom: ["matrix:@alice:example.org", "bob"] }, groupAllowFrom: ["@dana:example.org"], groups: { "!room1:example.org": { users: ["@carol:example.org"] }, "#alias:example.org": { users: [] }, }, }, }, } as unknown as CoreConfig; expect(matrixPlugin.directory).toBeTruthy(); expect(matrixPlugin.directory?.listPeers).toBeTruthy(); expect(matrixPlugin.directory?.listGroups).toBeTruthy(); await expect( matrixPlugin.directory!.listPeers!({ cfg, accountId: undefined, query: undefined, limit: undefined, runtime: runtimeEnv, }), ).resolves.toEqual( expect.arrayContaining([ { kind: "user", id: "user:@alice:example.org" }, { kind: "user", id: "bob", name: "incomplete id; expected @user:server" }, { kind: "user", id: "user:@carol:example.org" }, { kind: "user", id: "user:@dana:example.org" }, ]), ); await expect( matrixPlugin.directory!.listGroups!({ cfg, accountId: undefined, query: undefined, limit: undefined, runtime: runtimeEnv, }), ).resolves.toEqual( expect.arrayContaining([ { kind: "group", id: "room:!room1:example.org" }, { kind: "group", id: "#alias:example.org" }, ]), ); }); it("resolves replyToMode from account config", () => { const cfg = { channels: { matrix: { replyToMode: "off", accounts: { Assistant: { replyToMode: "all", }, }, }, }, } as unknown as CoreConfig; expect(matrixPlugin.threading?.resolveReplyToMode).toBeTruthy(); expect( matrixPlugin.threading?.resolveReplyToMode?.({ cfg, accountId: "assistant", chatType: "direct", }), ).toBe("all"); expect( matrixPlugin.threading?.resolveReplyToMode?.({ cfg, accountId: "default", chatType: "direct", }), ).toBe("off"); }); it("only exposes real Matrix thread ids in tool context", () => { expect( matrixPlugin.threading?.buildToolContext?.({ cfg: {} as CoreConfig, context: { To: "room:!room:example.org", ReplyToId: "$reply", }, hasRepliedRef: { value: false }, }), ).toEqual({ currentChannelId: "room:!room:example.org", currentThreadTs: undefined, hasRepliedRef: { value: false }, }); expect( matrixPlugin.threading?.buildToolContext?.({ cfg: {} as CoreConfig, context: { To: "room:!room:example.org", ReplyToId: "$reply", MessageThreadId: "$thread", }, hasRepliedRef: { value: true }, }), ).toEqual({ currentChannelId: "room:!room:example.org", currentThreadTs: "$thread", hasRepliedRef: { value: true }, }); }); it("exposes Matrix direct user id in dm tool context", () => { expect( matrixPlugin.threading?.buildToolContext?.({ cfg: {} as CoreConfig, context: { From: "matrix:@alice:example.org", To: "room:!dm:example.org", ChatType: "direct", MessageThreadId: "$thread", }, hasRepliedRef: { value: false }, }), ).toEqual({ currentChannelId: "room:!dm:example.org", currentThreadTs: "$thread", currentDirectUserId: "@alice:example.org", hasRepliedRef: { value: false }, }); }); it("accepts raw room ids when inferring Matrix direct user ids", () => { expect( matrixPlugin.threading?.buildToolContext?.({ cfg: {} as CoreConfig, context: { From: "user:@alice:example.org", To: "!dm:example.org", ChatType: "direct", }, hasRepliedRef: { value: false }, }), ).toEqual({ currentChannelId: "!dm:example.org", currentThreadTs: undefined, currentDirectUserId: "@alice:example.org", hasRepliedRef: { value: false }, }); }); it("resolves group mention policy from account config", () => { const cfg = { channels: { matrix: { groups: { "!room:example.org": { requireMention: true }, }, accounts: { Assistant: { groups: { "!room:example.org": { requireMention: false }, }, }, }, }, }, } as unknown as CoreConfig; expect(matrixPlugin.groups!.resolveRequireMention!({ cfg, groupId: "!room:example.org" })).toBe( true, ); expect( matrixPlugin.groups!.resolveRequireMention!({ cfg, accountId: "assistant", groupId: "!room:example.org", }), ).toBe(false); expect( matrixPlugin.groups!.resolveRequireMention!({ cfg, accountId: "assistant", groupId: "matrix:room:!room:example.org", }), ).toBe(false); }); it("matches prefixed Matrix aliases in group context", () => { const cfg = { channels: { matrix: { groups: { "#ops:example.org": { requireMention: false }, }, }, }, } as unknown as CoreConfig; expect( matrixPlugin.groups!.resolveRequireMention!({ cfg, groupId: "matrix:room:!room:example.org", groupChannel: "matrix:channel:#ops:example.org", }), ).toBe(false); }); it("reports room access warnings against the active Matrix config path", () => { expect( matrixPlugin.security?.collectWarnings?.({ cfg: { channels: { matrix: { groupPolicy: "open", }, }, } as CoreConfig, account: resolveMatrixAccount({ cfg: { channels: { matrix: { groupPolicy: "open", }, }, } as CoreConfig, accountId: "default", }), }), ).toEqual([ '- Matrix rooms: groupPolicy="open" allows any room to trigger (mention-gated). Set channels.matrix.groupPolicy="allowlist" + channels.matrix.groups (and optionally channels.matrix.groupAllowFrom) to restrict rooms.', ]); expect( matrixPlugin.security?.collectWarnings?.({ cfg: { channels: { matrix: { defaultAccount: "assistant", accounts: { assistant: { groupPolicy: "open", }, }, }, }, } as CoreConfig, account: resolveMatrixAccount({ cfg: { channels: { matrix: { defaultAccount: "assistant", accounts: { assistant: { groupPolicy: "open", }, }, }, }, } as CoreConfig, accountId: "assistant", }), }), ).toEqual([ '- Matrix rooms: groupPolicy="open" allows any room to trigger (mention-gated). Set channels.matrix.accounts.assistant.groupPolicy="allowlist" + channels.matrix.accounts.assistant.groups (and optionally channels.matrix.accounts.assistant.groupAllowFrom) to restrict rooms.', ]); }); it("reports invite auto-join warnings only when explicitly enabled", () => { expect( matrixPlugin.security?.collectWarnings?.({ cfg: { channels: { matrix: { groupPolicy: "allowlist", autoJoin: "always", }, }, } as CoreConfig, account: resolveMatrixAccount({ cfg: { channels: { matrix: { groupPolicy: "allowlist", autoJoin: "always", }, }, } as CoreConfig, accountId: "default", }), }), ).toEqual([ '- Matrix invites: autoJoin="always" joins any invited room before message policy applies. Set channels.matrix.autoJoin="allowlist" + channels.matrix.autoJoinAllowlist (or channels.matrix.autoJoin="off") to restrict joins.', ]); }); it("writes matrix non-default account credentials under channels.matrix.accounts", () => { const cfg = { channels: { matrix: { homeserver: "https://default.example.org", accessToken: "default-token", deviceId: "DEFAULTDEVICE", avatarUrl: "mxc://server/avatar", encryption: true, threadReplies: "inbound", groups: { "!room:example.org": { requireMention: true }, }, }, }, } as unknown as CoreConfig; const updated = matrixPlugin.setup!.applyAccountConfig({ cfg, accountId: "ops", input: { homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", }, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accessToken).toBeUndefined(); expect(updated.channels?.["matrix"]?.deviceId).toBeUndefined(); expect(updated.channels?.["matrix"]?.avatarUrl).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.default).toMatchObject({ accessToken: "default-token", homeserver: "https://default.example.org", deviceId: "DEFAULTDEVICE", avatarUrl: "mxc://server/avatar", encryption: true, threadReplies: "inbound", groups: { "!room:example.org": { requireMention: true }, }, }); expect(updated.channels?.["matrix"]?.accounts?.ops).toMatchObject({ enabled: true, homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", }); expect(resolveMatrixConfigForAccount(updated, "ops", {})).toMatchObject({ homeserver: "https://matrix.example.org", userId: "@ops:example.org", accessToken: "ops-token", deviceId: undefined, }); }); it("writes default matrix account credentials under channels.matrix.accounts.default", () => { const cfg = { channels: { matrix: { homeserver: "https://legacy.example.org", accessToken: "legacy-token", }, }, } as unknown as CoreConfig; const updated = matrixPlugin.setup!.applyAccountConfig({ cfg, accountId: "default", input: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", accessToken: "bot-token", }, }) as CoreConfig; expect(updated.channels?.["matrix"]).toMatchObject({ enabled: true, homeserver: "https://matrix.example.org", userId: "@bot:example.org", accessToken: "bot-token", }); expect(updated.channels?.["matrix"]?.accounts).toBeUndefined(); }); it("requires account-scoped env vars when --use-env is set for non-default accounts", () => { const envKeys = [ "MATRIX_OPS_HOMESERVER", "MATRIX_OPS_USER_ID", "MATRIX_OPS_ACCESS_TOKEN", "MATRIX_OPS_PASSWORD", ] as const; const previousEnv = Object.fromEntries(envKeys.map((key) => [key, process.env[key]])) as Record< (typeof envKeys)[number], string | undefined >; for (const key of envKeys) { delete process.env[key]; } try { const error = matrixPlugin.setup!.validateInput?.({ cfg: {} as CoreConfig, accountId: "ops", input: { useEnv: true }, }); expect(error).toBe( 'Set per-account env vars for "ops" (for example MATRIX_OPS_HOMESERVER + MATRIX_OPS_ACCESS_TOKEN or MATRIX_OPS_USER_ID + MATRIX_OPS_PASSWORD).', ); } finally { for (const key of envKeys) { if (previousEnv[key] === undefined) { delete process.env[key]; } else { process.env[key] = previousEnv[key]; } } } }); it("accepts --use-env for non-default account when scoped env vars are present", () => { const envKeys = { MATRIX_OPS_HOMESERVER: process.env.MATRIX_OPS_HOMESERVER, MATRIX_OPS_ACCESS_TOKEN: process.env.MATRIX_OPS_ACCESS_TOKEN, }; process.env.MATRIX_OPS_HOMESERVER = "https://ops.example.org"; process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-token"; try { const error = matrixPlugin.setup!.validateInput?.({ cfg: {} as CoreConfig, accountId: "ops", input: { useEnv: true }, }); expect(error).toBeNull(); } finally { for (const [key, value] of Object.entries(envKeys)) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } } }); it("clears stored auth fields when switching a Matrix account to env-backed auth", () => { const envKeys = { MATRIX_OPS_HOMESERVER: process.env.MATRIX_OPS_HOMESERVER, MATRIX_OPS_ACCESS_TOKEN: process.env.MATRIX_OPS_ACCESS_TOKEN, MATRIX_OPS_DEVICE_ID: process.env.MATRIX_OPS_DEVICE_ID, MATRIX_OPS_DEVICE_NAME: process.env.MATRIX_OPS_DEVICE_NAME, }; process.env.MATRIX_OPS_HOMESERVER = "https://ops.env.example.org"; process.env.MATRIX_OPS_ACCESS_TOKEN = "ops-env-token"; process.env.MATRIX_OPS_DEVICE_ID = "OPSENVDEVICE"; process.env.MATRIX_OPS_DEVICE_NAME = "Ops Env Device"; try { const cfg = { channels: { matrix: { accounts: { ops: { homeserver: "https://ops.inline.example.org", userId: "@ops:inline.example.org", accessToken: "ops-inline-token", password: "ops-inline-password", // pragma: allowlist secret deviceId: "OPSINLINEDEVICE", deviceName: "Ops Inline Device", encryption: true, }, }, }, }, } as unknown as CoreConfig; const updated = matrixPlugin.setup!.applyAccountConfig({ cfg, accountId: "ops", input: { useEnv: true, name: "Ops", }, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accounts?.ops).toMatchObject({ name: "Ops", enabled: true, encryption: true, }); expect(updated.channels?.["matrix"]?.accounts?.ops?.homeserver).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.ops?.userId).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.ops?.accessToken).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.ops?.password).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.ops?.deviceId).toBeUndefined(); expect(updated.channels?.["matrix"]?.accounts?.ops?.deviceName).toBeUndefined(); expect(resolveMatrixConfigForAccount(updated, "ops", process.env)).toMatchObject({ homeserver: "https://ops.env.example.org", accessToken: "ops-env-token", deviceId: "OPSENVDEVICE", deviceName: "Ops Env Device", }); } finally { for (const [key, value] of Object.entries(envKeys)) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } } }); it("resolves account id from input name when explicit account id is missing", () => { const accountId = matrixPlugin.setup!.resolveAccountId?.({ cfg: {} as CoreConfig, accountId: undefined, input: { name: "Main Bot" }, }); expect(accountId).toBe("main-bot"); }); it("resolves binding account id from agent id when omitted", () => { const accountId = matrixPlugin.setup!.resolveBindingAccountId?.({ cfg: {} as CoreConfig, agentId: "Ops", accountId: undefined, }); expect(accountId).toBe("ops"); }); it("clears stale access token when switching an account to password auth", () => { const cfg = { channels: { matrix: { accounts: { default: { homeserver: "https://matrix.example.org", accessToken: "old-token", }, }, }, }, } as unknown as CoreConfig; const updated = matrixPlugin.setup!.applyAccountConfig({ cfg, accountId: "default", input: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", password: "new-password", // pragma: allowlist secret }, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accounts?.default?.password).toBe("new-password"); expect(updated.channels?.["matrix"]?.accounts?.default?.accessToken).toBeUndefined(); }); it("clears stale password when switching an account to token auth", () => { const cfg = { channels: { matrix: { accounts: { default: { homeserver: "https://matrix.example.org", userId: "@bot:example.org", password: "old-password", // pragma: allowlist secret }, }, }, }, } as unknown as CoreConfig; const updated = matrixPlugin.setup!.applyAccountConfig({ cfg, accountId: "default", input: { homeserver: "https://matrix.example.org", accessToken: "new-token", }, }) as CoreConfig; expect(updated.channels?.["matrix"]?.accounts?.default?.accessToken).toBe("new-token"); expect(updated.channels?.["matrix"]?.accounts?.default?.password).toBeUndefined(); }); });