diff --git a/CHANGELOG.md b/CHANGELOG.md index 914f6553e8e..2c006ac384f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai - 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: move core-only channel contract fixtures under the channel contract test tree and retire the old `test/helpers/channels` bridge directory so plugin tests stay on focused SDK surfaces. Thanks @vincentkoc. - Plugin SDK: move maintained bundled channels off the deprecated `channel-config-schema-legacy` subpath, add an explicit bundled-channel schema SDK surface, and track both remaining legacy test/config compatibility barrels with dated removal windows. Thanks @vincentkoc. +- Plugin SDK/testing: expose media provider capability assertions and provider HTTP mocks through focused SDK test subpaths, and retire the repo-only media-generation test helper bridge. Thanks @vincentkoc. - 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 `plugin-sdk/channel-target-testing` for shared channel target-resolution cases, document channel reaction helpers on `plugin-sdk/channel-feedback`, and keep the old `plugin-sdk/test-utils` alias as compatibility-only. Thanks @vincentkoc. diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 4aa0fef7c1a..4f52bb0a557 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -8f23f155251c05cab51ee8926e7a359bd64a0ba34e82a80d93d0ed96d07c8a04 plugin-sdk-api-baseline.json -181fea7f35c49032e6894605a06ca1419e5b6ccc1a3d8987d952a1d24a8154bc plugin-sdk-api-baseline.jsonl +31fd2178f08a4fcb28d6319eaa464b572b1e36a0fab700056f643feaccf95aa8 plugin-sdk-api-baseline.json +65b239e91e4d5f4cac71527058aa53179a8dcf65f8c50f4eabab346def966e74 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index c7b686e1ecd..e2b50c6d1eb 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -67,6 +67,26 @@ Use `--json` for stable machine-readable output in CI annotations. OpenClaw core should expose contracts and fixtures the inspector can consume, but should not publish the inspector binary from the main `openclaw` package. +### Maintainer acceptance lane + +Use Blacksmith Testbox for the installable-package acceptance lane when validating +the external inspector against OpenClaw plugin packages. Run it from a clean +OpenClaw checkout after the package is built: + +```sh +blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90 +blacksmith testbox run --id "pnpm install && pnpm build && npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/telegram --json" +blacksmith testbox run --id "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/discord --json" +blacksmith testbox run --id "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- --json" +blacksmith testbox stop +``` + +Keep this lane opt-in for maintainers because it installs an external npm +package and may inspect plugin packages cloned outside the repo. The local repo +guards cover the SDK export map, compatibility registry metadata, deprecated +SDK-import burn-down, and bundled extension import boundaries; Testbox inspector +proof covers the package as external plugin authors consume it. + ## Deprecation policy OpenClaw should not remove a documented plugin contract in the same release diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 30234aff80a..e85865654ab 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -16,23 +16,24 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) ## Plugin entry -| Subpath | Key exports | -| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `plugin-sdk/plugin-entry` | `definePluginEntry` | -| `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` | -| `plugin-sdk/config-schema` | `OpenClawSchema` | -| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | -| `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, hook, and generic channel contract test helpers | -| `plugin-sdk/channel-target-testing` | Shared channel target-resolution error-case test suite | -| `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 | -| `plugin-sdk/test-env` | Test environment, fetch/network, live-test, temporary filesystem, and time-control fixtures | -| `plugin-sdk/test-fixtures` | Generic CLI, sandbox, skill, agent-message, system-event, terminal, chunking, auth-token, and typed-case test fixtures | -| `plugin-sdk/migration` | Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | -| `plugin-sdk/migration-runtime` | Runtime migration helpers such as `copyMigrationFileItem` and `writeMigrationReport` | +| Subpath | Key exports | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `plugin-sdk/plugin-entry` | `definePluginEntry` | +| `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` | +| `plugin-sdk/config-schema` | `OpenClawSchema` | +| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | +| `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, hook, and generic channel contract test helpers | +| `plugin-sdk/channel-target-testing` | Shared channel target-resolution error-case test suite | +| `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, media capability, web-search/fetch, and wizard contract helpers | +| `plugin-sdk/provider-http-test-mocks` | Opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | +| `plugin-sdk/test-env` | Test environment, fetch/network, live-test, temporary filesystem, and time-control fixtures | +| `plugin-sdk/test-fixtures` | Generic CLI, sandbox, skill, agent-message, system-event, terminal, chunking, auth-token, and typed-case test fixtures | +| `plugin-sdk/migration` | Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | +| `plugin-sdk/migration-runtime` | Runtime migration helpers such as `copyMigrationFileItem` and `writeMigrationReport` | @@ -274,7 +275,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `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/channel-target-testing` | Shared target-resolution error-case suite for channel tests | | `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/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, wizard, media capability, web-search/fetch, and stream contract helpers | + | `plugin-sdk/provider-http-test-mocks` | Opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | | `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 4009410c042..a4345928cfc 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -33,6 +33,8 @@ plugins. **Provider contract import:** `openclaw/plugin-sdk/provider-test-contracts` +**Provider HTTP mock import:** `openclaw/plugin-sdk/provider-http-test-mocks` + **Environment/network test import:** `openclaw/plugin-sdk/test-env` **Generic fixture import:** `openclaw/plugin-sdk/test-fixtures` @@ -52,6 +54,7 @@ import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-help import { describePluginRegistrationContract } from "openclaw/plugin-sdk/plugin-test-contracts"; import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime"; import { describeOpenAIProviderRuntimeContract } from "openclaw/plugin-sdk/provider-test-contracts"; +import { getProviderHttpMocks } from "openclaw/plugin-sdk/provider-http-test-mocks"; import { withEnv, withFetchPreconnect } from "openclaw/plugin-sdk/test-env"; import { createCliRuntimeCapture, typedCases } from "openclaw/plugin-sdk/test-fixtures"; ``` @@ -76,6 +79,11 @@ import { createCliRuntimeCapture, typedCases } from "openclaw/plugin-sdk/test-fi | `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` | +| `expectExplicitVideoGenerationCapabilities` | Assert video providers declare explicit generation mode capabilities. Import from `plugin-sdk/provider-test-contracts` | +| `expectExplicitMusicGenerationCapabilities` | Assert music providers declare explicit generation/edit capabilities. Import from `plugin-sdk/provider-test-contracts` | +| `mockSuccessfulDashscopeVideoTask` | Install a successful DashScope-compatible video task response. Import from `plugin-sdk/provider-test-contracts` | +| `getProviderHttpMocks` | Access opt-in provider HTTP/auth Vitest mocks. Import from `plugin-sdk/provider-http-test-mocks` | +| `installProviderHttpMockCleanup` | Reset provider HTTP/auth mocks after each test. Import from `plugin-sdk/provider-http-test-mocks` | | `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling. Import from `plugin-sdk/channel-target-testing` | | `shouldAckReaction` | Check whether a channel should add an ack reaction. Import from `plugin-sdk/channel-feedback` | | `removeAckReactionAfterReply` | Remove ack reaction after reply delivery. Import from `plugin-sdk/channel-feedback` | @@ -112,9 +120,10 @@ 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. +`plugin-sdk/provider-http-test-mocks`, `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/alibaba/video-generation-provider.test.ts b/extensions/alibaba/video-generation-provider.test.ts index c8c389bdd80..1b2eefce578 100644 --- a/extensions/alibaba/video-generation-provider.test.ts +++ b/extensions/alibaba/video-generation-provider.test.ts @@ -1,14 +1,14 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { - expectDashscopeVideoTaskPoll, - expectSuccessfulDashscopeVideoResult, - mockSuccessfulDashscopeVideoTask, -} from "../../test/helpers/media-generation/dashscope-video-provider.js"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { + expectDashscopeVideoTaskPoll, + expectExplicitVideoGenerationCapabilities, + expectSuccessfulDashscopeVideoResult, + mockSuccessfulDashscopeVideoTask, +} from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/extensions/byteplus/video-generation-provider.test.ts b/extensions/byteplus/video-generation-provider.test.ts index 4efb9046ebb..4a6e180c6cb 100644 --- a/extensions/byteplus/video-generation-provider.test.ts +++ b/extensions/byteplus/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/extensions/comfy/music-generation-provider.test.ts b/extensions/comfy/music-generation-provider.test.ts index b4da7b5aab2..6fdd77a5830 100644 --- a/extensions/comfy/music-generation-provider.test.ts +++ b/extensions/comfy/music-generation-provider.test.ts @@ -1,5 +1,5 @@ +import { expectExplicitMusicGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { describe, expect, it, vi } from "vitest"; -import { expectExplicitMusicGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { buildComfyMusicGenerationProvider } from "./music-generation-provider.js"; import { _setComfyFetchGuardForTesting } from "./workflow-runtime.js"; diff --git a/extensions/comfy/video-generation-provider.test.ts b/extensions/comfy/video-generation-provider.test.ts index 550d8a294be..c6479a61d4d 100644 --- a/extensions/comfy/video-generation-provider.test.ts +++ b/extensions/comfy/video-generation-provider.test.ts @@ -1,5 +1,5 @@ +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { buildComfyConfig, mockComfyCloudJobResponses, diff --git a/extensions/deepinfra/video-generation-provider.test.ts b/extensions/deepinfra/video-generation-provider.test.ts index 19c6006dd5b..f14167eff59 100644 --- a/extensions/deepinfra/video-generation-provider.test.ts +++ b/extensions/deepinfra/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, resolveProviderHttpRequestConfigMock } = getProviderHttpMocks(); diff --git a/extensions/fal/video-generation-provider.test.ts b/extensions/fal/video-generation-provider.test.ts index f0ac363ed64..71ee6444384 100644 --- a/extensions/fal/video-generation-provider.test.ts +++ b/extensions/fal/video-generation-provider.test.ts @@ -1,7 +1,7 @@ import * as providerAuth from "openclaw/plugin-sdk/provider-auth-runtime"; import * as providerHttp from "openclaw/plugin-sdk/provider-http"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { _setFalVideoFetchGuardForTesting, buildFalVideoGenerationProvider, diff --git a/extensions/google/music-generation-provider.test.ts b/extensions/google/music-generation-provider.test.ts index 746197fb617..6d9282d808d 100644 --- a/extensions/google/music-generation-provider.test.ts +++ b/extensions/google/music-generation-provider.test.ts @@ -17,7 +17,7 @@ vi.mock("./google-genai-runtime.js", () => ({ })); import * as providerAuthRuntime from "openclaw/plugin-sdk/provider-auth-runtime"; -import { expectExplicitMusicGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; +import { expectExplicitMusicGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { buildGoogleMusicGenerationProvider } from "./music-generation-provider.js"; describe("google music generation provider", () => { diff --git a/extensions/google/speech-provider.test.ts b/extensions/google/speech-provider.test.ts index 47d952c7ee8..c611a3fb031 100644 --- a/extensions/google/speech-provider.test.ts +++ b/extensions/google/speech-provider.test.ts @@ -1,8 +1,8 @@ -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; const transcodeAudioBufferToOpusMock = vi.hoisted(() => vi.fn()); diff --git a/extensions/google/video-generation-provider.test.ts b/extensions/google/video-generation-provider.test.ts index 538e2398cb9..3db4de23aab 100644 --- a/extensions/google/video-generation-provider.test.ts +++ b/extensions/google/video-generation-provider.test.ts @@ -26,7 +26,7 @@ vi.mock("./google-genai-runtime.js", () => ({ })); import * as providerAuthRuntime from "openclaw/plugin-sdk/provider-auth-runtime"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { buildGoogleVideoGenerationProvider } from "./video-generation-provider.js"; describe("google video generation provider", () => { diff --git a/extensions/minimax/music-generation-provider.test.ts b/extensions/minimax/music-generation-provider.test.ts index 3ff3e5aa719..b4e7f5b0a9f 100644 --- a/extensions/minimax/music-generation-provider.test.ts +++ b/extensions/minimax/music-generation-provider.test.ts @@ -1,5 +1,5 @@ +import { expectExplicitMusicGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitMusicGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getMinimaxProviderHttpMocks, installMinimaxProviderHttpMockCleanup, diff --git a/extensions/minimax/video-generation-provider.test.ts b/extensions/minimax/video-generation-provider.test.ts index 09972fa619f..ff71b0fca1c 100644 --- a/extensions/minimax/video-generation-provider.test.ts +++ b/extensions/minimax/video-generation-provider.test.ts @@ -1,5 +1,5 @@ +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getMinimaxProviderHttpMocks, installMinimaxProviderHttpMockCleanup, diff --git a/extensions/openai/video-generation-provider.test.ts b/extensions/openai/video-generation-provider.test.ts index c1015c2ed02..30551952db4 100644 --- a/extensions/openai/video-generation-provider.test.ts +++ b/extensions/openai/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock, resolveProviderHttpRequestConfigMock } = getProviderHttpMocks(); diff --git a/extensions/qwen/video-generation-provider.test.ts b/extensions/qwen/video-generation-provider.test.ts index 628c5d06144..07e9c2bdf6e 100644 --- a/extensions/qwen/video-generation-provider.test.ts +++ b/extensions/qwen/video-generation-provider.test.ts @@ -1,14 +1,14 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { - expectDashscopeVideoTaskPoll, - expectSuccessfulDashscopeVideoResult, - mockSuccessfulDashscopeVideoTask, -} from "../../test/helpers/media-generation/dashscope-video-provider.js"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { + expectDashscopeVideoTaskPoll, + expectExplicitVideoGenerationCapabilities, + expectSuccessfulDashscopeVideoResult, + mockSuccessfulDashscopeVideoTask, +} from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/extensions/runway/video-generation-provider.test.ts b/extensions/runway/video-generation-provider.test.ts index d9cccedcafd..154b4d38a99 100644 --- a/extensions/runway/video-generation-provider.test.ts +++ b/extensions/runway/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/extensions/together/video-generation-provider.test.ts b/extensions/together/video-generation-provider.test.ts index 225fc7c6dae..e75f8f4672d 100644 --- a/extensions/together/video-generation-provider.test.ts +++ b/extensions/together/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/extensions/vydra/video-generation-provider.test.ts b/extensions/vydra/video-generation-provider.test.ts index db8ac906f80..78cb25d96e3 100644 --- a/extensions/vydra/video-generation-provider.test.ts +++ b/extensions/vydra/video-generation-provider.test.ts @@ -1,6 +1,6 @@ +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; import { installPinnedHostnameTestHooks } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { binaryResponse, jsonResponse, diff --git a/extensions/xai/video-generation-provider.test.ts b/extensions/xai/video-generation-provider.test.ts index bed2b9b814b..66e0e1f4bfa 100644 --- a/extensions/xai/video-generation-provider.test.ts +++ b/extensions/xai/video-generation-provider.test.ts @@ -1,9 +1,9 @@ -import { beforeAll, describe, expect, it, vi } from "vitest"; -import { expectExplicitVideoGenerationCapabilities } from "../../test/helpers/media-generation/provider-capability-assertions.js"; import { getProviderHttpMocks, installProviderHttpMockCleanup, -} from "../../test/helpers/media-generation/provider-http-mocks.js"; +} from "openclaw/plugin-sdk/provider-http-test-mocks"; +import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts"; +import { beforeAll, describe, expect, it, vi } from "vitest"; const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks(); diff --git a/package.json b/package.json index c2c5f5ff6d3..68947cc5630 100644 --- a/package.json +++ b/package.json @@ -522,6 +522,10 @@ "types": "./dist/plugin-sdk/plugin-test-runtime.d.ts", "default": "./dist/plugin-sdk/plugin-test-runtime.js" }, + "./plugin-sdk/provider-http-test-mocks": { + "types": "./dist/plugin-sdk/provider-http-test-mocks.d.ts", + "default": "./dist/plugin-sdk/provider-http-test-mocks.js" + }, "./plugin-sdk/provider-test-contracts": { "types": "./dist/plugin-sdk/provider-test-contracts.d.ts", "default": "./dist/plugin-sdk/provider-test-contracts.js" diff --git a/scripts/check-no-extension-test-core-imports.ts b/scripts/check-no-extension-test-core-imports.ts index 024fff89cc2..958c9db1c76 100644 --- a/scripts/check-no-extension-test-core-imports.ts +++ b/scripts/check-no-extension-test-core-imports.ts @@ -33,6 +33,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ 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: /["'](?:\.\.\/)+(?:test\/helpers\/media-generation\/)[^"']+["']/, + hint: "Use openclaw/plugin-sdk/provider-test-contracts or openclaw/plugin-sdk/provider-http-test-mocks instead of repo-only media provider helper bridges.", + }, { pattern: /["'](?:\.\.\/)+(?:src\/channels\/plugins\/contracts\/test-helpers\/)[^"']+["']/, hint: "Use openclaw/plugin-sdk/channel-test-helpers or another focused SDK test subpath instead of core-only channel contract helpers.", @@ -107,6 +111,9 @@ const RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES = [ "test/helpers/plugins/typed-cases.ts", "test/helpers/plugins/web-fetch-provider-contract.ts", "test/helpers/plugins/web-search-provider-contract.ts", + "test/helpers/media-generation/dashscope-video-provider.ts", + "test/helpers/media-generation/provider-capability-assertions.ts", + "test/helpers/media-generation/provider-http-mocks.ts", ]; function isExtensionTestFile(filePath: string): boolean { diff --git a/scripts/lib/plugin-sdk-doc-metadata.ts b/scripts/lib/plugin-sdk-doc-metadata.ts index f4dfc540f3c..64e48c09afd 100644 --- a/scripts/lib/plugin-sdk-doc-metadata.ts +++ b/scripts/lib/plugin-sdk-doc-metadata.ts @@ -119,6 +119,9 @@ export const pluginSdkDocMetadata = { "provider-test-contracts": { category: "utilities", }, + "provider-http-test-mocks": { + category: "utilities", + }, "test-env": { category: "utilities", }, diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 6be7821d2cb..9fc5d56231e 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -114,6 +114,7 @@ "plugin-test-api", "plugin-test-contracts", "plugin-test-runtime", + "provider-http-test-mocks", "provider-test-contracts", "test-env", "test-fixtures", diff --git a/src/plugin-sdk/provider-http-test-mocks.ts b/src/plugin-sdk/provider-http-test-mocks.ts new file mode 100644 index 00000000000..80e0ba6c664 --- /dev/null +++ b/src/plugin-sdk/provider-http-test-mocks.ts @@ -0,0 +1,4 @@ +export { + getProviderHttpMocks, + installProviderHttpMockCleanup, +} from "./test-helpers/provider-http-mocks.js"; diff --git a/src/plugin-sdk/provider-test-contracts.ts b/src/plugin-sdk/provider-test-contracts.ts index cb8d7988668..db72eb31f99 100644 --- a/src/plugin-sdk/provider-test-contracts.ts +++ b/src/plugin-sdk/provider-test-contracts.ts @@ -32,6 +32,17 @@ export { createConfigWithFallbacks, createLegacyProviderConfig, } from "./test-helpers/onboard-config.js"; +export { + expectDashscopeVideoTaskPoll, + expectSuccessfulDashscopeVideoResult, + mockSuccessfulDashscopeVideoTask, + resetDashscopeVideoProviderMocks, + type DashscopeVideoProviderMocks, +} from "./test-helpers/dashscope-video-provider.js"; +export { + expectExplicitMusicGenerationCapabilities, + expectExplicitVideoGenerationCapabilities, +} from "./test-helpers/provider-media-capability-assertions.js"; export { expectProviderOnboardAllowlistAlias, expectProviderOnboardMergedLegacyConfig, diff --git a/test/helpers/media-generation/dashscope-video-provider.ts b/src/plugin-sdk/test-helpers/dashscope-video-provider.ts similarity index 97% rename from test/helpers/media-generation/dashscope-video-provider.ts rename to src/plugin-sdk/test-helpers/dashscope-video-provider.ts index efae883402c..72869f23f39 100644 --- a/test/helpers/media-generation/dashscope-video-provider.ts +++ b/src/plugin-sdk/test-helpers/dashscope-video-provider.ts @@ -1,5 +1,5 @@ -import type { VideoGenerationResult } from "openclaw/plugin-sdk/video-generation"; import { expect, vi } from "vitest"; +import type { VideoGenerationResult } from "../video-generation.js"; type ClearableMock = { mockClear(): unknown; diff --git a/test/helpers/media-generation/provider-http-mocks.ts b/src/plugin-sdk/test-helpers/provider-http-mocks.ts similarity index 98% rename from test/helpers/media-generation/provider-http-mocks.ts rename to src/plugin-sdk/test-helpers/provider-http-mocks.ts index e66cbbfd26d..863079c850e 100644 --- a/test/helpers/media-generation/provider-http-mocks.ts +++ b/src/plugin-sdk/test-helpers/provider-http-mocks.ts @@ -1,9 +1,9 @@ +import { afterEach, vi } from "vitest"; import type { pollProviderOperationJson, resolveProviderHttpRequestConfig, sanitizeConfiguredModelProviderRequest, -} from "openclaw/plugin-sdk/provider-http"; -import { afterEach, vi } from "vitest"; +} from "../provider-http.js"; type ResolveProviderHttpRequestConfigParams = Parameters< typeof resolveProviderHttpRequestConfig diff --git a/test/helpers/media-generation/provider-capability-assertions.ts b/src/plugin-sdk/test-helpers/provider-media-capability-assertions.ts similarity index 87% rename from test/helpers/media-generation/provider-capability-assertions.ts rename to src/plugin-sdk/test-helpers/provider-media-capability-assertions.ts index b84493bcb84..570a26cbbf7 100644 --- a/test/helpers/media-generation/provider-capability-assertions.ts +++ b/src/plugin-sdk/test-helpers/provider-media-capability-assertions.ts @@ -1,10 +1,8 @@ import { expect } from "vitest"; -import { listSupportedMusicGenerationModes } from "../../../src/music-generation/capabilities.js"; -import type { - MusicGenerationProviderPlugin, - VideoGenerationProviderPlugin, -} from "../../../src/plugins/types.js"; -import { listSupportedVideoGenerationModes } from "../../../src/video-generation/capabilities.js"; +import { listSupportedMusicGenerationModes } from "../../music-generation/capabilities.js"; +import type { MusicGenerationProviderPlugin } from "../../plugins/types.js"; +import type { VideoGenerationProviderPlugin } from "../../plugins/types.js"; +import { listSupportedVideoGenerationModes } from "../../video-generation/capabilities.js"; function hasPositiveModeLimit( value: number | undefined, diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index e209195f9c7..d4a76548342 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -24,6 +24,13 @@ const PRIVATE_BUNDLED_SDK_SURFACE_PATTERN = const GENERIC_CORE_HELPER_FILES = ["src/polls.ts", "src/poll-params.ts"] as const; const GENERIC_CORE_PLUGIN_OWNER_NAME_PATTERN = /\b(?:bluebubbles|discord|feishu|googlechat|matrix|mattermost|msteams|slack|telegram|whatsapp|zalo|zalouser)\b/gi; +const DEPRECATED_EXTENSION_SDK_SPECIFIERS = new Set([ + "openclaw/plugin-sdk", + "openclaw/plugin-sdk/channel-config-schema-legacy", + "openclaw/plugin-sdk/compat", + "openclaw/plugin-sdk/testing", + "openclaw/plugin-sdk/test-utils", +]); function collectPluginSdkPackageExports(): string[] { const packageJson = JSON.parse(readFileSync(resolve(REPO_ROOT, "package.json"), "utf8")) as { @@ -267,6 +274,32 @@ function collectExtensionTestHelperImportLeaks(): Array<{ file: string; specifie return leaks; } +function collectDeprecatedExtensionSdkImports(): Array<{ file: string; specifier: string }> { + const leaks: Array<{ file: string; specifier: string }> = []; + const importPatterns = [ + /\b(?:import|export)\b[\s\S]*?\bfrom\s*["'](openclaw\/plugin-sdk(?:\/[a-z0-9][a-z0-9-]*)?)["']/g, + /\bimport\s*\(\s*["'](openclaw\/plugin-sdk(?:\/[a-z0-9][a-z0-9-]*)?)["']\s*\)/g, + /\bvi\.(?:mock|doMock)\s*\(\s*["'](openclaw\/plugin-sdk(?:\/[a-z0-9][a-z0-9-]*)?)["']/g, + ]; + for (const file of collectExtensionFiles(resolve(REPO_ROOT, "extensions"))) { + const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/"); + const source = readFileSync(file, "utf8"); + for (const importPattern of importPatterns) { + for (const match of source.matchAll(importPattern)) { + const specifier = match[1]; + if (!specifier || !DEPRECATED_EXTENSION_SDK_SPECIFIERS.has(specifier)) { + continue; + } + leaks.push({ + file: repoRelativePath, + specifier, + }); + } + } + } + return leaks; +} + function collectCrossOwnerReservedSdkImports(): Array<{ file: string; specifier: string; @@ -430,6 +463,10 @@ describe("plugin-sdk package contract guardrails", () => { expect(collectExtensionTestHelperImportLeaks()).toEqual([]); }); + it("keeps extension sources off deprecated plugin-sdk compatibility imports", () => { + expect(collectDeprecatedExtensionSdkImports()).toEqual([]); + }); + it("keeps reserved SDK compatibility subpaths inside their owning bundled plugins", () => { expect(collectCrossOwnerReservedSdkImports()).toEqual([]); }); diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index e9a4ec18469..416e2aae74e 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -59,10 +59,12 @@ const PUBLIC_SDK_TEST_HELPER_SUBPATHS = [ "plugin-test-api", "plugin-test-contracts", "plugin-test-runtime", + "provider-http-test-mocks", "provider-test-contracts", "test-env", "test-fixtures", ] as const; +const PUBLIC_SDK_TEST_HELPER_SUBPATHS_WITH_TOP_LEVEL_MOCKS = ["provider-http-test-mocks"] as const; const importResolvedPluginSdkSubpath = async (specifier: string) => import(specifier); @@ -760,16 +762,25 @@ describe("plugin-sdk subpath exports", () => { "installCommonResolveTargetErrorCases", "ResolveTargetFn", ]); + expectSourceMentions("provider-http-test-mocks", [ + "getProviderHttpMocks", + "installProviderHttpMockCleanup", + ]); }); - it("keeps public SDK test helper subpaths free of top-level Vitest module mocks", () => { - const violations = PUBLIC_SDK_TEST_HELPER_SUBPATHS.flatMap((subpath) => - collectReexportedSourceFiles(resolve(PLUGIN_SDK_DIR, `${subpath}.ts`)).flatMap((file) => - topLevelVitestModuleMockLines(file).map( - (line) => `${file.slice(REPO_ROOT.length + 1)}:${line}`, + it("keeps public SDK test helper subpaths free of top-level Vitest module mocks outside opt-in mock helpers", () => { + const optInMockSubpaths = new Set(PUBLIC_SDK_TEST_HELPER_SUBPATHS_WITH_TOP_LEVEL_MOCKS); + const violations = PUBLIC_SDK_TEST_HELPER_SUBPATHS.filter( + (subpath) => !optInMockSubpaths.has(subpath), + ) + .flatMap((subpath) => + collectReexportedSourceFiles(resolve(PLUGIN_SDK_DIR, `${subpath}.ts`)).flatMap((file) => + topLevelVitestModuleMockLines(file).map( + (line) => `${file.slice(REPO_ROOT.length + 1)}:${line}`, + ), ), - ), - ).toSorted(); + ) + .toSorted(); expect(violations).toEqual([]); });