

























@@ -0,0 +1,116 @@
1+import fs from "node:fs";
2+import path from "node:path";
3+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4+import { cleanupTrackedTempDirs, makeTrackedTempDir } from "../plugins/test-helpers/fs-fixtures.js";
5+6+const tempDirs: string[] = [];
7+8+const { loadPluginMetadataSnapshotMock, loadBundledPluginPublicArtifactModuleSyncMock } =
9+vi.hoisted(() => ({
10+loadPluginMetadataSnapshotMock: vi.fn(),
11+loadBundledPluginPublicArtifactModuleSyncMock: vi.fn(() => {
12+throw new Error(
13+"Unable to resolve bundled plugin public surface discord/secret-contract-api.js",
14+);
15+}),
16+}));
17+18+vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({
19+loadPluginMetadataSnapshot: loadPluginMetadataSnapshotMock,
20+}));
21+22+vi.mock("../plugins/public-surface-loader.js", () => ({
23+loadBundledPluginPublicArtifactModuleSync: loadBundledPluginPublicArtifactModuleSyncMock,
24+}));
25+26+import { loadChannelSecretContractApi } from "./channel-contract-api.js";
27+28+function writeExternalChannelPlugin(params: { pluginId: string; channelId: string }) {
29+const rootDir = makeTrackedTempDir("openclaw-channel-secret-contract", tempDirs);
30+fs.writeFileSync(
31+path.join(rootDir, "secret-contract-api.cjs"),
32+`
33+module.exports = {
34+ secretTargetRegistryEntries: [
35+ {
36+ id: "channels.${params.channelId}.token",
37+ targetType: "channels.${params.channelId}.token",
38+ configFile: "openclaw.json",
39+ pathPattern: "channels.${params.channelId}.token",
40+ secretShape: "secret_input",
41+ expectedResolvedValue: "string",
42+ includeInPlan: true,
43+ includeInConfigure: true,
44+ includeInAudit: true
45+ }
46+ ],
47+ collectRuntimeConfigAssignments(params) {
48+ params.context.assignments.push({
49+ path: "channels.${params.channelId}.token",
50+ ref: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
51+ expected: "string",
52+ apply() {}
53+ });
54+ }
55+};
56+`,
57+"utf8",
58+);
59+return {
60+id: params.pluginId,
61+origin: "global",
62+channels: [params.channelId],
63+channelConfigs: {},
64+ rootDir,
65+};
66+}
67+68+describe("external channel secret contract api", () => {
69+beforeEach(() => {
70+loadPluginMetadataSnapshotMock.mockReset();
71+loadBundledPluginPublicArtifactModuleSyncMock.mockClear();
72+});
73+74+afterEach(() => {
75+cleanupTrackedTempDirs(tempDirs);
76+});
77+78+it("loads root secret-contract-api sidecars for external channel plugins", () => {
79+const record = writeExternalChannelPlugin({ pluginId: "discord", channelId: "discord" });
80+loadPluginMetadataSnapshotMock.mockReturnValue({
81+plugins: [record],
82+});
83+84+const api = loadChannelSecretContractApi({
85+channelId: "discord",
86+config: { channels: { discord: {} } },
87+env: {},
88+loadablePluginOrigins: new Map([["discord", "global"]]),
89+});
90+91+expect(api?.secretTargetRegistryEntries).toEqual(
92+expect.arrayContaining([
93+expect.objectContaining({
94+id: "channels.discord.token",
95+}),
96+]),
97+);
98+expect(api?.collectRuntimeConfigAssignments).toBeTypeOf("function");
99+});
100+101+it("skips external channel records outside the loadable plugin origin set", () => {
102+const record = writeExternalChannelPlugin({ pluginId: "discord", channelId: "discord" });
103+loadPluginMetadataSnapshotMock.mockReturnValue({
104+plugins: [record],
105+});
106+107+const api = loadChannelSecretContractApi({
108+channelId: "discord",
109+config: { channels: { discord: {} } },
110+env: {},
111+loadablePluginOrigins: new Map([["other", "global"]]),
112+});
113+114+expect(api).toBeUndefined();
115+});
116+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。