Files
orca/config/scripts/pr-e2e-route-contracts.test.mjs
T
Neil f59bd43c46 fix(wsl-host): Remove keeps a WSL host's server that another Orca on this computer shares (R39) (#34699)
* fix(wsl-host): Remove keeps a WSL host's server that another Orca on this computer shares

A WSL host's server lives in the Linux login's ~/.orca, so two Orca installs on
one Windows account (stable and dev), or a desktop reaching the distro over SSH,
share one server. Remove used to close every terminal there and decommission
it, ending the other desktop's sessions and agents.

The server now answers host.peerDesktops (capability orcad.peer-desktops.v1):
the other desktops paired with it, whether each is connected now, and when each
was last seen. Before tearing anything down, Remove asks. If another desktop is
connected or was seen in the last 30 days, it only closes this desktop's bridge
and reports the server kept. If the server can't answer, the teardown is
refused. An older server without the capability gets today's teardown, plus a
warning.

* fix(wsl-host): a shared Remove releases this Orca from the host, and says the host keeps running

- On a shared server, Remove now revokes this desktop's own device (and its
  phones) on the host, through the new host.releaseDesktop, before letting go.
  Otherwise its recent row would make a peer's later Remove keep the server
  forever. Peers also skip rows of the caller's own installation (same caller
  fingerprint), such as an enrollment from before a re-add.
- The renderer says when a removal kept the host for another Orca, and the
  Remove confirmation no longer promises to end terminals unconditionally.
- Remove-path files now route CI to the fake transport e2e spec, which
  exercises Remove.

* i18n(wsl-host): Russian and Traditional Chinese for the shared-host Remove copy

* fix(wsl-host): tell desktops apart by device key, and release this Orca on every Remove

- Peers are no longer hidden by caller fingerprint. Every device-key fork of
  one pairing token row inherits that row's fingerprint, so two installations
  that paired through one code hid each other, and one's Remove could tear the
  server down under the other. Only a shared device public key now marks the
  same installation, as with a re-add under a new token.
- This desktop's own device is released as the last call over the bridge on
  every Remove that proceeds (no terminals left), not only a shared one. A
  later Add then Remove never meets a stale row of ours.
- When the release fails on a shared Remove, the toast says the host may keep
  running for a while. The Russian confirmation copy is corrected.

* fix(wsl-host): move undo refuses a removed host whose server another Orca kept

A Remove that kept a WSL host's server for another Orca (R39) now records
that. Before, undo of a move onto that host read the missing environment as
"its terminals and automations stopped with it", and would have restored the
project here while the host's copies kept running, duplicating its agents and
automations. Undo now reads such a host as unreachable and refuses, as it does
for any host it can't reach. A later Remove that proves the server stopped
clears the record. If the record can't be saved, the host stays listed as
pending instead.

* i18n(wsl-host): Turkish for the shared-host Remove copy

* fix(wsl-host): each WSL desktop splits off its own row from orcad's startup pairing

orcad publishes one startup pairing offer and never rotates it, so every
desktop that enrolled through it shared one token row and device id. Stable and
dev Orca on one distro were invisible to each other as peers, and releasing that
shared row broke the next re-add and pairing repair.

- New host.forkDesktop (same orcad.peer-desktops.v1 capability). A runtime
  device on a shared desktop token mints its own token row, forked from it and
  named by the caller. Forks share the device-key fork cap, and a released fork
  frees its slot.
- The WSL desktop forks right after enrolling through the startup offer, or
  after a repair lands back on it, and keeps the forked pairing (named after its
  userData folder, e.g. Orca or Orca Dev). On an older orcad, or if the fork
  fails, it keeps the shared pairing and warns.
- A token row that desktops forked from counts as a peer only while something
  is connected on it. One that nobody forked from keeps the last-seen rule.

* fix(wsl-host): keep host.forkDesktop to the startup offer's holder, over E2EE, with a clean label

host.forkDesktop mints a credential, so:
- only the holder of the desktop pairing offer this server advertised last
  (orcad's startup offer) may fork; an older offer, a fork, a phone or any
  other row is refused;
- the fork takes exactly the caller's grants and reach; params are just a label
  (strict, so a grant request is refused);
- it runs only on an authenticated E2EE socket; the token goes back only in
  that reply and is never logged;
- the label is display-only: control, zero-width and bidi characters stripped,
  whitespace collapsed, at most 64 characters; an empty result is refused.

* refactor(wsl-host): count only Orcas connected right now as WSL host peers; drop fork and release

Review found the token fork and self-release add more identity state than R39
warrants: orphan forks, a desktop left on the shared token releasing it, and a
changed caller fingerprint. So this drops host.forkDesktop, host.releaseDesktop
and the 30-day last-seen rule, and restores the restart test.

A peer is now another Orca installation with a control socket open on the
server right now. The desktop declares its installation (a hash of its userData
path) with host.declareClient on its shared-control socket after every ready,
including reconnects. The server tags that socket and drops the tag when it
closes. host.peerDesktops takes the caller's installation id, so its own other
sockets (multiplex, one-shot) never count, and two desktops sharing one startup
token stay distinct. Undeclared sockets, phones and browser sessions are
ignored. Peer names go through sanitizeDeviceLabel, and a startup-offer
"CLI <date>" name shows as "another Orca". An older orcad lacks the method:
nothing is declared, and Remove keeps today's teardown with a warning.

Limit: an Orca that's closed while its terminals keep running isn't protected.

* fix(wsl-host): keep a reconnecting Orca as a WSL host peer for 60 seconds

Lost contact isn't proof an Orca left. When an installation's last declared
control socket closes (a bridge exit, an orcad restart, resume from sleep,
reconnect backoff), the server keeps counting it as a peer for 60 seconds, or
until it re-declares. A Remove inside that gap no longer tears down its
terminals.

* fix(wsl-host): say an open Orca keeps the host, and name each peer once

- The Remove confirmation now reads "Unless another Orca that's open on this
  computer is using it", since only a connected Orca keeps the server.
  Updated in all 10 locales.
- The kept-host toast dedupes peer names, so several unnamed peers read
  "another Orca" once.

* test(wsl-host): pin the reconnect grace to the last socket's close, and to the server's own forget

- The unit test advances the clock between the two closes and checks that the
  peer still counts when the first close's grace would have run out.
- The integration test waits until the server has dropped the closed socket
  before reading peers, so it fails without the grace (checked by setting the
  grace to zero).

* fix(wsl-host): sweep expired reconnect graces on every close, on a monotonic clock

Expired graces are dropped at the start of forget() as well as peers(), so the
map stays bounded even when nobody asks for peers. The default clock is
performance.now(), so a wall-clock jump can't stretch or cut the grace.

* i18n(wsl-host): German for the shared-host Remove copy
2026-10-28 04:27:30 -07:00

588 lines
26 KiB
JavaScript

import { describe, expect, it } from 'vitest'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { parse } from 'yaml'
import {
classifyE2eJobs,
DEDICATED_E2E_SPECS,
MANAGED_SSH_E2E_SPEC_PREFIX,
ORCAD_SERVE_MODE_SWITCH_E2E_SPEC,
WINDOWS_COPY_FILE_E2E_SPEC,
WINDOWS_MISSING_APPDATA_E2E_SPEC,
selectGeneralE2eSpecs
} from './ci-e2e-job-selection.mjs'
import { classifyPrJobs } from './pr-code-change-scope.mjs'
import { testImportClosure } from './e2e-test-import-closure.mjs'
import { isLayoutOracleSource, LAYOUT_ORACLE_E2E_SPECS } from './pr-layout-oracle-route.mjs'
import {
hasNativeImeSourceChange,
MANUAL_ONLY_E2E_SPECS,
PR_E2E_SOURCE_ROUTES,
selectPrE2eSpecs,
shouldRunReusablePrE2e
} from './pr-e2e-source-routing.mjs'
const projectDir = resolve(import.meta.dirname, '../..')
const prWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8'))
const e2eWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/e2e.yml'), 'utf8'))
describe('browser native focus routing', () => {
it('selects the general E2E lane for focus adapter and image fixture changes', () => {
const spec = 'tests/e2e/browser-image-link-focus.spec.ts'
for (const file of [
'src/renderer/src/components/browser-pane/assemble-chrome/browser-page-guest-focus.ts',
'tests/e2e/helpers/browser-link-server.ts'
]) {
const specs = selectPrE2eSpecs([file])
expect(specs, file).toContain(spec)
expect(selectGeneralE2eSpecs(specs), file).toContain(spec)
expect(classifyE2eJobs(JSON.stringify(specs)).e2e_run_changed, file).toBe(true)
}
})
})
describe('pr-e2e-native-only-routing', () => {
const workflow = prWorkflow
const filterStep = workflow.jobs.code_paths.steps.find((step) => step.id === 'e2e_filter')
describe('native-only PR E2E routing', () => {
it('avoids generic E2E allocation for native-only changes while preserving its IME lane', () => {
for (const file of [
'tests/e2e/terminal-ibus-hangul-native.spec.ts',
'config/scripts/run-terminal-ibus-hangul-e2e.mjs'
]) {
expect(hasNativeImeSourceChange([file])).toBe(true)
expect(shouldRunReusablePrE2e([file])).toBe(false)
}
expect(shouldRunReusablePrE2e([])).toBe(false)
for (const spec of [
'tests/e2e/paired-startup-exec-readiness.spec.ts',
'tests/e2e/terminal-ime-exact-byte.spec.ts',
'tests/e2e/future.spec.ts'
]) {
expect(shouldRunReusablePrE2e([spec])).toBe(true)
expect(
shouldRunReusablePrE2e(['tests/e2e/terminal-ibus-hangul-native.spec.ts', spec])
).toBe(true)
}
expect(filterStep.run).toContain('pr-e2e-source-routing.mjs --reusable-workflow')
expect(filterStep.run).toContain('if [ "$SHOULD_RUN" = true ]; then')
})
})
})
describe('pr-e2e-pane-close-routing', () => {
// #21005 changed pane close/retirement across three transports with E2E skipped: routes covered binders but missed unbinders.
const PENDING_PANE_CLOSE_CHANGE = [
'src/renderer/src/components/terminal-pane/retire-unbound-ipc-terminal-pane.ts',
'src/renderer/src/components/terminal-pane/retire-unbound-runtime-terminal-pane.ts',
'src/renderer/src/components/terminal-pane/terminal-pane-close-admission.ts',
'src/renderer/src/components/terminal-pane/terminal-pane-retirement-ownership.ts',
'src/renderer/src/components/terminal-pane/use-terminal-pane-close-actions.ts',
'src/renderer/src/store/terminals/terminal-tab-close-providers.ts'
]
const CLOSE_ROUTE = PR_E2E_SOURCE_ROUTES.find(
(route) => route.id === 'terminal-pane.close-and-retirement'
)
describe('pane close and retirement PR E2E routing', () => {
it('selects the close specs for a pane close lifecycle change', () => {
expect(selectPrE2eSpecs(PENDING_PANE_CLOSE_CHANGE)).toEqual([
'tests/e2e/paired-remote-split-pane-host-retired-ghost.spec.ts',
'tests/e2e/terminal-pane-close-layout-consistency.spec.ts',
'tests/e2e/terminal-parked-close-retirement.spec.ts'
])
expect(shouldRunReusablePrE2e(PENDING_PANE_CLOSE_CHANGE)).toBe(true)
})
it('routes each unbind authority on its own, not just as a group', () => {
expect(CLOSE_ROUTE, 'the close route was renamed or removed').toBeDefined()
// Assert each single path; union could hide missed routes behind one match.
for (const file of PENDING_PANE_CLOSE_CHANGE) {
expect(selectPrE2eSpecs([file]), file).toEqual(CLOSE_ROUTE.specs.toSorted())
}
})
it('keeps every routed spec and authority a real file', () => {
for (const spec of CLOSE_ROUTE.specs) {
expect(existsSync(join(projectDir, spec)), spec).toBe(true)
}
for (const file of PENDING_PANE_CLOSE_CHANGE) {
expect(existsSync(join(projectDir, file)), file).toBe(true)
}
})
it('leaves unrelated changes alone', () => {
// Neighbours in the same directories that this route must not claim.
for (const file of [
'src/renderer/src/components/terminal-pane/CloseTerminalDialog.tsx',
'src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.ts',
'src/renderer/src/runtime/runtime-rpc-client.ts',
'src/renderer/src/store/terminals/terminal-tab-close-providers.test.ts'
]) {
expect(CLOSE_ROUTE.matches(file), file).toBe(false)
}
})
})
})
describe('pr-e2e-layout-oracle-routing', () => {
/** One product file per layout writer family; each alone must run the oracle. */
const LAYOUT_AUTHORITIES = [
'src/shared/workspace-layout/workspace-layout-pane-commands.ts',
'src/shared/workspace-layout/old-client/old-client-session-tab-rows.ts',
'src/main/persistence/terminal-topology/terminal-topology-commit.ts',
'src/main/persistence/restoring-sessions/session-owner-fields.ts',
'src/main/persistence/loading-store/pty-binding-persistence.ts',
'src/main/remote-layout-snapshot/remote-layout-snapshot-recorder.ts',
'src/main/runtime/terminal/launch/terminal-split-surface.ts',
'src/main/runtime/terminal/adoption/adopt-terminal-orphans-from-inventory.ts',
'src/main/runtime/runtime-terminal-orphan-adoption.ts',
'src/main/runtime/session-tabs/headless-tab-group-moves.ts',
'src/main/runtime/terminal/launch/terminal-create.ts',
'src/main/runtime/terminal/stop/worktree-terminal-stop.ts',
'src/renderer/src/runtime/mobile-session-tab-close.ts',
'src/renderer/src/runtime/runtime-layout-client.ts',
'src/renderer/src/store/slices/tabs.ts',
'src/renderer/src/store/slices/tabs/tabs-move-actions.ts',
'tests/e2e/workspace-layout-oracle-known-on-main.ts',
'tests/e2e/helpers/workspace-layout-oracle-compare.ts',
...LAYOUT_ORACLE_E2E_SPECS
]
/** Widened while the layout-holder stack is open: these still write layout. */
const STACK_WRITERS = [
'src/renderer/src/store/slices/terminals.ts',
'src/renderer/src/components/terminal-pane/TerminalPane.tsx',
'src/renderer/src/runtime/sync-runtime-graph.ts',
'src/renderer/src/runtime/web-session-tabs-sync/store-patch.ts',
'src/main/runtime/terminal/launch/terminal-split.ts',
'src/main/runtime/workspaces/workspace-file-session-targets.ts',
'src/main/runtime/session-graph/session-window-graph.ts',
'src/main/runtime/session-tabs/mobile-session-hydration.ts',
'src/main/runtime/session-tabs/mobile-session-reconciliation.ts',
'src/main/runtime/session-tabs/mobile-session-preservation.ts',
'src/main/runtime/workspaces/session-hosts/get-runtime-id.ts',
'src/main/runtime/mobile-session-layout-projection.ts',
'src/main/runtime/headless-runtime-graph.ts',
'src/main/persistence/loading-store/session-host-partitions.ts',
'src/main/persistence/loading-store/workspace-session-snapshot-publication.ts',
'src/renderer/src/store/slices/tab-group-reference-repair.ts',
'src/renderer/src/store/slices/terminal-session-row-hydration.ts',
'src/renderer/src/store/workspace-layout-cache.ts',
'src/renderer/src/app-shell/use-runtime-graph-sync.ts',
'src/renderer/src/components/tab-group/tab-drag-drop-commit.ts',
'src/shared/remote-layout-snapshot.ts',
'src/shared/terminal-leaf-move.ts',
'src/main/ipc/pty/ipc/leaf-move.ts',
'src/main/persistence/closed-terminal-tab-write-fence.ts',
'src/main/runtime/runtime-workspace-session-controller.ts',
'src/main/runtime/workspaces/persisted-surfaces/has-exact-persisted-terminal-surface-identity.ts',
'src/main/runtime/terminal-surface-close.ts',
'src/main/runtime/terminal/output/terminal-derived-agent-facts.ts',
'src/main/runtime/terminal/output/terminal-output-generation.ts',
'src/main/runtime/session-tabs/mobile-session-membership.ts',
'src/main/runtime/session-tabs/pty-backed-session-publication.ts',
'src/renderer/src/components/terminal/terminal-tab-actions.ts',
'src/renderer/src/components/tab-bar/web-runtime-tab-move-mirror.ts',
'src/shared/rpc-contract/workspace-layout-params.ts',
'src/renderer/src/store/slices/recently-closed-tabs.ts'
]
/** Every test file the oracle specs import, transitively: a change there can break the gate. */
const oracleHarnessFiles = () => testImportClosure(projectDir, LAYOUT_ORACLE_E2E_SPECS)
describe('workspace layout oracle merge gate', () => {
it('routes each layout authority and stack writer on its own', () => {
for (const file of [...LAYOUT_AUTHORITIES, ...STACK_WRITERS]) {
expect(isLayoutOracleSource(file), file).toBe(true)
expect(classifyPrJobs([file]).layout_oracle, file).toBe(true)
expect(existsSync(join(projectDir, file)), file).toBe(true)
}
})
it('routes every test file the oracle specs import', () => {
const harness = oracleHarnessFiles()
expect(harness.length).toBeGreaterThan(LAYOUT_ORACLE_E2E_SPECS.length)
expect(harness.filter((file) => !isLayoutOracleSource(file))).toEqual([])
})
it('leaves tests, fixtures, harnesses and unrelated runtime modules alone', () => {
for (const file of [
'src/main/persistence/terminal-topology/terminal-topology-commit.test.ts',
'src/main/persistence/terminal-topology/terminal-leaf-move-fixture.ts',
'src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-03.spec.ts',
'src/main/runtime/session-tabs/headless-tab-group-moves.test.ts',
'src/main/runtime/session-tabs/headless-tab-group-moves-fixture.ts',
'src/main/runtime/session-tabs/session-tabs-inventory-test-fixture.ts',
'src/main/runtime/terminal/stop/worktree-terminal-stop.test.ts',
'src/main/runtime/terminal/stop/worktree-terminal-stop-fixture.ts',
'src/main/runtime/terminal/stop/worktree-terminal-stop.ts.backup',
'src/main/runtime/terminal/stop/worktree-terminal-stop-extra.ts',
'src/main/runtime/runtime-durable-store-fixture.ts',
'src/main/runtime/orchestration/db/worker-terminal/worker-terminal-store.ts',
'src/main/runtime/rpc/methods/terminal/terminal-create.ts',
'src/main/runtime/runtime-rpc-database.ts',
'src/main/runtime/orca-runtime-files.ts',
'src/main/runtime/workspaces/persisted-surfaces/automation-operations.ts',
'src/main/runtime/workspaces/persisted-surfaces/has-exact-persisted-terminal-surface-identity.test.ts',
'src/renderer/src/runtime/remote-runtime-terminal-multiplexer.ts',
'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts',
'src/renderer/src/runtime/sync-runtime-graph-test-harness.ts',
'src/renderer/src/runtime/sync-runtime-graph.test.ts',
'tests/e2e/workspace-layout-oracle-latency.spec.ts'
]) {
expect(isLayoutOracleSource(file), file).toBe(false)
}
expect(classifyPrJobs(['src/main/runtime/orca-runtime-files.ts']).layout_oracle).toBe(false)
})
it('keeps the oracle specs out of the advisory e2e job', () => {
expect(selectPrE2eSpecs(LAYOUT_AUTHORITIES)).toEqual([])
expect(shouldRunReusablePrE2e(LAYOUT_AUTHORITIES)).toBe(false)
})
it('runs exactly the oracle specs, each in the lane that can run it', () => {
const job = prWorkflow.jobs.layout_oracle
expect(job.uses).toBe('./.github/workflows/e2e.yml')
// The merge commit, so a stale-base PR cannot pass on an old main.
expect(job.with.ref).toBe('${{ github.sha }}')
const specs = JSON.parse(job.with.test_files)
expect(specs.toSorted()).toEqual(LAYOUT_ORACLE_E2E_SPECS.toSorted())
// Window spec runs in the changed-spec lane; headless runs in the orcad lane.
expect(selectGeneralE2eSpecs(specs)).toEqual(['tests/e2e/workspace-layout-oracle.spec.ts'])
expect(classifyE2eJobs(job.with.test_files)).toEqual({
e2e_run_changed: true,
e2e_needs_build: true
})
})
it('blocks the merge on the oracle and passes when it is skipped', () => {
const verify = prWorkflow.jobs.verify
const gate = verify.steps.find((step) => step.name === 'Require successful checks')
expect(verify.needs).toContain('layout_oracle')
expect(gate.run).toContain(
'check_job layout_oracle "$LAYOUT_ORACLE" "$LAYOUT_ORACLE_SHOULD_RUN"'
)
// Two calls of the reusable workflow in one run must not share artifact names.
expect(prWorkflow.jobs.layout_oracle.with.artifact_suffix).toBeTruthy()
expect(prWorkflow.jobs.e2e.with.artifact_suffix).toBeUndefined()
// The merge gate keeps Blacksmith speed; the advisory lane runs free.
expect(prWorkflow.jobs.layout_oracle.with.fast_runners).toBe(true)
expect(prWorkflow.jobs.e2e.with.fast_runners).toBeUndefined()
})
})
})
describe('ssh-browser-e2e-routing', () => {
const workflow = e2eWorkflow
it('executes both Docker network routes in a Node job with their opt-in enabled', () => {
const spec = 'tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts'
const job = workflow.jobs['ssh-browser-network-route']
const install = job.steps.find(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
const run = job.steps.find(
(step) => step.name === 'Run Docker SSH browser network route journeys'
)
expect(job['runs-on']).toBe(
"${{ inputs.fast_runners && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}"
)
expect(job.if).toContain("inputs.test_files == ''")
expect(job.if).toContain(spec)
expect(install.with['native-runtime']).toBe('node')
expect(run.env.ORCA_RUN_DOCKER_SSH_BROWSER_E2E).toBe('1')
expect(run.run).toContain(`vitest run --config config/vitest.config.ts ${spec}`)
expect(run['continue-on-error']).toBeUndefined()
expect(DEDICATED_E2E_SPECS).toContain(spec)
for (const changed of [
spec,
'src/main/browser/ssh-browser-network-execution-route.ts',
'src/main/browser/browser-network-deferred-socket.ts',
'src/main/browser/browser-network-execution-route.ts',
'src/main/browser/system-ssh-socks-client-socket.ts',
'src/main/ssh/system-transport/system-ssh-dynamic-forward-process.ts',
'tests/e2e/helpers/docker-ssh-host-target.ts',
'tests/e2e/helpers/docker-ssh-host-image.ts'
]) {
expect(selectPrE2eSpecs([changed])).toContain(spec)
}
expect(selectPrE2eSpecs(['src/renderer/src/components/Unrelated.tsx'])).not.toContain(spec)
expect(selectPrE2eSpecs(['tests/e2e/helpers/docker-ssh-host-terminal-tabs.ts'])).not.toContain(
spec
)
})
})
describe('orcad-e2e-routing', () => {
const root = projectDir
const jobs = e2eWorkflow.jobs
const ORCAD_AUTO_CONVERT_E2E_SPEC = 'tests/e2e/ssh-orcad-auto-convert.spec.ts'
const ORCAD_IDLE_EXIT_E2E_SPEC = 'tests/e2e/ssh-orcad-idle-exit.spec.ts'
const ORCAD_OPEN_IN_OWNER_E2E_SPEC = 'tests/e2e/ssh-orcad-open-in-owner.spec.ts'
const ORCAD_JOURNEY_E2E_SPECS = [
'tests/e2e/ssh-orcad-terminal-journeys.spec.ts',
'tests/e2e/ssh-orcad-workspace-tools.spec.ts',
'tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts'
]
function expectRouted(files, spec) {
for (const file of files) {
expect(existsSync(resolve(root, file)), file).toBe(true)
expect(selectPrE2eSpecs([file]), file).toContain(spec)
expect(shouldRunReusablePrE2e([file]), file).toBe(true)
}
}
it('routes the mode-switch spec from its serve sources and harness', () => {
expectRouted(
[
'src/main/orcad/orcad-lifecycle.ts',
'src/main/daemon/daemon-spawner.ts',
'tests/e2e/helpers/orca-serve-cli-host.ts',
'tests/e2e/helpers/headless-paired-runtime-host.ts'
],
ORCAD_SERVE_MODE_SWITCH_E2E_SPEC
)
})
it('routes the missing-AppData spec from its startup sources and harness', () => {
expectRouted(
['src/main/startup/windows-app-data-path.ts', 'tests/e2e/helpers/orca-serve-cli-host.ts'],
WINDOWS_MISSING_APPDATA_E2E_SPEC
)
})
it('routes the Windows file-copy spec from its clipboard sources', () => {
expectRouted(
[
'src/main/window/clipboard-file-copy.ts',
'src/main/window/clipboard-ipc-handlers.ts',
'src/renderer/src/components/right-sidebar/file-explorer-row-file-transfer.ts'
],
WINDOWS_COPY_FILE_E2E_SPEC
)
})
it('routes the auto-convert spec from its conversion sources and harness', () => {
expectRouted(
[
'src/main/managed-server/lifecycle/orcad-runtime-conversion.ts',
'tests/e2e/helpers/orcad-convert-flow.ts',
'tests/e2e/helpers/orcad-convert-host.ts',
'tests/e2e/helpers/orcad-template-variant.ts',
'tests/e2e/helpers/orcad-upgrade-profile.ts'
],
ORCAD_AUTO_CONVERT_E2E_SPEC
)
expect(
selectPrE2eSpecs(['src/main/managed-server/lifecycle/orcad-runtime-conversion.test.ts'])
).not.toContain(ORCAD_AUTO_CONVERT_E2E_SPEC)
})
it('routes the idle-exit spec from its idle sources and the shared convert harness', () => {
expectRouted(
[
'src/shared/orcad-idle-exit.ts',
'src/main/managed-server/install/orcad-remote-runtime-control.ts',
'src/main/managed-server/lifecycle/orcad-managed-serving.ts',
'tests/e2e/helpers/orcad-convert-flow.ts',
'tests/e2e/helpers/orcad-convert-host.ts'
],
ORCAD_IDLE_EXIT_E2E_SPEC
)
})
it('builds the e2e app when only a build-dependent orcad spec is requested', () => {
for (const spec of [
ORCAD_SERVE_MODE_SWITCH_E2E_SPEC,
ORCAD_AUTO_CONVERT_E2E_SPEC,
ORCAD_IDLE_EXIT_E2E_SPEC,
ORCAD_OPEN_IN_OWNER_E2E_SPEC
]) {
expect(classifyE2eJobs(JSON.stringify([spec])), spec).toEqual({
e2e_run_changed: false,
e2e_needs_build: true
})
}
expect(jobs['orcad-ssh-docker'].needs).toEqual(['build'])
})
it('passes the requested managed-SSH specs to the sharded lane', () => {
const lane = jobs['orcad-ssh-docker']
expect(lane.if).toBe(
`inputs.test_files == '' || contains(inputs.test_files, '${MANAGED_SSH_E2E_SPEC_PREFIX}')`
)
expect(lane.strategy.matrix.shard).toBe(
`\${{ fromJSON(inputs.managed_ssh_shards || '["1/2","2/2"]') }}`
)
const run = lane.steps.find((step) => step.env?.ORCA_E2E_SSH_DOCKER === '1').run
expect(run).toContain('"${TEST_FILES[@]}"')
expect(run).toContain('--shard="$SHARD"')
for (const spec of [
ORCAD_AUTO_CONVERT_E2E_SPEC,
ORCAD_IDLE_EXIT_E2E_SPEC,
ORCAD_OPEN_IN_OWNER_E2E_SPEC,
...ORCAD_JOURNEY_E2E_SPECS
]) {
expect(existsSync(resolve(root, spec)), spec).toBe(true)
expect(spec.startsWith(MANAGED_SSH_E2E_SPEC_PREFIX), spec).toBe(true)
}
})
it('runs the Windows-only specs on one runner, each only when requested', () => {
expect(jobs['windows-missing-appdata-startup']).toBeUndefined()
const job = jobs['orcad-serve-mode-switch-windows']
for (const spec of [
ORCAD_SERVE_MODE_SWITCH_E2E_SPEC,
WINDOWS_MISSING_APPDATA_E2E_SPEC,
WINDOWS_COPY_FILE_E2E_SPEC
]) {
expect(job.if, spec).toContain(`contains(inputs.test_files, '${spec}')`)
const step = job.steps.find((candidate) => candidate.run?.includes(spec))
expect(step.if, spec).toContain(`contains(inputs.test_files, '${spec}')`)
expect(step.env.ORCA_STARTUP_DIAGNOSTICS, spec).toBeUndefined()
}
})
it('routes managed target-owner guards and menus to the real launch regression', () => {
expectRouted(
[
'src/renderer/src/lib/local-path-open-guard.ts',
'src/renderer/src/lib/external-editor-open-capability.ts',
'src/renderer/src/lib/worktree-runtime-owner.ts',
'src/renderer/src/components/sidebar/WorktreeOpenInMenu.tsx',
'src/renderer/src/components/sidebar/WorktreeContextMenuView.tsx',
'src/renderer/src/components/right-sidebar/FileExplorer.tsx',
'src/renderer/src/components/right-sidebar/FileExplorerToolbar.tsx',
'src/renderer/src/components/right-sidebar/source-control/listing/entry-context-menu.tsx',
'tests/e2e/helpers/orcad-convert-flow.ts',
'tests/e2e/helpers/orcad-convert-host.ts',
'tests/e2e/helpers/orcad-upgrade-profile.ts',
'tests/e2e/helpers/docker-ssh-host-target.ts'
],
ORCAD_OPEN_IN_OWNER_E2E_SPEC
)
expect(selectPrE2eSpecs(['src/renderer/src/lib/local-path-open-guard.test.ts'])).not.toContain(
ORCAD_OPEN_IN_OWNER_E2E_SPEC
)
})
it('routes the managed-host journey specs from their harness and sources', () => {
for (const spec of ORCAD_JOURNEY_E2E_SPECS) {
expectRouted(
[
'tests/e2e/helpers/orcad-managed-workspace.ts',
'src/renderer/src/components/terminal-pane/terminal-host-workspace-file.ts',
'src/renderer/src/components/terminal-pane/terminal-host-file-link-existence.ts',
'src/renderer/src/runtime/runtime-terminal-artifact-read.ts',
// #30130's Disconnect sources merged with this lane skipped.
'src/main/ipc/ssh/ssh-host-user-disconnect.ts',
'src/main/ipc/ssh/ssh-connect-flow.ts',
'src/main/ipc/ssh/ssh-connection-handlers.ts',
'src/main/ipc/runtime-environments/runtime-environment-connectivity-handlers.ts',
'src/main/ipc/runtime-environments/runtime-environment-manual-disconnect.ts',
'src/main/ssh/connection/ssh-connection-manager.ts',
'src/main/ssh/ssh-user-disconnect.ts',
'src/renderer/src/runtime/web-session-close-intent.ts',
'src/renderer/src/runtime/web-session-close-replay.ts'
],
spec
)
expect(classifyE2eJobs(JSON.stringify([spec])), spec).toEqual({
e2e_run_changed: false,
e2e_needs_build: true
})
}
})
})
function selectsManagedSshLane(file) {
return selectPrE2eSpecs([file]).some((spec) => spec.startsWith(MANAGED_SSH_E2E_SPEC_PREFIX))
}
describe('wsl-host remove routing', () => {
it('runs the fake transport spec, which covers Remove, for a remove-path change', () => {
for (const file of [
'src/main/wsl/wsl-host-server-stop.ts',
'src/main/wsl/wsl-host-peer-desktops.ts',
'src/main/wsl/wsl-host-removal.ts'
]) {
expect(selectPrE2eSpecs([file]), file).toContain('tests/e2e/wsl-host-fake-transport.spec.ts')
}
expect(selectPrE2eSpecs(['src/main/wsl/wsl-host-server-stop.test.ts'])).not.toContain(
'tests/e2e/wsl-host-fake-transport.spec.ts'
)
})
})
describe('wsl-host fake connector routing', () => {
it('runs both WSL host specs for a fake connector change', () => {
const specs = selectPrE2eSpecs(['src/main/wsl/wsl-host-e2e-fake-connector.ts'])
for (const spec of [
'tests/e2e/wsl-host-chat.spec.ts',
'tests/e2e/wsl-host-fake-transport.spec.ts'
]) {
expect(specs, spec).toContain(spec)
expect(existsSync(join(projectDir, spec)), spec).toBe(true)
}
expect(selectPrE2eSpecs(['src/main/wsl/wsl-host-e2e-fake-connector.test.ts'])).not.toContain(
'tests/e2e/wsl-host-chat.spec.ts'
)
})
})
describe('managed-SSH remote sources routing', () => {
it('gives every managed-SSH spec a source route', () => {
const routed = new Set(PR_E2E_SOURCE_ROUTES.flatMap((route) => route.specs))
const specs = readdirSync(join(projectDir, 'tests/e2e'))
.filter((name) => /^ssh-orcad-.*\.spec\.ts$/.test(name))
.map((name) => `tests/e2e/${name}`)
expect(specs.length).toBeGreaterThan(0)
expect(
specs.filter((spec) => !routed.has(spec) && !MANUAL_ONLY_E2E_SPECS.includes(spec))
).toEqual([])
})
it('starts the lane for remote product sources no hand-kept route names', () => {
for (const file of [
// #30130 and #30120 merged with the lane skipped.
'src/main/ipc/ssh/ssh.ts',
'src/cli/runtime/serve-orcad-launch.ts',
'src/main/orcad/orcad-entry.ts',
'src/main/ssh/connection/ssh-connection.ts',
'src/main/ipc/runtime-environments/runtime-environment-status-probe.ts',
'src/main/ipc/runtime-environments/orcad-runtime-lifecycle-handlers.ts',
'src/shared/protocol-version.ts',
'src/shared/remote-runtime/remote-runtime-client-error-classification.ts',
// #30047 and #30060: sidebar sleep and project removal.
'src/renderer/src/components/sidebar/sleep-worktree-flow.ts',
'src/renderer/src/components/sidebar/ssh-host-remove-resolution.ts',
'src/renderer/src/store/repos/repo-removal.ts',
// #30775: the pane banner for a managed server that refused this desktop.
'src/renderer/src/components/terminal-pane/TerminalRemoteRuntimeReconnectBanner.tsx',
'src/renderer/src/components/terminal-pane/use-managed-pairing-refused.ts',
'src/renderer/src/components/terminal-pane/TerminalPaneRuntimePortals.tsx'
]) {
expect(existsSync(join(projectDir, file)), file).toBe(true)
expect(selectsManagedSshLane(file), file).toBe(true)
}
})
it('leaves unit tests and local-only sources alone', () => {
for (const file of [
'src/main/ssh/connection/ssh-connection.test.ts',
'src/main/ipc/ssh/ssh.test.ts',
'src/main/daemon/daemon-server.ts',
'src/main/runtime/rpc/methods/accounts.ts',
'src/renderer/src/components/sidebar/WorktreeCard.tsx',
'src/renderer/src/store/repos/repo-ordering.ts',
'docs/STYLEGUIDE.md'
]) {
expect(selectsManagedSshLane(file), file).toBe(false)
}
})
})