test: expose provider media test helpers

This commit is contained in:
Peter Steinberger
2026-04-28 02:47:18 +01:00
parent 7f3dead335
commit 1945389374
33 changed files with 191 additions and 83 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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 <tbx_id> "pnpm install && pnpm build && npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/telegram --json"
blacksmith testbox run --id <tbx_id> "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/discord --json"
blacksmith testbox run --id <tbx_id> "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- <clawhub-plugin-dir> --json"
blacksmith testbox stop <tbx_id>
```
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

View File

@@ -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` |
<AccordionGroup>
<Accordion title="Channel subpaths">
@@ -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 |
</Accordion>

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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";

View File

@@ -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,

View File

@@ -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();

View File

@@ -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,

View File

@@ -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", () => {

View File

@@ -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());

View File

@@ -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", () => {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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,

View File

@@ -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();

View File

@@ -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"

View File

@@ -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 {

View File

@@ -119,6 +119,9 @@ export const pluginSdkDocMetadata = {
"provider-test-contracts": {
category: "utilities",
},
"provider-http-test-mocks": {
category: "utilities",
},
"test-env": {
category: "utilities",
},

View File

@@ -114,6 +114,7 @@
"plugin-test-api",
"plugin-test-contracts",
"plugin-test-runtime",
"provider-http-test-mocks",
"provider-test-contracts",
"test-env",
"test-fixtures",

View File

@@ -0,0 +1,4 @@
export {
getProviderHttpMocks,
installProviderHttpMockCleanup,
} from "./test-helpers/provider-http-mocks.js";

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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,

View File

@@ -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([]);
});

View File

@@ -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<string>(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([]);
});