mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-26 09:21:55 +00:00
test: collapse setup and monitor channel suites
This commit is contained in:
@@ -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) });
|
||||
});
|
||||
});
|
||||
@@ -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<typeof import("../../runtime-api.js")>();
|
||||
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<string>()),
|
||||
resolveMatrixAccount: () => ({
|
||||
accountId: "default",
|
||||
config: {
|
||||
dm: {},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
vi.mock("../accounts.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../accounts.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveConfiguredMatrixBotUserIds: vi.fn(() => new Set<string>()),
|
||||
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) });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
301
extensions/mattermost/src/setup.test.ts
Normal file
301
extensions/mattermost/src/setup.test.ts
Normal file
@@ -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<typeof import("./mattermost/accounts.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveMattermostAccount: (...args: Parameters<typeof actual.resolveMattermostAccount>) => {
|
||||
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<typeof import("./secret-input.js")>();
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user