























@@ -0,0 +1,153 @@
1+import { spawnSync } from "node:child_process";
2+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3+import { tmpdir } from "node:os";
4+import path from "node:path";
5+import { describe, expect, it } from "vitest";
6+7+const ASSERTIONS_SCRIPT = "scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs";
8+const REQUIRED_FULL_DIAGNOSTIC_CANARIES = [
9+"only bundled plugins can register trusted tool policies",
10+"plugin must declare contracts.tools for: kitchen-sink-tool",
11+'channel "kitchen-sink-channel-probe" registration missing required config helpers',
12+'agent harness "kitchen-sink-agent-harness" registration missing required runtime methods',
13+"session scheduler job registration requires unique id, sessionKey, and kind",
14+];
15+16+function writeJson(filePath: string, value: unknown) {
17+mkdirSync(path.dirname(filePath), { recursive: true });
18+writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
19+}
20+21+function fullSurfaceInspectPayload(pluginId: string) {
22+return {
23+commands: ["kitchen"],
24+diagnostics: [],
25+plugin: {
26+id: pluginId,
27+enabled: true,
28+status: "loaded",
29+channelIds: ["kitchen-sink-channel"],
30+providerIds: ["kitchen-sink-provider"],
31+speechProviderIds: ["kitchen-sink-speech"],
32+realtimeTranscriptionProviderIds: ["kitchen-sink-realtime-transcription"],
33+realtimeVoiceProviderIds: ["kitchen-sink-realtime-voice"],
34+mediaUnderstandingProviderIds: ["kitchen-sink-media"],
35+imageGenerationProviderIds: ["kitchen-sink-image"],
36+videoGenerationProviderIds: ["kitchen-sink-video"],
37+musicGenerationProviderIds: ["kitchen-sink-music"],
38+webFetchProviderIds: ["kitchen-sink-fetch"],
39+webSearchProviderIds: ["kitchen-sink-search"],
40+migrationProviderIds: ["kitchen-sink-migration-providers"],
41+agentHarnessIds: [],
42+hookCount: 30,
43+},
44+services: ["kitchen-sink-service"],
45+tools: [{ names: ["kitchen_sink_text"] }],
46+typedHooks: Array.from({ length: 30 }, (_, index) => `hook-${index}`),
47+};
48+}
49+50+function diagnosticErrors(messages: string[]) {
51+return messages.map((message) => ({ level: "error", message }));
52+}
53+54+function runAssertInstalled({
55+ diagnostics = [],
56+ env = {},
57+}: {
58+diagnostics?: Array<{ level: string; message: string }>;
59+env?: NodeJS.ProcessEnv;
60+} = {}) {
61+const label = `diagnostics-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
62+const pluginId = "openclaw-kitchen-sink-fixture";
63+const home = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-home-"));
64+const installPath = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-install-"));
65+const pluginsJsonPath = path.join("/tmp", `kitchen-sink-${label}-plugins.json`);
66+const inspectJsonPath = path.join("/tmp", `kitchen-sink-${label}-inspect.json`);
67+const inspectAllJsonPath = path.join("/tmp", `kitchen-sink-${label}-inspect-all.json`);
68+const installPathMarker = path.join("/tmp", `kitchen-sink-${label}-install-path.txt`);
69+const installsPath = path.join(home, ".openclaw", "plugins", "installs.json");
70+const spawnEnv = { ...process.env };
71+delete spawnEnv.KITCHEN_SINK_REQUIRE_ALL_DIAGNOSTICS;
72+73+try {
74+writeJson(pluginsJsonPath, {
75+ diagnostics,
76+plugins: [{ id: pluginId, status: "loaded" }],
77+});
78+writeJson(inspectJsonPath, fullSurfaceInspectPayload(pluginId));
79+writeJson(inspectAllJsonPath, { diagnostics: [] });
80+writeJson(installsPath, {
81+installRecords: {
82+[pluginId]: {
83+ installPath,
84+resolvedSpec: "@openclaw/kitchen-sink@latest",
85+resolvedVersion: "1.0.0",
86+source: "npm",
87+spec: "@openclaw/kitchen-sink@latest",
88+},
89+},
90+});
91+92+return spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "assert-installed"], {
93+encoding: "utf8",
94+env: {
95+ ...spawnEnv,
96+ ...env,
97+HOME: home,
98+KITCHEN_SINK_ID: pluginId,
99+KITCHEN_SINK_LABEL: label,
100+KITCHEN_SINK_SOURCE: "npm",
101+KITCHEN_SINK_SPEC: "npm:@openclaw/kitchen-sink@latest",
102+KITCHEN_SINK_SURFACE_MODE: "full",
103+},
104+});
105+} finally {
106+rmSync(home, { force: true, recursive: true });
107+rmSync(installPath, { force: true, recursive: true });
108+rmSync(pluginsJsonPath, { force: true });
109+rmSync(inspectJsonPath, { force: true });
110+rmSync(inspectAllJsonPath, { force: true });
111+rmSync(installPathMarker, { force: true });
112+}
113+}
114+115+describe("kitchen-sink plugin assertions", () => {
116+it.skipIf(process.platform === "win32")(
117+"fails full-surface installs when stable diagnostic canaries disappear",
118+() => {
119+const result = runAssertInstalled();
120+121+expect(result.status).not.toBe(0);
122+expect(`${result.stdout}\n${result.stderr}`).toContain(
123+"missing expected kitchen-sink diagnostic error",
124+);
125+},
126+);
127+128+it.skipIf(process.platform === "win32")(
129+"accepts published full-surface installs with stable diagnostic canaries",
130+() => {
131+const result = runAssertInstalled({
132+diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES),
133+});
134+135+expect(result.status).toBe(0);
136+},
137+);
138+139+it.skipIf(process.platform === "win32")(
140+"keeps exhaustive diagnostic matching available for synchronized fixtures",
141+() => {
142+const result = runAssertInstalled({
143+diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES),
144+env: { KITCHEN_SINK_REQUIRE_ALL_DIAGNOSTICS: "1" },
145+});
146+147+expect(result.status).not.toBe(0);
148+expect(`${result.stdout}\n${result.stderr}`).toContain(
149+"cli registration missing explicit commands metadata",
150+);
151+},
152+);
153+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。