mirror of https://github.com/openclaw/openclaw.git
test(contracts): isolate setup and status registries
This commit is contained in:
parent
d755709ddd
commit
f2204cb35a
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue