mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
refactor: expose channel contract test helpers
This commit is contained in:
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd.
|
||||
- Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.
|
||||
- Plugin SDK/testing: promote bundled plugin/provider/channel contract helpers to focused SDK test subpaths and retire the repo-only `test/helpers/plugins` TypeScript bridge. Thanks @vincentkoc.
|
||||
- Plugin SDK/testing: expose generic channel action, setup, status, and directory contract helpers through `plugin-sdk/channel-test-helpers` so bundled extension tests no longer import repo-only channel helper bridges. Thanks @vincentkoc.
|
||||
- Plugin SDK/testing: add a focused generic fixture subpath for CLI capture, sandbox, skill, agent-message, system-event, terminal, chunking, auth-token, and typed-case helpers. Thanks @vincentkoc.
|
||||
- Plugin SDK/testing: add focused plugin runtime and environment fixture subpaths so plugin tests can avoid the broad `plugin-sdk/testing` barrel for common setup helpers. Thanks @vincentkoc.
|
||||
- Plugin SDK/testing: add a focused `plugin-sdk/plugin-test-api` helper subpath and move bundled plugin registration tests off the repo-only plugin API bridge. Thanks @vincentkoc.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
5c810137267a9ddaeab9abc40219e4a02bafec32767533df06bcca7109ee1608 plugin-sdk-api-baseline.json
|
||||
ec3183fad4412002a07d61359e7f62a7b9ca6b2ddf70ddb1d1afb713cf5d053a plugin-sdk-api-baseline.jsonl
|
||||
f9c6ac5f5a7f49963a1dde734066293ab847fbb611f941403bed3096e19c4808 plugin-sdk-api-baseline.json
|
||||
d0c54afbd40ef44a40c27de0f0138178e5389c965a0f5794962c6f84226adaa8 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -22,9 +22,9 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` |
|
||||
| `plugin-sdk/config-schema` | `OpenClawSchema` |
|
||||
| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` |
|
||||
| `plugin-sdk/testing` | Public plugin test fixtures, provider registration/catalog helpers, wizard contract hooks, and bundled-plugin contract maintenance helpers |
|
||||
| `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests; prefer focused test subpaths for new extension tests |
|
||||
| `plugin-sdk/plugin-test-api` | Minimal `OpenClawPluginApi` mock builder for direct plugin registration unit tests |
|
||||
| `plugin-sdk/channel-test-helpers` | Channel account lifecycle, directory, send-config, runtime mock, and hook test helpers |
|
||||
| `plugin-sdk/channel-test-helpers` | Channel account lifecycle, directory, send-config, runtime mock, hook, and generic channel contract test helpers |
|
||||
| `plugin-sdk/plugin-test-contracts` | Plugin registration, package manifest, public artifact, runtime API, import side-effect, and direct import contract helpers |
|
||||
| `plugin-sdk/plugin-test-runtime` | Plugin runtime, registry, provider-registration, setup-wizard, and runtime task-flow fixtures for tests |
|
||||
| `plugin-sdk/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, web-search/fetch, and wizard contract helpers |
|
||||
@@ -267,9 +267,9 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/webhook-path` | Webhook path normalization helpers |
|
||||
| `plugin-sdk/web-media` | Shared remote/local media loading helpers |
|
||||
| `plugin-sdk/zod` | Re-exported `zod` for plugin SDK consumers |
|
||||
| `plugin-sdk/testing` | Public extension test helpers including plugin registry/runtime mocks, provider registration capture, setup-wizard helpers, fetch/env/temp/time fixtures, schema/media/live-test helpers, `installCommonResolveTargetErrorCases`, `writeSkill`, `createTestRegistry`, and live generation env loading. Extension `*.test-support.ts` helpers stay on this or focused SDK subpaths, not core internals |
|
||||
| `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests. New extension tests should import focused SDK subpaths such as `plugin-sdk/plugin-test-runtime`, `plugin-sdk/channel-test-helpers`, `plugin-sdk/test-env`, or `plugin-sdk/test-fixtures` instead |
|
||||
| `plugin-sdk/plugin-test-api` | Minimal `createTestPluginApi` helper for direct plugin registration unit tests without importing repo test helper bridges |
|
||||
| `plugin-sdk/channel-test-helpers` | Channel-oriented test helpers for account startup lifecycle, directory assertions, send-config threading, runtime mocks, status issues, outbound delivery, and hook registration |
|
||||
| `plugin-sdk/channel-test-helpers` | Channel-oriented test helpers for generic actions/setup/status contracts, directory assertions, account startup lifecycle, send-config threading, runtime mocks, status issues, outbound delivery, and hook registration |
|
||||
| `plugin-sdk/plugin-test-contracts` | Plugin package, registration, public artifact, direct import, runtime API, and import side-effect contract helpers |
|
||||
| `plugin-sdk/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, wizard, web-search/fetch, and stream contract helpers |
|
||||
| `plugin-sdk/test-fixtures` | Generic CLI runtime capture, sandbox context, skill writer, agent-message, system-event, terminal-text, chunking, auth-token, and typed-case fixtures |
|
||||
|
||||
@@ -19,7 +19,7 @@ plugins.
|
||||
|
||||
## Test utilities
|
||||
|
||||
**General import:** `openclaw/plugin-sdk/testing`
|
||||
**Compatibility import:** `openclaw/plugin-sdk/testing`
|
||||
|
||||
**Plugin API mock import:** `openclaw/plugin-sdk/plugin-test-api`
|
||||
|
||||
@@ -37,14 +37,12 @@ plugins.
|
||||
|
||||
**Generic fixture import:** `openclaw/plugin-sdk/test-fixtures`
|
||||
|
||||
The testing subpath exports a narrow set of helpers for plugin authors:
|
||||
Prefer the focused subpaths below for new plugin tests. The broad
|
||||
`openclaw/plugin-sdk/testing` barrel remains for compatibility with older tests
|
||||
and helpers that have not moved to a narrower documented surface yet.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
installCommonResolveTargetErrorCases,
|
||||
shouldAckReaction,
|
||||
removeAckReactionAfterReply,
|
||||
} from "openclaw/plugin-sdk/testing";
|
||||
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/testing";
|
||||
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
|
||||
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
|
||||
import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
@@ -57,59 +55,63 @@ import { createCliRuntimeCapture, typedCases } from "openclaw/plugin-sdk/test-fi
|
||||
|
||||
### Available exports
|
||||
|
||||
| Export | Purpose |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` |
|
||||
| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` |
|
||||
| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` |
|
||||
| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` |
|
||||
| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` |
|
||||
| `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling |
|
||||
| `shouldAckReaction` | Check whether a channel should add an ack reaction |
|
||||
| `removeAckReactionAfterReply` | Remove ack reaction after reply delivery |
|
||||
| `createTestRegistry` | Build a channel plugin registry fixture |
|
||||
| `createEmptyPluginRegistry` | Build an empty plugin registry fixture |
|
||||
| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests |
|
||||
| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` |
|
||||
| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` |
|
||||
| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` |
|
||||
| `createTempHomeEnv` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` |
|
||||
| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` |
|
||||
| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` |
|
||||
| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` |
|
||||
| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` |
|
||||
| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` |
|
||||
| `runProviderCatalog` | Execute a provider catalog hook with test dependencies |
|
||||
| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests |
|
||||
| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests |
|
||||
| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions |
|
||||
| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests |
|
||||
| `createProviderUsageFetch` | Build provider usage fetch fixtures |
|
||||
| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` |
|
||||
| `createTestWizardPrompter` | Build a mocked setup wizard prompter |
|
||||
| `createRuntimeTaskFlow` | Create isolated runtime task-flow state |
|
||||
| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` |
|
||||
| Export | Purpose |
|
||||
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` |
|
||||
| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` |
|
||||
| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` |
|
||||
| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `installChannelActionsContractSuite` | Install generic channel message-action contract cases. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `installChannelSetupContractSuite` | Install generic channel setup contract cases. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `installChannelStatusContractSuite` | Install generic channel status contract cases. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `expectDirectoryIds` | Assert channel directory ids from a directory-list function. Import from `plugin-sdk/channel-test-helpers` |
|
||||
| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` |
|
||||
| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` |
|
||||
| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` |
|
||||
| `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling. Import from `plugin-sdk/testing` until a narrower target-resolution test subpath exists |
|
||||
| `shouldAckReaction` | Check whether a channel should add an ack reaction. Import from `plugin-sdk/testing` until a narrower reaction test subpath exists |
|
||||
| `removeAckReactionAfterReply` | Remove ack reaction after reply delivery. Import from `plugin-sdk/testing` until a narrower reaction test subpath exists |
|
||||
| `createTestRegistry` | Build a channel plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` |
|
||||
| `createEmptyPluginRegistry` | Build an empty plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` |
|
||||
| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` |
|
||||
| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` |
|
||||
| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` |
|
||||
| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` |
|
||||
| `createTempHomeEnv` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` |
|
||||
| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` |
|
||||
| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` |
|
||||
| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` |
|
||||
| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` |
|
||||
| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` |
|
||||
| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` |
|
||||
| `runProviderCatalog` | Execute a provider catalog hook with test dependencies |
|
||||
| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests |
|
||||
| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests |
|
||||
| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions |
|
||||
| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests |
|
||||
| `createProviderUsageFetch` | Build provider usage fetch fixtures |
|
||||
| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` |
|
||||
| `createTestWizardPrompter` | Build a mocked setup wizard prompter |
|
||||
| `createRuntimeTaskFlow` | Create isolated runtime task-flow state |
|
||||
| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` |
|
||||
|
||||
Bundled-plugin contract suites also use SDK testing subpaths for test-only
|
||||
registry, manifest, public-artifact, and runtime fixture helpers. Core-only
|
||||
suites that depend on bundled OpenClaw inventory stay under `src/plugins/contracts`.
|
||||
Keep new extension tests on `openclaw/plugin-sdk/testing` or a narrower
|
||||
documented SDK subpath such as `plugin-sdk/plugin-test-api` or
|
||||
`plugin-sdk/channel-contract-testing`, `plugin-sdk/channel-test-helpers`,
|
||||
`plugin-sdk/plugin-test-contracts`, `plugin-sdk/plugin-test-runtime`,
|
||||
`plugin-sdk/provider-test-contracts`, `plugin-sdk/test-env`, or
|
||||
`plugin-sdk/test-fixtures` rather than
|
||||
importing repo `src/**` files or repo `test/helpers/plugins/*` bridges directly.
|
||||
Keep new extension tests on a documented focused SDK subpath such as
|
||||
`plugin-sdk/plugin-test-api`, `plugin-sdk/channel-contract-testing`,
|
||||
`plugin-sdk/channel-test-helpers`, `plugin-sdk/plugin-test-contracts`,
|
||||
`plugin-sdk/plugin-test-runtime`, `plugin-sdk/provider-test-contracts`,
|
||||
`plugin-sdk/test-env`, or `plugin-sdk/test-fixtures` rather than importing the
|
||||
broad `plugin-sdk/testing` compatibility barrel, repo `src/**` files, or repo
|
||||
`test/helpers/plugins/*` bridges directly.
|
||||
|
||||
### Types
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ vi.mock("./cli-auth-seam.js", async (importActual) => {
|
||||
|
||||
const { buildAnthropicCliMigrationResult, hasClaudeCliAuth } = await import("./cli-migration.js");
|
||||
const { createTestWizardPrompter, registerSingleProviderPlugin } =
|
||||
await import("openclaw/plugin-sdk/testing");
|
||||
await import("openclaw/plugin-sdk/plugin-test-runtime");
|
||||
const { default: anthropicPlugin } = await import("./index.js");
|
||||
|
||||
async function resolveAnthropicCliAuthMethod() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { installChannelActionsContractSuite } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe } from "vitest";
|
||||
import { installChannelActionsContractSuite } from "../../../test/helpers/channels/registry-contract-suites.js";
|
||||
import { discordPlugin } from "../api.js";
|
||||
|
||||
describe("discord actions contract", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BaseProbeResult, BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { expectDirectoryIds } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import { expectDirectoryIds } from "../../../test/helpers/channels/directory-ids.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import {
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "../../../test/helpers/channels/registry-contract-suites.js";
|
||||
} from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import { linePlugin, lineSetupPlugin } from "../api.js";
|
||||
|
||||
describe("line setup contract", () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import {
|
||||
installChannelActionsContractSuite,
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "../../../test/helpers/channels/registry-contract-suites.js";
|
||||
} from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import { mattermostPlugin, mattermostSetupPlugin } from "../channel-plugin-api.js";
|
||||
|
||||
describe("mattermost actions contract", () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import {
|
||||
installChannelActionsContractSuite,
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "../../../test/helpers/channels/registry-contract-suites.js";
|
||||
} from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect } from "vitest";
|
||||
import { slackPlugin } from "../api.js";
|
||||
import { slackSetupPlugin } from "../setup-plugin-api.js";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { expectDirectoryIds } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import { expectDirectoryIds } from "../../../test/helpers/channels/directory-ids.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { clearPluginCommands, registerPluginCommand } from "openclaw/plugin-sdk/
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
let registerTelegramNativeCommands: typeof import("./bot-native-commands.js").registerTelegramNativeCommands;
|
||||
let setActivePluginRegistry: typeof import("openclaw/plugin-sdk/testing").setActivePluginRegistry;
|
||||
let setActivePluginRegistry: typeof import("openclaw/plugin-sdk/plugin-test-runtime").setActivePluginRegistry;
|
||||
let createCommandBot: typeof import("./bot-native-commands.menu-test-support.js").createCommandBot;
|
||||
let createNativeCommandTestParams: typeof import("./bot-native-commands.menu-test-support.js").createNativeCommandTestParams;
|
||||
let createPrivateCommandContext: typeof import("./bot-native-commands.menu-test-support.js").createPrivateCommandContext;
|
||||
@@ -112,7 +112,7 @@ async function registerPairMenu(params: {
|
||||
|
||||
describe("registerTelegramNativeCommands real plugin registry", () => {
|
||||
beforeAll(async () => {
|
||||
({ setActivePluginRegistry } = await import("openclaw/plugin-sdk/testing"));
|
||||
({ setActivePluginRegistry } = await import("openclaw/plugin-sdk/plugin-test-runtime"));
|
||||
({ registerTelegramNativeCommands } = await import("./bot-native-commands.js"));
|
||||
({
|
||||
createCommandBot,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { installChannelActionsContractSuite } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe } from "vitest";
|
||||
import { installChannelActionsContractSuite } from "../../../test/helpers/channels/registry-contract-suites.js";
|
||||
import { telegramPlugin } from "../api.js";
|
||||
|
||||
describe("telegram actions contract", () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { BaseProbeResult, BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { expectDirectoryIds } from "openclaw/plugin-sdk/channel-test-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { withEnvAsync } from "openclaw/plugin-sdk/test-env";
|
||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import { expectDirectoryIds } from "../../../test/helpers/channels/directory-ids.js";
|
||||
import {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
|
||||
@@ -11,7 +11,11 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
|
||||
},
|
||||
{
|
||||
pattern: /["']openclaw\/plugin-sdk\/test-utils["']/,
|
||||
hint: "Use openclaw/plugin-sdk/testing or a focused plugin-sdk test subpath for the public extension test surface.",
|
||||
hint: "Use a focused plugin-sdk test subpath for the public extension test surface.",
|
||||
},
|
||||
{
|
||||
pattern: /["']openclaw\/plugin-sdk\/testing["']/,
|
||||
hint: "Use a focused plugin-sdk test subpath instead of the broad compatibility testing barrel.",
|
||||
},
|
||||
{
|
||||
pattern: /["']openclaw\/plugin-sdk\/compat["']/,
|
||||
@@ -25,6 +29,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
|
||||
pattern: /["'](?:\.\.\/)+(?:test\/helpers\/plugins\/)[^"']+["']/,
|
||||
hint: "Use a documented openclaw/plugin-sdk test subpath instead of repo-only plugin helper bridges.",
|
||||
},
|
||||
{
|
||||
pattern: /["'](?:\.\.\/)+(?:test\/helpers\/channels\/)[^"']+["']/,
|
||||
hint: "Use openclaw/plugin-sdk/channel-test-helpers or another focused SDK test subpath instead of repo-only channel helper bridges.",
|
||||
},
|
||||
{
|
||||
pattern: /["'](?:\.\.\/)+(?:src\/test-utils\/)[^"']+["']/,
|
||||
hint: "Use a documented openclaw/plugin-sdk test subpath for public surfaces.",
|
||||
@@ -45,7 +53,7 @@ const MOCK_RELATIVE_MODULE_PATTERN =
|
||||
/\bvi\.(?:mock|doMock|unmock|doUnmock)\s*\(\s*["']([^"']+)["']/g;
|
||||
|
||||
const RELATIVE_CORE_HINT =
|
||||
"Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals.";
|
||||
"Use a focused plugin-sdk test/runtime subpath instead of core internals.";
|
||||
|
||||
// Tombstones for retired repo-only plugin helper bridge files. Keep this list so
|
||||
// deleted bridges fail loudly if they are recreated instead of using SDK subpaths.
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export { createDirectoryTestRuntime, expectDirectorySurface } from "./test-helpers/directory.js";
|
||||
export { expectDirectoryIds, type DirectoryListFn } from "./test-helpers/directory-ids.js";
|
||||
export {
|
||||
expectChannelPluginContract,
|
||||
installChannelActionsContractSuite,
|
||||
installChannelPluginContractSuite,
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "./test-helpers/channel-contract-suites.js";
|
||||
export {
|
||||
addTestHook,
|
||||
createEmptyPluginRegistry,
|
||||
|
||||
242
src/plugin-sdk/test-helpers/channel-contract-suites.ts
Normal file
242
src/plugin-sdk/test-helpers/channel-contract-suites.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import { expect, it } from "vitest";
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
ChannelAccountState,
|
||||
ChannelSetupInput,
|
||||
} from "../../channels/plugins/types.core.js";
|
||||
import type {
|
||||
ChannelMessageActionName,
|
||||
ChannelMessageCapability,
|
||||
ChannelPlugin,
|
||||
} from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
function sortStrings(values: readonly string[]) {
|
||||
return [...values].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveContractMessageDiscovery(params: {
|
||||
plugin: Pick<ChannelPlugin, "actions">;
|
||||
cfg: OpenClawConfig;
|
||||
}) {
|
||||
const actions = params.plugin.actions;
|
||||
if (!actions) {
|
||||
return {
|
||||
actions: [] as ChannelMessageActionName[],
|
||||
capabilities: [] as readonly ChannelMessageCapability[],
|
||||
};
|
||||
}
|
||||
const discovery = actions.describeMessageTool({ cfg: params.cfg }) ?? null;
|
||||
return {
|
||||
actions: Array.isArray(discovery?.actions) ? [...discovery.actions] : [],
|
||||
capabilities: Array.isArray(discovery?.capabilities) ? discovery.capabilities : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function installChannelPluginContractSuite(params: {
|
||||
plugin: Pick<ChannelPlugin, "id" | "meta" | "capabilities" | "config">;
|
||||
}) {
|
||||
it("satisfies the base channel plugin contract", () => {
|
||||
expectChannelPluginContract(params.plugin);
|
||||
});
|
||||
}
|
||||
|
||||
export function expectChannelPluginContract(
|
||||
plugin: Pick<ChannelPlugin, "id" | "meta" | "capabilities" | "config">,
|
||||
) {
|
||||
expect(typeof plugin.id).toBe("string");
|
||||
expect(plugin.id.trim()).not.toBe("");
|
||||
|
||||
expect(plugin.meta.id).toBe(plugin.id);
|
||||
expect(plugin.meta.label.trim()).not.toBe("");
|
||||
expect(plugin.meta.selectionLabel.trim()).not.toBe("");
|
||||
expect(plugin.meta.docsPath).toMatch(/^\/channels\//);
|
||||
expect(plugin.meta.blurb.trim()).not.toBe("");
|
||||
|
||||
expect(plugin.capabilities.chatTypes.length).toBeGreaterThan(0);
|
||||
|
||||
expect(typeof plugin.config.listAccountIds).toBe("function");
|
||||
expect(typeof plugin.config.resolveAccount).toBe("function");
|
||||
}
|
||||
|
||||
type ChannelActionsContractCase = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
expectedActions: readonly ChannelMessageActionName[];
|
||||
expectedCapabilities?: readonly ChannelMessageCapability[];
|
||||
beforeTest?: () => void;
|
||||
};
|
||||
|
||||
export function installChannelActionsContractSuite(params: {
|
||||
plugin: Pick<ChannelPlugin, "id" | "actions">;
|
||||
cases: readonly ChannelActionsContractCase[];
|
||||
unsupportedAction?: ChannelMessageActionName;
|
||||
}) {
|
||||
it("exposes the base message actions contract", () => {
|
||||
expect(params.plugin.actions).toBeDefined();
|
||||
expect(typeof params.plugin.actions?.describeMessageTool).toBe("function");
|
||||
});
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`actions contract: ${testCase.name}`, () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const discovery = resolveContractMessageDiscovery({
|
||||
plugin: params.plugin,
|
||||
cfg: testCase.cfg,
|
||||
});
|
||||
const actions = discovery.actions;
|
||||
const capabilities = discovery.capabilities;
|
||||
|
||||
expect(actions).toEqual([...new Set(actions)]);
|
||||
expect(capabilities).toEqual([...new Set(capabilities)]);
|
||||
expect(sortStrings(actions)).toEqual(sortStrings(testCase.expectedActions));
|
||||
expect(sortStrings(capabilities)).toEqual(sortStrings(testCase.expectedCapabilities ?? []));
|
||||
|
||||
if (params.plugin.actions?.supportsAction) {
|
||||
for (const action of testCase.expectedActions) {
|
||||
expect(params.plugin.actions.supportsAction({ action })).toBe(true);
|
||||
}
|
||||
if (
|
||||
params.unsupportedAction &&
|
||||
!testCase.expectedActions.includes(params.unsupportedAction)
|
||||
) {
|
||||
expect(params.plugin.actions.supportsAction({ action: params.unsupportedAction })).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelSetupContractCase<ResolvedAccount> = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
input: ChannelSetupInput;
|
||||
expectedAccountId?: string;
|
||||
expectedValidation?: string | null;
|
||||
beforeTest?: () => void;
|
||||
assertPatchedConfig?: (cfg: OpenClawConfig) => void;
|
||||
assertResolvedAccount?: (account: ResolvedAccount, cfg: OpenClawConfig) => void;
|
||||
};
|
||||
|
||||
export function installChannelSetupContractSuite<ResolvedAccount>(params: {
|
||||
plugin: Pick<ChannelPlugin<ResolvedAccount>, "id" | "config" | "setup">;
|
||||
cases: readonly ChannelSetupContractCase<ResolvedAccount>[];
|
||||
}) {
|
||||
it("exposes the base setup contract", () => {
|
||||
expect(params.plugin.setup).toBeDefined();
|
||||
expect(typeof params.plugin.setup?.applyAccountConfig).toBe("function");
|
||||
});
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`setup contract: ${testCase.name}`, () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const resolvedAccountId =
|
||||
params.plugin.setup?.resolveAccountId?.({
|
||||
cfg: testCase.cfg,
|
||||
accountId: testCase.accountId,
|
||||
input: testCase.input,
|
||||
}) ??
|
||||
testCase.accountId ??
|
||||
"default";
|
||||
|
||||
expect(resolvedAccountId).toBe(testCase.expectedAccountId ?? resolvedAccountId);
|
||||
|
||||
const validation =
|
||||
params.plugin.setup?.validateInput?.({
|
||||
cfg: testCase.cfg,
|
||||
accountId: resolvedAccountId,
|
||||
input: testCase.input,
|
||||
}) ?? null;
|
||||
expect(validation).toBe(testCase.expectedValidation ?? null);
|
||||
|
||||
const nextCfg = params.plugin.setup?.applyAccountConfig({
|
||||
cfg: testCase.cfg,
|
||||
accountId: resolvedAccountId,
|
||||
input: testCase.input,
|
||||
});
|
||||
expect(nextCfg).toBeDefined();
|
||||
|
||||
const account = params.plugin.config.resolveAccount(nextCfg!, resolvedAccountId);
|
||||
testCase.assertPatchedConfig?.(nextCfg!);
|
||||
testCase.assertResolvedAccount?.(account, nextCfg!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelStatusContractCase<Probe> = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
runtime?: ChannelAccountSnapshot;
|
||||
probe?: Probe;
|
||||
beforeTest?: () => void;
|
||||
expectedState?: ChannelAccountState;
|
||||
resolveStateInput?: {
|
||||
configured: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
assertSnapshot?: (snapshot: ChannelAccountSnapshot) => void;
|
||||
assertSummary?: (summary: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
export function installChannelStatusContractSuite<ResolvedAccount, Probe = unknown>(params: {
|
||||
plugin: Pick<ChannelPlugin<ResolvedAccount, Probe>, "id" | "config" | "status">;
|
||||
cases: readonly ChannelStatusContractCase<Probe>[];
|
||||
}) {
|
||||
it("exposes the base status contract", () => {
|
||||
expect(params.plugin.status).toBeDefined();
|
||||
expect(typeof params.plugin.status?.buildAccountSnapshot).toBe("function");
|
||||
});
|
||||
|
||||
if (params.plugin.status?.defaultRuntime) {
|
||||
it("status contract: default runtime is shaped like an account snapshot", () => {
|
||||
expect(typeof params.plugin.status?.defaultRuntime?.accountId).toBe("string");
|
||||
});
|
||||
}
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`status contract: ${testCase.name}`, async () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const account = params.plugin.config.resolveAccount(testCase.cfg, testCase.accountId);
|
||||
const snapshot = await params.plugin.status!.buildAccountSnapshot!({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
runtime: testCase.runtime,
|
||||
probe: testCase.probe,
|
||||
});
|
||||
|
||||
expect(typeof snapshot.accountId).toBe("string");
|
||||
expect(snapshot.accountId.trim()).not.toBe("");
|
||||
testCase.assertSnapshot?.(snapshot);
|
||||
|
||||
if (params.plugin.status?.buildChannelSummary) {
|
||||
const defaultAccountId =
|
||||
params.plugin.config.defaultAccountId?.(testCase.cfg) ?? testCase.accountId ?? "default";
|
||||
const summary = await params.plugin.status.buildChannelSummary({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
defaultAccountId,
|
||||
snapshot,
|
||||
});
|
||||
expect(summary).toEqual(expect.any(Object));
|
||||
testCase.assertSummary?.(summary);
|
||||
}
|
||||
|
||||
if (testCase.expectedState && params.plugin.status?.resolveAccountState) {
|
||||
const state = params.plugin.status.resolveAccountState({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
configured: testCase.resolveStateInput?.configured ?? true,
|
||||
enabled: testCase.resolveStateInput?.enabled ?? true,
|
||||
});
|
||||
expect(state).toBe(testCase.expectedState);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
36
src/plugin-sdk/test-helpers/directory-ids.ts
Normal file
36
src/plugin-sdk/test-helpers/directory-ids.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { expect } from "vitest";
|
||||
import type { ChannelDirectoryEntry } from "../channel-contract.js";
|
||||
import type { OpenClawConfig } from "../config-types.js";
|
||||
|
||||
export type DirectoryListFn = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
query?: string | null;
|
||||
limit?: number | null;
|
||||
}) => Promise<ChannelDirectoryEntry[]>;
|
||||
|
||||
export async function expectDirectoryIds(
|
||||
listFn: DirectoryListFn,
|
||||
cfg: OpenClawConfig,
|
||||
expected: string[],
|
||||
options?: { sorted?: boolean },
|
||||
) {
|
||||
const entries = await listFn({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
const ids = entries.map((entry) => entry.id);
|
||||
expect(options?.sorted ? sortDirectoryIds(ids) : ids).toEqual(
|
||||
options?.sorted ? sortDirectoryIds(expected) : expected,
|
||||
);
|
||||
}
|
||||
|
||||
function compareDirectoryIds(left: string, right: string) {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
function sortDirectoryIds(values: string[]) {
|
||||
return values.toSorted(compareDirectoryIds);
|
||||
}
|
||||
@@ -1,36 +1,4 @@
|
||||
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { expect } from "vitest";
|
||||
|
||||
export type DirectoryListFn = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
query?: string | null;
|
||||
limit?: number | null;
|
||||
}) => Promise<ChannelDirectoryEntry[]>;
|
||||
|
||||
export async function expectDirectoryIds(
|
||||
listFn: DirectoryListFn,
|
||||
cfg: OpenClawConfig,
|
||||
expected: string[],
|
||||
options?: { sorted?: boolean },
|
||||
) {
|
||||
const entries = await listFn({
|
||||
cfg,
|
||||
accountId: "default",
|
||||
query: null,
|
||||
limit: null,
|
||||
});
|
||||
const ids = entries.map((entry) => entry.id);
|
||||
expect(options?.sorted ? sortDirectoryIds(ids) : ids).toEqual(
|
||||
options?.sorted ? sortDirectoryIds(expected) : expected,
|
||||
);
|
||||
}
|
||||
|
||||
function compareDirectoryIds(left: string, right: string) {
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
}
|
||||
|
||||
function sortDirectoryIds(values: string[]) {
|
||||
return values.toSorted(compareDirectoryIds);
|
||||
}
|
||||
export {
|
||||
expectDirectoryIds,
|
||||
type DirectoryListFn,
|
||||
} from "../../../src/plugin-sdk/test-helpers/directory-ids.js";
|
||||
|
||||
@@ -1,242 +1,7 @@
|
||||
import { expect, it } from "vitest";
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
ChannelAccountState,
|
||||
ChannelSetupInput,
|
||||
} from "../../../src/channels/plugins/types.core.js";
|
||||
import type {
|
||||
ChannelMessageActionName,
|
||||
ChannelMessageCapability,
|
||||
ChannelPlugin,
|
||||
} from "../../../src/channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
|
||||
function sortStrings(values: readonly string[]) {
|
||||
return [...values].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveContractMessageDiscovery(params: {
|
||||
plugin: Pick<ChannelPlugin, "actions">;
|
||||
cfg: OpenClawConfig;
|
||||
}) {
|
||||
const actions = params.plugin.actions;
|
||||
if (!actions) {
|
||||
return {
|
||||
actions: [] as ChannelMessageActionName[],
|
||||
capabilities: [] as readonly ChannelMessageCapability[],
|
||||
};
|
||||
}
|
||||
const discovery = actions.describeMessageTool({ cfg: params.cfg }) ?? null;
|
||||
return {
|
||||
actions: Array.isArray(discovery?.actions) ? [...discovery.actions] : [],
|
||||
capabilities: Array.isArray(discovery?.capabilities) ? discovery.capabilities : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function installChannelPluginContractSuite(params: {
|
||||
plugin: Pick<ChannelPlugin, "id" | "meta" | "capabilities" | "config">;
|
||||
}) {
|
||||
it("satisfies the base channel plugin contract", () => {
|
||||
expectChannelPluginContract(params.plugin);
|
||||
});
|
||||
}
|
||||
|
||||
export function expectChannelPluginContract(
|
||||
plugin: Pick<ChannelPlugin, "id" | "meta" | "capabilities" | "config">,
|
||||
) {
|
||||
expect(typeof plugin.id).toBe("string");
|
||||
expect(plugin.id.trim()).not.toBe("");
|
||||
|
||||
expect(plugin.meta.id).toBe(plugin.id);
|
||||
expect(plugin.meta.label.trim()).not.toBe("");
|
||||
expect(plugin.meta.selectionLabel.trim()).not.toBe("");
|
||||
expect(plugin.meta.docsPath).toMatch(/^\/channels\//);
|
||||
expect(plugin.meta.blurb.trim()).not.toBe("");
|
||||
|
||||
expect(plugin.capabilities.chatTypes.length).toBeGreaterThan(0);
|
||||
|
||||
expect(typeof plugin.config.listAccountIds).toBe("function");
|
||||
expect(typeof plugin.config.resolveAccount).toBe("function");
|
||||
}
|
||||
|
||||
type ChannelActionsContractCase = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
expectedActions: readonly ChannelMessageActionName[];
|
||||
expectedCapabilities?: readonly ChannelMessageCapability[];
|
||||
beforeTest?: () => void;
|
||||
};
|
||||
|
||||
export function installChannelActionsContractSuite(params: {
|
||||
plugin: Pick<ChannelPlugin, "id" | "actions">;
|
||||
cases: readonly ChannelActionsContractCase[];
|
||||
unsupportedAction?: ChannelMessageActionName;
|
||||
}) {
|
||||
it("exposes the base message actions contract", () => {
|
||||
expect(params.plugin.actions).toBeDefined();
|
||||
expect(typeof params.plugin.actions?.describeMessageTool).toBe("function");
|
||||
});
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`actions contract: ${testCase.name}`, () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const discovery = resolveContractMessageDiscovery({
|
||||
plugin: params.plugin,
|
||||
cfg: testCase.cfg,
|
||||
});
|
||||
const actions = discovery.actions;
|
||||
const capabilities = discovery.capabilities;
|
||||
|
||||
expect(actions).toEqual([...new Set(actions)]);
|
||||
expect(capabilities).toEqual([...new Set(capabilities)]);
|
||||
expect(sortStrings(actions)).toEqual(sortStrings(testCase.expectedActions));
|
||||
expect(sortStrings(capabilities)).toEqual(sortStrings(testCase.expectedCapabilities ?? []));
|
||||
|
||||
if (params.plugin.actions?.supportsAction) {
|
||||
for (const action of testCase.expectedActions) {
|
||||
expect(params.plugin.actions.supportsAction({ action })).toBe(true);
|
||||
}
|
||||
if (
|
||||
params.unsupportedAction &&
|
||||
!testCase.expectedActions.includes(params.unsupportedAction)
|
||||
) {
|
||||
expect(params.plugin.actions.supportsAction({ action: params.unsupportedAction })).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelSetupContractCase<ResolvedAccount> = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
input: ChannelSetupInput;
|
||||
expectedAccountId?: string;
|
||||
expectedValidation?: string | null;
|
||||
beforeTest?: () => void;
|
||||
assertPatchedConfig?: (cfg: OpenClawConfig) => void;
|
||||
assertResolvedAccount?: (account: ResolvedAccount, cfg: OpenClawConfig) => void;
|
||||
};
|
||||
|
||||
export function installChannelSetupContractSuite<ResolvedAccount>(params: {
|
||||
plugin: Pick<ChannelPlugin<ResolvedAccount>, "id" | "config" | "setup">;
|
||||
cases: readonly ChannelSetupContractCase<ResolvedAccount>[];
|
||||
}) {
|
||||
it("exposes the base setup contract", () => {
|
||||
expect(params.plugin.setup).toBeDefined();
|
||||
expect(typeof params.plugin.setup?.applyAccountConfig).toBe("function");
|
||||
});
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`setup contract: ${testCase.name}`, () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const resolvedAccountId =
|
||||
params.plugin.setup?.resolveAccountId?.({
|
||||
cfg: testCase.cfg,
|
||||
accountId: testCase.accountId,
|
||||
input: testCase.input,
|
||||
}) ??
|
||||
testCase.accountId ??
|
||||
"default";
|
||||
|
||||
expect(resolvedAccountId).toBe(testCase.expectedAccountId ?? resolvedAccountId);
|
||||
|
||||
const validation =
|
||||
params.plugin.setup?.validateInput?.({
|
||||
cfg: testCase.cfg,
|
||||
accountId: resolvedAccountId,
|
||||
input: testCase.input,
|
||||
}) ?? null;
|
||||
expect(validation).toBe(testCase.expectedValidation ?? null);
|
||||
|
||||
const nextCfg = params.plugin.setup?.applyAccountConfig({
|
||||
cfg: testCase.cfg,
|
||||
accountId: resolvedAccountId,
|
||||
input: testCase.input,
|
||||
});
|
||||
expect(nextCfg).toBeDefined();
|
||||
|
||||
const account = params.plugin.config.resolveAccount(nextCfg!, resolvedAccountId);
|
||||
testCase.assertPatchedConfig?.(nextCfg!);
|
||||
testCase.assertResolvedAccount?.(account, nextCfg!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelStatusContractCase<Probe> = {
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string;
|
||||
runtime?: ChannelAccountSnapshot;
|
||||
probe?: Probe;
|
||||
beforeTest?: () => void;
|
||||
expectedState?: ChannelAccountState;
|
||||
resolveStateInput?: {
|
||||
configured: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
assertSnapshot?: (snapshot: ChannelAccountSnapshot) => void;
|
||||
assertSummary?: (summary: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
export function installChannelStatusContractSuite<ResolvedAccount, Probe = unknown>(params: {
|
||||
plugin: Pick<ChannelPlugin<ResolvedAccount, Probe>, "id" | "config" | "status">;
|
||||
cases: readonly ChannelStatusContractCase<Probe>[];
|
||||
}) {
|
||||
it("exposes the base status contract", () => {
|
||||
expect(params.plugin.status).toBeDefined();
|
||||
expect(typeof params.plugin.status?.buildAccountSnapshot).toBe("function");
|
||||
});
|
||||
|
||||
if (params.plugin.status?.defaultRuntime) {
|
||||
it("status contract: default runtime is shaped like an account snapshot", () => {
|
||||
expect(typeof params.plugin.status?.defaultRuntime?.accountId).toBe("string");
|
||||
});
|
||||
}
|
||||
|
||||
for (const testCase of params.cases) {
|
||||
it(`status contract: ${testCase.name}`, async () => {
|
||||
testCase.beforeTest?.();
|
||||
|
||||
const account = params.plugin.config.resolveAccount(testCase.cfg, testCase.accountId);
|
||||
const snapshot = await params.plugin.status!.buildAccountSnapshot!({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
runtime: testCase.runtime,
|
||||
probe: testCase.probe,
|
||||
});
|
||||
|
||||
expect(typeof snapshot.accountId).toBe("string");
|
||||
expect(snapshot.accountId.trim()).not.toBe("");
|
||||
testCase.assertSnapshot?.(snapshot);
|
||||
|
||||
if (params.plugin.status?.buildChannelSummary) {
|
||||
const defaultAccountId =
|
||||
params.plugin.config.defaultAccountId?.(testCase.cfg) ?? testCase.accountId ?? "default";
|
||||
const summary = await params.plugin.status.buildChannelSummary({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
defaultAccountId,
|
||||
snapshot,
|
||||
});
|
||||
expect(summary).toEqual(expect.any(Object));
|
||||
testCase.assertSummary?.(summary);
|
||||
}
|
||||
|
||||
if (testCase.expectedState && params.plugin.status?.resolveAccountState) {
|
||||
const state = params.plugin.status.resolveAccountState({
|
||||
account,
|
||||
cfg: testCase.cfg,
|
||||
configured: testCase.resolveStateInput?.configured ?? true,
|
||||
enabled: testCase.resolveStateInput?.enabled ?? true,
|
||||
});
|
||||
expect(state).toBe(testCase.expectedState);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export {
|
||||
expectChannelPluginContract,
|
||||
installChannelActionsContractSuite,
|
||||
installChannelPluginContractSuite,
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "../../../src/plugin-sdk/test-helpers/channel-contract-suites.js";
|
||||
|
||||
Reference in New Issue
Block a user