openclaw/test/scripts/package-compat.test.ts
Peter Steinberger 7cbaa6b36f
refactor(scripts): deslop shared tooling flows (#162824)
Consolidate script lifecycle and helper ownership while preserving CLI, generated-output, cleanup, and guard contracts. Reject inherited object keys in model-matrix option admission with the existing unknown-argument diagnostic.

Validation: 90 full sibling suites; original-fails parser regression; byte-identical native outputs; changed checks; SDK surface and API comparison; both zero-cycle checks; independent isolated review.
2026-10-01 21:30:19 +00:00

414 lines
14 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { fixtureCapabilityConsentArgs } from "../../scripts/e2e/lib/package-compat.mjs";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const consent = "--accept-capabilities";
const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash";
function writeCandidate(
root: string,
options: {
commands?: string[];
helpStatus?: number;
hang?: boolean;
exitCode?: number;
preserveUninstallIntent?: boolean;
} = {},
name = "candidate.cjs",
) {
const entry = path.join(root, name);
writeFileSync(
entry,
`
const fs = require('node:fs');
const options = ${JSON.stringify(options)};
const args = process.argv.slice(2);
fs.appendFileSync(process.env.ARGV_LOG, JSON.stringify(args) + '\\n');
const supported = (options.commands ?? ['install', 'enable', 'update']).includes(args[1]);
if (args.includes('--help')) {
console.log('OpenClaw 2026.8.1\\nOptions:');
console.log(supported ? ' --accept-capabilities Accept capabilities' : ' --force Confirm source');
if (options.hang) setInterval(() => {}, 1000);
else process.exit(options.helpStatus ?? 0);
} else {
if (options.preserveUninstallIntent) {
const marker = process.env.ARGV_LOG + '.disabled';
if (args[0] === 'plugins' && args[2] === 'demo-plugin-npm') {
if (args[1] === 'uninstall') fs.writeFileSync(marker, 'disabled');
if (args[1] === 'enable') fs.rmSync(marker, { force: true });
}
if (args[0] === 'demo-npm' && fs.existsSync(marker)) {
console.error('OpenClaw does not know the command "demo-npm".');
process.exit(43);
}
}
const accepted = args.includes('--accept-capabilities');
if (accepted && !supported) {
console.error('unknown option --accept-capabilities');
process.exit(2);
}
if (args[2]?.includes('invalid-metadata') || args[2] === 'missing-version') process.exit(41);
const needsConsent = args[0] === 'plugins' && (args[1] === 'install' ||
(args[1] === 'enable' && args[2] === 'claude-bundle-e2e') ||
(args[1] === 'update' && args[2] === 'marketplace-shortcut' && !args.includes('--dry-run')));
if (supported && needsConsent && !accepted) {
console.error('requires capability consent');
process.exit(42);
}
console.log('{}');
process.exit(options.exitCode ?? 0);
}
`,
);
return entry;
}
function runShell(root: string, entry: string, script: string) {
const result = spawnSync(BASH_BIN, ["-c", `set -euo pipefail\n${script}`], {
encoding: "utf8",
timeout: 30_000,
env: {
...process.env,
HOME: root,
OPENCLAW_ENTRY: entry,
OPENCLAW_PLUGINS_TMP_DIR: root,
KITCHEN_SINK_TMP_DIR: root,
ARGV_LOG: path.join(root, "argv.jsonl"),
OPENCLAW_PLUGINS_E2E_CLAWHUB: "0",
OPENCLAW_PLUGINS_CLI_TIMEOUT: "5s",
OPENCLAW_TEST_STATE_SCRIPT_B64: Buffer.from(":").toString("base64"),
},
});
const log = path.join(root, "argv.jsonl");
const calls: string[][] = existsSync(log)
? readFileSync(log, "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line))
: [];
return { ...result, calls };
}
const fixtureCommand = `
source scripts/lib/openclaw-e2e-instance.sh
openclaw_e2e_fixture_plugin_command openclaw_e2e_maybe_timeout 5s node "$OPENCLAW_ENTRY" --
`.trim();
// Replace artifact construction and inventory assertions, not the CLI runner or scenario order.
const sweepFixtureLoader = `
source() {
if [[ "$1" == scripts/e2e/lib/plugins/fixtures.sh ]]; then
pack_fixture_plugin() { :; }
pack_fixture_plugin_with_cli_registry_dependency() { :; }
pack_fake_is_number_package() { :; }
pack_fixture_plugin_with_invalid_extension_entry() { :; }
start_npm_fixture_registry() { :; }
openclaw_plugins_cleanup_fixture_servers() { :; }
else
builtin source "$@"
fi
}
node() {
case "$1" in
scripts/e2e/lib/fixture.mjs) mkdir -p "$3" ;;
scripts/e2e/lib/plugins/assertions.mjs) return 0 ;;
*) command node "$@" ;;
esac
}
git() { printf 'fixture-commit\\n'; }
source scripts/e2e/lib/plugins/sweep.sh
`;
const kitchenFixtureLoader = `
export KITCHEN_SINK_SWEEP_SOURCE_ONLY=1
source scripts/e2e/lib/kitchen-sink-plugin/sweep.sh
node() {
case "$1" in
scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs) return 0 ;;
*) command node "$@" ;;
esac
}
KITCHEN_SINK_LABEL=fixture
KITCHEN_SINK_ID=fixture-id
KITCHEN_SINK_SPEC=fixture-spec
KITCHEN_SINK_PREINSTALL_SPEC=preinstall-spec
KITCHEN_SINK_SOURCE=npm
run_success_scenario
KITCHEN_SINK_SPEC=missing-version
run_failure_scenario
`;
describe("package fixture consent compatibility", () => {
it.each([true, false])(
"runs the plugin sweep with selective consent (supported=%s)",
(supported) => {
const root = tempDirs.make("openclaw-consent-sweep-");
const entry = writeCandidate(root, {
commands: supported ? undefined : [],
preserveUninstallIntent: true,
});
const result = runShell(root, entry, sweepFixtureLoader);
expect(result.status, result.stderr).toBe(0);
const mutations = result.calls.filter(
(args) => args[0] === "plugins" && !args.includes("--help"),
);
const installs = mutations.filter((args) => args[1] === "install");
expect(installs).toHaveLength(12);
for (const args of mutations) {
const positive =
(args[1] === "install" && !args.some((arg) => arg.includes("invalid-metadata"))) ||
args[1] === "enable" ||
(args[1] === "update" &&
args[2] === "marketplace-shortcut" &&
!args.includes("--dry-run"));
expect(args.includes(consent), args.join(" ")).toBe(supported && positive);
}
expect(mutations.filter((args) => args[1] === "update")).toHaveLength(5);
expect(mutations.filter((args) => args[1] === "enable").map((args) => args[2])).toEqual([
"demo-plugin-npm",
"claude-bundle-e2e",
]);
},
);
it.each([true, false])(
"runs kitchen-sink success and registry failure (supported=%s)",
(supported) => {
const root = tempDirs.make("openclaw-consent-kitchen-");
const result = runShell(
root,
writeCandidate(root, { commands: supported ? undefined : [] }),
kitchenFixtureLoader,
);
expect(result.status, result.stderr).toBe(0);
const installs = result.calls.filter(
(args) => args[1] === "install" && !args.includes("--help"),
);
expect(installs.map((args) => args[2])).toEqual([
"preinstall-spec",
"fixture-spec",
"missing-version",
]);
expect(installs.map((args) => args.includes(consent))).toEqual([supported, supported, false]);
expect(result.calls.find((args) => args[1] === "enable")).not.toContain(consent);
},
);
it("probes each command and preserves literal argv", () => {
const root = tempDirs.make("openclaw-consent-argv-");
const entry = writeCandidate(root, { commands: ["install", "update"] });
const result = runShell(
root,
entry,
`${fixtureCommand} plugins install 'file:a b/$(literal)' --force ''
${fixtureCommand} plugins enable fixture
${fixtureCommand} plugins update fixture`,
);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toEqual([
["plugins", "install", "--help"],
["plugins", "install", "file:a b/$(literal)", "--force", "", consent],
["plugins", "enable", "--help"],
["plugins", "enable", "fixture"],
["plugins", "update", "--help"],
["plugins", "update", "fixture", consent],
]);
});
it.each([true, false])(
"records candidate capability consent support (supported=%s)",
(supported) => {
const root = tempDirs.make("openclaw-consent-support-");
const result = runShell(
root,
writeCandidate(root, { commands: supported ? undefined : [] }),
`${fixtureCommand} plugins install fixture
printf 'support=%s\\n' "$OPENCLAW_E2E_LAST_FIXTURE_PLUGIN_CAPABILITY_CONSENT_SUPPORTED"`,
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(`support=${supported ? "1" : "0"}`);
},
);
it.each([true, false])(
"consents to ClawHub install but not unchanged update (supported=%s)",
(supported) => {
const root = tempDirs.make("openclaw-consent-clawhub-");
const result = runShell(
root,
writeCandidate(root, { commands: supported ? undefined : [] }),
`
export OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY=1
export OPENCLAW_PLUGINS_E2E_CLAWHUB=1
export OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=1
export OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC=clawhub:@example/consent-fixture
export OPENCLAW_PLUGINS_E2E_CLAWHUB_ID=consent-fixture
source scripts/e2e/lib/plugins/sweep.sh
node() {
case "$1" in
scripts/e2e/lib/plugins/assertions.mjs) return 0 ;;
*) command node "$@" ;;
esac
}
run_plugins_clawhub_scenario
`,
);
expect(result.status, result.stderr).toBe(0);
expect(
result.calls
.find((args) => args[1] === "install" && !args.includes("--help"))
?.includes(consent),
).toBe(supported);
expect(result.calls.find((args) => args[1] === "update")).not.toContain(consent);
},
);
it("requires an explicit package identity for live ClawHub E2E", () => {
const root = tempDirs.make("openclaw-clawhub-live-requirements-");
const result = runShell(
root,
writeCandidate(root),
`
export OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY=1
export OPENCLAW_PLUGINS_E2E_CLAWHUB=1
export OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=1
unset OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC OPENCLAW_PLUGINS_E2E_CLAWHUB_ID
source scripts/e2e/lib/plugins/sweep.sh
run_plugins_clawhub_scenario
`,
);
expect(result.status).toBe(2);
expect(result.stderr).toContain("the Kitchen Sink listing has been retired");
});
it.each([
{
environment: `
export OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=0
unset OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC OPENCLAW_PLUGINS_E2E_CLAWHUB_ID
`,
expectedId: "openclaw-kitchen-sink-fixture",
expectedSpec: "clawhub:@openclaw/plugin-e2e-fixture",
name: "fixture default",
},
{
environment: `
export OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=1
export OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC=clawhub:@example/custom-plugin
export OPENCLAW_PLUGINS_E2E_CLAWHUB_ID=custom-plugin
`,
expectedId: "custom-plugin",
expectedSpec: "clawhub:@example/custom-plugin",
name: "explicit override",
},
])("selects the ClawHub identity for $name", ({ environment, expectedId, expectedSpec }) => {
const root = tempDirs.make("openclaw-clawhub-identity-");
const result = runShell(
root,
writeCandidate(root),
`
export OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY=1
export OPENCLAW_PLUGINS_E2E_CLAWHUB=1
${environment}
source scripts/e2e/lib/plugins/sweep.sh
node() {
case "$1" in
scripts/e2e/lib/clawhub-fixture-server.cjs)
printf '12345\\n' > "$3"
while true; do sleep 1; done
;;
scripts/e2e/lib/plugins/assertions.mjs) return 0 ;;
*) command node "$@" ;;
esac
}
run_plugins_clawhub_scenario
`,
);
expect(result.status, result.stderr).toBe(0);
const install = result.calls.find((args) => args[1] === "install" && !args.includes("--help"));
expect(install?.[2]).toBe(expectedSpec);
expect(result.calls.filter((args) => args[1] === "inspect").map((args) => args[2])).toEqual([
expectedId,
expectedId,
]);
expect(result.calls.find((args) => args[1] === "update")?.[2]).toBe(expectedId);
expect(result.calls.find((args) => args[1] === "uninstall")?.[2]).toBe(expectedSpec);
});
it.each([
[" --accept-capabilities Accept\n", [consent]],
[" \u001b[32m--accept-capabilities\u001b[0m Accept\n", [consent]],
[" --accept-capabilities-extra Other\n", []],
["See --accept-capabilities in newer releases\n", []],
[" --force Confirm\n", []],
])("reads only an advertised option from help %j", (help, expected) => {
expect(fixtureCapabilityConsentArgs(help)).toEqual(expected);
});
it.each([
["plugins", "error"],
["plugins", "timeout"],
["kitchen-sink", "error"],
["kitchen-sink", "timeout"],
])("%s logger fails before mutation on help %s", (suite, failure) => {
const root = tempDirs.make("openclaw-consent-failure-");
const entry = writeCandidate(root, failure === "error" ? { helpStatus: 47 } : { hang: true });
const result = runShell(
root,
entry,
suite === "plugins"
? `
export OPENCLAW_PLUGINS_SWEEP_SOURCE_ONLY=1
export OPENCLAW_PLUGINS_CLI_TIMEOUT=1s
source scripts/e2e/lib/plugins/sweep.sh
run_plugins_fixture_logged failure plugins install fixture --force
`
: `
export KITCHEN_SINK_SWEEP_SOURCE_ONLY=1
export KITCHEN_SINK_CLI_TIMEOUT=1s
source scripts/e2e/lib/kitchen-sink-plugin/sweep.sh
run_kitchen_sink_fixture_logged failure plugins install fixture --force
`,
);
expect(result.status, result.stderr).toBe(failure === "error" ? 47 : 124);
expect(result.stdout + result.stderr).toContain("help probe failed");
expect(result.calls).toEqual([["plugins", "install", "--help"]]);
});
it("returns the mutation exit status", () => {
const root = tempDirs.make("openclaw-consent-exit-");
const result = runShell(
root,
writeCandidate(root, { exitCode: 49 }),
`${fixtureCommand} plugins install fixture`,
);
expect(result.status).toBe(49);
expect(result.calls.at(-1)).toEqual(["plugins", "install", "fixture", consent]);
});
it("reprobes a replacement at the same executable path and version", () => {
const root = tempDirs.make("openclaw-consent-replacement-");
const entry = writeCandidate(root, { commands: [] });
writeCandidate(root, {}, "replacement.cjs");
const result = runShell(
root,
entry,
`${fixtureCommand} plugins install fixture
cp "$HOME/replacement.cjs" "$OPENCLAW_ENTRY"
${fixtureCommand} plugins install fixture`,
);
expect(result.status, result.stderr).toBe(0);
expect(result.calls).toEqual([
["plugins", "install", "--help"],
["plugins", "install", "fixture"],
["plugins", "install", "--help"],
["plugins", "install", "fixture", consent],
]);
});
});