openclaw/test/scripts/bundled-plugin-runtime-command.test-support.ts
Peter Steinberger 1b85a3ee63
test(ui,scripts): remove low-value tests (batch d460) (#182505)
* test(ui): deslop q0089 tests

Remove duplicated E2E layers while retaining browser geometry, keyboard/pointer behavior, requester isolation, replay ordering, and both real-Gateway scenarios.

Remaining owners: chat-composer-mobile.browser.test.ts retains first-pointer focus; chat-state-terminal-recovery.test.ts retains missed terminal publication recovery; tool-stream-preamble.commentary-dedupe.node.test.ts retains identified commentary across reconnect; link-reader-page-preview.test.ts and link-reader-prefetch.test.tsx retain disabled-fetch and specialized-link exclusions; desktop-panel.test.ts retains takeover demotion; terminal-panel.test.ts retains selected-agent opening, missing-PTY retirement, and OSC theme replies; app-host.dock-suppression.test.ts retains route-owned terminal agent selection; app-sidebar.pinned-agents.test.ts and creator-flow.test.ts retain saved pins, pin menus, and successful Claw creation in the existing main session. Equivalent Android and embedded-host picker rows remain in this shard.

CI-only campaign: oxfmt, git diff --check, and plain oxlint passed. No test, coverage, build, Testbox, or typecheck run. Reduction stops below the 40 percent target because the remaining browser-specific and real-Gateway scenarios have distinct proof; do not infer measured coverage preservation.

* test(ui): finish q0089 structural reduction

Cumulative shard test LOC: 3411 to 2043 (-40.1%). Keep the earlier shard commit intact. CI-only mode: no tests, coverage, builds or typechecks run; oxfmt, plain oxlint and diff-check pass.

Retire the stream-reconciliation browser replay layer: chat-state.test.ts covers durable-answer finishing, before/after persistence and reconnect replay; chat-history.inflight.test.ts covers steered hydration; chat-history-cursor.test.ts covers commentary recovery and replay; chat-gateway.test.ts covers duplicate terminal delivery. chat-flow.messaging.e2e.test.ts, chat-flow.streaming.e2e.test.ts and chat-error-details.e2e.test.ts retain DOM delivery, streaming and diagnostic presentation. Remove only the deleted path from owner inventory and timing data.

Keep requester-only navigation and app-view delivery. Desktop pending/show/control preservation lives in desktop-panel-session.test.ts and desktop-panel.test.ts; portal pending and iframe continuity live in portals-page.test.ts. Terminal keyboard interception remains in app-host.panel-toggles.test.ts and terminal-runtime.e2e.test.ts; configured build exemption remains in build-info.test.ts. Retain stale-build admission and native-auth close-target geometry in this shard.

Merge paused-ResizeObserver expansion geometry into the retained visualization ordering scenarios, preserving the regression from 3e4daf8497. Remove repeated link metadata variants covered by link-reader-page-preview.test.ts and link-reader-hovercard.test.ts; keep cold activation identity, missing-runtime fallback, actual browser navigation and long-content bounds. Camera lifetime and audio policy stay in chat-camera-capture.test.ts and chat-attachment-inputs.test.tsx; file/photo cancellation and removal stay in chat-attachment-focus.e2e.test.ts and chat-attachment-remove.e2e.test.ts. Keep platform selection, native capture, picker anchoring and real camera confirmation.

Trim screenshot-only scaffolding from real-Gateway wait-clock and quota tests without removing their lifecycle, catalog-generation, credential, CLI or Ready-status assertions. Remove repeated sidebar inventory assertions already owned by app-sidebar.pinned-agents.test.ts; keep responsive geometry and focus restoration.

* test(macos): deslop q0090 tests

Reuse the existing Mac case lifetime adapter, shared source-restoration assertions, and generated fault flag names while preserving all 124 expanded cases.

Drop repeated source inventories covered by the rendered-launchd test, legacy upgrade/recovery scenario, and mac-elevation-artifact policy and receipt-binding tests. Retain unique signing, publication, ownership, and lifecycle guards.

* test(macos): consolidate q0090 owner proofs

Replace owner-conflict and degraded-TCC source greps with executable lifecycle cases. Keep only compact authority, launch-context, immutable-output, and signer-identity guards.

Remove metadata success and format replays covered by the real Apple-signed metadata case and mac-elevation-artifact native-format cases. CUA verification remains in preserves CUA omission for a driver; installation rejection remains here. Move both architecture receipt assertions into recovers the prior app and source job, and remove the redundant simulated-host replay.

Share plist fixture serialization and collapse schema rejection variants to one case per branch. CI-only: formatting, plain oxlint, and diff checks only; 40 percent line target remains unmet to preserve distinct custody and recovery proofs.

* test(scripts): deslop q0092 tests

Remove redundant SDK entry inventory assertions. The tsdown-config suite owns canonical public declaration selection; the retained writer scenario still checks emitted outputs, private-QA selection, cache restoration, and nominal identity. No execution paths or test cases removed. CI-only audit; runtime and coverage verification deferred to hosted CI.

* test(scripts): consolidate q0092 boundary fixtures

Remove redundant hook and cache inventories, log/call-shape replays, and same-branch mobile input variants. Share pinned-browser, cancellation-settling, termination, and direct-timeout/output fixtures while keeping distinct authority and lifecycle paths.

Retired SDK fixture-guard replay remains covered by native-declaration-boundary rechecks and tsdown-declaration-resolution package-symlink refusal. Transitive syntax failure remains covered by tsdown-build successful-native-declarations preservation. Mobile lost-response reconciliation remains in the generic lost-push readback cases. Native EPERM cases retained for regression 478ac90c3e.

CI-only: oxfmt, plain oxlint, and git diff --check passed; no tests or coverage run. Shard remains above the requested 40 percent reduction because remaining process, publication, and compiler paths have distinct behavior.

* test(scripts): reduce q0092 transport and recovery layers

Use a real local gh stub instead of emulated child-process streams, reuse qualification fixture responses, and let fixture-lifetime own native signal cleanup. Consolidate Firebase publication/recovery and retain one auto-recovery transport scenario instead of replaying it for every cancellation variant.

SDK cross-root cache behavior remains in build-all restores-exact-declaration-snapshots and build-artifact-cache declaration-locality tests; keep a small SDK warm-restore check. Asset timeout ownership remains in plugin-npm-runtime-assets; retain the bundled caller dispatch guard. Native EPERM regression coverage remains intact. CI-only: formatting, plain oxlint and diff-check passed; no test runs. The 40-percent target remains unmet to preserve distinct behavior.

* test(gateway): deslop q0095 tests

Reuse existing HTTP proof helpers, consolidate goal-state waits, and share the ClawRouter patch fixture with its persistence assertions. Preserve all scenario cases, assertions, and timeout budgets; remove only redundant fixture plumbing and the assertion-free success report.

* test(gateway): remove redundant q0095 proof layers

Reduce the shard from 3129 to 1875 lines (40.08%), retaining native restart, revocation, startup-handle, and both real WAL-alias proofs. No cases are folded; validation is formatting, plain oxlint, and diff checks only under the campaign CI-only rule.

Remove the heartbeat executor proof retired by 5b4f83ce86. Ordinary wake now routes through cronWakeHandler, not the retired heartbeat runner.

Completion replay remains covered by src/agents/subagents/completion/subagent-completion-replay.test.ts: persists a blocked completion across the announce tail and reopen; does not wake again after ordinary announcement recovery permanently fails; rechecks blocked completion after wake admission.

Compaction remains covered by packages/ai/src/transports/openai-responses-compaction-replay.test.ts (instruction and transcript ordering), src/worker/worker.fault-injection.test.ts (captured compaction through commit and canonical reopen), and src/agents/embedded-agent-runner/run/terminal-resolution.test.ts plus run.session-prompt-state.test.ts (checkpoint and reasoning continuation). Non-terminal identical writes remain in src/agents/sessions/tools/write.test.ts; next model turns remain in agent-session-loop-correctness.test.ts.

Elapsed node deadlines remain in src/gateway/node-registry.test.ts (shared pairing/serialization budget, early hard timer) and server-methods/nodes.invoke-wake.test.ts (late wake deadline). Keep the multi-acquisition scenario consumed by test/helpers/gateway-multi-acquisition.test.ts.

Removed CLI path variants remain in src/commands/doctor-sqlite-maintenance-lock.test.ts and doctor.test.ts. Backup redaction, empty history, and invalid HEAD remain in src/snapshot/git-backup.test.ts. Keep the CLI-specific startup and configured/shared WAL checks. Drop redundant shutdown budget/count log snapshots while retaining native deadline, receipts, process counts through the provider, and recovery assertions.

* test(scripts): deslop q0094 tests

Remove the duplicated package-directory suite; all 11 cases remain in test/scripts/test-projects-package-directories.test.ts. Retain the generic complete, exactly-once agent selection check instead of its copied owner example inventory. Reuse the existing run-plan fixture for expected plans.

* test(scripts): remove duplicate q0094 planner layers

Keep runtime preparation and reader admission proof in test-projects-build-admission.test.ts, canonical ownership in vitest-projects-config.test.ts, and Matrix/Telegram process-budget proof in extension-test-plan.test.ts. Remove duplicate helper matrices and copied route inventories, retain a representative semantic Docker union, and factor repeated exact source-fact defaults.

* test(scripts): deduplicate q0094 routing contracts

Drop separator replay covered by test-projects-routing.test.ts and copied YAML owner tables covered by the retained generic semantic/reference union. Keep representative prompt-snapshot routing branches and one deduplicating package-fixture selection flow.

* test(ui): deslop q0093 tests

Remove the keyboard suite copied into desktop-panel-fullscreen; desktop-keyboard.e2e.test.ts retains the identical wire, modifier, Unicode and control-handoff cases. Keep the identical alias retry/reconnect lifecycle in device-alias-rename.e2e.test.ts only.

Keep composition guards for Enter and Escape with both IME signals. Normal key navigation and selection remain in chat-model-picker.browser.test.tsx (windows a large catalog while keeping keyboard choices and accessible positions live; focuses the filter on keyboard open and reopen, and returns Escape to the trigger) and the retained provider-search scenario. Use the suite-owned page lifecycle instead of duplicated shared-page cancellation code.

The catalog-refresh slash-selection scenario subsumes the plain slash click. Transcript stale-result, blank-query and request-error paths remain in sessions-page.typing.test.ts; retain the distinct empty-indexing retry E2E. Share memory page options and capture setup without dropping its assertions.

CI-only: oxfmt, plain oxlint and diff checks passed. No tests, builds or coverage run. Shard LOC 3705 -> 3089; declarations 39 -> 32; expanded cases 92 -> 69. Below the 40 percent line target: retain distinct credential, recovery, layout, roster and terminal lifecycle proof.

* test(ui): remove redundant q0093 e2e layers

Delete memory-settings-engine E2E and its single CI owner watch. Busy-radio focus and blocked repeat input remain in settings-ui.browser.test.tsx: keeps a busy radio focused and ignores arrow keys until the save settles. Memory ordering, rejected writes and Off draining remain in memory-page.test.ts: defaults to the disabled slot owner and offers Enable even when another engine is enabled; persists Off and drains its autosave before re-enabling memory; reports a rejected engine change instead of silently snapping back. Missing-engine selection remains in memory.test.tsx: keeps a configured missing engine selected and labels it unavailable. Committed add-on refresh failures remain in memory-mutation-owner.test.ts: clears a rendered refresh warning after reconnect without losing its runtime warning; clears a %s refresh warning when %s refreshes authoritative configuration.

Keep only the distinct empty-indexing retry transcript E2E. sessions-page.typing.test.ts covers bounded cross-agent submission, off-roster result provenance/navigation, clear and archive scope retirement. control-ui-e2e.sessions.search.test.ts covers scoped search snapshots beyond held list pages. Keep picker geometry and IME E2E; search/disclosure persistence and keyboard navigation remain in chat-model-picker.browser.test.tsx: retains provider toggles after closing through %s; windows a large catalog while keeping keyboard choices and accessible positions live; focuses the filter on keyboard open and reopen, and returns Escape to the trigger.

Sidebar selected-child refresh remains in app-sidebar.lineage-freshness.test.ts: keeps the fresh selected child when $first finishes first. Ordering/expansion remain in app-sidebar-session-navigation-logic.test.ts (sorts timestamped sessions newest-first ahead of legacy sessions), app-sidebar-session-projection.test.ts (reveals newly added children while preserving a manual collapse through ordinary updates), app-sidebar-cases/child-sessions.ts (nests the selected spawned session and reveals its active ancestor path through a run), and sidebar-cached-list-stability.e2e.test.ts (keeps expanded spawned-session rows still through cached replay).

Merge mobile header/navigation assertions into opens the team composer directly from %s and trim unused transcript fixture data. Newer focus during delayed Side chat mount remains in chat-session-companion-manual-open-focus.e2e.test.ts: honors focus intent in %s while a manually opened Side chat loads; alias parsing remains in companion-question.test.ts: extracts both Control UI command aliases. Remove only the duplicate startup worker version probe, covered by service-worker-lifecycle.test.ts: answers a resumed document from embedded build identity, not the registering URL. Keep all private-work reload authority checks. Compare terminal screenshot bytes directly instead of hashing them.

CI-only: plain oxlint, oxfmt and git diff --check passed; no tests, coverage or builds run. Follow-up shard LOC 3089 -> 2222; cumulative 3705 -> 2222, down 40.03 percent. No production changes.

* test(ui): deslop q0097 tests

Remove duplicate update confirmation, retry, dismissal, and native authority scenarios. Keep those browser contracts in update-confirmation.e2e.test.ts and update-native-authority.e2e.test.ts; Git target refresh remains in update-confirmation and revision layout remains in update-git-revisions.e2e.test.ts. Retain coalesced progress, disconnect ordering, mobile permission guidance, and native decline after sidebar retirement here.

Keep configured catalog failure/recovery at model-providers-page.catalog.test.ts (actionable warning, retained choices, Retry recovery) and load.test.ts (catalog failure retention). Keep retained-snapshot effort recovery in the first model-catalog-partial-refresh scenario, including the no-write assertion. Remove optional screenshot recording layers while preserving geometry and interaction assertions.

CI-only campaign: plain oxlint, oxfmt, and git diff --check pass. No tests or coverage executed. Shard lines 3783 to 3001 (20.7 percent); below the 40 percent target because remaining scenarios protect distinct browser, consent, ordering, and state-retirement behavior.

* test(ui): remove redundant q0097 E2E layers

Keep transcript grouping and hidden-heartbeat identity in chat-agent-run-grouping.test.ts and footer copy/reply proof in components/chat-transcript-render.test.ts. Keep subagent settlement and resumption in chat-subagent-waiting.e2e.test.ts and panel transitions in chat-sidebar-region.test.ts and chat-pane-sidebar-layout.test.tsx; retain browser lazy-load and disclosure checks.

Keep native consent, mandatory sandbox, and cancellation variants in chat-model-restriction-recovery.test.ts. Retain first-message durable/volatile recovery and attachment cancel/retry E2E proof. Permission event/default projection stays in lib/sessions/session-permission-projection.test.ts; retain refresh-error presentation and browser geometry.

Device eligibility and runtime requirements remain in device-placement.test.ts, where-chip.test.ts, and model-control.test.ts. Locked-model rendering remains in chat-pane-session-controls.test.tsx, alongside the retained E2E effort and unavailable-catalog checks. Trim unused layout fixture inventory and consolidate same-path usage setup.

CI-only: no test, build, typecheck, or coverage runs. Plain oxlint, oxfmt, and git diff --check pass. Additional cut 3001 to 2267 lines; cumulative cut from 3783 is 40.1 percent.

* test(scripts): deslop q0096 tests

Share iOS timeline subprocess and expected-record setup, trim unused Mantis fixture metadata, and remove Docker call-shape and step-name assertions. Retain every test case and all publication, path, timeout, privacy, and lifecycle boundaries.

The Docker malformed-config cases still assert the platform-specific failure, cause, and zero writes. The digest-pinning case and workflow approval/checkout/permission guards remain; only redundant command-shape and step inventory checks are removed.

CI-only validation: plain oxlint, oxfmt, and git diff --check pass. No tests or coverage run. Shard LOC 3150 -> 3090 (1.9%); the 40% target was not safely achievable without removing distinct behavior.

* test(scripts): deslop q0096 proof layers

Replace the cron Gateway simulation with installed-SDK seeding proof and a focused service-install credential-stripping test. Bootstrap ordering remains in upgrade-survivor-baseline-order.test.ts (preserves all migration rows after baseline setup), authored config restoration in upgrade-survivor-config-parking.test.ts, and debris handling in upgrade-survivor-plugin-dependencies.test.ts.

Remove package-import CLI replays: runtime imports around JSDoc retains static, named/re-export, dynamic and CommonJS parsing; sloppy CommonJS retains top-level return; limits URL dependencies retains outside-dist exclusions; the bounded-heap CLI case retains missing-import failure propagation. Keep the named re-export edge in the retained parser case.

Replace Docker job/permission inventories with a generic single approved writer and trusted-checkout invariant. Release publication is owned by docker-release-artifacts.test.ts (keeps preparation unable to publish and serializes the single approved writer). Shrink Mantis fixtures; setup-failure artifact retention also remains in mantis-web-ui-chat-evidence.test.ts.

Delete same-branch CLI extra-argument, known phase, invalid selector, cron count/owner variants, duplicate pooled round-trip and Bun call-shape/source-text assertions. Keep public translation-memory records exact while dropping serialization/layout restatements.

CI-only: oxfmt, plain oxlint (exit 0), and git diff --check passed. No tests, coverage or builds run. Additional 211 lines removed; original shard 3150 -> 2879 (-8.6%), below the 40% target. Retained cases have distinct parser, transport-bound, authority, refresh/provenance or lifecycle paths with no replacement proof found.

* test(scripts): consolidate q0096 fixtures and duplicate proof

Keep image config errors at vercel-container-registry-publish.test.ts (rejects before copying an attested source), and executable closure at managed-handoff-build-config.test.ts (built artifact and missing-helper control). Bun lifecycle and signal-forwarding tests retain duplicate launcher/platform contracts.

Share Mantis, SDK receipt, i18n and package fixtures; retain distinct publication, privacy, parsing and lifecycle boundaries. Restore shallow-glob and optional-link proof during preservation review. CI-only: plain oxlint, oxfmt and diff-check passed; no tests run. Original shard 3150 -> 2637 lines, below the requested 40 percent reduction.

* test(scripts): deslop q0098 tests

Deduplicate database-ratchet filesystem fixtures and diagnostics. Remove redundant numeric failure, publish-attempt, latest-rerun, transport cross-product, and mock-wiring cases while retaining distinct authority and lifecycle paths.

Remaining proof: performance final-attempt reconciliation and terminal-failure cases; signed-attempt npm recovery; real plain-gh bytes/streams/limits and refusal tests; correction transport and gate cases; pr-review-artifacts.node.mjs final UI runtime-path regression. Preserve separate PR-number and head-SHA identity checks.

CI-only: oxfmt, plain oxlint, and diff-check pass; tests and coverage not run. Honest line reduction is below 40% because remaining fixtures and cases protect distinct security, provenance, publication, and cancellation contracts.

* test(scripts): remove redundant q0098 fixture layers

Remove signed correction setup covered by pr-baseline-publication and pr-baseline-signing, stubbed review-guard failure covered by pr-main-refresh, and runtime classification replay covered by pr-review-artifacts.node.mjs. Retain all post-proof authority mutation axes.

Drop the duplicate database wrapper case covered by database-worker-compat and the mutable rerun replay covered by release-beta-verifier. Consolidate plain-gh route and color assertions into existing credential cases; remove fixture-only ESM nesting and exact Windows call/count inventories.

CI-only validation: oxfmt, plain oxlint and diff-check passed. No tests or coverage run. Shard remains below the 40 percent target; remaining distinct security, provenance, lifecycle, and shared-fixture contracts retained.

* test(scripts): prune remaining q0098 case replays

Keep hung-process lifecycle proof while removing ordinary-failure replays covered by the concurrent-writer and exhausted-fetch cases. Reuse the main-refresh fixture review and remove duplicate npm validation and invocation assertions enforced by the strict response fixture.

Trim ACL input variants that take the retained read-only, write, and inherited-allow branches. Keep ownership, generic-all, delete, directory-delete, unsupported ACE, signature, service identity, and reparse boundary cases.

Static-only validation: oxfmt, plain oxlint and diff-check passed; no tests or coverage run. Cumulative shard reduction is 3275 to 3057 lines, short of 40 percent. Further deletion requires dropping distinct behavior or changing the allowed scope.

* test(ui): deslop q0099 tests

Remove duplicate first-paint cases already covered by background-startup.e2e.test.ts (saved none/absent on cold start and reload).

Keep Usage transport reconnect E2E; drop redundant rebuild and scope-change scenarios covered by usage-page.test.ts (uncomputed cache, scoped refresh publications), usage-page-query-ownership.test.ts (late scope results), and gateway-store.observers.test.ts (publication deduplication). Keep Goal recovery E2E; oversized edit and corrected submission remain covered by chat-goals.test.ts.

Drop optional header/cron screenshot plumbing and verbose touch failure telemetry, preserving gesture assertions. Script-payload trigger exclusion remains in pages/cron/view.test.ts. Retain exact 30-second trigger boundary, keyboard/clipboard diagnostics, nested-widget handoff, responsive geometry, and authenticated recovery.

CI-only: oxfmt, plain oxlint, and git diff --check pass. No test/build/coverage commands run. LOC 2957 -> 2565 (-13.3%); declarations 26 -> 22; expanded cases 42 -> 37. Below the 40% target: further identified cuts would remove distinct browser behavior.

* test(ui): consolidate q0099 owner-boundary coverage

Delete chat-goal-recovery.e2e.test.ts: chat-goals.test.ts covers pending/uncertain pause, lost clear/resume ACK recovery, unavailable Resume reconciliation, authenticated recovery scope, history admission, and failure to save recovery. chat-composer-goal.test.tsx covers Resume progress and saved retry controls; chat-details-goal.test.tsx keeps recovery available after Goal removal. Remove only its owner-inventory and timing entries.

Drop the scheduler/config replay: cron-page.lifecycle.test.ts refreshes trigger authoring from scheduler status after reconnect; cron-page.reconnect.test.ts preserves drafts while fencing old reads. Keep exact triggered/untriggered interval boundaries and rejected-save fieldset recovery in Chromium.

Consolidate shared HubPageHeader geometry in the Sessions/Worktrees scenario with LTR, RTL, and mobile cases. plugins-hub-header.test.tsx proves Plugins uses the shared header and keeps tabs/actions live. Keep native/legacy controls and Claws-specific clearance checks.

Drop repeated sidebar contracts covered by sidebar-attention-update.test.ts (channel eligibility), updates-settings.e2e.test.ts (no update on navigation), sidebar-refresh-availability.e2e.test.ts and gateway-status.e2e.test.ts (status lifecycle), settings-sidebar.test.tsx (footer status/save indicator), app-sidebar-identity-keyboard.browser.test.ts (menu input), and tooltip.test.ts (hover intent). Retain build-hover/click navigation and its capture support.

Shrink Usage to the existing minimal sessionsUsageResponse fixture; preserve real proxy interruption, TTL, hidden-page deferral, and recovery requests. Overview metrics remain in usage-overview.e2e.test.ts. Remove duplicate contrast calculation from presentation E2E: session-background.test.ts covers worst-pixel contrast and palette changes; session-background.browser.test.tsx covers inherited-palette sampling. Keep rendered writing-surface geometry and opaque surfaces.

CI-only static gates: plain oxlint, oxfmt, git diff --check passed; no tests, coverage, builds, or check-changed run. Cumulative shard LOC 2957 -> 1774 (-40.01%); declarations 26 -> 15; expanded cases 42 -> 27. No production or shared-helper edits.

* test(ui): deslop q0103 tests

Reuse the existing usage overview fixture and production projection instead of hand-building response pages and aggregate metadata. Keep paging, all-session totals, cost sorting, quoted filters, and beyond-thousand selection assertions. Remove optional screenshot/report artifact layers.

Consolidate cached-route rendering and send admission in one outcome matrix. Keep same, deleted, moved, mismatched identity, empty startup, reconnect, live metadata, and restored outbox coverage. route-snapshot.test.ts independently exercises cached identity replacement; chat-route-write-readiness.test.ts covers direct and restored send admission.

Remove the duplicate saved-credential activation scenario: model-setup-replacement.e2e.test.ts retains failure, cancellation, explicit activation and no-auth-write proof; pages/model-setup/view.test.ts retains current-model candidate filtering; pages/model-providers/model-providers-page.test.ts retains scoped probes and pages/model-providers/view.test.ts retains probe result presentation.

CI-only campaign: local oxfmt, diff-check and plain oxlint passed. No runtime tests, coverage or check-changed run. Shard lines 2383 -> 2095 (12.1%); below the 40% target because retained scenarios cover distinct authority, lifecycle, layout and real-Gateway behavior.

* test(ui): remove redundant q0103 e2e layers

Delete chat-header-session-outcomes.e2e.test.ts and only its inventory/timing entries. Covering tests: chat-pane.test.ts (does not resurrect a reveal failure), chat-pane-placement-stop.test.ts (retries a failed reclaim from the same dialog; does not publish a reclaim failure after leaving and returning), chat-pane-sharing.test.ts (drops a failure after leaving and returning to the retained pane), components/chat-pane-placement.test.ts (offers only the restart or stop-first action after worker failure), and session-menu.test.ts (dispatches archive and stop-cloud-worker actions). chat-placement-recovery.e2e.test.ts retains rendered worker recovery.

Remove cached-route outcome/outbox replay matrices: route-snapshot.test.ts covers full/compact UUID admission and missing/moved/replaced authority; chat-route-write-readiness.test.ts covers direct and restored queued sends after empty startup. Keep the browser metadata-actions and stale-reconnect scenarios.

Remove synthetic restart and countdown/fallback E2E replays. Covering tests: chat-system-notice.test.ts (pending-to-history system notices), chat-thread-live-final.test.ts (live/terminal continuity with system recovery inputs), chat-history.recovery-status.test.ts (retry/fallback retirement and reconnect), and components/chat-retry-status.test.ts (deadline countdown). Keep terminal failure reload, touch, copy, partial output, model picker and safe retry E2E.

Shrink unused local-provider responses and metadata observation fixtures. The retained real-Gateway tests still prove saved Off, unsupported effort rejection, Ultra-only choices in chat/session/new views, and absence of unintended writes. Drop repeated setup/catalog projections and advisory event-shape assertions in favor of the observed model change and rendered controls.

CI-only: plain oxlint exited 0; oxfmt and git diff --check passed. No tests, coverage, builds, Testbox, typechecks or check-changed run. Original shard test LOC 2383 -> 1428 (-40.08%); this additional commit removes 667 test lines from the prior 2095-line result.

* test: reconcile d460 replay formatting and imports

* test: retain contested boundary proofs and repair d460 fixtures

* test: keep publication fixture request keys defined
2026-10-24 10:40:03 +00:00

335 lines
11 KiB
TypeScript

import childProcess, { type ChildProcess } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { setTimeout as waitForProcessTick } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { afterAll, beforeAll, expect, it, type TestContext, vi } from "vitest";
import { hasErrnoCode } from "../../src/infra/errno.js";
import {
fixtureReceiptClientSource,
openFixtureReceiptChannel,
type FixtureReceiptChannel,
} from "../helpers/fixture-receipts.js";
import { withinTest } from "../helpers/promise.js";
const runtimeSmokePath = path.resolve(
"scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs",
);
let receipts: FixtureReceiptChannel;
beforeAll(async () => {
receipts = await openFixtureReceiptChannel();
});
afterAll(() => receipts.close());
export function fixtureReadySource(filePath: string): string {
return `${fixtureReceiptClientSource(receipts.endpoint)}
sendReceipt(${JSON.stringify(filePath)}, "ready");`;
}
export function registerFixtureCleanup(
onTestFinished: TestContext["onTestFinished"],
cleanup: () => Promise<void>,
): () => Promise<void> {
let pending: Promise<void> | undefined;
const finish = () => (pending ??= cleanup());
// Abort resumes finally, but Vitest only joins explicitly registered cleanup.
onTestFinished(finish);
return finish;
}
export function childClosed(child: ChildProcess): Promise<void> {
return new Promise((resolve, reject) => {
child.once("close", () => resolve());
child.once("error", reject);
});
}
async function fixtureReadyBeforeSettlement(
filePath: string,
operation: PromiseLike<unknown>,
signal: AbortSignal,
ready = () => fs.existsSync(filePath),
message = `timeout waiting for ${filePath}`,
): Promise<void> {
// Separate transports may deliver settlement before the receipt. The fixture
// commits this record before reporting readiness or allowing its owner to exit.
await withinTest(
Promise.race([
receipts.waitFor(filePath, "ready"),
Promise.resolve(operation).then(() => {
if (!ready()) {
throw new Error(message);
}
}),
]),
signal,
);
}
function parseCompletedPidFile(content: string): number | undefined {
const pid = Number(/^([1-9]\d*)\n$/u.exec(content)?.[1]);
return Number.isSafeInteger(pid) ? pid : undefined;
}
export function readCompletedPidFile(filePath: string): number | undefined {
try {
return parseCompletedPidFile(fs.readFileSync(filePath, "utf8"));
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) {
return undefined;
}
throw error;
}
}
export async function waitForPidFile(
filePath: string,
operation: PromiseLike<unknown>,
signal: AbortSignal,
): Promise<number> {
await fixtureReadyBeforeSettlement(
filePath,
operation,
signal,
() => readCompletedPidFile(filePath) !== undefined,
`timeout waiting for pid in ${filePath}`,
);
return readOwnedDescendantPid(filePath);
}
export function pidIsAlive(pid: number): boolean {
if (!Number.isSafeInteger(pid) || pid <= 0) {
return false;
}
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
// Foreign descendants have no ChildProcess handle here. The runtime sends tree
// signals but does not join exact-PID extinction, including after stopGateway.
export async function waitForDead(pid: number, signal: AbortSignal): Promise<void> {
try {
while (pidIsAlive(pid)) {
await waitForProcessTick(20, undefined, { signal });
}
} catch (cause) {
throw new Error(`timeout waiting for pid ${pid} to exit`, { cause });
}
}
export function killPidIfAlive(pid: number | undefined): void {
if (pid !== undefined && pidIsAlive(pid)) {
killOwnedPid(pid);
}
}
function killOwnedPid(pid: number): void {
try {
process.kill(pid, "SIGKILL");
} catch (error) {
// The process can exit after the liveness probe; ESRCH already satisfies cleanup.
if (!hasErrnoCode(error, "ESRCH")) {
throw error;
}
}
}
function readOwnedDescendantPid(filePath: string): number {
const pid = parseCompletedPidFile(fs.readFileSync(filePath, "utf8"));
if (pid === undefined) {
throw new Error("owned descendant PID is incomplete");
}
return pid;
}
function killOwnedRuntimeCommand(child: ChildProcess | undefined, detached: boolean): void {
if (!child?.pid) {
return;
}
if (detached) {
killOwnedPid(-child.pid);
} else if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
}
}
async function observeRuntimeCommandTimeout(params: {
args: string[];
ready: (child: ChildProcess, operation: Promise<unknown>) => Promise<number[]>;
timeoutMs: number;
detached: boolean;
runDetached?: boolean;
root?: string;
descendantPidPath?: string;
signal: AbortSignal;
onTestFinished: TestContext["onTestFinished"];
onCleanupRegistered: () => void;
}): Promise<Error> {
const runtimeSmoke = await import(pathToFileURL(runtimeSmokePath).href);
params.signal.throwIfAborted();
const spawnSpy = vi.spyOn(childProcess, "spawn");
let child: ChildProcess | undefined;
let commandResult: Promise<unknown> | undefined;
let closed: Promise<void> | undefined;
let descendantPid: number | undefined;
let cleanupSignalsSent = false;
let settled = false;
const cleanup = registerFixtureCleanup(params.onTestFinished, async () => {
try {
try {
// Recover ownership before fallible joins, including failed readiness.
if (params.descendantPidPath && fs.existsSync(params.descendantPidPath)) {
descendantPid = readOwnedDescendantPid(params.descendantPidPath);
}
} finally {
try {
killOwnedRuntimeCommand(child, params.detached);
} finally {
// Even broken group termination must reap the descendant after death assertions.
killPidIfAlive(descendantPid);
}
}
cleanupSignalsSent = true;
} finally {
vi.useRealTimers();
spawnSpy.mockRestore();
await commandResult;
await closed;
if (descendantPid !== undefined && !params.signal.aborted) {
await waitForDead(descendantPid, params.signal);
}
if (settled && cleanupSignalsSent && params.root) {
fs.rmSync(params.root, { force: true, recursive: true });
}
}
});
params.onCleanupRegistered();
// Freeze the parent's policy clock, not subprocess startup, I/O, readiness or cleanup.
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
try {
const command: Promise<unknown> = runtimeSmoke
.runCommand(process.execPath, params.args, {
detached: params.runDetached,
timeoutMs: params.timeoutMs,
})
.catch((error: unknown) => error)
.finally(() => {
settled = true;
});
commandResult = command;
const spawned = spawnSpy.mock.results[0];
if (spawned?.type !== "return" || !spawned.value.pid) {
throw new Error("runtime command did not expose its spawned child");
}
child = spawned.value;
closed = childClosed(child);
const descendants = await withinTest(params.ready(child, command), params.signal);
const assertRunning = () => {
for (const pid of [child!.pid!, ...descendants]) {
expect(pidIsAlive(pid)).toBe(true);
}
expect(settled).toBe(false);
};
assertRunning();
await vi.advanceTimersByTimeAsync(params.timeoutMs - 1);
assertRunning();
await vi.advanceTimersByTimeAsync(1);
const error = await withinTest(command, params.signal);
if (!(error instanceof Error)) {
throw new Error("expected runtime command to time out");
}
await withinTest(closed, params.signal);
expect(pidIsAlive(child.pid!)).toBe(false);
for (const pid of descendants) {
await waitForDead(pid, params.signal);
}
return error;
} finally {
await cleanup();
}
}
function registerTimeoutCase(
detached: boolean,
createPackageRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), "runtime-command-")),
): void {
const timeoutMs = detached ? 250 : 200;
(detached && process.platform === "win32" ? it.skip : it)(
detached
? "kills timed-out runtime command groups"
: "bounds non-detached runtime commands and preserves captured output",
async ({ signal, onTestFinished }) => {
const startedAt = Date.now();
const root = createPackageRoot();
let cleanupRegistered = false;
onTestFinished(() => {
if (!cleanupRegistered) {
fs.rmSync(root, { recursive: true, force: true });
}
});
const commandPath = path.join(root, "timeout-command.mjs");
const descendantPidPath = path.join(root, "timed-out-descendant.pid");
const readyPath = path.join(root, "timed-out-descendant.ready");
const descendantSource = `import fs from 'node:fs';
process.on('SIGTERM', () => {});
setInterval(() => {}, 1000);
fs.writeFileSync(${JSON.stringify(readyPath)}, "ready");
${fixtureReadySource(readyPath)}`;
const spawnDescendant = `import childProcess from 'node:child_process';
const child = childProcess.spawn(process.execPath, ["--input-type=module", "--eval", ${JSON.stringify(descendantSource)}], { stdio: "ignore" });`;
fs.writeFileSync(
commandPath,
`import fs from 'node:fs';
${detached ? spawnDescendant : ""}
setInterval(() => {}, 1000);
process.stdout.write('partial\\n');
process.stderr.write('problem\\n');
fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(${detached ? "child" : "process"}.pid) + "\\n");
${fixtureReadySource(descendantPidPath)}
`,
"utf8",
);
const error = await observeRuntimeCommandTimeout({
args: [commandPath],
ready: async (child, operation) => {
const pid = await waitForPidFile(descendantPidPath, operation, signal);
if (detached) {
await fixtureReadyBeforeSettlement(readyPath, operation, signal);
return [pid];
}
expect(pid).toBe(child.pid);
return [];
},
timeoutMs,
detached,
runDetached: detached ? undefined : false,
root,
descendantPidPath: detached ? descendantPidPath : undefined,
signal,
onTestFinished,
onCleanupRegistered: () => {
cleanupRegistered = true;
},
});
if (detached) {
expect(error.message).toMatch(/timed out after 250ms/u);
} else {
expect(error.message).toMatch(/timed out after 200ms[\s\S]*partial[\s\S]*problem/u);
expect(Date.now() - startedAt).toBeLessThan(2_500);
}
},
);
}
export function registerRuntimeCommandTimeoutTests(createPackageRoot: () => string): void {
registerTimeoutCase(true, createPackageRoot);
}
export function registerRuntimeCommandOutputTimeoutTest(): void {
registerTimeoutCase(false);
}