fix(e2e): harden Windows kitchen sink assertions

This commit is contained in:
Vincent Koc
2026-05-24 17:47:19 +02:00
parent c643370fd8
commit 87a2eba427
4 changed files with 60 additions and 51 deletions

View File

@@ -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]`.

View File

@@ -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)) {

View File

@@ -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
}

View File

@@ -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",
);
});
});