test(contracts): isolate setup and status registries

This commit is contained in:
Vincent Koc 2026-04-04 00:32:13 +09:00
parent d755709ddd
commit f2204cb35a
3 changed files with 229 additions and 224 deletions

View File

@ -0,0 +1,227 @@
import { expect } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { requireBundledChannelPlugin } from "../bundled.js";
import type { ChannelPlugin } from "../types.js";
type SetupContractEntry = {
id: string;
plugin: Pick<ChannelPlugin, "id" | "config" | "setup">;
cases: Array<{
name: string;
cfg: OpenClawConfig;
accountId?: string;
input: Record<string, unknown>;
expectedAccountId?: string;
expectedValidation?: string | null;
beforeTest?: () => void;
assertPatchedConfig?: (cfg: OpenClawConfig) => void;
assertResolvedAccount?: (account: unknown, cfg: OpenClawConfig) => void;
}>;
};
type StatusContractEntry = {
id: string;
plugin: Pick<ChannelPlugin, "id" | "config" | "status">;
cases: Array<{
name: string;
cfg: OpenClawConfig;
accountId?: string;
runtime?: Record<string, unknown>;
probe?: unknown;
beforeTest?: () => void;
assertSnapshot?: (snapshot: Record<string, unknown>) => void;
assertSummary?: (summary: Record<string, unknown>) => void;
}>;
};
let setupContractRegistryCache: SetupContractEntry[] | undefined;
let statusContractRegistryCache: StatusContractEntry[] | undefined;
export function getSetupContractRegistry(): SetupContractEntry[] {
setupContractRegistryCache ??= [
{
id: "slack",
plugin: requireBundledChannelPlugin("slack"),
cases: [
{
name: "default account stores tokens and enables the channel",
cfg: {} as OpenClawConfig,
input: {
botToken: "xoxb-test",
appToken: "xapp-test",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.slack?.enabled).toBe(true);
expect(cfg.channels?.slack?.botToken).toBe("xoxb-test");
expect(cfg.channels?.slack?.appToken).toBe("xapp-test");
},
},
{
name: "non-default env setup is rejected",
cfg: {} as OpenClawConfig,
accountId: "ops",
input: {
useEnv: true,
},
expectedAccountId: "ops",
expectedValidation: "Slack env tokens can only be used for the default account.",
},
],
},
{
id: "mattermost",
plugin: requireBundledChannelPlugin("mattermost"),
cases: [
{
name: "default account stores token and normalized base URL",
cfg: {} as OpenClawConfig,
input: {
botToken: "test-token",
httpUrl: "https://chat.example.com/",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.mattermost?.enabled).toBe(true);
expect(cfg.channels?.mattermost?.botToken).toBe("test-token");
expect(cfg.channels?.mattermost?.baseUrl).toBe("https://chat.example.com");
},
},
{
name: "missing credentials are rejected",
cfg: {} as OpenClawConfig,
input: {
httpUrl: "",
},
expectedAccountId: "default",
expectedValidation: "Mattermost requires --bot-token and --http-url (or --use-env).",
},
],
},
{
id: "line",
plugin: requireBundledChannelPlugin("line"),
cases: [
{
name: "default account stores token and secret",
cfg: {} as OpenClawConfig,
input: {
channelAccessToken: "line-token",
channelSecret: "line-secret",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.line?.enabled).toBe(true);
expect(cfg.channels?.line?.channelAccessToken).toBe("line-token");
expect(cfg.channels?.line?.channelSecret).toBe("line-secret");
},
},
{
name: "non-default env setup is rejected",
cfg: {} as OpenClawConfig,
accountId: "ops",
input: {
useEnv: true,
},
expectedAccountId: "ops",
expectedValidation: "LINE_CHANNEL_ACCESS_TOKEN can only be used for the default account.",
},
],
},
];
return setupContractRegistryCache;
}
export function getStatusContractRegistry(): StatusContractEntry[] {
statusContractRegistryCache ??= [
{
id: "slack",
plugin: requireBundledChannelPlugin("slack"),
cases: [
{
name: "configured account produces a configured status snapshot",
cfg: {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
connected: true,
running: true,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
},
},
],
},
{
id: "mattermost",
plugin: requireBundledChannelPlugin("mattermost"),
cases: [
{
name: "configured account preserves connectivity details in the snapshot",
cfg: {
channels: {
mattermost: {
enabled: true,
botToken: "test-token",
baseUrl: "https://chat.example.com",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
connected: true,
lastConnectedAt: 1234,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
expect(snapshot.connected).toBe(true);
expect(snapshot.baseUrl).toBe("https://chat.example.com");
},
},
],
},
{
id: "line",
plugin: requireBundledChannelPlugin("line"),
cases: [
{
name: "configured account produces a webhook status snapshot",
cfg: {
channels: {
line: {
enabled: true,
channelAccessToken: "line-token",
channelSecret: "line-secret",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
running: true,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
expect(snapshot.mode).toBe("webhook");
},
},
],
},
];
return statusContractRegistryCache;
}

View File

@ -1,4 +1,4 @@
import { expect, vi } from "vitest";
import { vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import {
listLineAccountIds,
@ -35,37 +35,6 @@ type ActionsContractEntry = {
}>;
};
type SetupContractEntry = {
id: string;
plugin: Pick<ChannelPlugin, "id" | "config" | "setup">;
cases: Array<{
name: string;
cfg: OpenClawConfig;
accountId?: string;
input: Record<string, unknown>;
expectedAccountId?: string;
expectedValidation?: string | null;
beforeTest?: () => void;
assertPatchedConfig?: (cfg: OpenClawConfig) => void;
assertResolvedAccount?: (account: unknown, cfg: OpenClawConfig) => void;
}>;
};
type StatusContractEntry = {
id: string;
plugin: Pick<ChannelPlugin, "id" | "config" | "status">;
cases: Array<{
name: string;
cfg: OpenClawConfig;
accountId?: string;
runtime?: Record<string, unknown>;
probe?: unknown;
beforeTest?: () => void;
assertSnapshot?: (snapshot: Record<string, unknown>) => void;
assertSummary?: (summary: Record<string, unknown>) => void;
}>;
};
type SurfaceContractEntry = {
id: string;
plugin: Pick<
@ -125,8 +94,6 @@ vi.mock(buildBundledPluginModuleId("matrix", "runtime-api.js"), async () => {
let pluginContractRegistryCache: PluginContractEntry[] | undefined;
let actionContractRegistryCache: ActionsContractEntry[] | undefined;
let setupContractRegistryCache: SetupContractEntry[] | undefined;
let statusContractRegistryCache: StatusContractEntry[] | undefined;
let surfaceContractRegistryCache: SurfaceContractEntry[] | undefined;
let threadingContractRegistryCache: ThreadingContractEntry[] | undefined;
let directoryContractRegistryCache: DirectoryContractEntry[] | undefined;
@ -334,195 +301,6 @@ export function getActionContractRegistry(): ActionsContractEntry[] {
return actionContractRegistryCache;
}
export function getSetupContractRegistry(): SetupContractEntry[] {
setupContractRegistryCache ??= [
{
id: "slack",
plugin: requireBundledChannelPlugin("slack"),
cases: [
{
name: "default account stores tokens and enables the channel",
cfg: {} as OpenClawConfig,
input: {
botToken: "xoxb-test",
appToken: "xapp-test",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.slack?.enabled).toBe(true);
expect(cfg.channels?.slack?.botToken).toBe("xoxb-test");
expect(cfg.channels?.slack?.appToken).toBe("xapp-test");
},
},
{
name: "non-default env setup is rejected",
cfg: {} as OpenClawConfig,
accountId: "ops",
input: {
useEnv: true,
},
expectedAccountId: "ops",
expectedValidation: "Slack env tokens can only be used for the default account.",
},
],
},
{
id: "mattermost",
plugin: requireBundledChannelPlugin("mattermost"),
cases: [
{
name: "default account stores token and normalized base URL",
cfg: {} as OpenClawConfig,
input: {
botToken: "test-token",
httpUrl: "https://chat.example.com/",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.mattermost?.enabled).toBe(true);
expect(cfg.channels?.mattermost?.botToken).toBe("test-token");
expect(cfg.channels?.mattermost?.baseUrl).toBe("https://chat.example.com");
},
},
{
name: "missing credentials are rejected",
cfg: {} as OpenClawConfig,
input: {
httpUrl: "",
},
expectedAccountId: "default",
expectedValidation: "Mattermost requires --bot-token and --http-url (or --use-env).",
},
],
},
{
id: "line",
plugin: requireBundledChannelPlugin("line"),
cases: [
{
name: "default account stores token and secret",
cfg: {} as OpenClawConfig,
input: {
channelAccessToken: "line-token",
channelSecret: "line-secret",
},
expectedAccountId: "default",
assertPatchedConfig: (cfg) => {
expect(cfg.channels?.line?.enabled).toBe(true);
expect(cfg.channels?.line?.channelAccessToken).toBe("line-token");
expect(cfg.channels?.line?.channelSecret).toBe("line-secret");
},
},
{
name: "non-default env setup is rejected",
cfg: {} as OpenClawConfig,
accountId: "ops",
input: {
useEnv: true,
},
expectedAccountId: "ops",
expectedValidation: "LINE_CHANNEL_ACCESS_TOKEN can only be used for the default account.",
},
],
},
];
return setupContractRegistryCache;
}
export function getStatusContractRegistry(): StatusContractEntry[] {
statusContractRegistryCache ??= [
{
id: "slack",
plugin: requireBundledChannelPlugin("slack"),
cases: [
{
name: "configured account produces a configured status snapshot",
cfg: {
channels: {
slack: {
botToken: "xoxb-test",
appToken: "xapp-test",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
connected: true,
running: true,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
},
},
],
},
{
id: "mattermost",
plugin: requireBundledChannelPlugin("mattermost"),
cases: [
{
name: "configured account preserves connectivity details in the snapshot",
cfg: {
channels: {
mattermost: {
enabled: true,
botToken: "test-token",
baseUrl: "https://chat.example.com",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
connected: true,
lastConnectedAt: 1234,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
expect(snapshot.connected).toBe(true);
expect(snapshot.baseUrl).toBe("https://chat.example.com");
},
},
],
},
{
id: "line",
plugin: requireBundledChannelPlugin("line"),
cases: [
{
name: "configured account produces a webhook status snapshot",
cfg: {
channels: {
line: {
enabled: true,
channelAccessToken: "line-token",
channelSecret: "line-secret",
},
},
} as OpenClawConfig,
runtime: {
accountId: "default",
running: true,
},
probe: { ok: true },
assertSnapshot: (snapshot) => {
expect(snapshot.accountId).toBe("default");
expect(snapshot.enabled).toBe(true);
expect(snapshot.configured).toBe(true);
expect(snapshot.mode).toBe("webhook");
},
},
],
},
];
return statusContractRegistryCache;
}
export function getSurfaceContractRegistry(): SurfaceContractEntry[] {
surfaceContractRegistryCache ??= listBundledChannelPlugins().map((plugin) => ({
id: plugin.id,

View File

@ -1,5 +1,5 @@
import { describe } from "vitest";
import { getSetupContractRegistry, getStatusContractRegistry } from "./registry.js";
import { getSetupContractRegistry, getStatusContractRegistry } from "./registry-setup-status.js";
import { installChannelSetupContractSuite, installChannelStatusContractSuite } from "./suites.js";
for (const entry of getSetupContractRegistry()) {