From e1acb61317e7c209e861c203ea2f7d674f914ac9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 03:27:56 +0100 Subject: [PATCH] refactor: expose SDK test helper subpaths --- CHANGELOG.md | 1 + .../.generated/plugin-sdk-api-baseline.sha256 | 4 +- docs/plugins/sdk-subpaths.md | 42 ++--- docs/plugins/sdk-testing.md | 124 ++++++++------ .../src/runtime-internals/mcp-proxy.test.ts | 2 +- extensions/bluebubbles/src/actions.test.ts | 2 +- .../browser/chrome.default-browser.test.ts | 6 +- extensions/deepgram/audio.live.test.ts | 6 +- extensions/discord/index.test.ts | 2 +- .../monitor/monitor.agent-components.test.ts | 2 +- extensions/elevenlabs/elevenlabs.live.test.ts | 6 +- extensions/feishu/src/directory.test.ts | 2 +- extensions/google/google.live.test.ts | 2 +- extensions/irc/index.test.ts | 2 +- extensions/kilocode/index.test.ts | 2 +- extensions/line/src/setup-surface.test.ts | 2 +- extensions/line/src/webhook-node.test.ts | 2 +- ...gacy-crypto-inspector-availability.test.ts | 2 +- extensions/matrix/src/legacy-crypto.test.ts | 2 +- extensions/matrix/src/legacy-state.test.ts | 2 +- .../monitor/legacy-crypto-restore.test.ts | 2 +- .../matrix/src/migration-config.test.ts | 2 +- .../matrix/src/migration-snapshot.test.ts | 2 +- .../matrix/src/startup-maintenance.test.ts | 2 +- extensions/mistral/mistral.live.test.ts | 6 +- .../nextcloud-talk/src/monitor.replay.test.ts | 2 +- extensions/openai/openai.live.test.ts | 2 +- extensions/opencode-go/index.test.ts | 2 +- extensions/opencode/index.test.ts | 2 +- extensions/openrouter/index.test.ts | 2 +- ...ends-tool-summaries-responseprefix.test.ts | 2 +- extensions/slack/index.test.ts | 2 +- .../slack/src/monitor.tool-result.test.ts | 2 +- .../slack/src/sent-thread-cache.test.ts | 2 +- extensions/telegram/index.test.ts | 2 +- .../src/bot.create-telegram-bot.test.ts | 2 +- extensions/telegram/src/bot.test.ts | 2 +- extensions/telegram/src/draft-stream.test.ts | 2 +- extensions/telegram/src/send.test.ts | 2 +- .../telegram/src/thread-bindings.test.ts | 2 +- extensions/twitch/index.test.ts | 2 +- .../voice-call/src/webhook/tailscale.test.ts | 2 +- extensions/whatsapp/index.test.ts | 2 +- ...o-reply.connection-and-logging.e2e.test.ts | 2 +- extensions/whatsapp/src/test-helpers.ts | 2 +- extensions/xai/web-search.test.ts | 6 +- extensions/xai/xai.live.test.ts | 2 +- extensions/zalo/index.test.ts | 2 +- .../src/monitor.pairing.lifecycle.test.ts | 2 +- .../src/monitor.reply-once.lifecycle.test.ts | 2 +- extensions/zalo/src/monitor.webhook.test.ts | 2 +- package.json | 4 + .../check-no-extension-test-core-imports.ts | 31 ++++ scripts/lib/plugin-sdk-entrypoints.json | 1 + src/agents/context.eager-warmup.test.ts | 2 +- src/agents/models-config.e2e-harness.ts | 2 +- .../models-config.write-serialization.test.ts | 2 + .../openrouter-model-capabilities.test.ts | 2 +- src/agents/pi-embedded-runner/runs.test.ts | 2 +- ...irective.directive-behavior.e2e-harness.ts | 2 +- src/auto-reply/reply/commands-status.test.ts | 2 +- .../reply/get-reply-run.media-only.test.ts | 2 +- src/auto-reply/reply/get-reply.test-loader.ts | 2 +- src/auto-reply/reply/inbound-dedupe.test.ts | 2 +- src/auto-reply/reply/queue.dedupe.test.ts | 2 +- .../reply/queue.drain-restart.test.ts | 2 +- .../stage-sandbox-media.test-harness.ts | 2 +- src/auto-reply/status.test.ts | 2 +- .../plugins/bundled-root-caches.test.ts | 2 +- .../plugins/bundled.shape-guard.test.ts | 2 +- src/channels/plugins/module-loader.test.ts | 2 +- .../setup-helpers.import-safety.test.ts | 2 +- ...command-secret-resolution.coverage.test.ts | 2 +- src/cli/deps.test.ts | 2 +- src/cli/npm-resolution.test.ts | 2 +- src/cli/plugin-install-plan.test.ts | 2 +- src/cli/plugins-cli.install.test.ts | 2 +- src/cli/plugins-cli.uninstall.test.ts | 2 +- src/cli/plugins-install-config.test.ts | 2 +- src/cli/program/preaction.test.ts | 2 +- src/cli/update-cli/restart-helper.test.ts | 2 +- src/commands/agent-command.test-support.ts | 2 +- src/commands/agent.acp.test.ts | 2 +- src/commands/agent.runtime-config.test.ts | 2 +- src/commands/agent.session.test.ts | 2 +- src/commands/agent.test.ts | 2 +- .../channel-setup/plugin-install.test.ts | 5 +- src/commands/doctor-config-flow.test.ts | 2 +- .../shared/bundled-plugin-load-paths.test.ts | 5 +- src/commands/models/list.probe.test.ts | 2 +- src/commands/setup.test.ts | 2 +- .../bundled-channel-config-runtime.test.ts | 2 +- .../load-channel-config-surface.test.ts | 2 +- src/config/mcp-config.test.ts | 2 +- src/config/normalize-paths.test.ts | 2 +- src/config/sessions/targets.test.ts | 2 +- src/config/test-helpers.ts | 2 +- .../zod-schema.providers.lazy-runtime.test.ts | 2 +- src/cron/isolated-agent.test-harness.ts | 2 +- src/daemon/systemd.test.ts | 2 +- src/dockerfile.test.ts | 2 +- src/gateway/server-methods/config.test.ts | 2 +- src/gateway/session-archive.imports.test.ts | 2 +- src/infra/gateway-processes.test.ts | 5 +- src/infra/machine-name.test.ts | 4 +- src/infra/os-summary.test.ts | 3 +- .../outbound/cfg-threading.guard.test.ts | 2 +- src/infra/outbound/channel-resolution.test.ts | 2 +- .../plugin-install-path-warnings.test.ts | 4 +- src/infra/process-respawn.test.ts | 2 +- src/infra/restart-stale-pids.test.ts | 4 +- src/infra/restart.test.ts | 3 +- src/infra/run-node.test.ts | 10 +- src/infra/ssh-config.test.ts | 2 +- src/infra/system-presence.version.test.ts | 2 +- src/infra/tsdown-config.test.ts | 2 +- src/infra/update-global.test.ts | 2 +- src/infra/update-runner.test.ts | 2 +- src/infra/watch-node.test.ts | 2 +- src/infra/windows-task-restart.test.ts | 2 +- src/logging/diagnostic.test.ts | 2 +- src/logging/logger-transport.test.ts | 2 +- src/logging/logger.browser-import.test.ts | 2 +- src/media/store.test.ts | 2 +- src/pairing/pairing-messages.test.ts | 2 +- src/plugin-sdk/channel-entry-contract.test.ts | 2 +- src/plugin-sdk/channel-test-helpers.ts | 7 + src/plugin-sdk/provider-test-contracts.ts | 8 + src/plugin-sdk/runtime-store.test.ts | 2 +- src/plugin-sdk/test-env.ts | 3 + src/plugin-sdk/test-fixtures.ts | 17 ++ .../test-helpers/envelope-timestamp.ts | 43 +++++ .../test-helpers/node-builtin-mocks.test.ts | 54 ++++++ .../test-helpers/provider-replay-policy.ts | 35 ++++ src/plugin-sdk/test-helpers/stt-live-audio.ts | 139 +++++++++++++++ src/plugin-sdk/test-helpers/temp-home.ts | 160 ++++++++++++++++++ src/plugin-sdk/test-node-mocks.ts | 7 + src/plugins/bundled-sources.test.ts | 2 +- .../contracts/boundary-invariants.test.ts | 8 +- .../contracts/plugin-sdk-subpaths.test.ts | 20 +++ src/plugins/discovery.test.ts | 2 +- src/plugins/jiti-loader-cache.test.ts | 2 +- src/plugins/loader.jiti-filename.test.ts | 2 +- src/plugins/public-surface-loader.test.ts | 2 +- .../runtime-live-state-guardrails.test.ts | 2 +- .../runtime-plugin-boundary.whatsapp.test.ts | 2 +- src/plugins/sdk-alias.test.ts | 4 +- .../stage-bundled-plugin-runtime.test.ts | 2 +- src/plugins/update.test.ts | 2 +- src/process/command-queue.test.ts | 2 +- src/process/exec.windows.test.ts | 2 +- src/process/kill-tree.test.ts | 2 +- src/scripts/ci-changed-scope.test.ts | 2 +- src/scripts/test-projects.test.ts | 2 +- src/security/windows-acl.test.ts | 2 +- test/scripts/test-projects.test.ts | 6 +- 156 files changed, 791 insertions(+), 242 deletions(-) create mode 100644 src/plugin-sdk/test-helpers/envelope-timestamp.ts create mode 100644 src/plugin-sdk/test-helpers/node-builtin-mocks.test.ts create mode 100644 src/plugin-sdk/test-helpers/provider-replay-policy.ts create mode 100644 src/plugin-sdk/test-helpers/stt-live-audio.ts create mode 100644 src/plugin-sdk/test-helpers/temp-home.ts create mode 100644 src/plugin-sdk/test-node-mocks.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index eda26a4e709..21e937d8de6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai - Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd. - Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd. - Plugin SDK/testing: 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/testing: expose generic module reload, bundled-path, Node builtin mock, channel pairing/envelope, HTTP server, temp-home, replay-policy, and live STT helpers through focused SDK test subpaths so extension tests no longer depend on repo-only helper bridges. 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. diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 4f52bb0a557..873aebb88e1 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -31fd2178f08a4fcb28d6319eaa464b572b1e36a0fab700056f643feaccf95aa8 plugin-sdk-api-baseline.json -65b239e91e4d5f4cac71527058aa53179a8dcf65f8c50f4eabab346def966e74 plugin-sdk-api-baseline.jsonl +6b4c0c05e24f13c08daebf36a9853e66056ab677e10dac9c35e8d514f87ca74e plugin-sdk-api-baseline.json +e6ea9f91aa5118bf2c61cb50f774d96740509723e862b9b618e2061b04504dc7 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index e85865654ab..74eba9f8604 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -16,24 +16,25 @@ 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, 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` | +| 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, bundled channel entry, envelope timestamp, pairing reply, 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, replay policy, realtime STT live-audio, 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, disposable HTTP server, incoming request, live-test, temporary filesystem, and time-control fixtures | +| `plugin-sdk/test-fixtures` | Generic CLI, sandbox, skill, agent-message, system-event, module reload, bundled plugin path, terminal, chunking, auth-token, and typed-case test fixtures | +| `plugin-sdk/test-node-mocks` | Focused Node builtin mock helpers for use inside Vitest `vi.mock("node:*")` factories | +| `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` | @@ -275,9 +276,10 @@ 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, media capability, web-search/fetch, and stream contract helpers | + | `plugin-sdk/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, wizard, media capability, replay policy, realtime STT live-audio, 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 | + | `plugin-sdk/test-fixtures` | Generic CLI runtime capture, sandbox context, skill writer, agent-message, system-event, module reload, bundled plugin path, terminal-text, chunking, auth-token, and typed-case fixtures | + | `plugin-sdk/test-node-mocks` | Focused Node builtin mock helpers for use inside Vitest `vi.mock("node:*")` factories | diff --git a/docs/plugins/sdk-testing.md b/docs/plugins/sdk-testing.md index a4345928cfc..930bd13bab0 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -39,6 +39,8 @@ plugins. **Generic fixture import:** `openclaw/plugin-sdk/test-fixtures` +**Node builtin mock import:** `openclaw/plugin-sdk/test-node-mocks` + Prefer the focused subpaths below for new plugin tests. The broad `openclaw/plugin-sdk/testing` barrel is legacy compatibility only. @@ -55,63 +57,79 @@ import { describePluginRegistrationContract } from "openclaw/plugin-sdk/plugin-t 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"; +import { withEnv, withFetchPreconnect, withServer } from "openclaw/plugin-sdk/test-env"; +import { + bundledPluginRoot, + createCliRuntimeCapture, + typedCases, +} from "openclaw/plugin-sdk/test-fixtures"; +import { mockNodeBuiltinModule } from "openclaw/plugin-sdk/test-node-mocks"; ``` ### Available exports -| Export | Purpose | -| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` | -| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` | -| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` | -| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelActionsContractSuite` | Install generic channel message-action contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelSetupContractSuite` | Install generic channel setup contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelStatusContractSuite` | Install generic channel status contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `expectDirectoryIds` | Assert channel directory ids from a directory-list function. Import from `plugin-sdk/channel-test-helpers` | -| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` | -| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` | -| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` | -| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` | -| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` | -| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` | -| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` | -| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` | -| `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` | -| `createTestRegistry` | Build a channel plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `createEmptyPluginRegistry` | Build an empty plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` | -| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` | -| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` | -| `createTempHomeEnv` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` | -| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` | -| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` | -| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` | -| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` | -| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` | -| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` | -| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` | -| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` | -| `runProviderCatalog` | Execute a provider catalog hook with test dependencies | -| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests | -| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests | -| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions | -| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests | -| `createProviderUsageFetch` | Build provider usage fetch fixtures | -| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` | -| `createTestWizardPrompter` | Build a mocked setup wizard prompter | -| `createRuntimeTaskFlow` | Create isolated runtime task-flow state | -| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` | +| Export | Purpose | +| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` | +| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` | +| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` | +| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelActionsContractSuite` | Install generic channel message-action contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelSetupContractSuite` | Install generic channel setup contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelStatusContractSuite` | Install generic channel status contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `expectDirectoryIds` | Assert channel directory ids from a directory-list function. Import from `plugin-sdk/channel-test-helpers` | +| `assertBundledChannelEntries` | Assert bundled channel entrypoints expose the expected public contract. Import from `plugin-sdk/channel-test-helpers` | +| `formatEnvelopeTimestamp` | Format deterministic envelope timestamps. Import from `plugin-sdk/channel-test-helpers` | +| `expectPairingReplyText` | Assert channel pairing reply text and extract its code. Import from `plugin-sdk/channel-test-helpers` | +| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` | +| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` | +| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` | +| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` | +| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` | +| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` | +| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` | +| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` | +| `expectPassthroughReplayPolicy` | Assert provider replay policies pass through provider-owned tools and metadata. Import from `plugin-sdk/provider-test-contracts` | +| `runRealtimeSttLiveTest` | Run a live realtime STT provider test with shared audio fixtures. Import from `plugin-sdk/provider-test-contracts` | +| `normalizeTranscriptForMatch` | Normalize live transcript output before fuzzy assertions. 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` | +| `createTestRegistry` | Build a channel plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `createEmptyPluginRegistry` | Build an empty plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` | +| `withServer` | Run tests against a disposable local HTTP server. Import from `plugin-sdk/test-env` | +| `createMockIncomingRequest` | Build a minimal incoming HTTP request object. Import from `plugin-sdk/test-env` | +| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` | +| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` | +| `createTempHomeEnv` / `withTempHome` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` | +| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` | +| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` | +| `importFreshModule` | Import an ESM module with a fresh query token to bypass module cache. Import from `plugin-sdk/test-fixtures` | +| `bundledPluginRoot` / `bundledPluginFile` | Resolve bundled plugin source or dist fixture paths. Import from `plugin-sdk/test-fixtures` | +| `mockNodeBuiltinModule` | Install narrow Node builtin Vitest mocks. Import from `plugin-sdk/test-node-mocks` | +| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` | +| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` | +| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` | +| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` | +| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` | +| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` | +| `runProviderCatalog` | Execute a provider catalog hook with test dependencies | +| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests | +| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests | +| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions | +| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests | +| `createProviderUsageFetch` | Build provider usage fetch fixtures | +| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` | +| `createTestWizardPrompter` | Build a mocked setup wizard prompter | +| `createRuntimeTaskFlow` | Create isolated runtime task-flow state | +| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` | Bundled-plugin contract suites also use SDK testing subpaths for test-only registry, manifest, public-artifact, and runtime fixture helpers. Core-only diff --git a/extensions/acpx/src/runtime-internals/mcp-proxy.test.ts b/extensions/acpx/src/runtime-internals/mcp-proxy.test.ts index 2864645d48d..894a3704da3 100644 --- a/extensions/acpx/src/runtime-internals/mcp-proxy.test.ts +++ b/extensions/acpx/src/runtime-internals/mcp-proxy.test.ts @@ -2,8 +2,8 @@ import { spawn } from "node:child_process"; import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it } from "vitest"; -import { bundledPluginFile } from "../../../../test/helpers/bundled-plugin-paths.js"; const tempDirs: string[] = []; const proxyPath = path.resolve(bundledPluginFile("acpx", "src/runtime-internals/mcp-proxy.mjs")); diff --git a/extensions/bluebubbles/src/actions.test.ts b/extensions/bluebubbles/src/actions.test.ts index ef8b7d6d4e4..b575834217a 100644 --- a/extensions/bluebubbles/src/actions.test.ts +++ b/extensions/bluebubbles/src/actions.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi, beforeEach } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import { sendBlueBubblesAttachment } from "./attachments.js"; import { editBlueBubblesMessage, setGroupIconBlueBubbles } from "./chat.js"; import { resolveBlueBubblesMessageId } from "./monitor-reply-cache.js"; diff --git a/extensions/browser/src/browser/chrome.default-browser.test.ts b/extensions/browser/src/browser/chrome.default-browser.test.ts index c28bd486e41..ac91ceb787f 100644 --- a/extensions/browser/src/browser/chrome.default-browser.test.ts +++ b/extensions/browser/src/browser/chrome.default-browser.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { @@ -10,7 +10,7 @@ vi.mock("node:child_process", async () => { ); }); vi.mock("node:fs", async () => { - const { mockNodeBuiltinModule } = await import("../../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); const existsSync = vi.fn(); const readFileSync = vi.fn(); return mockNodeBuiltinModule( @@ -20,7 +20,7 @@ vi.mock("node:fs", async () => { ); }); vi.mock("node:os", async () => { - const { mockNodeBuiltinModule } = await import("../../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); const homedir = vi.fn(); return mockNodeBuiltinModule( () => vi.importActual("node:os"), diff --git a/extensions/deepgram/audio.live.test.ts b/extensions/deepgram/audio.live.test.ts index 31b5bc5f742..1983dd71b6b 100644 --- a/extensions/deepgram/audio.live.test.ts +++ b/extensions/deepgram/audio.live.test.ts @@ -1,9 +1,9 @@ -import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; -import { describe, expect, it } from "vitest"; import { runRealtimeSttLiveTest, synthesizeElevenLabsLiveSpeech, -} from "../../test/helpers/stt-live-audio.js"; +} from "openclaw/plugin-sdk/provider-test-contracts"; +import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; +import { describe, expect, it } from "vitest"; import { transcribeDeepgramAudio } from "./audio.js"; import { buildDeepgramRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js"; diff --git a/extensions/discord/index.test.ts b/extensions/discord/index.test.ts index 07f5cd173db..e0a50533924 100644 --- a/extensions/discord/index.test.ts +++ b/extensions/discord/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/discord/src/monitor/monitor.agent-components.test.ts b/extensions/discord/src/monitor/monitor.agent-components.test.ts index b927cbe4067..c3a5058e516 100644 --- a/extensions/discord/src/monitor/monitor.agent-components.test.ts +++ b/extensions/discord/src/monitor/monitor.agent-components.test.ts @@ -1,10 +1,10 @@ import type { ButtonInteraction, ComponentData, StringSelectMenuInteraction } from "@buape/carbon"; import { ChannelType } from "discord-api-types/v10"; +import { expectPairingReplyText } from "openclaw/plugin-sdk/channel-test-helpers"; import type { DiscordAccountConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; import { peekSystemEvents, resetSystemEventsForTest } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { expectPairingReplyText } from "../../../../test/helpers/pairing-reply.js"; import { enqueueSystemEventMock, readAllowFromStoreMock, diff --git a/extensions/elevenlabs/elevenlabs.live.test.ts b/extensions/elevenlabs/elevenlabs.live.test.ts index 6a2760e7850..5cee30688ff 100644 --- a/extensions/elevenlabs/elevenlabs.live.test.ts +++ b/extensions/elevenlabs/elevenlabs.live.test.ts @@ -2,13 +2,13 @@ import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; -import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; -import { describe, expect, it } from "vitest"; import { normalizeTranscriptForMatch, runRealtimeSttLiveTest, synthesizeElevenLabsLiveSpeech, -} from "../../test/helpers/stt-live-audio.js"; +} from "openclaw/plugin-sdk/provider-test-contracts"; +import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; +import { describe, expect, it } from "vitest"; import plugin from "./index.js"; import { elevenLabsMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { buildElevenLabsRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js"; diff --git a/extensions/feishu/src/directory.test.ts b/extensions/feishu/src/directory.test.ts index fd0e797f5df..25f5cd61e52 100644 --- a/extensions/feishu/src/directory.test.ts +++ b/extensions/feishu/src/directory.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import type { ClawdbotConfig } from "../runtime-api.js"; const createFeishuClientMock = vi.hoisted(() => vi.fn()); diff --git a/extensions/google/google.live.test.ts b/extensions/google/google.live.test.ts index d04bbf250d8..d8ef01b399e 100644 --- a/extensions/google/google.live.test.ts +++ b/extensions/google/google.live.test.ts @@ -2,9 +2,9 @@ import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { normalizeTranscriptForMatch } from "openclaw/plugin-sdk/provider-test-contracts"; import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; -import { normalizeTranscriptForMatch } from "../../test/helpers/stt-live-audio.js"; import plugin from "./index.js"; import { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js"; diff --git a/extensions/irc/index.test.ts b/extensions/irc/index.test.ts index 5149616f867..a3ac2842f85 100644 --- a/extensions/irc/index.test.ts +++ b/extensions/irc/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/kilocode/index.test.ts b/extensions/kilocode/index.test.ts index c5857bd4af7..1f7ce78b1bf 100644 --- a/extensions/kilocode/index.test.ts +++ b/extensions/kilocode/index.test.ts @@ -1,8 +1,8 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { Context, Model } from "@mariozechner/pi-ai"; import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { expectPassthroughReplayPolicy } from "openclaw/plugin-sdk/provider-test-contracts"; import { describe, expect, it } from "vitest"; -import { expectPassthroughReplayPolicy } from "../../test/helpers/provider-replay-policy.ts"; import plugin from "./index.js"; describe("kilocode provider plugin", () => { diff --git a/extensions/line/src/setup-surface.test.ts b/extensions/line/src/setup-surface.test.ts index d6c35095eef..c7ec4184cff 100644 --- a/extensions/line/src/setup-surface.test.ts +++ b/extensions/line/src/setup-surface.test.ts @@ -7,9 +7,9 @@ import { runSetupWizardConfigure, } from "openclaw/plugin-sdk/plugin-test-runtime"; import type { WizardPrompter } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import ts from "typescript"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { bundledPluginRoot } from "../../../test/helpers/bundled-plugin-paths.js"; import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount } from "../api.js"; import { linePlugin } from "./channel.js"; import { lineGatewayAdapter } from "./gateway.js"; diff --git a/extensions/line/src/webhook-node.test.ts b/extensions/line/src/webhook-node.test.ts index 3e4f5d8042e..19de8e3d16b 100644 --- a/extensions/line/src/webhook-node.test.ts +++ b/extensions/line/src/webhook-node.test.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import type { IncomingMessage, ServerResponse } from "node:http"; +import { createMockIncomingRequest } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { createMockIncomingRequest } from "../../../test/helpers/mock-incoming-request.js"; import { createLineNodeWebhookHandler, readLineWebhookRequestBody } from "./webhook-node.js"; import { createLineWebhookMiddleware } from "./webhook.js"; diff --git a/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts b/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts index 9cf472f6028..e2753105b94 100644 --- a/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts +++ b/extensions/matrix/src/legacy-crypto-inspector-availability.test.ts @@ -7,7 +7,7 @@ const availabilityState = vi.hoisted(() => ({ })); vi.mock("node:fs", async () => { - const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:fs"), { diff --git a/extensions/matrix/src/legacy-crypto.test.ts b/extensions/matrix/src/legacy-crypto.test.ts index 29bd9ea8e04..a44bb55119e 100644 --- a/extensions/matrix/src/legacy-crypto.test.ts +++ b/extensions/matrix/src/legacy-crypto.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; const legacyCryptoInspectorAvailability = vi.hoisted(() => ({ available: true, diff --git a/extensions/matrix/src/legacy-state.test.ts b/extensions/matrix/src/legacy-state.test.ts index c495d575b42..ce7f110a9a2 100644 --- a/extensions/matrix/src/legacy-state.test.ts +++ b/extensions/matrix/src/legacy-state.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; import { autoMigrateLegacyMatrixState, detectLegacyMatrixState } from "./legacy-state.js"; function writeFile(filePath: string, value: string) { diff --git a/extensions/matrix/src/matrix/monitor/legacy-crypto-restore.test.ts b/extensions/matrix/src/matrix/monitor/legacy-crypto-restore.test.ts index 7a6472d0c71..541acc60d12 100644 --- a/extensions/matrix/src/matrix/monitor/legacy-crypto-restore.test.ts +++ b/extensions/matrix/src/matrix/monitor/legacy-crypto-restore.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../../../../test/helpers/temp-home.js"; import { resolveMatrixAccountStorageRoot } from "../../storage-paths.js"; import type { MatrixRoomKeyBackupRestoreResult } from "../sdk.js"; import { maybeRestoreLegacyMatrixBackup } from "./legacy-crypto-restore.js"; diff --git a/extensions/matrix/src/migration-config.test.ts b/extensions/matrix/src/migration-config.test.ts index c59d03b1b57..ffdcf62cf02 100644 --- a/extensions/matrix/src/migration-config.test.ts +++ b/extensions/matrix/src/migration-config.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; import { resolveMatrixMigrationAccountTarget } from "./migration-config.js"; import { MATRIX_OPS_ACCESS_TOKEN, diff --git a/extensions/matrix/src/migration-snapshot.test.ts b/extensions/matrix/src/migration-snapshot.test.ts index 2ee71746047..9482068d176 100644 --- a/extensions/matrix/src/migration-snapshot.test.ts +++ b/extensions/matrix/src/migration-snapshot.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; const legacyCryptoInspectorAvailability = vi.hoisted(() => ({ available: true, diff --git a/extensions/matrix/src/startup-maintenance.test.ts b/extensions/matrix/src/startup-maintenance.test.ts index 7c8538515a7..df8f01c909e 100644 --- a/extensions/matrix/src/startup-maintenance.test.ts +++ b/extensions/matrix/src/startup-maintenance.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; const legacyCryptoInspectorAvailability = vi.hoisted(() => ({ available: true, diff --git a/extensions/mistral/mistral.live.test.ts b/extensions/mistral/mistral.live.test.ts index b2f86e67b73..f6d251f35c6 100644 --- a/extensions/mistral/mistral.live.test.ts +++ b/extensions/mistral/mistral.live.test.ts @@ -1,10 +1,10 @@ -import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; -import { describe, expect, it } from "vitest"; import { normalizeTranscriptForMatch, runRealtimeSttLiveTest, synthesizeElevenLabsLiveSpeech, -} from "../../test/helpers/stt-live-audio.js"; +} from "openclaw/plugin-sdk/provider-test-contracts"; +import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env"; +import { describe, expect, it } from "vitest"; import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { buildMistralRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js"; diff --git a/extensions/nextcloud-talk/src/monitor.replay.test.ts b/extensions/nextcloud-talk/src/monitor.replay.test.ts index 9af0996b7e3..34d65a3be54 100644 --- a/extensions/nextcloud-talk/src/monitor.replay.test.ts +++ b/extensions/nextcloud-talk/src/monitor.replay.test.ts @@ -1,5 +1,5 @@ +import { createMockIncomingRequest } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { createMockIncomingRequest } from "../../../test/helpers/mock-incoming-request.js"; import { NextcloudTalkRetryableWebhookError, processNextcloudTalkReplayGuardedMessage, diff --git a/extensions/openai/openai.live.test.ts b/extensions/openai/openai.live.test.ts index e02c82a4197..f4ab84d3aac 100644 --- a/extensions/openai/openai.live.test.ts +++ b/extensions/openai/openai.live.test.ts @@ -11,9 +11,9 @@ import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { runRealtimeSttLiveTest } from "openclaw/plugin-sdk/provider-test-contracts"; import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot"; import { describe, expect, it } from "vitest"; -import { runRealtimeSttLiveTest } from "../../test/helpers/stt-live-audio.js"; import plugin from "./index.js"; const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? ""; diff --git a/extensions/opencode-go/index.test.ts b/extensions/opencode-go/index.test.ts index 670aebbc7ad..3201d1278b0 100644 --- a/extensions/opencode-go/index.test.ts +++ b/extensions/opencode-go/index.test.ts @@ -3,8 +3,8 @@ import { registerProviderPlugin, registerSingleProviderPlugin, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { expectPassthroughReplayPolicy } from "openclaw/plugin-sdk/provider-test-contracts"; import { describe, expect, it } from "vitest"; -import { expectPassthroughReplayPolicy } from "../../test/helpers/provider-replay-policy.ts"; import plugin from "./index.js"; describe("opencode-go provider plugin", () => { diff --git a/extensions/opencode/index.test.ts b/extensions/opencode/index.test.ts index 599e3042930..c3e1b5d0c21 100644 --- a/extensions/opencode/index.test.ts +++ b/extensions/opencode/index.test.ts @@ -2,8 +2,8 @@ import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { expectPassthroughReplayPolicy } from "openclaw/plugin-sdk/provider-test-contracts"; import { describe, expect, it } from "vitest"; -import { expectPassthroughReplayPolicy } from "../../test/helpers/provider-replay-policy.ts"; import plugin from "./index.js"; describe("opencode provider plugin", () => { diff --git a/extensions/openrouter/index.test.ts b/extensions/openrouter/index.test.ts index 7faf5d5cfc4..f540f332759 100644 --- a/extensions/openrouter/index.test.ts +++ b/extensions/openrouter/index.test.ts @@ -2,8 +2,8 @@ import { registerProviderPlugin, registerSingleProviderPlugin, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { expectPassthroughReplayPolicy } from "openclaw/plugin-sdk/provider-test-contracts"; import { describe, expect, it, vi } from "vitest"; -import { expectPassthroughReplayPolicy } from "../../test/helpers/provider-replay-policy.ts"; import openrouterPlugin from "./index.js"; import { buildOpenrouterProvider, diff --git a/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index b4f9c8a0769..51b256f62ca 100644 --- a/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/extensions/signal/src/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -1,8 +1,8 @@ +import { expectPairingReplyText } from "openclaw/plugin-sdk/channel-test-helpers"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime"; import { describe, expect, it, vi } from "vitest"; -import { expectPairingReplyText } from "../../../test/helpers/pairing-reply.js"; import { createSignalToolResultConfig, config, diff --git a/extensions/slack/index.test.ts b/extensions/slack/index.test.ts index bdef1336697..98faa8508a6 100644 --- a/extensions/slack/index.test.ts +++ b/extensions/slack/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/slack/src/monitor.tool-result.test.ts b/extensions/slack/src/monitor.tool-result.test.ts index 5a04899a7b0..0c427e7aa43 100644 --- a/extensions/slack/src/monitor.tool-result.test.ts +++ b/extensions/slack/src/monitor.tool-result.test.ts @@ -1,8 +1,8 @@ import { CURRENT_MESSAGE_MARKER } from "openclaw/plugin-sdk/channel-mention-gating"; +import { expectPairingReplyText } from "openclaw/plugin-sdk/channel-test-helpers"; import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-dedupe"; import { HISTORY_CONTEXT_MARKER } from "openclaw/plugin-sdk/reply-history"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { expectPairingReplyText } from "../../../test/helpers/pairing-reply.js"; import { defaultSlackTestConfig, getSlackTestState, diff --git a/extensions/slack/src/sent-thread-cache.test.ts b/extensions/slack/src/sent-thread-cache.test.ts index 1e215af252c..a6d45d85e99 100644 --- a/extensions/slack/src/sent-thread-cache.test.ts +++ b/extensions/slack/src/sent-thread-cache.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import { clearSlackThreadParticipationCache, hasSlackThreadParticipation, diff --git a/extensions/telegram/index.test.ts b/extensions/telegram/index.test.ts index fcf9094dedb..adcf2d86d62 100644 --- a/extensions/telegram/index.test.ts +++ b/extensions/telegram/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { beforeEach, describe, vi } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index fa15d1e01df..d0d29990e2d 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -1,6 +1,6 @@ +import { escapeRegExp, formatEnvelopeTimestamp } from "openclaw/plugin-sdk/channel-test-helpers"; import type { GetReplyOptions, MsgContext } from "openclaw/plugin-sdk/reply-runtime"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js"; import type { TelegramBotOptions } from "./bot.types.js"; const harness = await import("./bot.create-telegram-bot.test-harness.js"); const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime"); diff --git a/extensions/telegram/src/bot.test.ts b/extensions/telegram/src/bot.test.ts index 0d765a6894f..8fcffcf5549 100644 --- a/extensions/telegram/src/bot.test.ts +++ b/extensions/telegram/src/bot.test.ts @@ -72,7 +72,7 @@ function waitForReplyCalls(count: number) { } async function loadEnvelopeTimestampHelpers() { - return await import("../../../test/helpers/envelope-timestamp.js"); + return await import("openclaw/plugin-sdk/channel-test-helpers"); } async function loadInboundContextContract() { diff --git a/extensions/telegram/src/draft-stream.test.ts b/extensions/telegram/src/draft-stream.test.ts index c5fa46a7ec4..c7eca6ce3ef 100644 --- a/extensions/telegram/src/draft-stream.test.ts +++ b/extensions/telegram/src/draft-stream.test.ts @@ -1,6 +1,6 @@ import type { Bot } from "grammy"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import { __testing, createTelegramDraftStream } from "./draft-stream.js"; type TelegramDraftStreamParams = Parameters[0]; diff --git a/extensions/telegram/src/send.test.ts b/extensions/telegram/src/send.test.ts index eff077108f2..46917ef7b51 100644 --- a/extensions/telegram/src/send.test.ts +++ b/extensions/telegram/src/send.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import type { Bot } from "grammy"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import { getTelegramSendTestMocks, importTelegramSendModule, diff --git a/extensions/telegram/src/thread-bindings.test.ts b/extensions/telegram/src/thread-bindings.test.ts index a76f19265f1..20385bdf854 100644 --- a/extensions/telegram/src/thread-bindings.test.ts +++ b/extensions/telegram/src/thread-bindings.test.ts @@ -4,8 +4,8 @@ import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runtime"; import { resolveStateDir } from "openclaw/plugin-sdk/state-paths"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; const writeJsonFileAtomicallyMock = vi.hoisted(() => vi.fn()); const readAcpSessionEntryMock = vi.hoisted(() => vi.fn()); diff --git a/extensions/twitch/index.test.ts b/extensions/twitch/index.test.ts index 1bf76d359e2..54e1b0e0f6d 100644 --- a/extensions/twitch/index.test.ts +++ b/extensions/twitch/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/voice-call/src/webhook/tailscale.test.ts b/extensions/voice-call/src/webhook/tailscale.test.ts index 55e6b786972..d3abfa3632f 100644 --- a/extensions/voice-call/src/webhook/tailscale.test.ts +++ b/extensions/voice-call/src/webhook/tailscale.test.ts @@ -6,7 +6,7 @@ const { spawnMock } = vi.hoisted(() => ({ })); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/extensions/whatsapp/index.test.ts b/extensions/whatsapp/index.test.ts index c5e2c4a5836..2ccc7832ceb 100644 --- a/extensions/whatsapp/index.test.ts +++ b/extensions/whatsapp/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts b/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts index 0e1dd5b345b..f37441ae1e8 100644 --- a/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +++ b/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts @@ -1,11 +1,11 @@ import "./test-helpers.js"; import crypto from "node:crypto"; import fs from "node:fs/promises"; +import { escapeRegExp, formatEnvelopeTimestamp } from "openclaw/plugin-sdk/channel-test-helpers"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { setLoggerOverride } from "openclaw/plugin-sdk/runtime-env"; import { withEnvAsync } from "openclaw/plugin-sdk/test-env"; import { beforeAll, describe, expect, it, vi } from "vitest"; -import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js"; import { WhatsAppAuthUnstableError } from "./auth-store.js"; import { createWebInboundDeliverySpies, diff --git a/extensions/whatsapp/src/test-helpers.ts b/extensions/whatsapp/src/test-helpers.ts index 340d16d8654..3746319cf86 100644 --- a/extensions/whatsapp/src/test-helpers.ts +++ b/extensions/whatsapp/src/test-helpers.ts @@ -1,9 +1,9 @@ import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import { formatEnvelopeTimestamp } from "openclaw/plugin-sdk/channel-test-helpers"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; import { vi } from "vitest"; -import { formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js"; import type { MockBaileysSocket } from "../../../test/mocks/baileys.js"; import { createMockBaileys } from "../../../test/mocks/baileys.js"; diff --git a/extensions/xai/web-search.test.ts b/extensions/xai/web-search.test.ts index 8c47ed5edd7..946ef7198e9 100644 --- a/extensions/xai/web-search.test.ts +++ b/extensions/xai/web-search.test.ts @@ -1,8 +1,8 @@ +import { createTestWizardPrompter } from "openclaw/plugin-sdk/plugin-test-runtime"; import { NON_ENV_SECRETREF_MARKER } from "openclaw/plugin-sdk/provider-auth-runtime"; import { createNonExitingRuntime } from "openclaw/plugin-sdk/runtime-env"; import { withEnv, withEnvAsync } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { createWizardPrompter } from "../../test/helpers/wizard-prompter.js"; import { resolveXaiCatalogEntry } from "./model-definitions.js"; import { isModernXaiModel, resolveXaiForwardCompatModel } from "./provider-models.js"; import { resolveFallbackXaiAuth } from "./src/tool-auth-shared.js"; @@ -115,7 +115,7 @@ describe("xai web search config resolution", () => { it("offers plugin-owned xSearch setup after Grok is selected", async () => { const provider = createXaiWebSearchProvider(); const select = vi.fn().mockResolvedValueOnce("yes").mockResolvedValueOnce("grok-4-1-fast"); - const prompter = createWizardPrompter({ + const prompter = createTestWizardPrompter({ select: select as never, }); @@ -175,7 +175,7 @@ describe("xai web search config resolution", () => { }, }, }; - const prompter = createWizardPrompter({}); + const prompter = createTestWizardPrompter(); const next = await provider.runSetup?.({ config, diff --git a/extensions/xai/xai.live.test.ts b/extensions/xai/xai.live.test.ts index 04b6e802efb..447cbf1206c 100644 --- a/extensions/xai/xai.live.test.ts +++ b/extensions/xai/xai.live.test.ts @@ -7,9 +7,9 @@ import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { runRealtimeSttLiveTest } from "openclaw/plugin-sdk/provider-test-contracts"; import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot"; import { describe, expect, it } from "vitest"; -import { runRealtimeSttLiveTest } from "../../test/helpers/stt-live-audio.js"; import plugin from "./index.js"; import { XAI_DEFAULT_STT_MODEL } from "./stt.js"; diff --git a/extensions/zalo/index.test.ts b/extensions/zalo/index.test.ts index d52d43e7cb1..bb56fca7569 100644 --- a/extensions/zalo/index.test.ts +++ b/extensions/zalo/index.test.ts @@ -1,5 +1,5 @@ +import { assertBundledChannelEntries } from "openclaw/plugin-sdk/channel-test-helpers"; import { describe } from "vitest"; -import { assertBundledChannelEntries } from "../../test/helpers/bundled-channel-entry.ts"; import entry from "./index.js"; import setupEntry from "./setup-entry.js"; diff --git a/extensions/zalo/src/monitor.pairing.lifecycle.test.ts b/extensions/zalo/src/monitor.pairing.lifecycle.test.ts index aa470783789..c4bef3c4926 100644 --- a/extensions/zalo/src/monitor.pairing.lifecycle.test.ts +++ b/extensions/zalo/src/monitor.pairing.lifecycle.test.ts @@ -1,5 +1,5 @@ +import { withServer } from "openclaw/plugin-sdk/test-env"; import { afterAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { withServer } from "../../../test/helpers/http-test-server.js"; import { createLifecycleMonitorSetup, createTextUpdate, diff --git a/extensions/zalo/src/monitor.reply-once.lifecycle.test.ts b/extensions/zalo/src/monitor.reply-once.lifecycle.test.ts index 3d847e82236..1203607dac9 100644 --- a/extensions/zalo/src/monitor.reply-once.lifecycle.test.ts +++ b/extensions/zalo/src/monitor.reply-once.lifecycle.test.ts @@ -1,5 +1,5 @@ +import { withServer } from "openclaw/plugin-sdk/test-env"; import { afterAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { withServer } from "../../../test/helpers/http-test-server.js"; import type { PluginRuntime } from "../runtime-api.js"; import { createLifecycleMonitorSetup, diff --git a/extensions/zalo/src/monitor.webhook.test.ts b/extensions/zalo/src/monitor.webhook.test.ts index bbc094ef1b7..4f8cfb85230 100644 --- a/extensions/zalo/src/monitor.webhook.test.ts +++ b/extensions/zalo/src/monitor.webhook.test.ts @@ -3,8 +3,8 @@ import { createEmptyPluginRegistry, setActivePluginRegistry, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { withServer } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { withServer } from "../../../test/helpers/http-test-server.js"; import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js"; import { createImageLifecycleCore, diff --git a/package.json b/package.json index 1fe8b412631..805efd27dbd 100644 --- a/package.json +++ b/package.json @@ -538,6 +538,10 @@ "types": "./dist/plugin-sdk/test-fixtures.d.ts", "default": "./dist/plugin-sdk/test-fixtures.js" }, + "./plugin-sdk/test-node-mocks": { + "types": "./dist/plugin-sdk/test-node-mocks.d.ts", + "default": "./dist/plugin-sdk/test-node-mocks.js" + }, "./plugin-sdk/testing": { "types": "./dist/plugin-sdk/testing.d.ts", "default": "./dist/plugin-sdk/testing.js" diff --git a/scripts/check-no-extension-test-core-imports.ts b/scripts/check-no-extension-test-core-imports.ts index 958c9db1c76..b28e7be179c 100644 --- a/scripts/check-no-extension-test-core-imports.ts +++ b/scripts/check-no-extension-test-core-imports.ts @@ -37,6 +37,26 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ 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: + /["'](?:\.\.\/)+(?:test\/helpers\/(?:bundled-channel-entry|envelope-timestamp|pairing-reply)\.(?:js|ts))["']/, + hint: "Use openclaw/plugin-sdk/channel-test-helpers instead of repo-only channel test helper bridges.", + }, + { + pattern: + /["'](?:\.\.\/)+(?:test\/helpers\/(?:http-test-server|mock-incoming-request|temp-home)\.(?:js|ts))["']/, + hint: "Use openclaw/plugin-sdk/test-env instead of repo-only environment/network test helper bridges.", + }, + { + pattern: + /["'](?:\.\.\/)+(?:test\/helpers\/(?:bundled-plugin-paths|import-fresh|node-builtin-mocks)\.(?:js|ts))["']/, + hint: "Use openclaw/plugin-sdk/test-fixtures instead of repo-only generic test helper bridges.", + }, + { + pattern: + /["'](?:\.\.\/)+(?:test\/helpers\/(?:provider-replay-policy|stt-live-audio)\.(?:js|ts))["']/, + hint: "Use openclaw/plugin-sdk/provider-test-contracts instead of repo-only provider test 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.", @@ -114,6 +134,17 @@ const RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES = [ "test/helpers/media-generation/dashscope-video-provider.ts", "test/helpers/media-generation/provider-capability-assertions.ts", "test/helpers/media-generation/provider-http-mocks.ts", + "test/helpers/bundled-channel-entry.ts", + "test/helpers/bundled-plugin-paths.ts", + "test/helpers/envelope-timestamp.ts", + "test/helpers/http-test-server.ts", + "test/helpers/import-fresh.ts", + "test/helpers/mock-incoming-request.ts", + "test/helpers/node-builtin-mocks.ts", + "test/helpers/pairing-reply.ts", + "test/helpers/provider-replay-policy.ts", + "test/helpers/stt-live-audio.ts", + "test/helpers/temp-home.ts", ]; function isExtensionTestFile(filePath: string): boolean { diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 9fc5d56231e..fbe330b50aa 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -118,6 +118,7 @@ "provider-test-contracts", "test-env", "test-fixtures", + "test-node-mocks", "testing", "temp-path", "logging-core", diff --git a/src/agents/context.eager-warmup.test.ts b/src/agents/context.eager-warmup.test.ts index 5cadb0cf263..cb9ae7c8e1e 100644 --- a/src/agents/context.eager-warmup.test.ts +++ b/src/agents/context.eager-warmup.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; const loadConfigMock = vi.hoisted(() => vi.fn()); diff --git a/src/agents/models-config.e2e-harness.ts b/src/agents/models-config.e2e-harness.ts index 965c4043390..9e6422ffbb5 100644 --- a/src/agents/models-config.e2e-harness.ts +++ b/src/agents/models-config.e2e-harness.ts @@ -1,9 +1,9 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, beforeEach, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import { clearConfigCache, clearRuntimeConfigSnapshot } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { withTempHome as withTempHomeBase } from "../plugin-sdk/test-helpers/temp-home.js"; import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import { resetPluginLoaderTestStateForTest } from "../plugins/loader.test-fixtures.js"; import { resetProviderRuntimeHookCacheForTest } from "../plugins/provider-runtime.js"; diff --git a/src/agents/models-config.write-serialization.test.ts b/src/agents/models-config.write-serialization.test.ts index a44069f14bc..942fa1e001f 100644 --- a/src/agents/models-config.write-serialization.test.ts +++ b/src/agents/models-config.write-serialization.test.ts @@ -153,6 +153,8 @@ describe("models-config write serialization", () => { const modelPath = path.join(resolveOpenClawAgentDir(), "models.json"); await fs.writeFile(modelPath, `${JSON.stringify({ external: true })}\n`, "utf8"); + const externalMtime = new Date(Date.now() + 2000); + await fs.utimes(modelPath, externalMtime, externalMtime); await ensureOpenClawModelsJson(CUSTOM_PROXY_MODELS_CONFIG); expect(planOpenClawModelsJsonMock).toHaveBeenCalledTimes(2); diff --git a/src/agents/pi-embedded-runner/openrouter-model-capabilities.test.ts b/src/agents/pi-embedded-runner/openrouter-model-capabilities.test.ts index da81b5dc180..d0e57db6cf6 100644 --- a/src/agents/pi-embedded-runner/openrouter-model-capabilities.test.ts +++ b/src/agents/pi-embedded-runner/openrouter-model-capabilities.test.ts @@ -1,8 +1,8 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; async function withOpenRouterStateDir(run: (stateDir: string) => Promise) { const stateDir = mkdtempSync(join(tmpdir(), "openclaw-openrouter-capabilities-")); diff --git a/src/agents/pi-embedded-runner/runs.test.ts b/src/agents/pi-embedded-runner/runs.test.ts index d8a0c913bc5..5297d65dc89 100644 --- a/src/agents/pi-embedded-runner/runs.test.ts +++ b/src/agents/pi-embedded-runner/runs.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import { __testing, abortEmbeddedPiRun, diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts index cd8ce4754d5..633a04fb5d5 100644 --- a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -1,6 +1,6 @@ import path from "node:path"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { afterEach, beforeEach, expect, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles.js"; import { resetSkillsRefreshForTest } from "../agents/skills/refresh.js"; import { clearSessionStoreCacheForTest, loadSessionStore } from "../config/sessions.js"; diff --git a/src/auto-reply/reply/commands-status.test.ts b/src/auto-reply/reply/commands-status.test.ts index d0dea685472..54b5a94ba26 100644 --- a/src/auto-reply/reply/commands-status.test.ts +++ b/src/auto-reply/reply/commands-status.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { normalizeTestText } from "../../../test/helpers/normalize-text.js"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; import { clearAgentHarnesses, registerAgentHarness } from "../../agents/harness/registry.js"; import type { AgentHarness } from "../../agents/harness/types.js"; import { diff --git a/src/auto-reply/reply/get-reply-run.media-only.test.ts b/src/auto-reply/reply/get-reply-run.media-only.test.ts index fbac41a80d2..e3a522ccdca 100644 --- a/src/auto-reply/reply/get-reply-run.media-only.test.ts +++ b/src/auto-reply/reply/get-reply-run.media-only.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.ts"; import { clearActiveEmbeddedRun, setActiveEmbeddedRun, diff --git a/src/auto-reply/reply/get-reply.test-loader.ts b/src/auto-reply/reply/get-reply.test-loader.ts index 4097937f040..a10a36612e3 100644 --- a/src/auto-reply/reply/get-reply.test-loader.ts +++ b/src/auto-reply/reply/get-reply.test-loader.ts @@ -1,4 +1,4 @@ -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; type GetReplyModule = typeof import("./get-reply.js"); type ReplyModule = typeof import("../reply.js"); diff --git a/src/auto-reply/reply/inbound-dedupe.test.ts b/src/auto-reply/reply/inbound-dedupe.test.ts index 1034f428a90..f4979f6ad35 100644 --- a/src/auto-reply/reply/inbound-dedupe.test.ts +++ b/src/auto-reply/reply/inbound-dedupe.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import type { MsgContext } from "../templating.js"; import { buildInboundDedupeKey, resetInboundDedupe } from "./inbound-dedupe.js"; diff --git a/src/auto-reply/reply/queue.dedupe.test.ts b/src/auto-reply/reply/queue.dedupe.test.ts index bc3f5f5c330..fa31d5bbe8a 100644 --- a/src/auto-reply/reply/queue.dedupe.test.ts +++ b/src/auto-reply/reply/queue.dedupe.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import type { FollowupRun, QueueSettings } from "./queue.js"; import { enqueueFollowupRun, diff --git a/src/auto-reply/reply/queue.drain-restart.test.ts b/src/auto-reply/reply/queue.drain-restart.test.ts index a06191c5d49..588652db2dc 100644 --- a/src/auto-reply/reply/queue.drain-restart.test.ts +++ b/src/auto-reply/reply/queue.drain-restart.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; import type { FollowupRun, QueueSettings } from "./queue.js"; import { enqueueFollowupRun, scheduleFollowupDrain } from "./queue.js"; import { diff --git a/src/auto-reply/stage-sandbox-media.test-harness.ts b/src/auto-reply/stage-sandbox-media.test-harness.ts index f72613f3f6f..2a932322f4f 100644 --- a/src/auto-reply/stage-sandbox-media.test-harness.ts +++ b/src/auto-reply/stage-sandbox-media.test-harness.ts @@ -1,5 +1,5 @@ import { join } from "node:path"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { MsgContext, TemplateContext } from "./templating.js"; diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index 33e35158db9..0d0a7663af1 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; import { normalizeTestText } from "../../test/helpers/normalize-text.js"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { MODEL_CONTEXT_TOKEN_CACHE } from "../agents/context-cache.js"; import type { OpenClawConfig } from "../config/config.js"; import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js"; diff --git a/src/channels/plugins/bundled-root-caches.test.ts b/src/channels/plugins/bundled-root-caches.test.ts index 8b379297b87..c13b33eb26e 100644 --- a/src/channels/plugins/bundled-root-caches.test.ts +++ b/src/channels/plugins/bundled-root-caches.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.ts"; const tempDirs: string[] = []; const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; diff --git a/src/channels/plugins/bundled.shape-guard.test.ts b/src/channels/plugins/bundled.shape-guard.test.ts index 9201a476ec1..146f8a228e7 100644 --- a/src/channels/plugins/bundled.shape-guard.test.ts +++ b/src/channels/plugins/bundled.shape-guard.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.ts"; import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js"; const bundledChannelEntrypointPaths = ["index.ts", "channel-entry.ts", "setup-entry.ts"] as const; diff --git a/src/channels/plugins/module-loader.test.ts b/src/channels/plugins/module-loader.test.ts index 66463bba47c..0f38abac390 100644 --- a/src/channels/plugins/module-loader.test.ts +++ b/src/channels/plugins/module-loader.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.ts"; import { shouldExpectNativeJitiForJavaScriptTestRuntime } from "../../test-utils/jiti-runtime.js"; import { isJavaScriptModulePath, diff --git a/src/channels/plugins/setup-helpers.import-safety.test.ts b/src/channels/plugins/setup-helpers.import-safety.test.ts index 6003e035d74..a5e24fe6361 100644 --- a/src/channels/plugins/setup-helpers.import-safety.test.ts +++ b/src/channels/plugins/setup-helpers.import-safety.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.ts"; import { clearSetupPromotionRuntimeModuleCache } from "./setup-helpers.js"; afterEach(() => { diff --git a/src/cli/command-secret-resolution.coverage.test.ts b/src/cli/command-secret-resolution.coverage.test.ts index 9da2c0f322b..ab432a8f569 100644 --- a/src/cli/command-secret-resolution.coverage.test.ts +++ b/src/cli/command-secret-resolution.coverage.test.ts @@ -1,5 +1,5 @@ +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { bundledPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { readCommandSource } from "./command-source.test-helpers.js"; const SECRET_TARGET_CALLSITES = [ diff --git a/src/cli/deps.test.ts b/src/cli/deps.test.ts index abfd05fb07b..04f547c4e5a 100644 --- a/src/cli/deps.test.ts +++ b/src/cli/deps.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import type { ChannelPlugin } from "../channels/plugins/types.js"; const runtimeFactories = vi.hoisted(() => ({ diff --git a/src/cli/npm-resolution.test.ts b/src/cli/npm-resolution.test.ts index 90650834aa0..93261b052df 100644 --- a/src/cli/npm-resolution.test.ts +++ b/src/cli/npm-resolution.test.ts @@ -1,5 +1,5 @@ +import { installedPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { installedPluginRoot } from "../../test/helpers/bundled-plugin-paths.js"; import { buildNpmInstallRecordFields, logPinnedNpmSpecMessages, diff --git a/src/cli/plugin-install-plan.test.ts b/src/cli/plugin-install-plan.test.ts index 2ec5e196f3e..ff30d515b5d 100644 --- a/src/cli/plugin-install-plan.test.ts +++ b/src/cli/plugin-install-plan.test.ts @@ -1,5 +1,5 @@ +import { installedPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; -import { installedPluginRoot } from "../../test/helpers/bundled-plugin-paths.js"; import { PLUGIN_INSTALL_ERROR_CODE } from "../plugins/install.js"; import { resolveBundledInstallPlanForCatalogEntry, diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 5b64d869347..b715519dacc 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { installedPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { installedPluginRoot } from "../../test/helpers/bundled-plugin-paths.js"; import type { OpenClawConfig } from "../config/config.js"; import { applyExclusiveSlotSelection, diff --git a/src/cli/plugins-cli.uninstall.test.ts b/src/cli/plugins-cli.uninstall.test.ts index 74655260e3e..2e39655774d 100644 --- a/src/cli/plugins-cli.uninstall.test.ts +++ b/src/cli/plugins-cli.uninstall.test.ts @@ -1,5 +1,5 @@ +import { installedPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it } from "vitest"; -import { installedPluginRoot } from "../../test/helpers/bundled-plugin-paths.js"; import type { OpenClawConfig } from "../config/config.js"; import { applyPluginUninstallDirectoryRemoval, diff --git a/src/cli/plugins-install-config.test.ts b/src/cli/plugins-install-config.test.ts index fbae58b3c22..53371ef1bfd 100644 --- a/src/cli/plugins-install-config.test.ts +++ b/src/cli/plugins-install-config.test.ts @@ -1,5 +1,5 @@ +import { bundledPluginRootAt, repoInstallSpec } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { bundledPluginRootAt, repoInstallSpec } from "../../test/helpers/bundled-plugin-paths.js"; import type { OpenClawConfig } from "../config/config.js"; import type { ConfigFileSnapshot } from "../config/types.openclaw.js"; import { diff --git a/src/cli/program/preaction.test.ts b/src/cli/program/preaction.test.ts index f14389727e1..259d595bd3f 100644 --- a/src/cli/program/preaction.test.ts +++ b/src/cli/program/preaction.test.ts @@ -1,6 +1,6 @@ import { Command } from "commander"; +import { repoInstallSpec } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { repoInstallSpec } from "../../../test/helpers/bundled-plugin-paths.js"; import { loggingState } from "../../logging/state.js"; import { setCommandJsonMode } from "./json-mode.js"; diff --git a/src/cli/update-cli/restart-helper.test.ts b/src/cli/update-cli/restart-helper.test.ts index 99c841e7659..a35d78995cf 100644 --- a/src/cli/update-cli/restart-helper.test.ts +++ b/src/cli/update-cli/restart-helper.test.ts @@ -6,7 +6,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { prepareRestartScript, runRestartScript } from "./restart-helper.js"; vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/commands/agent-command.test-support.ts b/src/commands/agent-command.test-support.ts index e49ae46a289..a1eed2aa639 100644 --- a/src/commands/agent-command.test-support.ts +++ b/src/commands/agent-command.test-support.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import type { OpenClawConfig } from "../config/types.openclaw.js"; type AgentDefaultConfig = NonNullable["defaults"]>; diff --git a/src/commands/agent.acp.test.ts b/src/commands/agent.acp.test.ts index 2a79bb85d97..9953fae20b4 100644 --- a/src/commands/agent.acp.test.ts +++ b/src/commands/agent.acp.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import "./agent-command.test-mocks.js"; import * as acpManagerModule from "../acp/control-plane/manager.js"; import { AcpRuntimeError } from "../acp/runtime/errors.js"; diff --git a/src/commands/agent.runtime-config.test.ts b/src/commands/agent.runtime-config.test.ts index 0872429570f..838955bd68a 100644 --- a/src/commands/agent.runtime-config.test.ts +++ b/src/commands/agent.runtime-config.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import { resolveAgentRuntimeConfig } from "../agents/agent-runtime-config.js"; import { resolveSession } from "../agents/command/session.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; diff --git a/src/commands/agent.session.test.ts b/src/commands/agent.session.test.ts index c916d9c1229..ce607a442ee 100644 --- a/src/commands/agent.session.test.ts +++ b/src/commands/agent.session.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import { resolveAgentDir, resolveSessionAgentId } from "../agents/agent-scope.js"; import { resolveSession } from "../agents/command/session.js"; import { clearSessionStoreCacheForTest } from "../config/sessions/store.js"; diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 6645f7bc056..cdd48bd8c61 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, type MockInstance, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; import "./agent-command.test-mocks.js"; import { __testing as acpManagerTesting } from "../acp/control-plane/manager.js"; import * as authProfileStoreModule from "../agents/auth-profiles/store.js"; diff --git a/src/commands/channel-setup/plugin-install.test.ts b/src/commands/channel-setup/plugin-install.test.ts index 105b8f9fd75..ca9da3878da 100644 --- a/src/commands/channel-setup/plugin-install.test.ts +++ b/src/commands/channel-setup/plugin-install.test.ts @@ -1,9 +1,6 @@ import path from "node:path"; +import { bundledPluginRoot, bundledPluginRootAt } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - bundledPluginRoot, - bundledPluginRootAt, -} from "../../../test/helpers/bundled-plugin-paths.js"; vi.mock("node:fs", async () => { const actual = await vi.importActual("node:fs"); diff --git a/src/commands/doctor-config-flow.test.ts b/src/commands/doctor-config-flow.test.ts index cda4300d0d1..bb7653b28fe 100644 --- a/src/commands/doctor-config-flow.test.ts +++ b/src/commands/doctor-config-flow.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js"; import { getDoctorConfigInputForTest, diff --git a/src/commands/doctor/shared/bundled-plugin-load-paths.test.ts b/src/commands/doctor/shared/bundled-plugin-load-paths.test.ts index 44e56e16371..82c0ecdc41f 100644 --- a/src/commands/doctor/shared/bundled-plugin-load-paths.test.ts +++ b/src/commands/doctor/shared/bundled-plugin-load-paths.test.ts @@ -1,9 +1,6 @@ import path from "node:path"; +import { bundledDistPluginRootAt, bundledPluginRootAt } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - bundledDistPluginRootAt, - bundledPluginRootAt, -} from "../../../../test/helpers/bundled-plugin-paths.js"; import type { BundledPluginSource } from "../../../plugins/bundled-sources.js"; import * as bundledSources from "../../../plugins/bundled-sources.js"; import { diff --git a/src/commands/models/list.probe.test.ts b/src/commands/models/list.probe.test.ts index e474bb1eddb..2b02950f860 100644 --- a/src/commands/models/list.probe.test.ts +++ b/src/commands/models/list.probe.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeAll, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; let probeModule: typeof import("./list.probe.js"); diff --git a/src/commands/setup.test.ts b/src/commands/setup.test.ts index eed8d0c2456..ef5396a2dbb 100644 --- a/src/commands/setup.test.ts +++ b/src/commands/setup.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { setupCommand } from "./setup.js"; function createSetupDeps(home: string) { diff --git a/src/config/bundled-channel-config-runtime.test.ts b/src/config/bundled-channel-config-runtime.test.ts index 2d186ab8298..4d6365b7e16 100644 --- a/src/config/bundled-channel-config-runtime.test.ts +++ b/src/config/bundled-channel-config-runtime.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; vi.mock("../plugins/bundled-plugin-metadata.js", () => ({ listBundledPluginMetadata: () => [ diff --git a/src/config/load-channel-config-surface.test.ts b/src/config/load-channel-config-surface.test.ts index a3ac1f1f3e0..ad1b4ede15c 100644 --- a/src/config/load-channel-config-surface.test.ts +++ b/src/config/load-channel-config-surface.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; import { loadChannelConfigSurfaceModule } from "../../scripts/load-channel-config-surface.ts"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import { withTempDir } from "../test-helpers/temp-dir.js"; async function importLoaderWithMissingBun() { diff --git a/src/config/mcp-config.test.ts b/src/config/mcp-config.test.ts index a242e414d24..36a462202cf 100644 --- a/src/config/mcp-config.test.ts +++ b/src/config/mcp-config.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it, vi } from "vitest"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { listConfiguredMcpServers, setConfiguredMcpServer, diff --git a/src/config/normalize-paths.test.ts b/src/config/normalize-paths.test.ts index ba5c3f5bd72..8ecea99ae9a 100644 --- a/src/config/normalize-paths.test.ts +++ b/src/config/normalize-paths.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { normalizeConfigPaths } from "./normalize-paths.js"; describe("normalizeConfigPaths", () => { diff --git a/src/config/sessions/targets.test.ts b/src/config/sessions/targets.test.ts index e7aa304f508..b6f3b91308d 100644 --- a/src/config/sessions/targets.test.ts +++ b/src/config/sessions/targets.test.ts @@ -1,8 +1,8 @@ import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; -import { withTempHome } from "../../../test/helpers/temp-home.js"; import type { OpenClawConfig } from "../config.js"; import { resolveStorePath } from "./paths.js"; import { diff --git a/src/config/test-helpers.ts b/src/config/test-helpers.ts index 923aa5454aa..0472701e1cb 100644 --- a/src/config/test-helpers.ts +++ b/src/config/test-helpers.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import { resetPluginLoaderTestStateForTest } from "../plugins/loader.test-fixtures.js"; import { clearPluginSetupRegistryCache } from "../plugins/setup-registry.js"; import { resetConfigRuntimeState, type OpenClawConfig } from "./config.js"; diff --git a/src/config/zod-schema.providers.lazy-runtime.test.ts b/src/config/zod-schema.providers.lazy-runtime.test.ts index de6d482e18e..1ba42b05239 100644 --- a/src/config/zod-schema.providers.lazy-runtime.test.ts +++ b/src/config/zod-schema.providers.lazy-runtime.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import type { BundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js"; import type { PluginManifestChannelConfig } from "../plugins/manifest.js"; diff --git a/src/cron/isolated-agent.test-harness.ts b/src/cron/isolated-agent.test-harness.ts index 305cfd5cd3e..09861c17b27 100644 --- a/src/cron/isolated-agent.test-harness.ts +++ b/src/cron/isolated-agent.test-harness.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; +import { withTempHome as withTempHomeBase } from "openclaw/plugin-sdk/test-env"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { CronJob } from "./types.js"; diff --git a/src/daemon/systemd.test.ts b/src/daemon/systemd.test.ts index bb12a8ca0eb..646774dc71f 100644 --- a/src/daemon/systemd.test.ts +++ b/src/daemon/systemd.test.ts @@ -6,7 +6,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; const execFileMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeChildProcessExecFile } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeChildProcessExecFile } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeChildProcessExecFile( Object.assign(execFileMock, { __promisify__: vi.fn(), diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts index 950d4195164..d6854120fa4 100644 --- a/src/dockerfile.test.ts +++ b/src/dockerfile.test.ts @@ -1,8 +1,8 @@ import { readFile } from "node:fs/promises"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { BUNDLED_PLUGIN_ROOT_DIR } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { BUNDLED_PLUGIN_ROOT_DIR } from "../test/helpers/bundled-plugin-paths.js"; const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), ".."); const dockerfilePath = join(repoRoot, "Dockerfile"); diff --git a/src/gateway/server-methods/config.test.ts b/src/gateway/server-methods/config.test.ts index dfb08f4138a..a73e0fdd057 100644 --- a/src/gateway/server-methods/config.test.ts +++ b/src/gateway/server-methods/config.test.ts @@ -5,7 +5,7 @@ import { createConfigHandlerHarness } from "./config.test-helpers.js"; const execFileMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/gateway/session-archive.imports.test.ts b/src/gateway/session-archive.imports.test.ts index 142ec03e890..5113046f078 100644 --- a/src/gateway/session-archive.imports.test.ts +++ b/src/gateway/session-archive.imports.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; describe("session archive runtime import guards", () => { it.each([ diff --git a/src/infra/gateway-processes.test.ts b/src/infra/gateway-processes.test.ts index 8f76e33cb60..90a26c7190a 100644 --- a/src/infra/gateway-processes.test.ts +++ b/src/infra/gateway-processes.test.ts @@ -8,13 +8,12 @@ const isGatewayArgvMock = vi.hoisted(() => vi.fn()); const findGatewayPidsOnPortSyncMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeChildProcessSpawnSync } = - await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeChildProcessSpawnSync } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeChildProcessSpawnSync(spawnSyncMock); }); vi.mock("node:fs", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:fs"), { diff --git a/src/infra/machine-name.test.ts b/src/infra/machine-name.test.ts index c4db53410b3..e5be3d8f1d9 100644 --- a/src/infra/machine-name.test.ts +++ b/src/infra/machine-name.test.ts @@ -1,11 +1,11 @@ import os from "node:os"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; const execFileMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeChildProcessExecFile } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeChildProcessExecFile } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeChildProcessExecFile( Object.assign(execFileMock, { __promisify__: vi.fn(), diff --git a/src/infra/os-summary.test.ts b/src/infra/os-summary.test.ts index f55b14c3f9b..b4effb900e0 100644 --- a/src/infra/os-summary.test.ts +++ b/src/infra/os-summary.test.ts @@ -4,8 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; const spawnSyncMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeChildProcessSpawnSync } = - await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeChildProcessSpawnSync } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeChildProcessSpawnSync(spawnSyncMock); }); diff --git a/src/infra/outbound/cfg-threading.guard.test.ts b/src/infra/outbound/cfg-threading.guard.test.ts index 9c6ad4caadf..9a2913a13e3 100644 --- a/src/infra/outbound/cfg-threading.guard.test.ts +++ b/src/infra/outbound/cfg-threading.guard.test.ts @@ -1,8 +1,8 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { bundledPluginFile } from "../../../test/helpers/bundled-plugin-paths.js"; const thisFilePath = fileURLToPath(import.meta.url); const thisDir = path.dirname(thisFilePath); diff --git a/src/infra/outbound/channel-resolution.test.ts b/src/infra/outbound/channel-resolution.test.ts index 975b19271ba..d363d589f31 100644 --- a/src/infra/outbound/channel-resolution.test.ts +++ b/src/infra/outbound/channel-resolution.test.ts @@ -43,7 +43,7 @@ vi.mock("../../utils/message-channel.js", () => ({ isDeliverableMessageChannel: (...args: unknown[]) => isDeliverableMessageChannelMock(...args), })); -import { importFreshModule } from "../../../test/helpers/import-fresh.js"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; async function importChannelResolution(scope: string) { return await importFreshModule( diff --git a/src/infra/plugin-install-path-warnings.test.ts b/src/infra/plugin-install-path-warnings.test.ts index 43e6a9e55b7..ed65705307e 100644 --- a/src/infra/plugin-install-path-warnings.test.ts +++ b/src/infra/plugin-install-path-warnings.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { withTempHome } from "openclaw/plugin-sdk/test-env"; +import { repoInstallSpec } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { repoInstallSpec } from "../../test/helpers/bundled-plugin-paths.js"; -import { withTempHome } from "../../test/helpers/temp-home.js"; import { detectPluginInstallPathIssue, formatPluginInstallPathIssue, diff --git a/src/infra/process-respawn.test.ts b/src/infra/process-respawn.test.ts index 2a61a673b96..e63e0eb7c6d 100644 --- a/src/infra/process-respawn.test.ts +++ b/src/infra/process-respawn.test.ts @@ -6,7 +6,7 @@ const spawnMock = vi.hoisted(() => vi.fn()); const triggerOpenClawRestartMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/infra/restart-stale-pids.test.ts b/src/infra/restart-stale-pids.test.ts index 0ba01d77a60..0be731a2b85 100644 --- a/src/infra/restart-stale-pids.test.ts +++ b/src/infra/restart-stale-pids.test.ts @@ -34,7 +34,7 @@ const mockReadWindowsProcessArgsResult = vi.hoisted(() => const mockReadFileSync = vi.hoisted(() => vi.fn()); vi.mock("node:fs", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:fs"), (actual) => ({ @@ -51,7 +51,7 @@ vi.mock("node:fs", async () => { }); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/infra/restart.test.ts b/src/infra/restart.test.ts index 9455b365084..bee3936117f 100644 --- a/src/infra/restart.test.ts +++ b/src/infra/restart.test.ts @@ -6,8 +6,7 @@ const resolveLsofCommandSyncMock = vi.hoisted(() => vi.fn()); const resolveGatewayPortMock = vi.hoisted(() => vi.fn()); vi.mock("node:child_process", async () => { - const { mockNodeChildProcessSpawnSync } = - await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeChildProcessSpawnSync } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeChildProcessSpawnSync(spawnSyncMock); }); diff --git a/src/infra/run-node.test.ts b/src/infra/run-node.test.ts index 05d3a5e5601..1fcdd7ef2a9 100644 --- a/src/infra/run-node.test.ts +++ b/src/infra/run-node.test.ts @@ -2,6 +2,11 @@ import { EventEmitter } from "node:events"; import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import { + bundledDistPluginFile, + bundledPluginFile, + bundledPluginRoot, +} from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; import { acquireRunNodeBuildLock, @@ -9,11 +14,6 @@ import { resolveRuntimePostBuildRequirement, runNodeMain, } from "../../scripts/run-node.mjs"; -import { - bundledDistPluginFile, - bundledPluginFile, - bundledPluginRoot, -} from "../../test/helpers/bundled-plugin-paths.js"; import { withTempDir } from "../test-helpers/temp-dir.js"; const ROOT_SRC = "src/index.ts"; diff --git a/src/infra/ssh-config.test.ts b/src/infra/ssh-config.test.ts index b1b34171b59..de4a3cdf15f 100644 --- a/src/infra/ssh-config.test.ts +++ b/src/infra/ssh-config.test.ts @@ -17,7 +17,7 @@ function createMockSpawnChild() { } vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); const spawn = vi.fn(() => { const { child, stdout } = createMockSpawnChild(); process.nextTick(() => { diff --git a/src/infra/system-presence.version.test.ts b/src/infra/system-presence.version.test.ts index 576e6426075..4653517e26e 100644 --- a/src/infra/system-presence.version.test.ts +++ b/src/infra/system-presence.version.test.ts @@ -1,6 +1,6 @@ import os from "node:os"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; import { withEnvAsync } from "../test-utils/env.js"; import { VERSION as runtimeVersion } from "../version.js"; diff --git a/src/infra/tsdown-config.test.ts b/src/infra/tsdown-config.test.ts index 508e51330f4..6d20667e8fd 100644 --- a/src/infra/tsdown-config.test.ts +++ b/src/infra/tsdown-config.test.ts @@ -1,5 +1,5 @@ +import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { bundledPluginRoot } from "../../test/helpers/bundled-plugin-paths.js"; import tsdownConfig from "../../tsdown.config.ts"; type TsdownConfigEntry = { diff --git a/src/infra/update-global.test.ts b/src/infra/update-global.test.ts index 7666343b70b..f64c8bd1f76 100644 --- a/src/infra/update-global.test.ts +++ b/src/infra/update-global.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/runtime-sidecar-paths.js"; import { withTempDir } from "../test-helpers/temp-dir.js"; import { captureEnv } from "../test-utils/env.js"; diff --git a/src/infra/update-runner.test.ts b/src/infra/update-runner.test.ts index 3aa7dc94647..c06e5dff003 100644 --- a/src/infra/update-runner.test.ts +++ b/src/infra/update-runner.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/runtime-sidecar-paths.js"; import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js"; import { withEnvAsync } from "../test-utils/env.js"; diff --git a/src/infra/watch-node.test.ts b/src/infra/watch-node.test.ts index 47427154afb..f761899ae50 100644 --- a/src/infra/watch-node.test.ts +++ b/src/infra/watch-node.test.ts @@ -2,10 +2,10 @@ import { createHash } from "node:crypto"; import { EventEmitter } from "node:events"; import fs from "node:fs"; import path from "node:path"; +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it, vi } from "vitest"; import { runNodeWatchedPaths } from "../../scripts/run-node.mjs"; import { runWatchMain } from "../../scripts/watch-node.mjs"; -import { bundledPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { withTempDir } from "../test-helpers/temp-dir.js"; const VOICE_CALL_README = bundledPluginFile("voice-call", "README.md"); diff --git a/src/infra/windows-task-restart.test.ts b/src/infra/windows-task-restart.test.ts index ed64a6fc10e..19552a627c0 100644 --- a/src/infra/windows-task-restart.test.ts +++ b/src/infra/windows-task-restart.test.ts @@ -14,7 +14,7 @@ const resolveTaskScriptPathMock = vi.hoisted(() => ); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/logging/diagnostic.test.ts b/src/logging/diagnostic.test.ts index 7b5ee6d4443..df164f08e26 100644 --- a/src/logging/diagnostic.test.ts +++ b/src/logging/diagnostic.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; import { emitDiagnosticEvent, onDiagnosticEvent, diff --git a/src/logging/logger-transport.test.ts b/src/logging/logger-transport.test.ts index 1a68a967881..ca7e2fd6037 100644 --- a/src/logging/logger-transport.test.ts +++ b/src/logging/logger-transport.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; import { createSuiteLogPathTracker } from "./log-test-helpers.js"; type LoggerModule = typeof import("./logger.js"); diff --git a/src/logging/logger.browser-import.test.ts b/src/logging/logger.browser-import.test.ts index aefaf26a4fa..f249c62d99a 100644 --- a/src/logging/logger.browser-import.test.ts +++ b/src/logging/logger.browser-import.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; type LoggerModule = typeof import("./logger.js"); diff --git a/src/media/store.test.ts b/src/media/store.test.ts index 9334a74c500..335015890be 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -1,9 +1,9 @@ import fs from "node:fs/promises"; import path from "node:path"; import JSZip from "jszip"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import sharp from "sharp"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import { isPathWithinBase } from "../../test/helpers/paths.js"; import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; diff --git a/src/pairing/pairing-messages.test.ts b/src/pairing/pairing-messages.test.ts index 4c75602c9ff..8dfb4dcb119 100644 --- a/src/pairing/pairing-messages.test.ts +++ b/src/pairing/pairing-messages.test.ts @@ -1,5 +1,5 @@ +import { expectPairingReplyText } from "openclaw/plugin-sdk/channel-test-helpers"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { expectPairingReplyText } from "../../test/helpers/pairing-reply.js"; import { captureEnv } from "../test-utils/env.js"; import { buildPairingReply } from "./pairing-messages.js"; diff --git a/src/plugin-sdk/channel-entry-contract.test.ts b/src/plugin-sdk/channel-entry-contract.test.ts index 293e9d8ca3e..8448435112f 100644 --- a/src/plugin-sdk/channel-entry-contract.test.ts +++ b/src/plugin-sdk/channel-entry-contract.test.ts @@ -2,8 +2,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import type { OpenClawPluginApi, PluginRegistrationMode } from "../plugins/types.js"; import { defineBundledChannelEntry, loadBundledEntryExportSync } from "./channel-entry-contract.js"; diff --git a/src/plugin-sdk/channel-test-helpers.ts b/src/plugin-sdk/channel-test-helpers.ts index 0e91ffbe89b..83e361e3e46 100644 --- a/src/plugin-sdk/channel-test-helpers.ts +++ b/src/plugin-sdk/channel-test-helpers.ts @@ -39,3 +39,10 @@ export { getRequiredHookHandler, registerHookHandlersForTest, } from "./test-helpers/subagent-hooks.js"; +export { assertBundledChannelEntries } from "./test-helpers/bundled-channel-entry.js"; +export { + escapeRegExp, + formatEnvelopeTimestamp, + formatLocalEnvelopeTimestamp, +} from "./test-helpers/envelope-timestamp.js"; +export { expectPairingReplyText, extractPairingCode } from "./test-helpers/pairing-reply.js"; diff --git a/src/plugin-sdk/provider-test-contracts.ts b/src/plugin-sdk/provider-test-contracts.ts index db72eb31f99..dd9804c29b1 100644 --- a/src/plugin-sdk/provider-test-contracts.ts +++ b/src/plugin-sdk/provider-test-contracts.ts @@ -65,6 +65,14 @@ export { describeProviderWizardModelPickerContract, describeProviderWizardSetupOptionsContract, } from "./test-helpers/provider-wizard-contract-suites.js"; +export { expectPassthroughReplayPolicy } from "./test-helpers/provider-replay-policy.js"; export { createCapturedThinkingConfigStream } from "./test-helpers/stream-hooks.js"; +export { + normalizeTranscriptForMatch, + runRealtimeSttLiveTest, + streamAudioForLiveTest, + synthesizeElevenLabsLiveSpeech, + waitForLiveExpectation, +} from "./test-helpers/stt-live-audio.js"; export { describeWebFetchProviderContracts } from "./test-helpers/web-fetch-provider-contract.js"; export { describeWebSearchProviderContracts } from "./test-helpers/web-search-provider-contract.js"; diff --git a/src/plugin-sdk/runtime-store.test.ts b/src/plugin-sdk/runtime-store.test.ts index c961008421c..c3343eae9d9 100644 --- a/src/plugin-sdk/runtime-store.test.ts +++ b/src/plugin-sdk/runtime-store.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, test } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; import { createPluginRuntimeStore } from "./runtime-store.js"; describe("createPluginRuntimeStore", () => { diff --git a/src/plugin-sdk/test-env.ts b/src/plugin-sdk/test-env.ts index 365e1a3ef3f..f743c23b668 100644 --- a/src/plugin-sdk/test-env.ts +++ b/src/plugin-sdk/test-env.ts @@ -62,3 +62,6 @@ export { createMockServerResponse } from "../test-utils/mock-http-response.js"; export { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; export { withTempDir } from "../test-utils/temp-dir.js"; export { useFrozenTime, useRealTime } from "../test-utils/frozen-time.js"; +export { withServer } from "./test-helpers/http-test-server.js"; +export { createMockIncomingRequest } from "./test-helpers/mock-incoming-request.js"; +export { withTempHome } from "./test-helpers/temp-home.js"; diff --git a/src/plugin-sdk/test-fixtures.ts b/src/plugin-sdk/test-fixtures.ts index 6f1f7f4f489..e8d246a5334 100644 --- a/src/plugin-sdk/test-fixtures.ts +++ b/src/plugin-sdk/test-fixtures.ts @@ -20,3 +20,20 @@ export { sanitizeTerminalText } from "../terminal/safe-text.js"; export { countLines, hasBalancedFences } from "../test-utils/chunk-test-helpers.js"; export { expectGeneratedTokenPersistedToGatewayAuth } from "../test-utils/auth-token-assertions.js"; export { typedCases } from "../test-utils/typed-cases.js"; +export { + BUNDLED_PLUGIN_PATH_PREFIX, + BUNDLED_PLUGIN_ROOT_DIR, + BUNDLED_PLUGIN_TEST_GLOB, + bundledDistPluginFile, + bundledDistPluginFileAt, + bundledDistPluginRoot, + bundledDistPluginRootAt, + bundledPluginDirPrefix, + bundledPluginFile, + bundledPluginFileAt, + bundledPluginRoot, + bundledPluginRootAt, + installedPluginRoot, + repoInstallSpec, +} from "./test-helpers/bundled-plugin-paths.js"; +export { importFreshModule } from "./test-helpers/import-fresh.js"; diff --git a/src/plugin-sdk/test-helpers/envelope-timestamp.ts b/src/plugin-sdk/test-helpers/envelope-timestamp.ts new file mode 100644 index 00000000000..bdbcf8a9e27 --- /dev/null +++ b/src/plugin-sdk/test-helpers/envelope-timestamp.ts @@ -0,0 +1,43 @@ +import { + formatUtcTimestamp, + formatZonedTimestamp, +} from "../../infra/format-time/format-datetime.js"; + +export { escapeRegExp } from "../../utils.js"; + +type EnvelopeTimestampZone = string; + +export function formatEnvelopeTimestamp(date: Date, zone: EnvelopeTimestampZone = "utc"): string { + const trimmedZone = zone.trim(); + const normalized = trimmedZone.toLowerCase(); + const weekday = (() => { + try { + if (normalized === "utc" || normalized === "gmt") { + return new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "short" }).format(date); + } + if (normalized === "local" || normalized === "host") { + return new Intl.DateTimeFormat("en-US", { weekday: "short" }).format(date); + } + return new Intl.DateTimeFormat("en-US", { timeZone: trimmedZone, weekday: "short" }).format( + date, + ); + } catch { + return undefined; + } + })(); + + if (normalized === "utc" || normalized === "gmt") { + const ts = formatUtcTimestamp(date); + return weekday ? `${weekday} ${ts}` : ts; + } + if (normalized === "local" || normalized === "host") { + const ts = formatZonedTimestamp(date) ?? formatUtcTimestamp(date); + return weekday ? `${weekday} ${ts}` : ts; + } + const ts = formatZonedTimestamp(date, { timeZone: trimmedZone }) ?? formatUtcTimestamp(date); + return weekday ? `${weekday} ${ts}` : ts; +} + +export function formatLocalEnvelopeTimestamp(date: Date): string { + return formatEnvelopeTimestamp(date, "local"); +} diff --git a/src/plugin-sdk/test-helpers/node-builtin-mocks.test.ts b/src/plugin-sdk/test-helpers/node-builtin-mocks.test.ts new file mode 100644 index 00000000000..38d90950f6c --- /dev/null +++ b/src/plugin-sdk/test-helpers/node-builtin-mocks.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; +import { mockNodeBuiltinModule } from "./node-builtin-mocks.js"; + +describe("mockNodeBuiltinModule", () => { + it("merges partial overrides into the original module", async () => { + const actual = { readFileSync: () => "actual", watch: () => "watch" }; + const readFileSync = () => "mock"; + + const mocked = await mockNodeBuiltinModule(async () => actual, { + readFileSync, + }); + + expect(mocked.readFileSync).toBe(readFileSync); + expect(mocked.watch).toBe(actual.watch); + expect("default" in mocked).toBe(false); + }); + + it("mirrors overrides into the default export when requested", async () => { + const homedir = () => "/tmp/home"; + + const mocked = await mockNodeBuiltinModule<{ + tmpdir: () => string; + homedir?: () => string; + default?: Record; + }>(async () => ({ tmpdir: () => "/tmp" }), { homedir }, { mirrorToDefault: true }); + + expect(mocked.default).toMatchObject({ + homedir, + tmpdir: expect.any(Function), + }); + }); + + it("preserves existing default exports while overriding members", async () => { + const actual = { + readFileSync: () => "actual", + default: { + readFileSync: () => "actual", + statSync: () => "stat", + }, + }; + const readFileSync = () => "mock"; + + const mocked = await mockNodeBuiltinModule( + async () => actual, + { readFileSync }, + { mirrorToDefault: true }, + ); + + expect(mocked.default).toMatchObject({ + readFileSync, + statSync: expect.any(Function), + }); + }); +}); diff --git a/src/plugin-sdk/test-helpers/provider-replay-policy.ts b/src/plugin-sdk/test-helpers/provider-replay-policy.ts new file mode 100644 index 00000000000..c8010c5a502 --- /dev/null +++ b/src/plugin-sdk/test-helpers/provider-replay-policy.ts @@ -0,0 +1,35 @@ +import { expect } from "vitest"; +import { registerSingleProviderPlugin } from "../plugin-test-runtime.js"; + +export async function expectPassthroughReplayPolicy(params: { + modelId: string; + plugin: unknown; + providerId: string; + sanitizeThoughtSignatures?: boolean; +}) { + const provider = await registerSingleProviderPlugin(params.plugin as never); + const policy = provider.buildReplayPolicy?.({ + provider: params.providerId, + modelApi: "openai-completions", + modelId: params.modelId, + } as never); + + expect(policy).toMatchObject({ + applyAssistantFirstOrderingFix: false, + validateGeminiTurns: false, + validateAnthropicTurns: false, + }); + + if (params.sanitizeThoughtSignatures) { + expect(policy).toMatchObject({ + sanitizeThoughtSignatures: { + allowBase64Only: true, + includeCamelCase: true, + }, + }); + } else { + expect(policy).not.toHaveProperty("sanitizeThoughtSignatures"); + } + + return provider; +} diff --git a/src/plugin-sdk/test-helpers/stt-live-audio.ts b/src/plugin-sdk/test-helpers/stt-live-audio.ts new file mode 100644 index 00000000000..c2e75e41593 --- /dev/null +++ b/src/plugin-sdk/test-helpers/stt-live-audio.ts @@ -0,0 +1,139 @@ +import { expect } from "vitest"; +import type { + RealtimeTranscriptionProviderConfig, + RealtimeTranscriptionProviderPlugin, +} from "../realtime-transcription.js"; + +const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io"; +const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE"; +const DEFAULT_ELEVENLABS_TTS_MODEL_ID = "eleven_multilingual_v2"; + +export function normalizeTranscriptForMatch(value: string): string { + return value.toLowerCase().replace(/[^a-z0-9]+/g, ""); +} + +type ExpectedTranscriptMatch = RegExp | string; + +const DEFAULT_OPENCLAW_TRANSCRIPT_MATCH = /open(?:claw|flaw|clar)/; + +export async function waitForLiveExpectation(expectation: () => void, timeoutMs = 30_000) { + const started = Date.now(); + let lastError: unknown; + while (Date.now() - started < timeoutMs) { + try { + expectation(); + return; + } catch (error) { + lastError = error; + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + throw lastError; +} + +export async function synthesizeElevenLabsLiveSpeech(params: { + text: string; + apiKey: string; + outputFormat: "mp3_44100_128" | "ulaw_8000"; + timeoutMs?: number; +}): Promise { + const baseUrl = process.env.ELEVENLABS_BASE_URL?.trim() || DEFAULT_ELEVENLABS_BASE_URL; + const voiceId = process.env.ELEVENLABS_LIVE_VOICE_ID?.trim() || DEFAULT_ELEVENLABS_VOICE_ID; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), params.timeoutMs ?? 30_000); + try { + const url = new URL(`${baseUrl.replace(/\/+$/, "")}/v1/text-to-speech/${voiceId}`); + url.searchParams.set("output_format", params.outputFormat); + const response = await fetch(url, { + method: "POST", + headers: { + "xi-api-key": params.apiKey, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: params.text, + model_id: DEFAULT_ELEVENLABS_TTS_MODEL_ID, + voice_settings: { + stability: 0.5, + similarity_boost: 0.75, + style: 0, + use_speaker_boost: true, + speed: 1, + }, + }), + signal: controller.signal, + }); + if (!response.ok) { + throw new Error(`ElevenLabs live TTS failed (${response.status})`); + } + return Buffer.from(await response.arrayBuffer()); + } finally { + clearTimeout(timeout); + } +} + +export async function streamAudioForLiveTest(params: { + audio: Buffer; + sendAudio: (chunk: Buffer) => void; + chunkSize?: number; + delayMs?: number; +}) { + const chunkSize = params.chunkSize ?? 160; + const delayMs = params.delayMs ?? 5; + for (let offset = 0; offset < params.audio.byteLength; offset += chunkSize) { + params.sendAudio(params.audio.subarray(offset, offset + chunkSize)); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } +} + +export async function runRealtimeSttLiveTest(params: { + provider: RealtimeTranscriptionProviderPlugin; + providerConfig: RealtimeTranscriptionProviderConfig; + audio: Buffer; + expectedNormalizedText?: ExpectedTranscriptMatch; + timeoutMs?: number; + closeBeforeWait?: boolean; + chunkSize?: number; + delayMs?: number; +}): Promise<{ transcripts: string[]; partials: string[]; errors: Error[] }> { + const transcripts: string[] = []; + const partials: string[] = []; + const errors: Error[] = []; + const expected = params.expectedNormalizedText ?? DEFAULT_OPENCLAW_TRANSCRIPT_MATCH; + const session = params.provider.createSession({ + providerConfig: params.providerConfig, + onPartial: (partial) => partials.push(partial), + onTranscript: (transcript) => transcripts.push(transcript), + onError: (error) => errors.push(error), + }); + + try { + await session.connect(); + await streamAudioForLiveTest({ + audio: params.audio, + sendAudio: (chunk) => session.sendAudio(chunk), + chunkSize: params.chunkSize, + delayMs: params.delayMs, + }); + if (params.closeBeforeWait) { + session.close(); + } + + await waitForLiveExpectation(() => { + if (errors[0]) { + throw errors[0]; + } + const normalized = normalizeTranscriptForMatch(transcripts.join(" ")); + if (typeof expected === "string") { + expect(normalized).toContain(expected); + } else { + expect(normalized).toMatch(expected); + } + }, params.timeoutMs ?? 60_000); + } finally { + session.close(); + } + + expect(partials.length + transcripts.length).toBeGreaterThan(0); + return { transcripts, partials, errors }; +} diff --git a/src/plugin-sdk/test-helpers/temp-home.ts b/src/plugin-sdk/test-helpers/temp-home.ts new file mode 100644 index 00000000000..2877b3dd1ab --- /dev/null +++ b/src/plugin-sdk/test-helpers/temp-home.ts @@ -0,0 +1,160 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { cleanupSessionStateForTest } from "../../test-utils/session-state-cleanup.js"; + +type EnvValue = string | undefined | ((home: string) => string | undefined); + +type EnvSnapshot = { + home: string | undefined; + userProfile: string | undefined; + homeDrive: string | undefined; + homePath: string | undefined; + openclawHome: string | undefined; + stateDir: string | undefined; +}; + +type SharedHomeRootState = { + rootPromise: Promise; + nextCaseId: number; +}; + +const SHARED_HOME_ROOTS = new Map(); + +function snapshotEnv(): EnvSnapshot { + return { + home: process.env.HOME, + userProfile: process.env.USERPROFILE, + homeDrive: process.env.HOMEDRIVE, + homePath: process.env.HOMEPATH, + openclawHome: process.env.OPENCLAW_HOME, + stateDir: process.env.OPENCLAW_STATE_DIR, + }; +} + +function restoreEnv(snapshot: EnvSnapshot) { + const restoreKey = (key: string, value: string | undefined) => { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + }; + restoreKey("HOME", snapshot.home); + restoreKey("USERPROFILE", snapshot.userProfile); + restoreKey("HOMEDRIVE", snapshot.homeDrive); + restoreKey("HOMEPATH", snapshot.homePath); + restoreKey("OPENCLAW_HOME", snapshot.openclawHome); + restoreKey("OPENCLAW_STATE_DIR", snapshot.stateDir); +} + +function snapshotExtraEnv(keys: string[]): Record { + const snapshot: Record = {}; + for (const key of keys) { + snapshot[key] = process.env[key]; + } + return snapshot; +} + +function restoreExtraEnv(snapshot: Record) { + for (const [key, value] of Object.entries(snapshot)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } +} + +function setTempHome(base: string) { + process.env.HOME = base; + process.env.USERPROFILE = base; + // Ensure tests using HOME isolation aren't affected by leaked OPENCLAW_HOME. + delete process.env.OPENCLAW_HOME; + process.env.OPENCLAW_STATE_DIR = path.join(base, ".openclaw"); + + if (process.platform !== "win32") { + return; + } + const match = base.match(/^([A-Za-z]:)(.*)$/); + if (!match) { + return; + } + process.env.HOMEDRIVE = match[1]; + process.env.HOMEPATH = match[2] || "\\"; +} + +async function allocateTempHomeBase(prefix: string): Promise { + let state = SHARED_HOME_ROOTS.get(prefix); + if (!state) { + state = { + rootPromise: fs.mkdtemp(path.join(os.tmpdir(), prefix)), + nextCaseId: 0, + }; + SHARED_HOME_ROOTS.set(prefix, state); + } + const root = await state.rootPromise; + const base = path.join(root, `case-${state.nextCaseId++}`); + await fs.mkdir(base, { recursive: true }); + return base; +} + +export async function withTempHome( + fn: (home: string) => Promise, + opts: { + env?: Record; + prefix?: string; + skipSessionCleanup?: boolean; + } = {}, +): Promise { + const prefix = opts.prefix ?? "openclaw-test-home-"; + const base = await allocateTempHomeBase(prefix); + const snapshot = snapshotEnv(); + const envKeys = Object.keys(opts.env ?? {}); + for (const key of envKeys) { + if (key === "HOME" || key === "USERPROFILE" || key === "HOMEDRIVE" || key === "HOMEPATH") { + throw new Error(`withTempHome: use built-in home env (got ${key})`); + } + } + const envSnapshot = snapshotExtraEnv(envKeys); + + setTempHome(base); + await fs.mkdir(path.join(base, ".openclaw", "agents", "main", "sessions"), { recursive: true }); + if (opts.env) { + for (const [key, raw] of Object.entries(opts.env)) { + const value = typeof raw === "function" ? raw(base) : raw; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } + + try { + return await fn(base); + } finally { + if (!opts.skipSessionCleanup) { + await cleanupSessionStateForTest().catch(() => undefined); + } + restoreExtraEnv(envSnapshot); + restoreEnv(snapshot); + try { + if (process.platform === "win32") { + await fs.rm(base, { + recursive: true, + force: true, + maxRetries: 10, + retryDelay: 50, + }); + } else { + await fs.rm(base, { + recursive: true, + force: true, + }); + } + } catch { + // ignore cleanup failures in tests + } + } +} diff --git a/src/plugin-sdk/test-node-mocks.ts b/src/plugin-sdk/test-node-mocks.ts new file mode 100644 index 00000000000..5e88d8867c9 --- /dev/null +++ b/src/plugin-sdk/test-node-mocks.ts @@ -0,0 +1,7 @@ +// Focused public test helpers for Node builtin module mocks. + +export { + mockNodeBuiltinModule, + mockNodeChildProcessExecFile, + mockNodeChildProcessSpawnSync, +} from "./test-helpers/node-builtin-mocks.js"; diff --git a/src/plugins/bundled-sources.test.ts b/src/plugins/bundled-sources.test.ts index c415f10b66d..f34748b4800 100644 --- a/src/plugins/bundled-sources.test.ts +++ b/src/plugins/bundled-sources.test.ts @@ -1,5 +1,5 @@ +import { bundledPluginRootAt } from "openclaw/plugin-sdk/test-fixtures"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { bundledPluginRootAt } from "../../test/helpers/bundled-plugin-paths.js"; import { findBundledPluginSource, findBundledPluginSourceInMap, diff --git a/src/plugins/contracts/boundary-invariants.test.ts b/src/plugins/contracts/boundary-invariants.test.ts index 98ee6173e3c..40c60ceaf7b 100644 --- a/src/plugins/contracts/boundary-invariants.test.ts +++ b/src/plugins/contracts/boundary-invariants.test.ts @@ -255,7 +255,13 @@ describe("plugin contract boundary invariants", () => { if (file === "src/plugins/contracts/boundary-invariants.test.ts") { return false; } - return readRepoSource(file).includes("test/helpers/bundled-plugin-paths"); + const source = readRepoSource(file); + return ( + source.includes("openclaw/plugin-sdk/test-fixtures") && + /\b(?:BUNDLED_PLUGIN_|bundled(?:Dist)?Plugin(?:Root|File|DirPrefix)|installedPluginRoot|repoInstallSpec)\b/u.test( + source, + ) + ); }); expect(offenders).toEqual([]); }); diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 416e2aae74e..e1aecbd6864 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -63,6 +63,7 @@ const PUBLIC_SDK_TEST_HELPER_SUBPATHS = [ "provider-test-contracts", "test-env", "test-fixtures", + "test-node-mocks", ] as const; const PUBLIC_SDK_TEST_HELPER_SUBPATHS_WITH_TOP_LEVEL_MOCKS = ["provider-http-test-mocks"] as const; @@ -744,8 +745,20 @@ describe("plugin-sdk subpath exports", () => { "createPluginSetupWizardStatus", "runProviderCatalog", ]); + expectSourceMentions("channel-test-helpers", [ + "assertBundledChannelEntries", + "formatEnvelopeTimestamp", + "expectPairingReplyText", + ]); + expectSourceMentions("provider-test-contracts", [ + "expectPassthroughReplayPolicy", + "runRealtimeSttLiveTest", + ]); expectSourceMentions("test-env", [ "withEnv", + "withServer", + "withTempHome", + "createMockIncomingRequest", "withFetchPreconnect", "createRequestCaptureJsonFetch", "installPinnedHostnameTestHooks", @@ -753,11 +766,18 @@ describe("plugin-sdk subpath exports", () => { ]); expectSourceMentions("test-fixtures", [ "createCliRuntimeCapture", + "importFreshModule", + "bundledPluginRoot", "createSandboxTestContext", "makeAgentAssistantMessage", "peekSystemEvents", "typedCases", ]); + expectSourceMentions("test-node-mocks", [ + "mockNodeBuiltinModule", + "mockNodeChildProcessExecFile", + "mockNodeChildProcessSpawnSync", + ]); expectSourceMentions("channel-target-testing", [ "installCommonResolveTargetErrorCases", "ResolveTargetFn", diff --git a/src/plugins/discovery.test.ts b/src/plugins/discovery.test.ts index 2d919fd8e50..55650e3b797 100644 --- a/src/plugins/discovery.test.ts +++ b/src/plugins/discovery.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { clearPluginDiscoveryCache, discoverOpenClawPlugins } from "./discovery.js"; import { cleanupTrackedTempDirs, diff --git a/src/plugins/jiti-loader-cache.test.ts b/src/plugins/jiti-loader-cache.test.ts index 411958a470c..d327d5fd56e 100644 --- a/src/plugins/jiti-loader-cache.test.ts +++ b/src/plugins/jiti-loader-cache.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; afterEach(() => { vi.restoreAllMocks(); diff --git a/src/plugins/loader.jiti-filename.test.ts b/src/plugins/loader.jiti-filename.test.ts index 9974bc0dce5..b41d92cec31 100644 --- a/src/plugins/loader.jiti-filename.test.ts +++ b/src/plugins/loader.jiti-filename.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; const tempDirs: string[] = []; diff --git a/src/plugins/public-surface-loader.test.ts b/src/plugins/public-surface-loader.test.ts index a0d91d32dcd..d888e9b0352 100644 --- a/src/plugins/public-surface-loader.test.ts +++ b/src/plugins/public-surface-loader.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.ts"; const tempDirs: string[] = []; const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; diff --git a/src/plugins/runtime-live-state-guardrails.test.ts b/src/plugins/runtime-live-state-guardrails.test.ts index 1fab5321316..c1aac6f017b 100644 --- a/src/plugins/runtime-live-state-guardrails.test.ts +++ b/src/plugins/runtime-live-state-guardrails.test.ts @@ -1,8 +1,8 @@ import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { bundledPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); diff --git a/src/plugins/runtime-plugin-boundary.whatsapp.test.ts b/src/plugins/runtime-plugin-boundary.whatsapp.test.ts index c5032132ef3..2a392ded435 100644 --- a/src/plugins/runtime-plugin-boundary.whatsapp.test.ts +++ b/src/plugins/runtime-plugin-boundary.whatsapp.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it } from "vitest"; import { stageBundledPluginRuntime } from "../../scripts/stage-bundled-plugin-runtime.mjs"; -import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import type { PluginJitiLoaderCache } from "./jiti-loader-cache.js"; import { loadPluginBoundaryModuleWithJiti } from "./runtime/runtime-plugin-boundary.js"; import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js"; diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 2b233c96f17..f3f77fe0dd3 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -1,12 +1,12 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { afterAll, describe, expect, it, vi } from "vitest"; import { bundledDistPluginFile, bundledPluginFile, bundledPluginRoot, -} from "../../test/helpers/bundled-plugin-paths.js"; +} from "openclaw/plugin-sdk/test-fixtures"; +import { afterAll, describe, expect, it, vi } from "vitest"; import { withEnv } from "../test-utils/env.js"; import { buildPluginLoaderAliasMap, diff --git a/src/plugins/stage-bundled-plugin-runtime.test.ts b/src/plugins/stage-bundled-plugin-runtime.test.ts index a1920506680..0c482852fdb 100644 --- a/src/plugins/stage-bundled-plugin-runtime.test.ts +++ b/src/plugins/stage-bundled-plugin-runtime.test.ts @@ -1,9 +1,9 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { bundledDistPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; import { stageBundledPluginRuntime } from "../../scripts/stage-bundled-plugin-runtime.mjs"; -import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; import { discoverOpenClawPlugins } from "./discovery.js"; import { loadPluginManifestRegistry } from "./manifest-registry.js"; import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js"; diff --git a/src/plugins/update.test.ts b/src/plugins/update.test.ts index 7079d480e75..0e01ae442bc 100644 --- a/src/plugins/update.test.ts +++ b/src/plugins/update.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { bundledPluginRootAt } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { bundledPluginRootAt } from "../../test/helpers/bundled-plugin-paths.js"; import type { OpenClawConfig } from "../config/config.js"; import type { PluginNpmIntegrityDriftParams } from "./install.js"; diff --git a/src/process/command-queue.test.ts b/src/process/command-queue.test.ts index fec2d252c88..3466d3b8aa1 100644 --- a/src/process/command-queue.test.ts +++ b/src/process/command-queue.test.ts @@ -1,5 +1,5 @@ +import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { importFreshModule } from "../../test/helpers/import-fresh.js"; import { CommandLane } from "./lanes.js"; const diagnosticMocks = vi.hoisted(() => ({ diff --git a/src/process/exec.windows.test.ts b/src/process/exec.windows.test.ts index 0935dc07559..66065bec8be 100644 --- a/src/process/exec.windows.test.ts +++ b/src/process/exec.windows.test.ts @@ -19,7 +19,7 @@ const { spawnMock, spawnSyncMock, execFileMock, execFilePromisifyMock } = vi.hoi }); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/process/kill-tree.test.ts b/src/process/kill-tree.test.ts index ce0cf0d8425..a482c2490cc 100644 --- a/src/process/kill-tree.test.ts +++ b/src/process/kill-tree.test.ts @@ -5,7 +5,7 @@ const { spawnMock } = vi.hoisted(() => ({ })); vi.mock("node:child_process", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:child_process"), { diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index febfb024574..c8b4be14ea3 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -2,8 +2,8 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { bundledPluginFile } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it } from "vitest"; -import { bundledPluginFile } from "../../test/helpers/bundled-plugin-paths.js"; const { detectChangedScope, detectInstallSmokeScope, detectNodeFastScope, listChangedPaths } = (await import("../../scripts/ci-changed-scope.mjs")) as unknown as { diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index 33f64fbff2f..239afc045c5 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -790,7 +790,7 @@ describe("test-projects args", () => { }); it("widens top-level test helpers to sibling repo tests under contracts", () => { - expect(buildVitestRunPlans(["test/helpers/temp-home.ts"])).toEqual([ + expect(buildVitestRunPlans(["test/helpers/temp-dir.ts"])).toEqual([ { config: "test/vitest/vitest.tooling.config.ts", forwardedArgs: [], diff --git a/src/security/windows-acl.test.ts b/src/security/windows-acl.test.ts index 3da006d578a..b1786149f1f 100644 --- a/src/security/windows-acl.test.ts +++ b/src/security/windows-acl.test.ts @@ -13,7 +13,7 @@ const userInfoMock = vi.hoisted(() => ); vi.mock("node:os", async () => { - const { mockNodeBuiltinModule } = await import("../../test/helpers/node-builtin-mocks.js"); + const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks"); return mockNodeBuiltinModule( () => vi.importActual("node:os"), { userInfo: userInfoMock as unknown as typeof import("node:os").userInfo }, diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index bda3ec850d9..d8335aa20d2 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -701,9 +701,9 @@ describe("scripts/test-projects changed-target routing", () => { }); it("uses import-graph targets in default changed mode", () => { - expect( - resolveChangedTestTargetPlan(["test/helpers/provider-replay-policy.ts"]).targets, - ).toContain("extensions/openrouter/index.test.ts"); + expect(resolveChangedTestTargetPlan(["test/helpers/normalize-text.ts"]).targets).toContain( + "src/auto-reply/status.test.ts", + ); }); it.each([