mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
refactor: relocate channel contract helpers
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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.
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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<
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
expectDirectoryIds,
|
||||
type DirectoryListFn,
|
||||
} from "../../../src/plugin-sdk/test-helpers/directory-ids.js";
|
||||
@@ -1,7 +0,0 @@
|
||||
export {
|
||||
expectChannelPluginContract,
|
||||
installChannelActionsContractSuite,
|
||||
installChannelPluginContractSuite,
|
||||
installChannelSetupContractSuite,
|
||||
installChannelStatusContractSuite,
|
||||
} from "../../../src/plugin-sdk/test-helpers/channel-contract-suites.js";
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user