diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b5ef10f7b..163435951d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 183996a15e7..3271a234f7f 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -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 diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index b8f554b2242..6aa98498f4e 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -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 | diff --git a/docs/plugins/sdk-testing.md b/docs/plugins/sdk-testing.md index 21c8dd50652..b38f48e8e1c 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -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 diff --git a/extensions/anthropic/cli-migration.test.ts b/extensions/anthropic/cli-migration.test.ts index cc4b6ea0fe7..4eeb2609971 100644 --- a/extensions/anthropic/cli-migration.test.ts +++ b/extensions/anthropic/cli-migration.test.ts @@ -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() { diff --git a/extensions/discord/src/channel-actions.contract.test.ts b/extensions/discord/src/channel-actions.contract.test.ts index 25bb2db3884..ce4d8ab2fe2 100644 --- a/extensions/discord/src/channel-actions.contract.test.ts +++ b/extensions/discord/src/channel-actions.contract.test.ts @@ -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", () => { diff --git a/extensions/discord/src/directory-contract.test.ts b/extensions/discord/src/directory-contract.test.ts index bfef141500c..cdc0820ce3b 100644 --- a/extensions/discord/src/directory-contract.test.ts +++ b/extensions/discord/src/directory-contract.test.ts @@ -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, diff --git a/extensions/line/src/channel-setup-status.contract.test.ts b/extensions/line/src/channel-setup-status.contract.test.ts index aab5bf79ef5..0ca585fa98d 100644 --- a/extensions/line/src/channel-setup-status.contract.test.ts +++ b/extensions/line/src/channel-setup-status.contract.test.ts @@ -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", () => { diff --git a/extensions/mattermost/src/channel-actions-setup-status.contract.test.ts b/extensions/mattermost/src/channel-actions-setup-status.contract.test.ts index 13d8d377922..cb32b3fa034 100644 --- a/extensions/mattermost/src/channel-actions-setup-status.contract.test.ts +++ b/extensions/mattermost/src/channel-actions-setup-status.contract.test.ts @@ -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", () => { diff --git a/extensions/slack/src/channel-actions-setup-status.contract.test.ts b/extensions/slack/src/channel-actions-setup-status.contract.test.ts index abfccf34c87..992c9193e86 100644 --- a/extensions/slack/src/channel-actions-setup-status.contract.test.ts +++ b/extensions/slack/src/channel-actions-setup-status.contract.test.ts @@ -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"; diff --git a/extensions/slack/src/directory-contract.test.ts b/extensions/slack/src/directory-contract.test.ts index b6e27107842..f0d9397261f 100644 --- a/extensions/slack/src/directory-contract.test.ts +++ b/extensions/slack/src/directory-contract.test.ts @@ -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, diff --git a/extensions/telegram/src/bot-native-commands.registry.test.ts b/extensions/telegram/src/bot-native-commands.registry.test.ts index 36e547af2cb..a0bada5f869 100644 --- a/extensions/telegram/src/bot-native-commands.registry.test.ts +++ b/extensions/telegram/src/bot-native-commands.registry.test.ts @@ -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, diff --git a/extensions/telegram/src/channel-actions.contract.test.ts b/extensions/telegram/src/channel-actions.contract.test.ts index e79c59dd6fc..63978e70c68 100644 --- a/extensions/telegram/src/channel-actions.contract.test.ts +++ b/extensions/telegram/src/channel-actions.contract.test.ts @@ -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", () => { diff --git a/extensions/telegram/src/directory-contract.test.ts b/extensions/telegram/src/directory-contract.test.ts index 133ce3dfe56..f6c84bbb3df 100644 --- a/extensions/telegram/src/directory-contract.test.ts +++ b/extensions/telegram/src/directory-contract.test.ts @@ -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, diff --git a/scripts/check-no-extension-test-core-imports.ts b/scripts/check-no-extension-test-core-imports.ts index 22e2f4016a9..03e3e9b8686 100644 --- a/scripts/check-no-extension-test-core-imports.ts +++ b/scripts/check-no-extension-test-core-imports.ts @@ -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. diff --git a/src/plugin-sdk/channel-test-helpers.ts b/src/plugin-sdk/channel-test-helpers.ts index 4b2d52aace4..0e91ffbe89b 100644 --- a/src/plugin-sdk/channel-test-helpers.ts +++ b/src/plugin-sdk/channel-test-helpers.ts @@ -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, diff --git a/src/plugin-sdk/test-helpers/channel-contract-suites.ts b/src/plugin-sdk/test-helpers/channel-contract-suites.ts new file mode 100644 index 00000000000..6d8bd69e4b1 --- /dev/null +++ b/src/plugin-sdk/test-helpers/channel-contract-suites.ts @@ -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; + 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; +}) { + it("satisfies the base channel plugin contract", () => { + expectChannelPluginContract(params.plugin); + }); +} + +export function expectChannelPluginContract( + plugin: Pick, +) { + 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; + 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 = { + 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(params: { + plugin: Pick, "id" | "config" | "setup">; + cases: readonly ChannelSetupContractCase[]; +}) { + 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 = { + 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) => void; +}; + +export function installChannelStatusContractSuite(params: { + plugin: Pick, "id" | "config" | "status">; + cases: readonly ChannelStatusContractCase[]; +}) { + 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); + } + }); + } +} diff --git a/src/plugin-sdk/test-helpers/directory-ids.ts b/src/plugin-sdk/test-helpers/directory-ids.ts new file mode 100644 index 00000000000..453d63a3dd5 --- /dev/null +++ b/src/plugin-sdk/test-helpers/directory-ids.ts @@ -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; + +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); +} diff --git a/test/helpers/channels/directory-ids.ts b/test/helpers/channels/directory-ids.ts index a8500dd61e8..12271f08343 100644 --- a/test/helpers/channels/directory-ids.ts +++ b/test/helpers/channels/directory-ids.ts @@ -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; - -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"; diff --git a/test/helpers/channels/registry-contract-suites.ts b/test/helpers/channels/registry-contract-suites.ts index e7d8acc5ebe..928717a4093 100644 --- a/test/helpers/channels/registry-contract-suites.ts +++ b/test/helpers/channels/registry-contract-suites.ts @@ -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; - 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; -}) { - it("satisfies the base channel plugin contract", () => { - expectChannelPluginContract(params.plugin); - }); -} - -export function expectChannelPluginContract( - plugin: Pick, -) { - 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; - 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 = { - 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(params: { - plugin: Pick, "id" | "config" | "setup">; - cases: readonly ChannelSetupContractCase[]; -}) { - 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 = { - 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) => void; -}; - -export function installChannelStatusContractSuite(params: { - plugin: Pick, "id" | "config" | "status">; - cases: readonly ChannelStatusContractCase[]; -}) { - 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";