From 43058c021e7abb22644718719452e3d94da62ff4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 25 Mar 2026 04:24:16 +0000 Subject: [PATCH] test: collapse setup and monitor channel suites --- extensions/matrix/index.test.ts | 71 ----- .../matrix/src/matrix/monitor/index.test.ts | 128 ++++++-- extensions/mattermost/index.test.ts | 43 --- extensions/mattermost/src/setup-core.test.ts | 153 --------- .../mattermost/src/setup-status.test.ts | 24 -- .../mattermost/src/setup-surface.test.ts | 93 ------ extensions/mattermost/src/setup.test.ts | 301 ++++++++++++++++++ 7 files changed, 402 insertions(+), 411 deletions(-) delete mode 100644 extensions/matrix/index.test.ts delete mode 100644 extensions/mattermost/index.test.ts delete mode 100644 extensions/mattermost/src/setup-core.test.ts delete mode 100644 extensions/mattermost/src/setup-status.test.ts delete mode 100644 extensions/mattermost/src/setup-surface.test.ts create mode 100644 extensions/mattermost/src/setup.test.ts diff --git a/extensions/matrix/index.test.ts b/extensions/matrix/index.test.ts deleted file mode 100644 index 6f8445a4d31..00000000000 --- a/extensions/matrix/index.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import path from "node:path"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { loadRuntimeApiExportTypesViaJiti } from "../../test/helpers/extensions/jiti-runtime-api.ts"; - -const setMatrixRuntimeMock = vi.hoisted(() => vi.fn()); -const registerChannelMock = vi.hoisted(() => vi.fn()); - -vi.mock("./src/runtime.js", () => ({ - setMatrixRuntime: setMatrixRuntimeMock, -})); - -const { default: matrixPlugin } = await import("./index.js"); - -describe("matrix plugin registration", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it("loads the matrix runtime api through Jiti", () => { - const runtimeApiPath = path.join(process.cwd(), "extensions", "matrix", "runtime-api.ts"); - expect( - loadRuntimeApiExportTypesViaJiti({ - modulePath: runtimeApiPath, - exportNames: [ - "requiresExplicitMatrixDefaultAccount", - "resolveMatrixDefaultOrOnlyAccountId", - ], - realPluginSdkSpecifiers: [], - }), - ).toEqual({ - requiresExplicitMatrixDefaultAccount: "function", - resolveMatrixDefaultOrOnlyAccountId: "function", - }); - }, 240_000); - - it("loads the matrix src runtime api through Jiti without duplicate export errors", () => { - const runtimeApiPath = path.join( - process.cwd(), - "extensions", - "matrix", - "src", - "runtime-api.ts", - ); - expect( - loadRuntimeApiExportTypesViaJiti({ - modulePath: runtimeApiPath, - exportNames: ["resolveMatrixAccountStringValues"], - realPluginSdkSpecifiers: ["openclaw/plugin-sdk/matrix"], - }), - ).toEqual({ - resolveMatrixAccountStringValues: "function", - }); - }, 240_000); - - it("registers the channel without bootstrapping crypto runtime", () => { - const runtime = {} as never; - matrixPlugin.register({ - runtime, - logger: { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }, - registerChannel: registerChannelMock, - } as never); - - expect(setMatrixRuntimeMock).toHaveBeenCalledWith(runtime); - expect(registerChannelMock).toHaveBeenCalledWith({ plugin: expect.any(Object) }); - }); -}); diff --git a/extensions/matrix/src/matrix/monitor/index.test.ts b/extensions/matrix/src/matrix/monitor/index.test.ts index 1e7db90d4df..cf9cc9a4b4c 100644 --- a/extensions/matrix/src/matrix/monitor/index.test.ts +++ b/extensions/matrix/src/matrix/monitor/index.test.ts @@ -1,4 +1,6 @@ +import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { loadRuntimeApiExportTypesViaJiti } from "../../../../../test/helpers/extensions/jiti-runtime-api.ts"; const hoisted = vi.hoisted(() => { const callOrder: string[] = []; @@ -31,6 +33,7 @@ const hoisted = vi.hoisted(() => { const stopThreadBindingManager = vi.fn(); const releaseSharedClientInstance = vi.fn(async () => true); const setActiveMatrixClient = vi.fn(); + const setMatrixRuntime = vi.fn(); return { callOrder, client, @@ -41,29 +44,34 @@ const hoisted = vi.hoisted(() => { releaseSharedClientInstance, resolveTextChunkLimit, setActiveMatrixClient, + setMatrixRuntime, state, stopThreadBindingManager, }; }); -vi.mock("../../runtime-api.js", () => ({ - GROUP_POLICY_BLOCKED_LABEL: { - room: "room", - }, - mergeAllowlist: ({ existing, additions }: { existing: string[]; additions: string[] }) => [ - ...existing, - ...additions, - ], - resolveThreadBindingIdleTimeoutMsForChannel: () => 24 * 60 * 60 * 1000, - resolveThreadBindingMaxAgeMsForChannel: () => 0, - resolveAllowlistProviderRuntimeGroupPolicy: () => ({ - groupPolicy: "allowlist", - providerMissingFallbackApplied: false, - }), - resolveDefaultGroupPolicy: () => "allowlist", - summarizeMapping: vi.fn(), - warnMissingProviderGroupPolicyFallbackOnce: vi.fn(), -})); +vi.mock("../../runtime-api.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + GROUP_POLICY_BLOCKED_LABEL: { + room: "room", + }, + mergeAllowlist: ({ existing, additions }: { existing: string[]; additions: string[] }) => [ + ...existing, + ...additions, + ], + resolveThreadBindingIdleTimeoutMsForChannel: () => 24 * 60 * 60 * 1000, + resolveThreadBindingMaxAgeMsForChannel: () => 0, + resolveAllowlistProviderRuntimeGroupPolicy: () => ({ + groupPolicy: "allowlist", + providerMissingFallbackApplied: false, + }), + resolveDefaultGroupPolicy: () => "allowlist", + summarizeMapping: vi.fn(), + warnMissingProviderGroupPolicyFallbackOnce: vi.fn(), + }; +}); vi.mock("../../resolve-targets.js", () => ({ resolveMatrixTargets: vi.fn(async () => []), @@ -99,17 +107,22 @@ vi.mock("../../runtime.js", () => ({ loadWebMedia: vi.fn(), }, }), + setMatrixRuntime: hoisted.setMatrixRuntime, })); -vi.mock("../accounts.js", () => ({ - resolveConfiguredMatrixBotUserIds: vi.fn(() => new Set()), - resolveMatrixAccount: () => ({ - accountId: "default", - config: { - dm: {}, - }, - }), -})); +vi.mock("../accounts.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveConfiguredMatrixBotUserIds: vi.fn(() => new Set()), + resolveMatrixAccount: () => ({ + accountId: "default", + config: { + dm: {}, + }, + }), + }; +}); vi.mock("../active-client.js", () => ({ setActiveMatrixClient: hoisted.setActiveMatrixClient, @@ -378,3 +391,64 @@ describe("monitorMatrixProvider", () => { ); }); }); + +describe("matrix plugin registration", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("loads the matrix runtime api through Jiti", () => { + const runtimeApiPath = path.join(process.cwd(), "extensions", "matrix", "runtime-api.ts"); + expect( + loadRuntimeApiExportTypesViaJiti({ + modulePath: runtimeApiPath, + exportNames: [ + "requiresExplicitMatrixDefaultAccount", + "resolveMatrixDefaultOrOnlyAccountId", + ], + realPluginSdkSpecifiers: [], + }), + ).toEqual({ + requiresExplicitMatrixDefaultAccount: "function", + resolveMatrixDefaultOrOnlyAccountId: "function", + }); + }, 240_000); + + it("loads the matrix src runtime api through Jiti without duplicate export errors", () => { + const runtimeApiPath = path.join( + process.cwd(), + "extensions", + "matrix", + "src", + "runtime-api.ts", + ); + expect( + loadRuntimeApiExportTypesViaJiti({ + modulePath: runtimeApiPath, + exportNames: ["resolveMatrixAccountStringValues"], + realPluginSdkSpecifiers: ["openclaw/plugin-sdk/matrix"], + }), + ).toEqual({ + resolveMatrixAccountStringValues: "function", + }); + }, 240_000); + + it("registers the channel without bootstrapping crypto runtime", async () => { + const { default: matrixPlugin } = await import("../../../index.js"); + const runtime = {} as never; + const registerChannel = vi.fn(); + matrixPlugin.register({ + runtime, + logger: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }, + registerChannel, + } as never); + + expect(hoisted.setMatrixRuntime).toHaveBeenCalledWith(runtime); + expect(registerChannel).toHaveBeenCalledWith({ plugin: expect.any(Object) }); + }); +}); diff --git a/extensions/mattermost/index.test.ts b/extensions/mattermost/index.test.ts deleted file mode 100644 index 7ab3d87778a..00000000000 --- a/extensions/mattermost/index.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { createTestPluginApi } from "../../test/helpers/extensions/plugin-api.js"; -import plugin from "./index.js"; -import type { OpenClawPluginApi } from "./runtime-api.js"; - -function createApi( - registrationMode: OpenClawPluginApi["registrationMode"], - registerHttpRoute = vi.fn(), -): OpenClawPluginApi { - return createTestPluginApi({ - id: "mattermost", - name: "Mattermost", - source: "test", - config: {}, - runtime: {} as OpenClawPluginApi["runtime"], - registrationMode, - registerHttpRoute, - }); -} - -describe("mattermost plugin register", () => { - it("skips slash callback registration in setup-only mode", () => { - const registerHttpRoute = vi.fn(); - - plugin.register(createApi("setup-only", registerHttpRoute)); - - expect(registerHttpRoute).not.toHaveBeenCalled(); - }); - - it("registers slash callback routes in full mode", () => { - const registerHttpRoute = vi.fn(); - - plugin.register(createApi("full", registerHttpRoute)); - - expect(registerHttpRoute).toHaveBeenCalledTimes(1); - expect(registerHttpRoute).toHaveBeenCalledWith( - expect.objectContaining({ - path: "/api/channels/mattermost/command", - auth: "plugin", - }), - ); - }); -}); diff --git a/extensions/mattermost/src/setup-core.test.ts b/extensions/mattermost/src/setup-core.test.ts deleted file mode 100644 index 4d04a14a8cc..00000000000 --- a/extensions/mattermost/src/setup-core.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup"; -import { describe, expect, it, vi } from "vitest"; - -const resolveMattermostAccount = vi.hoisted(() => vi.fn()); -const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value)); -const hasConfiguredSecretInput = vi.hoisted(() => vi.fn((value: unknown) => Boolean(value))); - -vi.mock("./mattermost/accounts.js", () => ({ - resolveMattermostAccount, -})); - -vi.mock("./mattermost/client.js", () => ({ - normalizeMattermostBaseUrl, -})); - -vi.mock("./secret-input.js", () => ({ - hasConfiguredSecretInput, -})); - -describe("mattermost setup core", () => { - it("reports configuration only when token and base url are both present", async () => { - const { isMattermostConfigured } = await import("./setup-core.js"); - - expect( - isMattermostConfigured({ - botToken: "bot-token", - baseUrl: "https://chat.example.com", - config: {}, - } as never), - ).toBe(true); - - expect( - isMattermostConfigured({ - botToken: "", - baseUrl: "https://chat.example.com", - config: { botToken: "secret-ref" }, - } as never), - ).toBe(true); - - expect( - isMattermostConfigured({ - botToken: "", - baseUrl: "", - config: {}, - } as never), - ).toBe(false); - }); - - it("resolves accounts with unresolved secret refs allowed", async () => { - resolveMattermostAccount.mockReturnValue({ accountId: "default" }); - - const { resolveMattermostAccountWithSecrets } = await import("./setup-core.js"); - const cfg = { channels: { mattermost: {} } }; - - expect(resolveMattermostAccountWithSecrets(cfg as never, "default")).toEqual({ - accountId: "default", - }); - expect(resolveMattermostAccount).toHaveBeenCalledWith({ - cfg, - accountId: "default", - allowUnresolvedSecretRef: true, - }); - }); - - it("validates env and explicit credential requirements", async () => { - const { mattermostSetupAdapter } = await import("./setup-core.js"); - const validateInput = mattermostSetupAdapter.validateInput; - expect(validateInput).toBeTypeOf("function"); - - expect( - validateInput!({ - accountId: "secondary", - input: { useEnv: true }, - } as never), - ).toBe("Mattermost env vars can only be used for the default account."); - - normalizeMattermostBaseUrl.mockReturnValue(undefined); - expect( - validateInput!({ - accountId: DEFAULT_ACCOUNT_ID, - input: { useEnv: false, botToken: "tok", httpUrl: "not-a-url" }, - } as never), - ).toBe("Mattermost requires --bot-token and --http-url (or --use-env)."); - - normalizeMattermostBaseUrl.mockReturnValue("https://chat.example.com"); - expect( - validateInput!({ - accountId: DEFAULT_ACCOUNT_ID, - input: { useEnv: false, botToken: "tok", httpUrl: "https://chat.example.com" }, - } as never), - ).toBeNull(); - }); - - it("applies normalized config for default and named accounts", async () => { - normalizeMattermostBaseUrl.mockReturnValue("https://chat.example.com"); - const { mattermostSetupAdapter } = await import("./setup-core.js"); - const applyAccountConfig = mattermostSetupAdapter.applyAccountConfig; - expect(applyAccountConfig).toBeTypeOf("function"); - - expect( - applyAccountConfig!({ - cfg: { channels: { mattermost: {} } }, - accountId: DEFAULT_ACCOUNT_ID, - input: { - name: "Default", - botToken: "tok", - httpUrl: "https://chat.example.com", - }, - } as never), - ).toEqual({ - channels: { - mattermost: { - enabled: true, - name: "Default", - botToken: "tok", - baseUrl: "https://chat.example.com", - }, - }, - }); - - expect( - applyAccountConfig!({ - cfg: { - channels: { - mattermost: { - name: "Legacy", - }, - }, - }, - accountId: "Work Team", - input: { - name: "Work", - botToken: "tok2", - httpUrl: "https://chat.example.com", - }, - } as never), - ).toMatchObject({ - channels: { - mattermost: { - accounts: { - default: { name: "Legacy" }, - "work-team": { - enabled: true, - name: "Work", - botToken: "tok2", - baseUrl: "https://chat.example.com", - }, - }, - }, - }, - }); - }); -}); diff --git a/extensions/mattermost/src/setup-status.test.ts b/extensions/mattermost/src/setup-status.test.ts deleted file mode 100644 index 61423efb199..00000000000 --- a/extensions/mattermost/src/setup-status.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { OpenClawConfig } from "../runtime-api.js"; -import { mattermostSetupWizard } from "./setup-surface.js"; - -describe("mattermost setup status", () => { - it("treats SecretRef botToken as configured when baseUrl is present", async () => { - const configured = await mattermostSetupWizard.status.resolveConfigured({ - cfg: { - channels: { - mattermost: { - baseUrl: "https://chat.example.test", - botToken: { - source: "env", - provider: "default", - id: "MATTERMOST_BOT_TOKEN", - }, - }, - }, - } as OpenClawConfig, - }); - - expect(configured).toBe(true); - }); -}); diff --git a/extensions/mattermost/src/setup-surface.test.ts b/extensions/mattermost/src/setup-surface.test.ts deleted file mode 100644 index ace67ac854a..00000000000 --- a/extensions/mattermost/src/setup-surface.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../runtime-api.js"; -import { mattermostSetupWizard } from "./setup-surface.js"; - -describe("mattermost setup surface", () => { - afterEach(() => { - vi.unstubAllEnvs(); - }); - - it("treats secret-ref tokens plus base url as configured", async () => { - const configured = await mattermostSetupWizard.status.resolveConfigured({ - cfg: { - channels: { - mattermost: { - baseUrl: "https://chat.example.com", - botToken: { - source: "env", - provider: "default", - id: "MATTERMOST_BOT_TOKEN", - }, - }, - }, - } as OpenClawConfig, - }); - - expect(configured).toBe(true); - }); - - it("shows intro note only when the target account is not configured", () => { - expect( - mattermostSetupWizard.introNote?.shouldShow?.({ - cfg: { - channels: { - mattermost: {}, - }, - } as OpenClawConfig, - accountId: "default", - } as never), - ).toBe(true); - - expect( - mattermostSetupWizard.introNote?.shouldShow?.({ - cfg: { - channels: { - mattermost: { - baseUrl: "https://chat.example.com", - botToken: { - source: "env", - provider: "default", - id: "MATTERMOST_BOT_TOKEN", - }, - }, - }, - } as OpenClawConfig, - accountId: "default", - } as never), - ).toBe(false); - }); - - it("offers env shortcut only for the default account when env is present and config is empty", () => { - vi.stubEnv("MATTERMOST_BOT_TOKEN", "bot-token"); - vi.stubEnv("MATTERMOST_URL", "https://chat.example.com"); - - expect( - mattermostSetupWizard.envShortcut?.isAvailable?.({ - cfg: { channels: { mattermost: {} } } as OpenClawConfig, - accountId: "default", - } as never), - ).toBe(true); - - expect( - mattermostSetupWizard.envShortcut?.isAvailable?.({ - cfg: { channels: { mattermost: {} } } as OpenClawConfig, - accountId: "work", - } as never), - ).toBe(false); - }); - - it("keeps env shortcut as a no-op patch for the selected account", () => { - expect( - mattermostSetupWizard.envShortcut?.apply?.({ - cfg: { channels: { mattermost: { enabled: false } } } as OpenClawConfig, - accountId: "default", - } as never), - ).toEqual({ - channels: { - mattermost: { - enabled: true, - }, - }, - }); - }); -}); diff --git a/extensions/mattermost/src/setup.test.ts b/extensions/mattermost/src/setup.test.ts new file mode 100644 index 00000000000..fdd62f17b49 --- /dev/null +++ b/extensions/mattermost/src/setup.test.ts @@ -0,0 +1,301 @@ +import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createTestPluginApi } from "../../../test/helpers/extensions/plugin-api.js"; +import plugin from "../index.js"; +import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js"; +import { mattermostSetupWizard } from "./setup-surface.js"; + +const resolveMattermostAccount = vi.hoisted(() => vi.fn()); +const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value)); +const hasConfiguredSecretInput = vi.hoisted(() => vi.fn((value: unknown) => Boolean(value))); + +vi.mock("./mattermost/accounts.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveMattermostAccount: (...args: Parameters) => { + const mocked = resolveMattermostAccount(...args); + return mocked === undefined ? actual.resolveMattermostAccount(...args) : mocked; + }, + }; +}); + +vi.mock("./mattermost/client.js", () => ({ + normalizeMattermostBaseUrl, +})); + +vi.mock("./secret-input.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + hasConfiguredSecretInput, + }; +}); + +function createApi( + registrationMode: OpenClawPluginApi["registrationMode"], + registerHttpRoute = vi.fn(), +): OpenClawPluginApi { + return createTestPluginApi({ + id: "mattermost", + name: "Mattermost", + source: "test", + config: {}, + runtime: {} as OpenClawPluginApi["runtime"], + registrationMode, + registerHttpRoute, + }); +} + +describe("mattermost setup", () => { + afterEach(() => { + resolveMattermostAccount.mockReset(); + normalizeMattermostBaseUrl.mockReset(); + normalizeMattermostBaseUrl.mockImplementation((value: string | undefined) => value); + hasConfiguredSecretInput.mockReset(); + hasConfiguredSecretInput.mockImplementation((value: unknown) => Boolean(value)); + vi.unstubAllEnvs(); + }); + + it("reports configuration only when token and base url are both present", async () => { + const { isMattermostConfigured } = await import("./setup-core.js"); + + expect( + isMattermostConfigured({ + botToken: "bot-token", + baseUrl: "https://chat.example.com", + config: {}, + } as never), + ).toBe(true); + + expect( + isMattermostConfigured({ + botToken: "", + baseUrl: "https://chat.example.com", + config: { botToken: "secret-ref" }, + } as never), + ).toBe(true); + + expect( + isMattermostConfigured({ + botToken: "", + baseUrl: "", + config: {}, + } as never), + ).toBe(false); + }); + + it("resolves accounts with unresolved secret refs allowed", async () => { + resolveMattermostAccount.mockReturnValue({ accountId: "default" }); + + const { resolveMattermostAccountWithSecrets } = await import("./setup-core.js"); + const cfg = { channels: { mattermost: {} } }; + + expect(resolveMattermostAccountWithSecrets(cfg as never, "default")).toEqual({ + accountId: "default", + }); + expect(resolveMattermostAccount).toHaveBeenCalledWith({ + cfg, + accountId: "default", + allowUnresolvedSecretRef: true, + }); + }); + + it("validates env and explicit credential requirements", async () => { + const { mattermostSetupAdapter } = await import("./setup-core.js"); + const validateInput = mattermostSetupAdapter.validateInput; + expect(validateInput).toBeTypeOf("function"); + + expect( + validateInput!({ + accountId: "secondary", + input: { useEnv: true }, + } as never), + ).toBe("Mattermost env vars can only be used for the default account."); + + normalizeMattermostBaseUrl.mockReturnValue(undefined); + expect( + validateInput!({ + accountId: DEFAULT_ACCOUNT_ID, + input: { useEnv: false, botToken: "tok", httpUrl: "not-a-url" }, + } as never), + ).toBe("Mattermost requires --bot-token and --http-url (or --use-env)."); + + normalizeMattermostBaseUrl.mockReturnValue("https://chat.example.com"); + expect( + validateInput!({ + accountId: DEFAULT_ACCOUNT_ID, + input: { useEnv: false, botToken: "tok", httpUrl: "https://chat.example.com" }, + } as never), + ).toBeNull(); + }); + + it("applies normalized config for default and named accounts", async () => { + normalizeMattermostBaseUrl.mockReturnValue("https://chat.example.com"); + const { mattermostSetupAdapter } = await import("./setup-core.js"); + const applyAccountConfig = mattermostSetupAdapter.applyAccountConfig; + expect(applyAccountConfig).toBeTypeOf("function"); + + expect( + applyAccountConfig!({ + cfg: { channels: { mattermost: {} } }, + accountId: DEFAULT_ACCOUNT_ID, + input: { + name: "Default", + botToken: "tok", + httpUrl: "https://chat.example.com", + }, + } as never), + ).toEqual({ + channels: { + mattermost: { + enabled: true, + name: "Default", + botToken: "tok", + baseUrl: "https://chat.example.com", + }, + }, + }); + + expect( + applyAccountConfig!({ + cfg: { + channels: { + mattermost: { + name: "Legacy", + }, + }, + }, + accountId: "Work Team", + input: { + name: "Work", + botToken: "tok2", + httpUrl: "https://chat.example.com", + }, + } as never), + ).toMatchObject({ + channels: { + mattermost: { + accounts: { + default: { name: "Legacy" }, + "work-team": { + enabled: true, + name: "Work", + botToken: "tok2", + baseUrl: "https://chat.example.com", + }, + }, + }, + }, + }); + }); + + it("skips slash callback registration in setup-only mode", () => { + const registerHttpRoute = vi.fn(); + + plugin.register(createApi("setup-only", registerHttpRoute)); + + expect(registerHttpRoute).not.toHaveBeenCalled(); + }); + + it("registers slash callback routes in full mode", () => { + const registerHttpRoute = vi.fn(); + + plugin.register(createApi("full", registerHttpRoute)); + + expect(registerHttpRoute).toHaveBeenCalledTimes(1); + expect(registerHttpRoute).toHaveBeenCalledWith( + expect.objectContaining({ + path: "/api/channels/mattermost/command", + auth: "plugin", + }), + ); + }); + + it.each(["https://chat.example.com", "https://chat.example.test"])( + "treats secret-ref tokens plus base url as configured: %s", + async (baseUrl) => { + const configured = await mattermostSetupWizard.status.resolveConfigured({ + cfg: { + channels: { + mattermost: { + baseUrl, + botToken: { + source: "env", + provider: "default", + id: "MATTERMOST_BOT_TOKEN", + }, + }, + }, + } as OpenClawConfig, + }); + + expect(configured).toBe(true); + }, + ); + + it("shows intro note only when the target account is not configured", () => { + expect( + mattermostSetupWizard.introNote?.shouldShow?.({ + cfg: { + channels: { + mattermost: {}, + }, + } as OpenClawConfig, + accountId: "default", + } as never), + ).toBe(true); + + expect( + mattermostSetupWizard.introNote?.shouldShow?.({ + cfg: { + channels: { + mattermost: { + baseUrl: "https://chat.example.com", + botToken: { + source: "env", + provider: "default", + id: "MATTERMOST_BOT_TOKEN", + }, + }, + }, + } as OpenClawConfig, + accountId: "default", + } as never), + ).toBe(false); + }); + + it("offers env shortcut only for the default account when env is present and config is empty", () => { + vi.stubEnv("MATTERMOST_BOT_TOKEN", "bot-token"); + vi.stubEnv("MATTERMOST_URL", "https://chat.example.com"); + + expect( + mattermostSetupWizard.envShortcut?.isAvailable?.({ + cfg: { channels: { mattermost: {} } } as OpenClawConfig, + accountId: "default", + } as never), + ).toBe(true); + + expect( + mattermostSetupWizard.envShortcut?.isAvailable?.({ + cfg: { channels: { mattermost: {} } } as OpenClawConfig, + accountId: "work", + } as never), + ).toBe(false); + }); + + it("keeps env shortcut as a no-op patch for the selected account", () => { + expect( + mattermostSetupWizard.envShortcut?.apply?.({ + cfg: { channels: { mattermost: { enabled: false } } } as OpenClawConfig, + accountId: "default", + } as never), + ).toEqual({ + channels: { + mattermost: { + enabled: true, + }, + }, + }); + }); +});