refactor: relocate channel contract helpers

This commit is contained in:
Peter Steinberger
2026-04-28 02:13:58 +01:00
parent a66605bf23
commit d35ada2f54
67 changed files with 133 additions and 133 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
- Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd.
- Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.
- Plugin SDK/testing: 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: promote bundled plugin/provider/channel contract helpers to focused SDK test subpaths and retire the repo-only `test/helpers/plugins` TypeScript bridge. Thanks @vincentkoc.
- Plugin SDK/testing: expose generic channel action, setup, status, and directory contract helpers through `plugin-sdk/channel-test-helpers` so bundled extension tests no longer import repo-only channel helper bridges. Thanks @vincentkoc.
- Plugin SDK/testing: add `plugin-sdk/channel-target-testing` for shared channel target-resolution cases, document channel reaction helpers on `plugin-sdk/channel-feedback`, and keep the old `plugin-sdk/test-utils` alias as compatibility-only. Thanks @vincentkoc.

View File

@@ -19,8 +19,6 @@ plugins.
## Test utilities
**Compatibility import:** `openclaw/plugin-sdk/testing`
**Plugin API mock import:** `openclaw/plugin-sdk/plugin-test-api`
**Channel contract import:** `openclaw/plugin-sdk/channel-contract-testing`
@@ -40,8 +38,7 @@ plugins.
**Generic fixture import:** `openclaw/plugin-sdk/test-fixtures`
Prefer the focused subpaths below for new plugin tests. The broad
`openclaw/plugin-sdk/testing` barrel remains for compatibility with older tests
and helpers that have not moved to a narrower documented surface yet.
`openclaw/plugin-sdk/testing` barrel is legacy compatibility only.
```typescript
import {
@@ -121,17 +118,15 @@ broad `plugin-sdk/testing` compatibility barrel, repo `src/**` files, or repo
### Types
The testing subpath also re-exports types useful in test files:
Focused testing subpaths also re-export types useful in test files:
```typescript
import type {
ChannelAccountSnapshot,
ChannelGatewayContext,
OpenClawConfig,
PluginRuntime,
RuntimeEnv,
MockFn,
} from "openclaw/plugin-sdk/testing";
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import type { MockFn, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
```
## Testing target resolution

View File

@@ -18,7 +18,7 @@ title: "Tests"
- Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` summary. Vitest's own duration line stays the per-shard detail.
- Full, extension, and include-pattern shard runs update local timing data in `.artifacts/vitest-shard-timings.json`; later whole-config runs use those timings to balance slow and fast shards. Include-pattern CI shards append the shard name to the timing key, which keeps filtered shard timings visible without replacing whole-config timing data. Set `OPENCLAW_TEST_PROJECTS_TIMINGS=0` to ignore the local timing artifact.
- Selected `plugin-sdk` and `commands` test files now route through dedicated light lanes that keep only `test/setup.ts`, leaving runtime-heavy cases on their existing lanes.
- Source files with sibling tests map to that sibling before falling back to wider directory globs. Helper edits under `test/helpers/channels`, `src/plugin-sdk/test-helpers`, and `src/plugins/contracts` use a local import graph to run importing tests instead of broad-running every shard when the dependency path is precise.
- Source files with sibling tests map to that sibling before falling back to wider directory globs. Helper edits under `src/channels/plugins/contracts/test-helpers`, `src/plugin-sdk/test-helpers`, and `src/plugins/contracts` use a local import graph to run importing tests instead of broad-running every shard when the dependency path is precise.
- `auto-reply` now also splits into three dedicated configs (`core`, `top-level`, `reply`) so the reply harness does not dominate the lighter top-level status/token/helper tests.
- Base Vitest config now defaults to `pool: "threads"` and `isolate: false`, with the shared non-isolated runner enabled across the repo configs.
- `pnpm test:channels` runs `vitest.channels.config.ts`.

View File

@@ -33,6 +33,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
pattern: /["'](?:\.\.\/)+(?:test\/helpers\/channels\/)[^"']+["']/,
hint: "Use openclaw/plugin-sdk/channel-test-helpers or another focused SDK test subpath instead of repo-only channel helper bridges.",
},
{
pattern: /["'](?:\.\.\/)+(?: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.",
},
{
pattern: /["'](?:\.\.\/)+(?:src\/test-utils\/)[^"']+["']/,
hint: "Use a documented openclaw/plugin-sdk test subpath for public surfaces.",
@@ -178,10 +182,21 @@ function collectRelativeCoreImportOffenders(
function main() {
const extensionsDir = path.join(process.cwd(), "extensions");
const pluginHelpersDir = path.join(process.cwd(), "test/helpers/plugins");
const retiredChannelHelpersDir = path.join(process.cwd(), "test/helpers/channels");
const files = collectExtensionTestFiles(extensionsDir);
const pluginHelperFiles = collectPluginHelperFiles(pluginHelpersDir);
const retiredChannelHelperFiles = fs.existsSync(retiredChannelHelpersDir)
? collectFilesSync(retiredChannelHelpersDir, { includeFile: isCodeFile })
: [];
const offenders: Offender[] = [];
for (const file of retiredChannelHelperFiles) {
offenders.push({
file,
hint: "Keep core channel contract helpers under src/channels/plugins/contracts/test-helpers and public plugin helpers under focused openclaw/plugin-sdk test subpaths.",
});
}
for (const file of RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES) {
const filePath = path.join(process.cwd(), file);
if (!fs.existsSync(filePath)) {

View File

@@ -2,7 +2,7 @@ import {
describeBundledMetadataOnlyChannelCatalogContract,
describeChannelCatalogEntryContract,
describeOfficialFallbackChannelCatalogContract,
} from "../../../../test/helpers/channels/channel-catalog-contract.js";
} from "./test-helpers/channel-catalog-contract.js";
describeChannelCatalogEntryContract({
channelId: "msteams",

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installDirectoryContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installDirectoryContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installDirectoryContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { installChannelRuntimeGroupPolicyFallbackSuite } from "../../../../test/helpers/channels/group-policy-contract-suites.js";
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../config/runtime-group-policy.js";
import { installChannelRuntimeGroupPolicyFallbackSuite } from "./test-helpers/group-policy-contract-suites.js";
import {
resolveZaloRuntimeGroupPolicy,
resolveWhatsAppRuntimeGroupPolicy,
} from "../../../../test/helpers/channels/group-policy-contract.js";
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../config/runtime-group-policy.js";
} from "./test-helpers/group-policy-contract.js";
describe("channel runtime group policy fallback contract", () => {
describe("slack", () => {

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installPluginContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installPluginContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installPluginContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { describeChannelConfigWritePolicyContract } from "../../../../test/helpers/channels/config-write-contract-suites.js";
import { describeChannelConfigWritePolicyContract } from "./test-helpers/config-write-contract-suites.js";
describeChannelConfigWritePolicyContract();

View File

@@ -1,3 +1,3 @@
import { describeChannelConfigWriteTargetContract } from "../../../../test/helpers/channels/config-write-contract-suites.js";
import { describeChannelConfigWriteTargetContract } from "./test-helpers/config-write-contract-suites.js";
describeChannelConfigWriteTargetContract();

View File

@@ -1,3 +1,3 @@
import { describeChannelPluginCatalogEntriesContract } from "../../../../test/helpers/channels/channel-plugin-catalog-contract-suites.js";
import { describeChannelPluginCatalogEntriesContract } from "./test-helpers/channel-plugin-catalog-contract-suites.js";
describeChannelPluginCatalogEntriesContract();

View File

@@ -1,3 +1,3 @@
import { describeChannelPluginCatalogPathResolutionContract } from "../../../../test/helpers/channels/channel-plugin-catalog-contract-suites.js";
import { describeChannelPluginCatalogPathResolutionContract } from "./test-helpers/channel-plugin-catalog-contract-suites.js";
describeChannelPluginCatalogPathResolutionContract();

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { sessionBindingContractChannelIds } from "../../../../test/helpers/channels/manifest.js";
import { sessionBindingContractChannelIds } from "./test-helpers/manifest.js";
const discordSessionBindingAdapterChannels = ["discord"] as const;

View File

@@ -1,5 +1,5 @@
import { getSessionBindingContractRegistry } from "../../../../test/helpers/channels/registry-session-binding.js";
import { describeSessionBindingRegistryBackedContract } from "../../../../test/helpers/channels/session-binding-registry-backed-contract.js";
import { getSessionBindingContractRegistry } from "./test-helpers/registry-session-binding.js";
import { describeSessionBindingRegistryBackedContract } from "./test-helpers/session-binding-registry-backed-contract.js";
for (const entry of getSessionBindingContractRegistry()) {
describeSessionBindingRegistryBackedContract(entry.id);

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installSurfaceContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installSurfaceContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installSurfaceContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -1,14 +1,13 @@
# Test Helper Boundary
# Channel Contract Helper Boundary
This directory holds shared channel test helpers used by core and bundled plugin
tests.
This directory holds core-owned channel contract test helpers.
This file adds channel-specific rules on top of `test/helpers/AGENTS.md`.
This file adds channel-specific rules on top of `src/channels/AGENTS.md`.
## Bundled Plugin Imports
- Core test helpers in this directory must not hardcode repo-relative imports
into `extensions/**`.
- Core contract helpers in this directory must not hardcode repo-relative
imports into `extensions/**`.
- When a helper needs a bundled plugin public/test surface, go through
`src/test-utils/bundled-plugin-public-surface.ts`.
- Prefer `loadBundledPluginTestApiSync(...)` for eager access to exported test
@@ -28,7 +27,7 @@ This file adds channel-specific rules on top of `test/helpers/AGENTS.md`.
## Intent
- Keep shared test helpers aligned with the same public/plugin boundary that
- Keep core contract helpers aligned with the same public/plugin boundary that
production code uses.
- Avoid drift where core test helpers start reaching into bundled plugin private
files by path because it is convenient in one test.
- Avoid drift where core contract helpers start reaching into bundled plugin
private files by path because it is convenient in one test.

View File

@@ -1,14 +1,14 @@
import { listBundledChannelPluginIds as listCatalogBundledChannelPluginIds } from "../../../src/channels/plugins/bundled-ids.js";
import type { ChannelId } from "../../../src/channels/plugins/channel-id.types.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import {
listChannelCatalogEntries,
type PluginChannelCatalogEntry,
} from "../../../src/plugins/channel-catalog-registry.js";
} from "../../../../plugins/channel-catalog-registry.js";
import {
loadBundledPluginPublicSurface,
loadBundledPluginPublicSurfaceSync,
} from "../../../src/test-utils/bundled-plugin-public-surface.js";
} from "../../../../test-utils/bundled-plugin-public-surface.js";
import { listBundledChannelPluginIds as listCatalogBundledChannelPluginIds } from "../../bundled-ids.js";
import type { ChannelId } from "../../channel-id.types.js";
import type { ChannelPlugin } from "../../types.js";
type ChannelPluginApiModule = Record<string, unknown>;

View File

@@ -2,10 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
getChannelPluginCatalogEntry,
listChannelPluginCatalogEntries,
} from "../../../src/channels/plugins/catalog.js";
import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries } from "../../catalog.js";
type CatalogEntryMeta = {
id: string;

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { listChannelPluginCatalogEntries } from "../../../src/channels/plugins/catalog.js";
import { listChannelPluginCatalogEntries } from "../../catalog.js";
function createCatalogEntry(params: {
packageName: string;

View File

@@ -1,12 +1,12 @@
import { describe, expect, it } from "vitest";
import { INTERNAL_MESSAGE_CHANNEL } from "../../../../utils/message-channel.js";
import {
authorizeConfigWrite,
canBypassConfigWritePolicy,
formatConfigWriteDeniedMessage,
resolveExplicitConfigWriteTarget,
resolveConfigWriteTargetFromPath,
} from "../../../src/channels/plugins/config-writes.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../../../src/utils/message-channel.js";
} from "../../config-writes.js";
const demoOriginChannelId = "demo-origin";
const demoTargetChannelId = "demo-target";

View File

@@ -1,5 +1,5 @@
import { expect, it } from "vitest";
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../src/config/runtime-group-policy.js";
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../../config/runtime-group-policy.js";
type ResolvedGroupPolicy = ReturnType<typeof resolveOpenProviderRuntimeGroupPolicy>;

View File

@@ -1,4 +1,4 @@
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../src/config/runtime-group-policy.js";
import { resolveOpenProviderRuntimeGroupPolicy } from "../../../../config/runtime-group-policy.js";
const resolveWhatsAppRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy;
const resolveZaloRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy;

View File

@@ -1,6 +1,6 @@
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-contract";
import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-plugin-common";
import { loadBundledPluginTestApiSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
import { loadBundledPluginTestApiSync } from "../../../../test-utils/bundled-plugin-public-surface.js";
type CreateIMessageTestPlugin = (params?: { outbound?: ChannelOutboundAdapter }) => ChannelPlugin;

View File

@@ -1,7 +1,7 @@
import { expectChannelPluginContract } from "openclaw/plugin-sdk/channel-test-helpers";
import { describe, it } from "vitest";
import { getBundledChannelPluginAsync } from "./bundled-channel-plugin-loader.js";
import { channelPluginSurfaceKeys } from "./manifest.js";
import { expectChannelPluginContract } from "./registry-contract-suites.js";
import { getPluginContractRegistryShardRefs } from "./registry-plugin.js";
import {
getDirectoryContractRegistryShardRefs,

View File

@@ -1,6 +1,6 @@
import type { ChannelId } from "../../../src/channels/plugins/channel-id.types.js";
import { normalizeChannelMeta } from "../../../src/channels/plugins/meta-normalization.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { ChannelId } from "../../channel-id.types.js";
import { normalizeChannelMeta } from "../../meta-normalization.js";
import type { ChannelPlugin } from "../../types.js";
import {
getBundledChannelCatalogEntry,
getBundledChannelPlugin,

View File

@@ -2,22 +2,22 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { expect } from "vitest";
import { createChannelConversationBindingManager } from "../../../src/channels/plugins/conversation-bindings.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { OpenClawConfig } from "../../../../config/config.js";
import {
getSessionBindingService,
type SessionBindingCapabilities,
type SessionBindingRecord,
} from "../../../src/infra/outbound/session-binding-service.js";
import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
import { createTestRegistry } from "../../../src/test-utils/channel-plugins.js";
} from "../../../../infra/outbound/session-binding-service.js";
import { setActivePluginRegistry } from "../../../../plugins/runtime.js";
import { createTestRegistry } from "../../../../test-utils/channel-plugins.js";
import { createChannelConversationBindingManager } from "../../conversation-bindings.js";
import type { ChannelPlugin } from "../../types.js";
import {
sessionBindingContractChannelIds,
type SessionBindingContractChannelId,
} from "./manifest.js";
import { importBundledChannelContractArtifact } from "./runtime-artifacts.js";
import "../../../src/channels/plugins/registry.js";
import "../../registry.js";
type SessionBindingContractEntry = {
id: string;

View File

@@ -1,13 +1,13 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { resolveBundledChannelWorkspacePath } from "../../../src/plugins/bundled-channel-runtime.js";
import { resolveBundledChannelWorkspacePath } from "../../../../plugins/bundled-channel-runtime.js";
import {
resolvePluginRuntimeModulePath,
resolvePluginRuntimeRecord,
} from "../../../src/plugins/runtime/runtime-plugin-boundary.js";
} from "../../../../plugins/runtime/runtime-plugin-boundary.js";
const REPO_ROOT = fileURLToPath(new URL("../../../", import.meta.url));
const REPO_ROOT = fileURLToPath(new URL("../../../../../", import.meta.url));
function resolveBundledChannelWorkspaceArtifactPath(
pluginId: string,

View File

@@ -1,14 +1,11 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
clearRuntimeConfigSnapshot,
setRuntimeConfigSnapshot,
} from "../../../src/config/config.js";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../../../config/config.js";
import {
__testing as sessionBindingTesting,
type SessionBindingCapabilities,
type SessionBindingRecord,
} from "../../../src/infra/outbound/session-binding-service.js";
import { resetPluginRuntimeStateForTest } from "../../../src/plugins/runtime.js";
} from "../../../../infra/outbound/session-binding-service.js";
import { resetPluginRuntimeStateForTest } from "../../../../plugins/runtime.js";
import { getSessionBindingContractRegistry } from "./registry-session-binding.js";
function resolveSessionBindingContractRuntimeConfig(id: string) {

View File

@@ -1,6 +1,6 @@
import type { ChannelId } from "../../../src/channels/plugins/channel-id.types.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { OpenClawConfig } from "../../../../config/config.js";
import type { ChannelId } from "../../channel-id.types.js";
import type { ChannelPlugin } from "../../types.js";
import {
getBundledChannelPlugin,
listBundledChannelPluginIds,

View File

@@ -1,5 +1,5 @@
import { expect, it } from "vitest";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { ChannelPlugin } from "../../types.js";
export function installChannelSurfaceContractSuite(params: {
plugin: Pick<

View File

@@ -1,13 +1,13 @@
import { expect, it } from "vitest";
import type { OpenClawConfig } from "../../../../config/config.js";
import type { RuntimeEnv } from "../../../../runtime.js";
import type {
ChannelDirectoryEntry,
ChannelFocusedBindingContext,
ChannelReplyTransport,
ChannelThreadingToolContext,
} from "../../../src/channels/plugins/types.core.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import type { RuntimeEnv } from "../../../src/runtime.js";
} from "../../types.core.js";
import type { ChannelPlugin } from "../../types.js";
let contractRuntime: RuntimeEnv | undefined;
@@ -15,7 +15,7 @@ async function getDirectoryContractRuntime(): Promise<RuntimeEnv> {
if (contractRuntime) {
return contractRuntime;
}
const { createNonExitingRuntime } = await import("../../../src/runtime.js");
const { createNonExitingRuntime } = await import("../../../../runtime.js");
contractRuntime = createNonExitingRuntime();
return contractRuntime;
}

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 0, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 1, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 2, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 3, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 4, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 5, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 6, shardCount: 8 });

View File

@@ -1,3 +1,3 @@
import { installThreadingContractRegistryShard } from "../../../../test/helpers/channels/registry-backed-contract-shards.js";
import { installThreadingContractRegistryShard } from "./test-helpers/registry-backed-contract-shards.js";
installThreadingContractRegistryShard({ shardIndex: 7, shardCount: 8 });

View File

@@ -8,6 +8,7 @@ const repoRoot = path.resolve(import.meta.dirname, "..");
const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set(
GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES,
);
const CHANNEL_CONTRACT_TEST_HELPERS_PREFIX = "src/channels/plugins/contracts/test-helpers/";
const ROOTDIR_BOUNDARY_CANARY_RE =
/(^|\/)__rootdir_boundary_canary__\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u;
@@ -151,7 +152,9 @@ describe("non-extension test boundaries", () => {
});
it("keeps bundled plugin public-surface imports out of core source", () => {
const files = walkCode(path.join(repoRoot, "src"));
const files = walkCode(path.join(repoRoot, "src")).filter(
(file) => !file.startsWith(CHANNEL_CONTRACT_TEST_HELPERS_PREFIX),
);
const offenders = files.filter((file) => {
const source = fs.readFileSync(path.join(repoRoot, file), "utf8");
@@ -167,6 +170,7 @@ describe("non-extension test boundaries", () => {
...walkCode(path.join(repoRoot, "test")),
]
.filter((file) => !file.startsWith(BUNDLED_PLUGIN_PATH_PREFIX))
.filter((file) => !file.startsWith(CHANNEL_CONTRACT_TEST_HELPERS_PREFIX))
.filter((file) => !file.startsWith("test/helpers/"))
.filter((file) => file !== "test/extension-test-boundary.test.ts");
@@ -186,7 +190,10 @@ describe("non-extension test boundaries", () => {
const offenders = files.filter((file) => {
const source = fs.readFileSync(path.join(repoRoot, file), "utf8");
return source.includes("test/helpers/channels/security-audit-contract.js");
return (
source.includes("test/helpers/channels/security-audit-contract.js") ||
source.includes("src/channels/plugins/contracts/test-helpers/security-audit-contract.js")
);
});
expect(offenders).toEqual([]);
@@ -197,7 +204,7 @@ describe("non-extension test boundaries", () => {
const offenders = files.filter((file) => {
const source = fs.readFileSync(path.join(repoRoot, file), "utf8");
return source.includes("src/channels/plugins/contracts/test-helpers.js");
return source.includes("src/channels/plugins/contracts/test-helpers/");
});
expect(offenders).toEqual([]);
@@ -208,6 +215,7 @@ describe("non-extension test boundaries", () => {
/["']openclaw\/plugin-sdk\/testing["']/u,
/["']openclaw\/plugin-sdk\/test-utils["']/u,
/["'](?:\.\.\/)+(?:test\/helpers\/channels\/)[^"']+["']/u,
/["'](?:\.\.\/)+(?:src\/channels\/plugins\/contracts\/test-helpers\/)[^"']+["']/u,
/["'](?:\.\.\/)+(?:test\/helpers\/plugins\/)[^"']+["']/u,
];
const files = walkCode(path.join(repoRoot, "extensions"));

View File

@@ -1,4 +0,0 @@
export {
expectDirectoryIds,
type DirectoryListFn,
} from "../../../src/plugin-sdk/test-helpers/directory-ids.js";

View File

@@ -1,7 +0,0 @@
export {
expectChannelPluginContract,
installChannelActionsContractSuite,
installChannelPluginContractSuite,
installChannelSetupContractSuite,
installChannelStatusContractSuite,
} from "../../../src/plugin-sdk/test-helpers/channel-contract-suites.js";

View File

@@ -225,7 +225,7 @@ describe("scripts/test-projects changed-target routing", () => {
it("keeps shared test helpers cheap by default when no precise target exists", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"test/helpers/channels/plugin.ts",
"test/helpers/poll.ts",
]),
).toEqual([]);
});
@@ -235,26 +235,25 @@ describe("scripts/test-projects changed-target routing", () => {
resolveChangedTargetArgs(
["--changed", "origin/main"],
process.cwd(),
() => ["test/helpers/channels/plugin.ts"],
() => ["test/helpers/poll.ts"],
{ env: { OPENCLAW_TEST_CHANGED_BROAD: "1" } },
),
).toBeNull();
});
it("routes channel helper edits through the tests that import them", () => {
expect(resolveChangedTestTargetPlan(["test/helpers/channels/directory-ids.ts"])).toEqual({
mode: "targets",
targets: [
"extensions/discord/src/directory-contract.test.ts",
"extensions/slack/src/directory-contract.test.ts",
"extensions/telegram/src/directory-contract.test.ts",
],
});
it("routes channel contract helper edits through the tests that import them", () => {
const plan = resolveChangedTestTargetPlan([
"src/channels/plugins/contracts/test-helpers/manifest.ts",
]);
expect(plan.mode).toBe("targets");
expect(plan.targets).toContain("src/channels/plugins/contracts/registry.contract.test.ts");
expect(plan.targets).not.toContain("extensions/discord/src/directory-contract.test.ts");
});
it("routes channel contract helper edits through contract shards", () => {
const plan = resolveChangedTestTargetPlan([
"test/helpers/channels/registry-backed-contract-shards.ts",
"src/channels/plugins/contracts/test-helpers/registry-backed-contract-shards.ts",
]);
expect(plan.mode).toBe("targets");