openclaw/test/scripts/cli-invocation.test.ts
Peter Steinberger 99d08a6d92
test(ui,qa,scripts): remove low-value tests (batch d467) (#182570)
* test(ui): deslop q0178 tests

Remove the duplicate long-diff E2E layer: chat-session-diff-folding.real-gateway.e2e.test.ts covers the identical 64x680 fixture, unified/split rendering, and refresh collapse. session-diff-split.test.ts covers budget fallback and source preservation; chat-session-diff.e2e.test.ts covers deferred refresh.

Keep Mermaid touch, clipboard, and source-view expansion; markdown-mermaid-expansion.browser.test.ts owns click/keyboard activation and focus return. Remove success-only Mermaid/bootstrap media capture and compact the message fixture.

CI-only campaign: local oxlint, oxfmt, and diff checks passed; no tests or coverage run. Shard LOC 1310 to 1109 (15.3%); below the 40% target because remaining scenarios cover distinct behaviors.

* test(qa-lab): remove redundant q0174 product-proof layers

Port q0174 onto the campaign lane. The first source commit fb59ddd0922ae0e453987344a4b8be4d723ab551 is already present as 415487d52d22; apply the remaining cleanup from 43b21095aece1a8025b51228db60f96cc412344e without changing newer lane content.

Resolve the timing-map conflict by deleting only the inherited-policy entry, preserving the lane removal of the neighboring join-intro entry. Retain CLI SecretRef repair and exec gating, running-Gateway session cleanup, and manual worktree lifecycle proof; remove redundant product-proof layers.

Validation: oxlint and oxfmt --check passed on retained touched files; git diff --check and cached diff check passed. Independent read-only review found no actionable issues. Tests not run under the requested CI-only policy.

* test(ui): deslop q0180 tests

Remove duplicate RPC-envelope assertions and unasserted catalog screenshots. Use the shared page lifecycle for disk-space proof and one assertion helper for linked-session navigation before and after reload.

Remaining owner proof: browser-client.test.ts scopes only the session panel and sends transcript references only with its list; browser-panel-controller-input.test.ts captures a successfully navigated document when its tab snapshot fails; model-catalog-store.test.ts reuses pending catalogs until the native discovery owner invalidates them; chat-pane-model-catalog.test.ts revalidates the configured model catalog when the picker opens; cron-page.lifecycle.test.ts verifies configured agent-scoped model requests.

Preserve all 13 expanded cases: retry geometry/animation and text scale, export download/clipboard attribution, descendant network blocking through sandbox transitions, real response-body cancellation and error presentation, linked navigation identity/reload, dynamic reasoning/context convergence, accessible disk-pressure recovery, and retained/empty catalog warning isolation. These distinct browser behaviors prevent an honest 40% reduction. LOC 1054 -> 993 (-5.8%).

CI-only validation: plain oxlint exit 0, oxfmt and git diff --check passed. No tests, coverage, Testbox, typecheck, or build run.

* test(ui): remove redundant q0180 e2e layers

Delete chat-export-attribution and cron-agent-ownership E2E replays. Export transport/content remains covered by export.test.ts (readable download and speaker normalization), chat-send-submit.test.ts (local export commands), session-menu-navigation.test.ts (chronological transcript copy), and chat-message.forwarded-attribution.test.ts (never borrow viewer identity). Cron failure/empty recovery remains in cron-page.lifecycle.test.ts, draft preservation and event ordering in cron-page.catalog-notifications.test.ts, and catalog replacement/reopen in command-palette.test.ts. The palette model projection consumes models only, not catalog warning fields.

Retain browser screenshot authentication and visible failure, but remove Fetch instrumentation duplicated by browser-client.test.ts: rejects an unsuccessful screenshot without waiting for stream cancellation. Keep disk event-to-accessible-badge proof; chat-view.test.ts owns persistent disk guidance and recovery. Keep dynamic Chat convergence; new-session model-control tests own hydrated defaults/catalog rendering and metadata lifecycle. Drop request-count replays covered by chat-pane-model-catalog.test.ts and the catalog owner. Keep click and direct-link reload proof with colliding prefixes; the no-collision row takes the same full-UUID route branch.

Shrink only the deleted files entries in the private-server path list and E2E owner inventory. No production or helper changes. Original shard LOC 1054 -> 631 (-40.1%); this follow-up 993 -> 631. Eight expanded cases remain. CI-only: no tests, coverage, builds, or typechecks run. Plain oxlint exit 0, oxfmt, and git diff --check passed.

* test(ui): remove redundant q0172 lifecycle replays

Port q0172 from edd7ef1d27b7a9d29ba1340bbffb74c8f35eb854 and 0a4542847769cdc76fbcc911ec36bccf7ebfdaf5 onto the campaign lane. The first cleanup is already present; preserve it and all newer lane inventory changes while applying the remaining lifecycle replay removals and layout fixture cleanup.

Validation: plain oxlint, oxfmt --check, and git diff --check exited 0. Isolated review found no actionable issues. CI-only: no tests, coverage, Testbox, or check-changed were run.

* test(scripts): deslop q0177 tests

Reuse the existing HTTP fixture lifecycle in the installer proof and replace branching prefix fixture setup with explicit overrides. Preserve all installer, provenance, secret-handling, continuation, archive and prefix-stability contracts.

Drop the redundant explicit-true plugin fixture variant (default-enabled and disabled remain) and short-option budget replays (the retained long-option case reaches the same startsWith rejection). Keep budget-specific duplicate-option coverage.

CI-only campaign: formatting, diff-check and plain oxlint passed; tests and coverage deferred to hosted CI. The shard is already compact and the 40 percent LOC target was not achievable without dropping distinct boundary proof.

* test(scripts): consolidate q0177 owner-boundary proof

Remove the benchmark discovery lifecycle replay: src/gateway/server-discovery-runtime.test.ts owns starts registered local discovery services with gateway advertisement context and replaces advertisements and wide-area metadata across live mode and registry changes. Retain benchmark config and environment isolation here.

Keep formatted statistics and default plugin activation in test/scripts/bench-gateway-startup.test.ts, including requires seeded session access and clean shutdown only for populated fixtures and the teamBoot fixture assertions. Keep CI evidence emission in test/scripts/tsgo-core-test-shards.test.ts, including executes assigned root partitions serially through mixed CI and its transitive-consumer evidence cases.

Move runtime-log expectations into the saved-log CLI replay, consume the actual redaction result in notification proof, and assert timed report totals through the budget CLI. Share installer scenario orchestration and reuse the existing archive file builder without changing its public fixture interface.

CI-only: plain oxlint, oxfmt and diff-check passed. No tests or coverage run. Shard totals 1239 to 1087 lines; 40 percent remains unmet because the remaining independent security, platform, parser and lifecycle proof has no replacement.

* test(scripts): preserve installer failure propagation

Run the two POSIX installer subshells as standalone commands under set -e. The preceding consolidation put the healthy installer on the left side of &&, which can suppress errexit inside that subshell. Keep independent EXIT traps without changing the installer behavior under test. Static inspection only under the campaign CI-only instruction; no test runs.

* test(scripts): remove redundant q0177 metadata assertion

The retained real notify call reads the redaction-result file and requires a boolean notify_required before it can report the asserted skipped result. Remove the duplicate direct file assertion; preserve the notification-gating regression from 5a14b1c5c5.

Re-audited PASS 15 moves 1-5: no stale subjects, source-grep inventories, or further safe whole-case deletions remain. Existing distinct security, provenance, runtime accounting, and compiler lifecycle cases block the 40 percent target. Formatting, plain oxlint and diff-check pass; no tests or coverage run under CI-only mode.

* test(scripts): deslop q0179 tests

Trim same-branch documentation and runtime-path variants. Keep the import-cycle and Kysely regressions from 906eb2d2a9 and 38784a3aa5, plus the harness-runtime shutdown guard from 98813c2390.

Use a direct oversized command in the transport fixture; pr-crabbox-gate-plan.test.ts owns canonical command construction and forwarding. Retain transport hash, exact-byte, oversized payload, tamper and child-join coverage. Remove unused auth fixture timestamps and share legacy-owner assertions across absent and pre-v7 shared databases.

CI-only campaign: plain oxlint, oxfmt and diff-check passed; no tests or coverage run. 1200 to 1172 shard lines; below the 40% target because remaining suites protect distinct release, auth, CI and lifecycle boundaries. No production changes.

* test(scripts): reduce q0179 fixture and assertion layers

Derive copied release closures from entry points and reuse publication-source paths; retain explicit frozen-workflow inputs. Minimize SQLite auth fixtures to the actual queried schema while preserving all schema-owner and view-rejection scenarios.

Remove repeated path and Kysely preparation variants, diagnostic wording/file-count assertions, workflow-step traces, and the transport builder replay. The retained oversized Linux payload, tamper and cleanup cases own transport behavior; pr-crabbox-gate-plan.test.ts owns command generation. The startup corpus tier case in ci-node-test-plan.test.ts owns canonical startup inclusion. Keep exact runtime inventory and changed timing-root assertions.

CI-only validation: plain oxlint exit 0, oxfmt and diff-check clean. No tests, coverage, builds or Testbox. Shard totals 1200 -> 993 lines (17.3%); 40% remains unmet rather than deleting distinct security, release or lifecycle proof. No production changes or files deleted.

* test(scripts): deslop q0181 tests

Consolidate iOS fixture ownership and package-fixture subprocess assertions. Remove Android routing restatements covered by ci-workflow-planning tests for native Access job and gate selection.

Remove the CLI transport source grep: invocation behavior remains covered by cli-invocation.test.ts, node-runtime-recovery.test.ts (preserves invocation and replacement exit), and respawn-child-runner.process.test.ts (real transport completion).

CI-only campaign: formatting, diff check, and plain oxlint pass. No tests or coverage run. Retain distinct signing, release selection/evidence, SDK removal, correlation ordering, and transition ownership cases; the shard cannot meet 40 percent without deleting their only proof.

* test(scripts): remove redundant q0181 proof layers

Drop workflow wiring and copied inventories while retaining permission, evidence allowlist, tooling pin, executable shell guard, and native report/APK validation contracts. Package ignore behavior is asserted through the staged Git tree.

Replace the preload-byte snapshot with existing executable coverage in src/process/respawn-child-runner.process.test.ts, src/infra/node-runtime-recovery.test.ts, and test/scripts/bun-standalone-support.test.ts. Consolidate iOS canonical selection into the real metadata-parser fixture. Retain followup transition cases instead of repeating the same accepted merge/rebase branches; keep all refusal cases.

CI-only validation: plain oxlint, oxfmt, and git diff --check passed. No tests or coverage run. This second pass remains below the 40 percent target because the remaining suites retain distinct owner branches and shared merge assertion support.

* test(scripts): consolidate q0181 driving fixtures

Share standalone manifest setup across projection and admission tests without dropping metadata branches. Keep the three signing-team decision paths in a compact table and remove same-path SwiftLint invalid-name input variants.

Read-only sibling audit confirms CLI manifest admission, package offline installation/relocation, and committed merge/rebase resolve-undo ownership have no equivalent replacement tests. Shared PR assertion helpers also have out-of-shard callers and cannot be deleted without breaking them. No subject in this shard is retired.

CI-only: oxfmt, plain oxlint and diff check passed; no tests or coverage run. Total remains below the requested 40 percent reduction rather than deleting uniquely covered behavior.

* test(ui): deslop q0182 tests

Reuse existing board and response fixtures and consolidate repeated model filtering setup. Keep every scenario and distinct assertion: the remaining split-face URL poll subsumes the substring check, strict visible person locators subsume duplicate count checks, and the retained error-state filter cycle covers provider filtering.

* test(scripts): deslop q0183 tests

Keep one curl failure per native installer: exit 28 and 56 both terminate at the same set -e boundary, with no status-specific branch. Retain timeout flags, pinned URLs, checksum rejection, and version checks.

Share worker stdout/stderr capture and reuse the existing module-membership predicate without dropping stream closure, bounded output, bundle isolation, or restore/export assertions. CI-only campaign: formatting, diff check, and plain oxlint pass; runtime proof is deferred to hosted CI. Shard lines 1363 -> 1346; distinct contracts prevent the requested 40% cut.

* test(scripts): consolidate q0183 proof layers

Retain emitted stream isolation from regression d57f91b8fa while sharing owner assertions. The state-read-worker-stream fixture already asserts the selected emitted URL and ordered restore/export values; managed-child-process.drain and .output tests own process/pipe settlement. Retain managed command cleanup rather than reimplementing its observers here.

Keep one legacy-import scenario through recovery, retained blobs, and Git collection; generic immediate completion remains in pr-merge-outcome tests. Keep measured-large failure, empty-state equality, and explicit override budget branches. Drop duplicate tagged-reference input and process option restatements; keep inspection bounds and attestation rejection.

CI-only: plain oxlint, oxfmt, and diff check pass. No tests or coverage run. Shard 1363 -> 1275 lines, below 40 percent target; remaining proof covers distinct behavior, including security and regression boundaries.

* test(ui): deslop q0185 tests

Remove exact microphone-guidance replay covered by browser-talk-microphone-lifetime.e2e.test.ts, and numeric stepping/search dismissal replays covered by plugins-settings-keyboard.e2e.test.ts. Keep all consent decisions and whole-value schema branches.

Retire the mocked restart E2E layer: chat-pane-recovery.test.ts retains in-place recovery, rejected continuation, replacement navigation and draft preservation; chat-composer-disabled-banner.test.ts retains action dispatch and busy rendering. Both owner layers cover the original #173579 regression.

Keep sidebar keyboard, pushed-update and narrow viewport proof; session-hovercard.test.ts owns PR ordering/states/checks and session-progress-hovercard-target.test.ts owns pushed rate-limit notices. Keep tweet media, missing-preview and long-text paths; remove ordinary text rows already exercised by media and long-text cases.

Remove screenshot-only artifact generation and duplicate pagination failure diagnostics, preserving assertions and shared harness capture. CI-only campaign: no runtime or coverage runs. Static oxlint, oxfmt and diff checks pass. Test LOC 1120 -> 749 (33.1%); declarations 11 -> 7; expanded cases 25 -> 14. Below the 40% line target because remaining flows are distinct.

* test(media): deslop q0184 tests

Remove redundant media-understanding and fetch scenarios while retaining real CLI, proxy, channel lifecycle, SSRF, redirect and prepared music-provider integration proof.

Audio selection remains covered by src/media-understanding/attachments.guards.test.ts, apply.test.ts (preflight skips and last-preference transcript ordering), and audio-preflight.attachments.test.ts (configured selection and limits). Native vision handoff remains in runner.vision-skip.test.ts; current-turn-images.test.ts covers native bytes and described-image suppression.

Music fallback, unsupported controls and duration normalization remain in src/music-generation/runtime.test.ts. src/agents/tools/music-generate-tool.test.ts covers explicit deployment refs, ignored-override warnings and normalized-duration details. Keep prepared-provider invocation, audio persistence and invalid-format rejection here.

Both fetch overflow paths and empty-store cleanup remain in src/media/fetch.test.ts: cleans up streamed/content-length media overflow before a response clone is released. Remove the obsolete native-catalog mock baseline entry.

CI-only: no tests or coverage run. oxfmt, plain oxlint and git diff --check passed. Shard test lines: 1239 -> 1026 (17.2%); declarations: 12 -> 9. Below the 40% target because remaining transport, lifecycle and security compositions have no equivalent stronger proof.

* test(media): consolidate q0184 owner-boundary proof

Reuse the existing QA mock image endpoint instead of a second controlled server. Keep actual image delivery and internal completion provenance. Duplicate running/recent task interleavings remain in src/agents/tools/image-generate-tool.test.ts: starts image generation asynchronously and keeps the selected filename on completion; returns active status for a duplicate image request with the same prompt; returns recent status for a repeated image request after fast task completion.

Keep actual CONNECT routing and upstream socket identity. Oversize-before-provider execution remains in src/media-understanding/apply.test.ts: marks audio exceeding maxBytes in audio-only mode. Multipart model and file serialization remain in extensions/openai/media-understanding-provider.test.ts: builds the expected request payload.

Keep real cross-origin redirect rejection, empty-store checks and sensitive-header stripping. Initial private-address rejection remains in src/infra/net/fetch-guard.ssrf.test.ts: blocks private URLs and redacts their path, query and fragment from audit logs. Described-image suppression remains in src/auto-reply/media-note.test.ts and src/auto-reply/reply/current-turn-images.test.ts.

Retain prepared music-provider invocation, real persistence and invalid-format rejection; control normalization and forwarding remain in src/music-generation/runtime.test.ts and src/agents/tools/music-generate-tool.test.ts. Minimize unused catalog/override fixtures and remove redundant request cursor setup on a fresh mock server.

CI-only: no tests or coverage runs. Plain oxlint, oxfmt and git diff --check passed. Original shard: 1239 -> 740 test lines (-40.3%), 12 -> 8 declarations. This follow-up: 1026 -> 740 lines. All seven scenario test paths remain; no production or shared helper changes.

* test(scripts): consolidate q0179 owner-level proof

Port the remaining q0179 consolidation onto the campaign lane. The first two shard commits are already present as 8b5d5956ef16 and 6977002b3a0a. Preserve the lane removal of obsolete locale artifact-fault variants.

CI-only validation: plain oxlint and oxfmt --check passed on all nine touched files; git diff --check passed. Independent review found no actionable P0-P2 findings. No tests or builds were run.

* test(scripts): deslop q0186 tests

Reuse script fixture files and centralize release planning setup without deleting cases. Recovery retains persisted report, exact call count, unique nested IDs and exec/wait checks; remove redundant read/write spy ordering and cleanup assertion. Lifecycle cleanup remains covered by src/agents/code-mode.catalog-lifetime.test.ts and code-mode.test.ts.

CI-only validation: plain oxlint, oxfmt and git diff --check. No tests or coverage executed. Shard LOC 1201 to 1174; 40 percent target not reached because remaining suites exercise distinct release, isolation, cancellation and parser behavior.

* test(scripts): remove redundant q0186 proof layers

Delete the recovery matrix transport replay. code-mode-matrix-recovery-tasks.test.ts retains settlement effects, reconciliation, report persistence and negative controls. src/agents/code-mode.agent-loop.test.ts retains parked-error recovery without replay and partial-effect recovery; code-mode.wait.test.ts retains suspension ownership, original call identity and non-replay.

Keep report grouping and unlimited-output assertions without repeating the full header snapshot. Drop duplicate registry catch-path rows; npm-publish-plan.test.ts retains missing-selector classification and plugin-npm-prepared-release.test.ts retains 404 propagation. Keep malformed selectors, both package identity checks, byte integrity and size rejection. Default runtime still exercises the Bun gateway branch; test-runtime.test.ts owns explicit runtime selection. Keep one caller-cache override rather than replaying it with the string zero.

CI-only: oxfmt, plain oxlint and diff check pass. No tests or coverage run. Cumulative shard LOC 1201 to 1058; 40 percent target remains unmet. No obsolete subjects or further equivalent security/lifecycle proof found.

* test(scripts): consolidate q0186 owner-boundary proof

Drop duplicate skip-report CLI and grouping cases: inventory-report-cli.test.ts owns argument parsing and unlimited output; test-env-mutation-report.test.ts owns the shared grouped renderer. Keep skip-specific classification and empty output.

Consolidate same/new Android train execution in one two-row table with version, properties, plist and release-note assertions. Shared flag behavior remains in arg-utils.test.ts and android-version.test.ts. Private handoff observations now follow successful injection and store preflight, retaining independent launch matching and per-consumer witnesses.

CI-only: oxfmt, plain oxlint and diff check pass; no tests or coverage executed. Cumulative shard LOC 1201 to 970. Further reduction to 40 percent was not supported by equivalent retained boundary proof.

* test(ui): deslop q0187 tests

Keep browser coverage for blocked submission, IME/stale metadata, disappearing usage selections, avatar timeout layout, and rail geometry. Drop repeated scenario variants covered by the retained scenarios and owner tests.

Remaining owner proof: pages/new-session/composer.test.tsx (disabled Start reports attempts); pages/chat/components/chat-pane-header.test.tsx and e2e/session-management.rename.e2e.test.ts (IME and ordinary Escape); components/agent-select.test.ts and components/solid/identity-avatar.test.tsx (loaded/error avatar state and rerenders); packages/normalization-core/src/utf16-slice.test.ts (ASCII and surrogate truncation). All UI paths are under ui/src/.

CI-only campaign: oxfmt, plain oxlint, and git diff --check pass. No runtime tests or coverage executed. Retain distinct lifecycle, accessibility, geometry, and persistence scenarios; line reduction is below the 40 percent target.

* test(ui): deslop q0189 tests

Remove the Agents alias replay: pages/agents/view.test.tsx covers canonical names and configured aliases, and pages/agents/agents-page.test.ts covers loading the selected agent's configured catalog once. Retain the chat picker case for provider-suffix rendering and replace manual context cleanup with the suite-owned page lifecycle.

* test(ui): remove redundant q0189 e2e layers

Remove the llama.cpp provider replay: model-setup-lmstudio.e2e.test.ts owns the shared prepare, confirm, activate and settings flow. pages/model-setup/view.test.ts covers llama.cpp choices and icons; configured-model.test.ts covers its model state; model-setup-page.test.ts covers autonomous progress; model-setup-explicit.e2e.test.ts keeps consent; components/modal-dialog.browser.test.ts owns animation visibility.

Keep runtime budgets in Chromium. Delegate denied choices and default reset to pages/chat/components/chat-model-policy.test.ts, pages/chat/chat-model-runtime.test.ts and lib/chat/model-select-state.test.ts. Delegate private mirror hydration to components/session-background.test.ts and app/server-prefs.background.test.ts while retaining first-paint requests.

Keep rejected publication, prepared history, draft and canonicalization in one browser flow. route-prepared-identity.test.ts retains conflicting resolver and ordinary resolution branches. pages/custodian/custodian-session-store.test.ts owns persistence across stores; keep Home and palette navigation in Chromium. Reuse equivalent context and frame helpers; keep all ACK interleavings and typing work assertions.

CI-only: no tests, coverage or builds run. Plain oxlint, oxfmt and diff-check pass. Original shard 936 lines to 559 lines (40.3 percent reduction); only deleted-path entries removed from CI owner and timing lists.

* test(scripts): deslop q0188 tests

Remove copied suppression inventory and redundant planner serialization/prerequisite assertions. ci-changed-node-test-plan.test.ts retains canonical paired-owner checks and the Git tooling codec/prerequisite round trip. Keep precise selection ownership here.

Consolidate empty changelog proof at the Android release-note boundary, share model assertions and createRequire fixtures, and simplify preflight rejection setup without removing release safety cases.

CI-only campaign: oxlint, oxfmt and diff checks pass; runtime tests and coverage deferred to hosted CI. Reduction is 81 of 1194 lines (6.8%); retain distinct safeguards rather than force the 40% target.

* test(scripts): consolidate q0188 tooling proof layers

Remove the filename-based memory import graph, stale host-literal inventory, provider metadata restatements and repeated planner/runtime matrices. Keep curl limits at the executable boundary and reuse filesystem/Git fixtures.

Remaining owner proof: ci-changed-node-test-plan.test.ts retains delivery-cache coverage and private QA preparation, plus precise TUI/Git owner selections; ci-run-node-test-shard.test.ts keeps changed qualification inputs on Vitest; onboard-config-fixtures.test.ts writes guided skip-UI fixtures through the same mock config owner.

Keep unique store identity/exhaustion, auth retry/cooldown, ref-integrity, syntax scanner and release dispatch guards. Static lint/format/diff checks pass; no test or coverage runs in CI-only mode. Original shard 1194 lines -> 840 (29.6%): 40% target remains unmet.

* test: repair d467 fixtures and preserve private-target proof

* test: type optional lint fixture lines explicitly
2026-10-24 12:49:17 +00:00

131 lines
5 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { afterEach, expect, it, vi } from "vitest";
import * as cli from "../../cli-invocation.mjs";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const parentExecArgv = process.execArgv;
afterEach(() => {
process.execArgv = parentExecArgv;
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});
const execPath = "/synthetic/node";
const entryPath = "/synthetic/openclaw.mjs";
const execArgv = ["--import", "synthetic-loader"];
it.each([
["native source execution", undefined, undefined, [], false],
["loose source loader", undefined, undefined, execArgv, true],
["embedded executable", "file:///synthetic/runtime/", undefined, execArgv, false],
["standalone disk script", "file:///synthetic/runtime/", "1", execArgv, true],
] as const)("detects source loaders: %s", (_label, root, bunBeBun, argv, expected) => {
vi.stubGlobal("__openclawStandaloneRootUrl", root);
vi.stubEnv("BUN_BE_BUN", bunBeBun);
vi.stubEnv("NODE_OPTIONS", "");
process.execArgv = [...argv];
expect(cli.runtimeHasSourceLoader()).toBe(expected);
});
function expectInvocation(
options: Parameters<typeof cli.resolveOpenClawCliInvocation>[0] & { args: string[] },
prefix: string[] = [],
) {
expect(cli.resolveOpenClawCliInvocation(options)).toEqual({
command: options.execPath ?? process.execPath,
args: [...prefix, ...options.args],
});
}
it.each([
["loose", execPath, entryPath],
["explicit", "/synthetic/openclaw-wrapper", null],
["embedded", process.execPath, "/synthetic/runtime/openclaw.mjs"],
] as const)("preserves arguments and chooses the %s CLI prefix", (mode, command, entry) => {
if (mode === "embedded") {
vi.stubGlobal("__openclawStandaloneRootUrl", "file:///synthetic/runtime/");
}
const args = ["--profile", "fixture", "doctor", "--non-interactive"];
expectInvocation(
{ execPath: command, entryPath: entry, execArgv, args },
mode === "loose" ? [...execArgv, entryPath] : [],
);
});
function manifestFixture(extra = {}) {
const root = tempDirs.make("cli-manifest-");
const executable = path.join(root, "renamed-cli");
const runtime = path.join(root, "runtime");
const manifest = {
schemaVersion: 1,
mode: "jit",
executable: { path: "renamed-cli", sha256: "a".repeat(64) },
runtime: { path: "runtime", sha256: "b".repeat(64) },
openclaw: { version: "1", commit: "c".repeat(40) },
bun: { version: "1" },
...extra,
};
const file = path.join(root, "manifest.json");
const write = (value: unknown) => fs.writeFileSync(file, JSON.stringify(value));
write(manifest);
return { root, executable, runtime, manifest, file, write };
}
it("projects build facts but trusts the executing image over its sidecar", () => {
const build = {
mode: "aot",
openclaw: { version: "2026.10.6", commit: "a".repeat(40) },
bun: { version: "1.4.4", revision: "compiler-revision" },
target: { platform: "linux", arch: "arm64" },
build: {
id: "matching-build",
flavor: "standard",
pluginSetSha256: "b".repeat(64),
moduleGraphSha256: "c".repeat(64),
},
plugins: { embedded: ["codex"], omitted: ["matrix"] },
nativeModules: [
{
name: "sqlite",
version: "1",
artifacts: [],
qualification: "unverified",
backend: "sqlite",
},
],
};
const { executable, file } = manifestFixture(build);
expect(cli.getStandaloneRuntimeBuild(executable)).toMatchObject(build);
vi.stubGlobal("__openclawStandaloneBuildInfo", build);
fs.writeFileSync(file, "corrupted sidecar metadata");
expect(cli.getStandaloneRuntimeBuild()).toEqual(build);
});
it("uses renamed manifests and rejects unrelated or invalid runtime metadata", () => {
const { root, executable, runtime, manifest, file, write } = manifestFixture();
const invalid = (read: () => unknown) => expect(read).toThrow("Invalid standalone");
expect(cli.readStandaloneRuntimeManifest(executable)).toEqual(manifest);
expect(cli.readStandaloneRuntimeManifestForRoot(runtime)).toEqual(manifest);
invalid(() => cli.readStandaloneRuntimeManifestForRoot(path.join(root, "unrelated")));
expectInvocation({ execPath: executable, args: ["gateway"] });
expectInvocation({
execPath: executable,
entryPath: path.join(runtime, "openclaw.mjs"),
execArgv: ["--max-old-space-size=4096"],
args: ["--profile", "fixture", "gateway", "--port", "18790"],
});
expect(() =>
cli.resolveOpenClawCliInvocation({
execPath: executable,
entryPath: path.join(root, "other-install", "openclaw.mjs"),
args: ["doctor"],
}),
).toThrow("cannot run another installation");
write({ ...manifest, executable: { path: "another-runtime" } });
invalid(() => cli.isStandaloneRuntimeExecutable(executable));
write({ ...manifest, executable: { ...manifest.executable, path: "../outside" } });
invalid(() => cli.readStandaloneRuntimeManifestForRoot(runtime));
fs.writeFileSync(file, "{");
invalid(() => cli.readStandaloneRuntimeManifest(executable));
});