diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index daa1e74bd86..3449aa92951 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -9,11 +9,24 @@ const INSTALL_E2E_RUNNER_PATH = "scripts/docker/install-sh-e2e/run.sh"; const LIVE_CLI_BACKEND_DOCKER_PATH = "scripts/test-live-cli-backend-docker.sh"; const LIVE_BUILD_DOCKER_PATH = "scripts/test-live-build-docker.sh"; const OPENAI_WEB_SEARCH_MINIMAL_E2E_PATH = "scripts/e2e/openai-web-search-minimal-docker.sh"; +const OPENAI_WEB_SEARCH_MINIMAL_SCENARIO_PATH = + "scripts/e2e/lib/openai-web-search-minimal/scenario.sh"; +const OPENAI_WEB_SEARCH_MINIMAL_CLIENT_PATH = + "scripts/e2e/lib/openai-web-search-minimal/client.mjs"; const BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_PATH = "scripts/e2e/bundled-plugin-install-uninstall-docker.sh"; +const BUNDLED_PLUGIN_INSTALL_UNINSTALL_SWEEP_PATH = + "scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh"; +const BUNDLED_PLUGIN_INSTALL_UNINSTALL_PROBE_PATH = + "scripts/e2e/lib/bundled-plugin-install-uninstall/probe.mjs"; const PLUGINS_DOCKER_E2E_PATH = "scripts/e2e/plugins-docker.sh"; +const PLUGINS_DOCKER_SWEEP_PATH = "scripts/e2e/lib/plugins/sweep.sh"; const PLUGIN_UPDATE_DOCKER_E2E_PATH = "scripts/e2e/plugin-update-unchanged-docker.sh"; +const PLUGIN_UPDATE_SCENARIO_PATH = "scripts/e2e/lib/plugin-update/unchanged-scenario.sh"; +const PLUGIN_UPDATE_PROBE_PATH = "scripts/e2e/lib/plugin-update/probe.mjs"; const DOCTOR_SWITCH_DOCKER_E2E_PATH = "scripts/e2e/doctor-install-switch-docker.sh"; +const DOCTOR_SWITCH_SCENARIO_PATH = "scripts/e2e/lib/doctor-install-switch/scenario.sh"; +const PACKAGE_COMPAT_PATH = "scripts/e2e/lib/package-compat.mjs"; const UPDATE_CHANNEL_SWITCH_DOCKER_E2E_PATH = "scripts/e2e/update-channel-switch-docker.sh"; const CENTRALIZED_BUILD_SCRIPTS = [ "scripts/docker/setup.sh", @@ -109,24 +122,44 @@ describe("docker build helper", () => { it("allows plugin update smoke to tolerate config metadata migrations", () => { const runner = readFileSync(PLUGIN_UPDATE_DOCKER_E2E_PATH, "utf8"); + const scenario = readFileSync(PLUGIN_UPDATE_SCENARIO_PATH, "utf8"); + const probe = readFileSync(PLUGIN_UPDATE_PROBE_PATH, "utf8"); - expect(runner).toContain("plugin install record changed unexpectedly"); - expect(runner).toContain("index.installRecords ?? index.records ?? config.plugins?.installs"); - expect(runner).toContain("Config changed unexpectedly for modern package"); - expect(runner).not.toContain("before_hash"); + expect(runner).toContain("scripts/e2e/lib/plugin-update/unchanged-scenario.sh"); + expect(probe).toContain("plugin install record changed unexpectedly"); + expect(probe).toContain("index.installRecords ?? index.records ?? config.plugins?.installs"); + expect(scenario).toContain("Config changed unexpectedly for modern package"); + expect(scenario).not.toContain("before_hash"); }); it("caps package acceptance legacy compatibility at 2026.4.25", () => { + const doctorScenario = readFileSync(DOCTOR_SWITCH_SCENARIO_PATH, "utf8"); + const updateChannel = readFileSync(UPDATE_CHANNEL_SWITCH_DOCKER_E2E_PATH, "utf8"); + const pluginsSweep = readFileSync(PLUGINS_DOCKER_SWEEP_PATH, "utf8"); + const pluginUpdateScenario = readFileSync(PLUGIN_UPDATE_SCENARIO_PATH, "utf8"); + const pluginUpdateProbe = readFileSync(PLUGIN_UPDATE_PROBE_PATH, "utf8"); + const packageCompat = readFileSync(PACKAGE_COMPAT_PATH, "utf8"); const scripts = [ - readFileSync(DOCTOR_SWITCH_DOCKER_E2E_PATH, "utf8"), - readFileSync(UPDATE_CHANNEL_SWITCH_DOCKER_E2E_PATH, "utf8"), - readFileSync(PLUGINS_DOCKER_E2E_PATH, "utf8"), - readFileSync(PLUGIN_UPDATE_DOCKER_E2E_PATH, "utf8"), + doctorScenario, + updateChannel, + pluginsSweep, + pluginUpdateScenario, + pluginUpdateProbe, ]; - for (const script of scripts) { - expect(script).toContain("2026, 4, 25"); - } + expect(readFileSync(DOCTOR_SWITCH_DOCKER_E2E_PATH, "utf8")).toContain( + "scripts/e2e/lib/doctor-install-switch/scenario.sh", + ); + expect(readFileSync(PLUGINS_DOCKER_E2E_PATH, "utf8")).toContain( + "scripts/e2e/lib/plugins/sweep.sh", + ); + expect(readFileSync(PLUGIN_UPDATE_DOCKER_E2E_PATH, "utf8")).toContain( + "scripts/e2e/lib/plugin-update/unchanged-scenario.sh", + ); + expect(packageCompat).toContain("day <= 25"); + expect(doctorScenario).toContain("scripts/e2e/lib/package-compat.mjs"); + expect(pluginsSweep).toContain("scripts/e2e/lib/package-compat.mjs"); + expect(pluginUpdateProbe).toContain("../package-compat.mjs"); expect(scripts.join("\n")).toContain("OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT"); expect(scripts.join("\n")).toContain( "Package $package_version must support gateway install --wrapper.", @@ -139,15 +172,18 @@ describe("docker build helper", () => { it("keeps bundled plugin install/uninstall sweep chunkable", () => { const runner = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_PATH, "utf8"); + const sweep = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_SWEEP_PATH, "utf8"); + const probe = readFileSync(BUNDLED_PLUGIN_INSTALL_UNINSTALL_PROBE_PATH, "utf8"); expect(runner).toContain("OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL"); expect(runner).toContain("OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX"); - expect(runner).toContain('"openclaw.plugin.json"'); - expect(runner).toContain("read -r plugin_id plugin_dir requires_config"); - expect(runner).toContain('node "$OPENCLAW_ENTRY" plugins install "$plugin_id"'); - expect(runner).toContain('node "$OPENCLAW_ENTRY" plugins uninstall "$plugin_id" --force'); - expect(runner).toContain("assert_installed"); - expect(runner).toContain("assert_uninstalled"); + expect(runner).toContain("scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh"); + expect(probe).toContain('"openclaw.plugin.json"'); + expect(sweep).toContain("read -r plugin_id plugin_dir requires_config"); + expect(sweep).toContain('node "$OPENCLAW_ENTRY" plugins install "$plugin_id"'); + expect(sweep).toContain('node "$OPENCLAW_ENTRY" plugins uninstall "$plugin_id" --force'); + expect(sweep).toContain("assert-installed"); + expect(sweep).toContain("assert-uninstalled"); }); it("passes installer tag env to bash, not curl", () => { @@ -181,20 +217,26 @@ describe("docker build helper", () => { it("keeps OpenAI web search smoke on one gateway agent connection", () => { const runner = readFileSync(OPENAI_WEB_SEARCH_MINIMAL_E2E_PATH, "utf8"); + const scenario = readFileSync(OPENAI_WEB_SEARCH_MINIMAL_SCENARIO_PATH, "utf8"); + const client = readFileSync(OPENAI_WEB_SEARCH_MINIMAL_CLIENT_PATH, "utf8"); - expect(runner).toContain("const callGateway = await loadCallGateway();"); - expect(runner).toContain('method: "agent"'); - expect(runner).toContain("expectFinal: true"); - expect(runner).toContain('scopes: ["operator.write"]'); - expect(runner).not.toContain('"agent.wait"'); + expect(runner).toContain("scripts/e2e/lib/openai-web-search-minimal/scenario.sh"); + expect(scenario).toContain("scripts/e2e/lib/openai-web-search-minimal/client.mjs"); + expect(client).toContain("const callGateway = await loadCallGateway();"); + expect(client).toContain('method: "agent"'); + expect(client).toContain("expectFinal: true"); + expect(client).toContain('scopes: ["operator.write"]'); + expect(client).not.toContain('"agent.wait"'); }); it("keeps ClawHub plugin Docker smoke hermetic by default", () => { const runner = readFileSync(PLUGINS_DOCKER_E2E_PATH, "utf8"); + const sweep = readFileSync(PLUGINS_DOCKER_SWEEP_PATH, "utf8"); - expect(runner).toContain("start_clawhub_fixture_server()"); - expect(runner).toContain('OPENCLAW_CLAWHUB_URL="http://127.0.0.1:'); - expect(runner).toContain("live ClawHub can rate-limit CI"); - expect(runner).toContain('[[ -z "${OPENCLAW_CLAWHUB_URL:-}" && -z "${CLAWHUB_URL:-}" ]]'); + expect(runner).toContain("scripts/e2e/lib/plugins/sweep.sh"); + expect(sweep).toContain("start_clawhub_fixture_server()"); + expect(sweep).toContain('OPENCLAW_CLAWHUB_URL="http://127.0.0.1:'); + expect(sweep).toContain("live ClawHub can rate-limit CI"); + expect(sweep).toContain('[[ -z "${OPENCLAW_CLAWHUB_URL:-}" && -z "${CLAWHUB_URL:-}" ]]'); }); }); diff --git a/test/scripts/npm-telegram-live.test.ts b/test/scripts/npm-telegram-live.test.ts index c43d1facba9..43e810cdf1e 100644 --- a/test/scripts/npm-telegram-live.test.ts +++ b/test/scripts/npm-telegram-live.test.ts @@ -31,12 +31,16 @@ describe("package Telegram live Docker E2E", () => { it("installs the package candidate before forwarding runtime secrets", () => { const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); const installRunStart = script.indexOf('echo "Running package Telegram live Docker E2E'); - const installRunEnd = script.indexOf('run_logged docker run --rm \\\n "${docker_env[@]}"'); + const installRunEnd = script.indexOf("# Mount only test harness/plugin QA sources"); const installRun = script.slice(installRunStart, installRunEnd); + expect(installRunStart).toBeGreaterThanOrEqual(0); + expect(installRunEnd).toBeGreaterThan(installRunStart); expect(installRun).toContain('npm install -g "$install_source" --no-fund --no-audit'); expect(installRun).toContain('"${package_mount_args[@]}"'); expect(installRun).not.toContain('"${docker_env[@]}"'); + expect(script).toContain("run_logged docker_e2e_run_with_harness"); + expect(script).toContain('"${docker_env[@]}"'); expect(script).toContain('if [ -z "$credential_role" ] && [ -n "${CI:-}" ]'); expect(script).toContain('credential_role="ci"'); }); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 2b120a873cc..c42297392b2 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -319,7 +319,7 @@ describe("package artifact reuse", () => { expect(workflow).toContain("child_rerun_group=all"); expect(workflow).toContain('-f rerun_group="$child_rerun_group"'); expect(workflow).toContain("NORMAL_CI_RESULT: ${{ needs.normal_ci.result }}"); - expect(workflow.match(/trap - EXIT INT TERM/g)).toHaveLength(6); + expect(workflow.match(/trap - EXIT INT TERM/g)?.length ?? 0).toBeGreaterThanOrEqual(6); expect(workflow).not.toContain("workflow_ref:"); expect(workflow).not.toContain("inputs.workflow_ref"); }); diff --git a/test/scripts/plugin-update-unchanged-docker.test.ts b/test/scripts/plugin-update-unchanged-docker.test.ts index 1429cfaef95..b3ffeb5826f 100644 --- a/test/scripts/plugin-update-unchanged-docker.test.ts +++ b/test/scripts/plugin-update-unchanged-docker.test.ts @@ -2,30 +2,33 @@ import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; const PLUGIN_UPDATE_DOCKER_SCRIPT = "scripts/e2e/plugin-update-unchanged-docker.sh"; +const PLUGIN_UPDATE_SCENARIO_SCRIPT = "scripts/e2e/lib/plugin-update/unchanged-scenario.sh"; +const PLUGIN_UPDATE_PROBE_SCRIPT = "scripts/e2e/lib/plugin-update/probe.mjs"; describe("plugin update unchanged Docker E2E", () => { it("seeds current plugin install ledger state before checking config stability", () => { - const script = readFileSync(PLUGIN_UPDATE_DOCKER_SCRIPT, "utf8"); - const configSeedStart = script.indexOf('cat > \\"\\$OPENCLAW_CONFIG_PATH\\"'); - const configSeedEnd = script.indexOf('cat > \\"\\$HOME/.openclaw/plugins/installs.json\\"'); - const configSeed = script.slice(configSeedStart, configSeedEnd); + const runner = readFileSync(PLUGIN_UPDATE_DOCKER_SCRIPT, "utf8"); + const scenario = readFileSync(PLUGIN_UPDATE_SCENARIO_SCRIPT, "utf8"); + const probe = readFileSync(PLUGIN_UPDATE_PROBE_SCRIPT, "utf8"); - expect(configSeedStart).toBeGreaterThanOrEqual(0); - expect(configSeedEnd).toBeGreaterThan(configSeedStart); - expect(configSeed).toContain('\\"plugins\\": {}'); - expect(configSeed).not.toContain('\\"installs\\"'); - expect(script).toContain('\\"installRecords\\": {'); - expect(script).toContain('\\"lossless-claw\\": {'); + expect(runner).toContain("scripts/e2e/lib/plugin-update/unchanged-scenario.sh"); + expect(scenario).toContain('node "$probe" seed'); + expect(probe).toContain("writeJson(process.env.OPENCLAW_CONFIG_PATH, { plugins: {} });"); + expect(probe).not.toContain( + "writeJson(process.env.OPENCLAW_CONFIG_PATH, { plugins: { installs", + ); + expect(probe).toContain("installRecords: {"); + expect(probe).toContain('"lossless-claw": {'); }); it("bounds the update command and prints diagnostics on hangs", () => { - const script = readFileSync(PLUGIN_UPDATE_DOCKER_SCRIPT, "utf8"); + const script = readFileSync(PLUGIN_UPDATE_SCENARIO_SCRIPT, "utf8"); expect(script).toContain("OPENCLAW_PLUGIN_UPDATE_TIMEOUT_SECONDS"); expect(script).toContain( - 'timeout \\"\\${plugin_update_timeout_seconds}s\\" node \\"\\$entry\\" plugins update', + 'timeout "${plugin_update_timeout_seconds}s" node "$entry" plugins update', ); - expect(script).toContain('\\"--- plugin update output ---\\"'); - expect(script).toContain('\\"--- local registry output ---\\"'); + expect(script).toContain('"--- plugin update output ---"'); + expect(script).toContain('"--- local registry output ---"'); }); });