diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e6f3ec8d2..c0b82dc96e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Tests: keep kitchen-sink plugin assertion fixtures on a configurable temp root so native Windows runs no longer skip full-surface diagnostic coverage. - Config/secrets: allow exec SecretRef ids to include `#` selectors so AWS-style `secret#json_key` ids validate consistently. (#80731) Thanks @TurboTheTurtle. - Tests: keep the Telegram user credential helper on platform temp and path APIs so native Windows credential export and restore commands do not write through POSIX-only paths. - Installer: include the optional verify phase in the progress counter so `--verify` shows `[4/4] Verifying installation` instead of `[4/3]`. diff --git a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs index 71d089ba451..a147a907e72 100644 --- a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs +++ b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs @@ -1,9 +1,23 @@ import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; const command = process.argv[2]; +const scratchRoot = process.env.KITCHEN_SINK_TMP_DIR || os.tmpdir(); const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8")); +const scratchFile = (name) => path.join(scratchRoot, name); +const normalizedPath = (filePath) => filePath.replaceAll("\\", "/"); + +function resolveHomePath(value) { + if (value === "~") { + return process.env.HOME; + } + if (value?.startsWith("~/") || value?.startsWith("~\\")) { + return path.join(process.env.HOME, value.slice(2)); + } + return value; +} function expectFailure() { const outputFile = process.argv[3]; @@ -24,7 +38,7 @@ function expectFailure() { } function scanLogs() { - const roots = ["/tmp", path.join(process.env.HOME, ".openclaw")]; + const roots = [scratchRoot, path.join(process.env.HOME, ".openclaw")]; const files = []; const visit = (entry) => { if (!fs.existsSync(entry)) { @@ -38,7 +52,7 @@ function scanLogs() { return; } if (/\.(?:log|jsonl)$/u.test(entry) || /openclaw-kitchen-sink-/u.test(path.basename(entry))) { - if (entry.includes("/.npm/_logs/")) { + if (normalizedPath(entry).includes("/.npm/_logs/")) { return; } files.push(entry); @@ -287,9 +301,9 @@ function assertInstalled() { const source = process.env.KITCHEN_SINK_SOURCE; const surfaceMode = process.env.KITCHEN_SINK_SURFACE_MODE; const label = process.env.KITCHEN_SINK_LABEL; - const list = readJson(`/tmp/kitchen-sink-${label}-plugins.json`); - const inspect = readJson(`/tmp/kitchen-sink-${label}-inspect.json`); - const allInspect = readJson(`/tmp/kitchen-sink-${label}-inspect-all.json`); + const list = readJson(scratchFile(`kitchen-sink-${label}-plugins.json`)); + const inspect = readJson(scratchFile(`kitchen-sink-${label}-inspect.json`)); + const allInspect = readJson(scratchFile(`kitchen-sink-${label}-inspect-all.json`)); const plugin = (list.plugins || []).find((entry) => entry.id === pluginId); if (!plugin) { throw new Error(`kitchen-sink plugin not found after install: ${pluginId}`); @@ -429,20 +443,20 @@ function assertInstalled() { if (typeof record.installPath !== "string" || record.installPath.length === 0) { throw new Error("missing kitchen-sink install path"); } - const installPath = record.installPath.replace(/^~(?=$|\/)/u, process.env.HOME); + const installPath = resolveHomePath(record.installPath); if (!fs.existsSync(installPath)) { throw new Error(`kitchen-sink install path missing: ${record.installPath}`); } if (source === "clawhub" && record.artifactKind === "npm-pack") { assertClawHubExternalInstallContract(installPath); } - fs.writeFileSync(`/tmp/kitchen-sink-${label}-install-path.txt`, installPath, "utf8"); + fs.writeFileSync(scratchFile(`kitchen-sink-${label}-install-path.txt`), installPath, "utf8"); } function assertRemoved() { const pluginId = process.env.KITCHEN_SINK_ID; const label = process.env.KITCHEN_SINK_LABEL; - const list = readJson(`/tmp/kitchen-sink-${label}-uninstalled.json`); + const list = readJson(scratchFile(`kitchen-sink-${label}-uninstalled.json`)); if ((list.plugins || []).some((entry) => entry.id === pluginId)) { throw new Error(`kitchen-sink plugin still listed after uninstall: ${pluginId}`); } @@ -467,7 +481,7 @@ function assertRemoved() { if (config.channels?.["kitchen-sink-channel"]) { throw new Error("kitchen-sink channel config still present after uninstall"); } - const installPathFile = `/tmp/kitchen-sink-${label}-install-path.txt`; + const installPathFile = scratchFile(`kitchen-sink-${label}-install-path.txt`); if (fs.existsSync(installPathFile)) { const installPath = fs.readFileSync(installPathFile, "utf8").trim(); if (installPath && fs.existsSync(installPath)) { diff --git a/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh b/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh index 6f45fa06173..d09d0369364 100644 --- a/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh +++ b/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh @@ -6,13 +6,14 @@ source scripts/lib/docker-e2e-logs.sh OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)" export OPENCLAW_ENTRY +export KITCHEN_SINK_TMP_DIR="${KITCHEN_SINK_TMP_DIR:-/tmp}" openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}" run_expect_failure() { local label="$1" shift - local output_file="/tmp/kitchen-sink-expected-failure-${label}.txt" + local output_file="${KITCHEN_SINK_TMP_DIR}/kitchen-sink-expected-failure-${label}.txt" set +e "$@" >"$output_file" 2>&1 local status="$?" @@ -90,9 +91,9 @@ run_success_scenario() { run_logged_print "kitchen-sink-install-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins install "${install_args[@]}" configure_kitchen_sink_runtime run_logged_print "kitchen-sink-enable-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins enable "$KITCHEN_SINK_ID" - node "$OPENCLAW_ENTRY" plugins list --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-plugins.json" - node "$OPENCLAW_ENTRY" plugins inspect "$KITCHEN_SINK_ID" --runtime --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect.json" - node "$OPENCLAW_ENTRY" plugins inspect --all --runtime --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect-all.json" + node "$OPENCLAW_ENTRY" plugins list --json >"${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-plugins.json" + node "$OPENCLAW_ENTRY" plugins inspect "$KITCHEN_SINK_ID" --runtime --json >"${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect.json" + node "$OPENCLAW_ENTRY" plugins inspect --all --runtime --json >"${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-inspect-all.json" assert_kitchen_sink_installed if [ "$KITCHEN_SINK_SOURCE" = "clawhub" ]; then run_logged_print "kitchen-sink-uninstall-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins uninstall "$KITCHEN_SINK_SPEC" --force @@ -100,7 +101,7 @@ run_success_scenario() { run_logged_print "kitchen-sink-uninstall-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins uninstall "$KITCHEN_SINK_ID" --force fi remove_kitchen_sink_channel_config - node "$OPENCLAW_ENTRY" plugins list --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" + node "$OPENCLAW_ENTRY" plugins list --json >"${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" assert_kitchen_sink_removed } @@ -108,7 +109,7 @@ run_failure_scenario() { echo "Testing expected ${KITCHEN_SINK_LABEL} install failure from ${KITCHEN_SINK_SPEC}..." run_expect_failure "install-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins install "$KITCHEN_SINK_SPEC" remove_kitchen_sink_channel_config - node "$OPENCLAW_ENTRY" plugins list --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" + node "$OPENCLAW_ENTRY" plugins list --json >"${KITCHEN_SINK_TMP_DIR}/kitchen-sink-${KITCHEN_SINK_LABEL}-uninstalled.json" assert_kitchen_sink_removed } diff --git a/test/scripts/kitchen-sink-plugin-assertions.test.ts b/test/scripts/kitchen-sink-plugin-assertions.test.ts index cc741f8fbc1..5431e757235 100644 --- a/test/scripts/kitchen-sink-plugin-assertions.test.ts +++ b/test/scripts/kitchen-sink-plugin-assertions.test.ts @@ -62,10 +62,11 @@ function runAssertInstalled({ const pluginId = "openclaw-kitchen-sink-fixture"; const home = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-home-")); const installPath = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-install-")); - const pluginsJsonPath = path.join("/tmp", `kitchen-sink-${label}-plugins.json`); - const inspectJsonPath = path.join("/tmp", `kitchen-sink-${label}-inspect.json`); - const inspectAllJsonPath = path.join("/tmp", `kitchen-sink-${label}-inspect-all.json`); - const installPathMarker = path.join("/tmp", `kitchen-sink-${label}-install-path.txt`); + const scratchRoot = tmpdir(); + const pluginsJsonPath = path.join(scratchRoot, `kitchen-sink-${label}-plugins.json`); + const inspectJsonPath = path.join(scratchRoot, `kitchen-sink-${label}-inspect.json`); + const inspectAllJsonPath = path.join(scratchRoot, `kitchen-sink-${label}-inspect-all.json`); + const installPathMarker = path.join(scratchRoot, `kitchen-sink-${label}-install-path.txt`); const installsPath = path.join(home, ".openclaw", "plugins", "installs.json"); const spawnEnv = { ...process.env }; delete spawnEnv.KITCHEN_SINK_REQUIRE_ALL_DIAGNOSTICS; @@ -100,6 +101,7 @@ function runAssertInstalled({ KITCHEN_SINK_SOURCE: "npm", KITCHEN_SINK_SPEC: "npm:@openclaw/kitchen-sink@latest", KITCHEN_SINK_SURFACE_MODE: "full", + KITCHEN_SINK_TMP_DIR: scratchRoot, }, }); } finally { @@ -113,41 +115,32 @@ function runAssertInstalled({ } describe("kitchen-sink plugin assertions", () => { - it.skipIf(process.platform === "win32")( - "fails full-surface installs when stable diagnostic canaries disappear", - () => { - const result = runAssertInstalled(); + it("fails full-surface installs when stable diagnostic canaries disappear", () => { + const result = runAssertInstalled(); - expect(result.status).not.toBe(0); - expect(`${result.stdout}\n${result.stderr}`).toContain( - "missing expected kitchen-sink diagnostic error", - ); - }, - ); + expect(result.status).not.toBe(0); + expect(`${result.stdout}\n${result.stderr}`).toContain( + "missing expected kitchen-sink diagnostic error", + ); + }); - it.skipIf(process.platform === "win32")( - "accepts published full-surface installs with stable diagnostic canaries", - () => { - const result = runAssertInstalled({ - diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES), - }); + it("accepts published full-surface installs with stable diagnostic canaries", () => { + const result = runAssertInstalled({ + diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES), + }); - expect(result.status).toBe(0); - }, - ); + expect(result.status).toBe(0); + }); - it.skipIf(process.platform === "win32")( - "keeps exhaustive diagnostic matching available for synchronized fixtures", - () => { - const result = runAssertInstalled({ - diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES), - env: { KITCHEN_SINK_REQUIRE_ALL_DIAGNOSTICS: "1" }, - }); + it("keeps exhaustive diagnostic matching available for synchronized fixtures", () => { + const result = runAssertInstalled({ + diagnostics: diagnosticErrors(REQUIRED_FULL_DIAGNOSTIC_CANARIES), + env: { KITCHEN_SINK_REQUIRE_ALL_DIAGNOSTICS: "1" }, + }); - expect(result.status).not.toBe(0); - expect(`${result.stdout}\n${result.stderr}`).toContain( - "cli registration missing explicit commands metadata", - ); - }, - ); + expect(result.status).not.toBe(0); + expect(`${result.stdout}\n${result.stderr}`).toContain( + "cli registration missing explicit commands metadata", + ); + }); });