refactor: move plugin api test helper to sdk

This commit is contained in:
Peter Steinberger
2026-04-28 00:24:49 +01:00
parent f71f5bc586
commit 90b6665ded
45 changed files with 119 additions and 81 deletions

View File

@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
- 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: 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.
- Plugin SDK/testing: expose provider catalog, wizard, registry, manifest, public-artifact, outbound, and TTS contract helpers through documented SDK testing seams so bundled plugin tests no longer import repo `src/**` internals. Thanks @vincentkoc.
- Matrix: attach versioned structured approval metadata to pending approval messages so capable Matrix clients can render richer approval UI while body text and reaction fallback keep working. (#72432) Thanks @kakahu2015.

View File

@@ -1,2 +1,2 @@
7a2039df8cfdcfea0fbc82bbe425c96631ab27b2210c932c6e3f17d380aff350 plugin-sdk-api-baseline.json
ea9f134bc2a1aa17e8abf8f1335daaa927b2272c972523b9d0bd05bf4ac8e149 plugin-sdk-api-baseline.jsonl
0736a1666860383e3e5f8ada181c016455d8304a2852ac6966355765f799add4 plugin-sdk-api-baseline.json
761cdb609547f5912513e5714d8b0ec8fff2b29905690af376cc5bdd74f2c279 plugin-sdk-api-baseline.jsonl

View File

@@ -23,6 +23,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `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/plugin-test-api` | Minimal `OpenClawPluginApi` mock builder for direct plugin registration unit tests |
| `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` |
@@ -260,6 +261,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `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/plugin-test-api` | Minimal `createTestPluginApi` helper for direct plugin registration unit tests without importing repo test helper bridges |
</Accordion>
<Accordion title="Memory subpaths">

View File

@@ -19,7 +19,11 @@ plugins.
## Test utilities
**Import:** `openclaw/plugin-sdk/testing`
**General import:** `openclaw/plugin-sdk/testing`
**Plugin API mock import:** `openclaw/plugin-sdk/plugin-test-api`
**Channel contract import:** `openclaw/plugin-sdk/channel-contract-testing`
The testing subpath exports a narrow set of helpers for plugin authors:
@@ -29,44 +33,51 @@ import {
shouldAckReaction,
removeAckReactionAfterReply,
} from "openclaw/plugin-sdk/testing";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
```
### Available exports
| Export | Purpose |
| ------------------------------------------- | ------------------------------------------------------- |
| `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 |
| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed |
| `withEnv` / `withEnvAsync` | Temporarily patch environment variables |
| `createTempHomeEnv` / `withTempDir` | Create isolated filesystem test fixtures |
| `createMockServerResponse` | Create a minimal HTTP server response mock |
| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests |
| `registerProviderPlugin` | Capture all provider kinds from one plugin |
| `registerProviderPlugins` | Capture provider registrations across multiple plugins |
| `requireRegisteredProvider` | Assert that a provider collection contains an id |
| `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 |
| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment |
| `createTestWizardPrompter` | Build a mocked setup wizard prompter |
| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins |
| `createRuntimeTaskFlow` | Create isolated runtime task-flow state |
| `typedCases` | Preserve literal types for table-driven tests |
| 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` |
| `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 |
| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed |
| `withEnv` / `withEnvAsync` | Temporarily patch environment variables |
| `createTempHomeEnv` / `withTempDir` | Create isolated filesystem test fixtures |
| `createMockServerResponse` | Create a minimal HTTP server response mock |
| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests |
| `registerProviderPlugin` | Capture all provider kinds from one plugin |
| `registerProviderPlugins` | Capture provider registrations across multiple plugins |
| `requireRegisteredProvider` | Assert that a provider collection contains an id |
| `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 |
| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment |
| `createTestWizardPrompter` | Build a mocked setup wizard prompter |
| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins |
| `createRuntimeTaskFlow` | Create isolated runtime task-flow state |
| `typedCases` | Preserve literal types for table-driven tests |
Bundled-plugin contract suites also use this subpath for test-only registry,
manifest, public-artifact, and runtime fixture helpers. Keep new extension tests
on `openclaw/plugin-sdk/testing` or a narrower documented SDK subpath rather
than importing repo `src/**` files directly.
Bundled-plugin contract suites also use SDK testing subpaths for test-only
registry, manifest, public-artifact, and runtime fixture helpers. 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` rather than importing repo `src/**` files
directly.
### Types

View File

@@ -1,6 +1,6 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import setupPlugin from "./setup-api.js";
const { createAcpxRuntimeServiceMock, tryDispatchAcpReplyHookMock } = vi.hoisted(() => ({

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import {
browserPluginNodeHostCommands,
browserPluginReload,

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import { createCodexAppServerAgentHarness } from "./harness.js";
import plugin from "./index.js";

View File

@@ -1,9 +1,9 @@
import { resolveOpenClawAgentDir } from "openclaw/plugin-sdk/agent-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
import { isLiveTestEnabled } from "openclaw/plugin-sdk/testing";
import { beforeAll, describe, expect, it } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import plugin from "./index.js";
import { getComfyConfig, isComfyCapabilityConfigured } from "./workflow-runtime.js";

View File

@@ -5,8 +5,8 @@ import type {
OpenClawPluginCommandDefinition,
PluginCommandContext,
} from "openclaw/plugin-sdk/core";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi } from "./api.js";
import type { PendingPairingRequest } from "./notify.ts";

View File

@@ -1,8 +1,8 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
const listDevicePairingMock = vi.hoisted(() => vi.fn(async () => ({ pending: [] })));

View File

@@ -1,9 +1,9 @@
import fs from "node:fs/promises";
import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { createMockServerResponse } from "openclaw/plugin-sdk/testing";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawConfig } from "../api.js";
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../api.js";
import { registerDiffsPlugin } from "./plugin.js";

View File

@@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi } from "../api.js";
import type { DiffScreenshotter } from "./browser.js";
import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js";

View File

@@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../api.js";
import type { DiffScreenshotter } from "./browser.js";
import { DEFAULT_DIFFS_TOOL_DEFAULTS } from "./config.js";

View File

@@ -1,4 +1,4 @@
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/testing";
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import { describe, it } from "vitest";
import { buildFinalizedDiscordDirectInboundContext } from "./monitor/inbound-context.test-helpers.js";

View File

@@ -1,5 +1,5 @@
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/testing";
import { describe, expect, it } from "vitest";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
import { buildFinalizedDiscordDirectInboundContext } from "./inbound-context.test-helpers.js";

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi, PluginRuntime } from "../runtime-api.js";
const createFeishuClientMock = vi.hoisted(() => vi.fn());

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi, PluginRuntime } from "../runtime-api.js";
const createFeishuToolClientMock = vi.hoisted(() => vi.fn());

View File

@@ -5,8 +5,8 @@ import {
clearRuntimeAuthProfileStoreSnapshots,
ensureAuthProfileStore,
} from "openclaw/plugin-sdk/agent-runtime";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
const resolveCopilotApiTokenMock = vi.hoisted(() => vi.fn());

View File

@@ -1,6 +1,6 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { vi } from "vitest";
import { createTestPluginApi } from "../../../../test/helpers/plugins/plugin-api.ts";
type GoogleMeetTestPluginEntry = {
register(api: OpenClawPluginApi): void;

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
const buildHuggingfaceProviderMock = vi.hoisted(() =>
vi.fn(async () => ({

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi, OpenClawPluginToolContext } from "../runtime-api.js";
import { createLobsterTool } from "./lobster-tool.js";
import { createFakeTaskFlow } from "./taskflow-test-helpers.js";

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import { registerMatrixCliMetadata } from "./cli-metadata.js";
import entry, { registerMatrixFullRuntime } from "./index.js";

View File

@@ -1,6 +1,6 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js";
vi.mock("../../../test/helpers/config/bundled-channel-config-runtime.js", () => ({

View File

@@ -1,6 +1,6 @@
import { Command } from "commander";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
const mocks = vi.hoisted(() => ({
registerWikiCli: vi.fn(),

View File

@@ -1,8 +1,8 @@
import fs from "node:fs/promises";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { afterEach, vi } from "vitest";
import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi } from "../api.js";
import {
resolveMemoryWikiConfig,

View File

@@ -1,6 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import { getAccessTokenResultAsync } from "./cli.js";
import plugin from "./index.js";
import { buildFoundryConnectionTest, isValidTenantIdentifier } from "./onboard.js";

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import plugin from "./index.js";
const promptAndConfigureOllamaMock = vi.hoisted(() =>

View File

@@ -1,10 +1,10 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import * as providerAuth from "openclaw/plugin-sdk/provider-auth-runtime";
import * as providerHttp from "openclaw/plugin-sdk/provider-http";
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
import { registerProviderPlugin, requireRegisteredProvider } from "openclaw/plugin-sdk/testing";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js";
import plugin from "./index.js";
import {

View File

@@ -1,8 +1,8 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import registerPhoneControl from "./index.js";
import type {
OpenClawPluginApi,

View File

@@ -1,5 +1,5 @@
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/testing";
import { describe, it } from "vitest";
describe("Signal inbound context contract", () => {

View File

@@ -1,5 +1,5 @@
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.useRealTimers();
const [

View File

@@ -1,6 +1,6 @@
import { buildDispatchInboundCaptureMock } from "openclaw/plugin-sdk/channel-contract-testing";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
import { buildDispatchInboundCaptureMock } from "openclaw/plugin-sdk/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
type SignalMsgContext = Pick<MsgContext, "Body" | "WasMentioned"> & {

View File

@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-runtime";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import plugin, {
applyProposalToWorkspace,
createProposalFromMessages,

View File

@@ -1,6 +1,6 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../../../test/helpers/plugins/plugin-api.js";
import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js";
import { registerSlackPluginHttpRoutes } from "./plugin-routes.js";
import { registerSlackHttpHandler } from "./registry.js";

View File

@@ -1,7 +1,5 @@
import {
expectChannelInboundContextContract,
type OpenClawConfig,
} from "openclaw/plugin-sdk/testing";
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import { type OpenClawConfig } from "openclaw/plugin-sdk/testing";
import { describe, it } from "vitest";
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";

View File

@@ -1 +1 @@
export { expectChannelInboundContextContract } from "openclaw/plugin-sdk/testing";
export { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
const { tokenjuiceFactory, createTokenjuiceOpenClawEmbeddedExtension } = vi.hoisted(() => {
const tokenjuiceFactory = vi.fn();

View File

@@ -2,8 +2,8 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { Command } from "commander";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.ts";
import type { OpenClawPluginApi } from "./api.js";
import type { VoiceCallRuntime } from "./runtime-entry.js";

View File

@@ -1,5 +1,5 @@
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { describe, expect, it, vi } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import type { OpenClawPluginApi } from "./api.js";
import plugin from "./index.js";

View File

@@ -1,7 +1,7 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { registerProviderPlugin, registerSingleProviderPlugin } from "openclaw/plugin-sdk/testing";
import { describe, expect, it } from "vitest";
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
import plugin from "./index.js";
import setupPlugin from "./setup-api.js";
import {

View File

@@ -502,6 +502,10 @@
"types": "./dist/plugin-sdk/lazy-runtime.d.ts",
"default": "./dist/plugin-sdk/lazy-runtime.js"
},
"./plugin-sdk/plugin-test-api": {
"types": "./dist/plugin-sdk/plugin-test-api.d.ts",
"default": "./dist/plugin-sdk/plugin-test-api.js"
},
"./plugin-sdk/testing": {
"types": "./dist/plugin-sdk/testing.d.ts",
"default": "./dist/plugin-sdk/testing.js"

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { collectFilesSync, relativeToCwd } from "./check-file-utils.js";
import { collectFilesSync, isCodeFile, relativeToCwd } from "./check-file-utils.js";
type Offender = { file: string; hint: string; line?: number; specifier?: string };
@@ -49,6 +49,7 @@ const RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES = [
"test/helpers/plugins/frozen-time.ts",
"test/helpers/plugins/media-understanding.ts",
"test/helpers/plugins/mock-http-response.ts",
"test/helpers/plugins/plugin-api.ts",
"test/helpers/plugins/plugin-registration.ts",
"test/helpers/plugins/plugin-registry.ts",
"test/helpers/plugins/provider-registration.ts",
@@ -80,6 +81,12 @@ function collectExtensionTestFiles(rootDir: string): string[] {
});
}
function collectPluginHelperFiles(rootDir: string): string[] {
return collectFilesSync(rootDir, {
includeFile: isCodeFile,
});
}
function lineNumberForOffset(content: string, offset: number): number {
let line = 1;
for (let index = 0; index < offset; index += 1) {
@@ -127,7 +134,9 @@ function collectRelativeCoreImportOffenders(
function main() {
const extensionsDir = path.join(process.cwd(), "extensions");
const pluginHelpersDir = path.join(process.cwd(), "test/helpers/plugins");
const files = collectExtensionTestFiles(extensionsDir);
const pluginHelperFiles = collectPluginHelperFiles(pluginHelpersDir);
const offenders: Offender[] = [];
for (const file of RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES) {
@@ -137,7 +146,7 @@ function main() {
}
offenders.push({
file: filePath,
hint: "Import the helper directly from openclaw/plugin-sdk/testing instead of recreating this bridge.",
hint: "Import the helper directly from a documented openclaw/plugin-sdk testing subpath instead of recreating this bridge.",
});
}
@@ -157,8 +166,19 @@ function main() {
);
}
for (const file of pluginHelperFiles) {
const content = fs.readFileSync(file, "utf8");
offenders.push(
...collectRelativeCoreImportOffenders(file, content, {
includeDynamic: true,
}),
);
}
if (offenders.length > 0) {
console.error("Extension test files must stay on public plugin-sdk surfaces.");
console.error(
"Extension test files and plugin test helpers must stay on public plugin-sdk surfaces.",
);
for (const offender of offenders.toSorted((a, b) => a.file.localeCompare(b.file))) {
const location = offender.line
? `${relativeToCwd(offender.file)}:${offender.line}`
@@ -170,7 +190,7 @@ function main() {
}
console.log(
`OK: extension test files and support helpers avoid direct core test/internal imports (${files.length} checked).`,
`OK: extension test files, support helpers, and plugin test helpers avoid direct core test/internal imports (${files.length} extension files, ${pluginHelperFiles.length} plugin helpers checked).`,
);
}

View File

@@ -109,6 +109,7 @@
"acp-binding-runtime",
"acp-binding-resolve-runtime",
"lazy-runtime",
"plugin-test-api",
"testing",
"temp-path",
"logging-core",

View File

@@ -2,6 +2,7 @@ export {
expectChannelInboundContextContract,
primeChannelOutboundSendMock,
} from "../channels/plugins/contracts/test-helpers.js";
export { buildDispatchInboundCaptureMock } from "../channels/plugins/contracts/inbound-testkit.js";
export {
installChannelOutboundPayloadContractSuite,
type OutboundPayloadHarnessParams,

View File

@@ -1,6 +1,6 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import type { OpenClawPluginApi } from "./plugin-runtime.js";
type TestPluginApiInput = Partial<OpenClawPluginApi>;
export type TestPluginApiInput = Partial<OpenClawPluginApi>;
export function createTestPluginApi(api: TestPluginApiInput = {}): OpenClawPluginApi {
return {