Files
orca/src/main/ssh/ssh-relay-native-deps-install.test.ts
T
Rod BoevandOrcaWin f56e6ade80 fix(ssh): recover orphaned relay install locks (#9828) (#10207)
* fix(ssh): recover orphaned relay install locks (#9828)

* test(ssh): split staged upload relay specs (#9828)

* fix(ssh): verify staged relay upload namespace

* fix(ssh): bound stale relay stage cleanup

* fix(ssh): complete bounded stage recovery

* fix(ssh): generate valid PowerShell stage scripts

* fix(ssh): make staged upload cancellation safe

* fix(ssh): fence staged relay recovery

* test(ssh): align deploy timeout oracle

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 16:17:37 -07:00

910 lines
38 KiB
TypeScript

// Why: regression coverage for the install-probe contract — the "node-pty is not available" bug shipped because every guard layer was silent.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as RelayInstallMarkerModule from './ssh-relay-install-marker'
vi.mock('electron', () => ({
app: { getAppPath: () => '/mock/app' }
}))
vi.mock('fs', () => ({
existsSync: vi.fn().mockReturnValue(true),
readFileSync: vi.fn().mockReturnValue('0.1.0+testhash')
}))
vi.mock('./relay-protocol', () => ({
RELAY_VERSION: '0.1.0',
RELAY_REMOTE_DIR: '.orca-remote',
parseUnameToRelayPlatform: vi.fn().mockReturnValue('linux-x64'),
RELAY_SENTINEL: 'ORCA-RELAY v0.1.0 READY\n',
RELAY_SENTINEL_TIMEOUT_MS: 10_000
}))
vi.mock('./ssh-relay-deploy-helpers', () => ({
uploadDirectory: vi.fn().mockResolvedValue(undefined),
waitForSentinel: vi.fn().mockResolvedValue({
write: vi.fn(),
onData: vi.fn(),
onClose: vi.fn()
}),
isUnconfirmedSshCommandTermination: (error: unknown) =>
error instanceof Error &&
(error as Error & { sshChannelCloseConfirmed?: boolean }).sshChannelCloseConfirmed === false,
execCommand: vi.fn()
}))
vi.mock('./ssh-remote-node-resolution', () => ({
resolveRemoteNodePath: vi.fn().mockResolvedValue('/usr/bin/node')
}))
vi.mock('./ssh-relay-install-marker', async (importOriginal) => ({
...(await importOriginal<typeof RelayInstallMarkerModule>()),
createRelayInstallMarkerFileName: () => '.sftp-namespace-00000000000000000000000000000000'
}))
vi.mock('./ssh-relay-versioned-install', () => ({
readLocalFullVersion: vi.fn().mockReturnValue('0.1.0+testhash'),
computeRemoteRelayDir: (home: string, v: string) => `${home}/.orca-remote/relay-${v}`,
isRelayAlreadyInstalled: vi.fn().mockResolvedValue(false),
finalizeInstall: vi.fn().mockResolvedValue(undefined),
abandonInstall: vi.fn().mockResolvedValue(undefined),
gcOldRelayVersions: vi.fn().mockResolvedValue(undefined)
}))
vi.mock('./ssh-relay-install-lock', () => ({
acquireInstallLock: vi.fn().mockResolvedValue(undefined),
RELAY_INSTALL_LOCK_NAME: '.install-lock'
}))
vi.mock('./ssh-relay-repair-lock', () => ({
tryAcquireRelayRepairLock: vi.fn().mockResolvedValue('acquired')
}))
vi.mock('./ssh-relay-gc-claim', () => ({
releaseRelayGcClaimWithRetry: vi.fn().mockResolvedValue('released'),
tryAcquireRelayGcClaim: vi.fn().mockResolvedValue('launch-token'),
waitForRelayGcClaimRelease: vi.fn().mockResolvedValue(undefined)
}))
vi.mock('./ssh-connection-utils', () => ({
shellEscape: (s: string) => `'${s}'`
}))
import { deployAndLaunchRelay } from './ssh-relay-deploy'
import { execCommand, uploadDirectory } from './ssh-relay-deploy-helpers'
import { RELAY_DEPLOY_TIMEOUT_MS } from './ssh-relay-deploy-timing'
import { parseUnameToRelayPlatform } from './relay-protocol'
import { resolveRemoteNodePath } from './ssh-remote-node-resolution'
import {
abandonInstall,
finalizeInstall,
isRelayAlreadyInstalled
} from './ssh-relay-versioned-install'
import { acquireInstallLock } from './ssh-relay-install-lock'
import { tryAcquireRelayRepairLock } from './ssh-relay-repair-lock'
import {
decodePowerShellCommand,
makeExecResponses,
makeMockConnection,
makeRepairToolchainSkipExecResponses,
makeStagedFirstInstallExecPrefix,
type ExecResponse,
type SftpWriteCapture
} from './ssh-relay-native-deps-install-fixture'
describe('installNativeDeps (via deployAndLaunchRelay)', () => {
let warnSpy: ReturnType<typeof vi.spyOn>
const sftpCapture: SftpWriteCapture = {
paths: [],
contents: {},
execCallCountAtWrite: {}
}
beforeEach(() => {
vi.clearAllMocks()
// mockReset because clearAllMocks keeps queued mockResolvedValueOnce entries, so a leaked response would bleed into the next test.
vi.mocked(execCommand).mockReset().mockResolvedValue('')
vi.mocked(uploadDirectory).mockResolvedValue(undefined)
sftpCapture.paths.length = 0
for (const k of Object.keys(sftpCapture.contents)) {
delete sftpCapture.contents[k]
}
for (const k of Object.keys(sftpCapture.execCallCountAtWrite)) {
delete sftpCapture.execCallCountAtWrite[k]
}
// Re-prime as defense-in-depth: factory mocks survive clearAllMocks but a test's own resetAllMocks would drop them.
vi.mocked(parseUnameToRelayPlatform).mockReturnValue('linux-x64')
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(false)
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
})
afterEach(() => {
warnSpy.mockRestore()
})
function feed(execResponses: ExecResponse[]): void {
const mockExec = vi.mocked(execCommand)
for (const r of execResponses) {
if (typeof r === 'string') {
mockExec.mockResolvedValueOnce(r)
} else {
mockExec.mockRejectedValueOnce(new Error(r.reject))
}
}
}
it('propagates a hard `npm install` failure so the deploy aborts before finalizeInstall', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'npm ERR! E404 Not Found node-pty' },
probe: 'ok'
})
)
await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/npm ERR/)
// Regression: previously the catch swallowed the throw and finalizeInstall ran anyway.
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NATIVE-DEPS-INSTALL-FAIL]'))).toBe(true)
})
it('connects without node-pty when the remote toolchain is missing, instead of failing the host', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'gyp ERR! stack Error: not found: make' },
nodePtySkipRetry: 'ok',
toolchainProbe: 'PKG apk'
})
)
// node-pty only backs terminals, so a host that cannot compile it still gets files and git.
await expect(deployAndLaunchRelay(conn)).resolves.toBeDefined()
expect(vi.mocked(finalizeInstall)).toHaveBeenCalled()
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const reinstall = execCalls.findLast((c) => c.includes('npm install')) ?? ''
expect(reinstall).toContain('@parcel/watcher@')
expect(reinstall).not.toContain('node-pty@')
// The skip path must stop here: rebuilding is pointless on a host with no compiler.
expect(execCalls.some((c) => c.includes('npm rebuild'))).toBe(false)
// node-pty is legitimately absent, so its probe result must not raise the degraded-mode alarm.
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][WATCHER-MISSING-NPTY-SKIPPED]'))).toBe(
false
)
// The rewritten manifest must drop node-pty too, or npm reconciles it back and rebuilds.
const pkgPath = sftpCapture.paths.findLast((p) => p.endsWith('/package.json')) as string
// The capture concatenates every write to a path; the rewrite is the last manifest line.
const latest = sftpCapture.contents[pkgPath].trim().split('\n').at(-1) as string
const deps = (JSON.parse(latest) as { dependencies: object }).dependencies
expect(deps).toHaveProperty('@parcel/watcher')
expect(deps).not.toHaveProperty('node-pty')
})
it('warns when @parcel/watcher is also unloadable after node-pty was skipped', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'gyp ERR! stack Error: not found: make' },
nodePtySkipRetry: 'ok',
nodePtySkipWatcher: 'missing'
})
)
// Watcher failure (e.g. glibc below the floor) is non-fatal, but silent dead file watching is not acceptable.
await expect(deployAndLaunchRelay(conn)).resolves.toBeDefined()
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][WATCHER-MISSING-NPTY-SKIPPED]'))).toBe(
true
)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.some((c) => c.includes('npm rebuild'))).toBe(false)
})
it('hard-fails on a gyp error when the remote toolchain is actually complete', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'gyp ERR! stack Error: not found: make' },
// Probe contradicts the gyp output: the tools are all there, so the real cause is unknown.
toolchainProbe: 'HAVE make\nHAVE g++\nHAVE python3\nPKG apt-get'
})
)
const error = await deployAndLaunchRelay(conn).catch((e: Error) => e)
// Degrading here would silently drop terminals on a host that can build them.
expect((error as Error).message).toContain('gyp ERR!')
expect((error as Error).message).not.toContain('build tools')
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.filter((c) => c.includes('npm install'))).toHaveLength(1)
expect(execCalls.some((c) => c.includes("rm -rf 'node_modules/node-pty'"))).toBe(false)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
})
it('still reports the actionable build-tools error when the node-pty-less retry also fails', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'gyp ERR! stack Error: not found: make' },
// No HAVE lines + apk present: the tailored hint must come from the remote probe, not a hardcoded apt fallback.
toolchainProbe: 'PKG apk',
nodePtySkipRetry: { reject: 'npm ERR! registry unreachable' }
})
)
const error = await deployAndLaunchRelay(conn).catch((e: Error) => e)
expect(error).toBeInstanceOf(Error)
const message = (error as Error).message
// Actionable: names the missing tools and the exact install command.
expect(message).toContain('build tools')
expect(message).toContain('make')
expect(message).toContain('sudo apk add build-base python3')
// The raw npm/node-gyp output is preserved for triage, not discarded.
expect(message).toContain('not found: make')
// The retry's own cause is unrelated to the toolchain, so it must survive as cause + a log line.
expect((error as Error).cause).toBeInstanceOf(Error)
expect(((error as Error).cause as Error).message).toContain('registry unreachable')
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(
warnMessages.some(
(m) => m.includes('[ssh-relay][NPTY-SKIP-RETRY-FAIL]') && m.includes('registry unreachable')
)
).toBe(true)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
})
it('preserves the original npm error when it is not a native build-tool failure', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: { reject: 'npm ERR! network ETIMEDOUT' },
probe: 'ok',
// A network error must stay a network error even if the host also lacks build tools.
toolchainProbe: 'PKG apt-get'
})
)
// Non-toolchain npm error (network/registry): surface the real error, not a misleading "install build tools".
const error = await deployAndLaunchRelay(conn).catch((e: Error) => e)
expect((error as Error).message).toContain('npm ERR! network ETIMEDOUT')
expect((error as Error).message).not.toContain('build tools')
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.some((c) => c.includes('command -v "$t"'))).toBe(false)
})
it('preserves redirected npm stdout for non-toolchain failures without probing', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: {
reject:
'Command "export PATH=/usr/bin:$PATH && cd /home/u/.orca-remote/relay && npm install node-pty@1.1.0 2>&1" failed (exit 1): npm ERR! network ETIMEDOUT'
},
probe: 'ok',
toolchainProbe: 'PKG apt-get'
})
)
const error = await deployAndLaunchRelay(conn).catch((e: Error) => e)
expect((error as Error).message).toContain('npm ERR! network ETIMEDOUT')
expect((error as Error).message).not.toContain('build tools')
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.some((c) => c.includes('command -v "$t"'))).toBe(false)
})
it('warns clearly when node-pty installs but require() fails (built-but-unloadable)', async () => {
const conn = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'missing' }))
await deployAndLaunchRelay(conn)
// Probe failure is non-fatal by design (docs/ssh-relay-versioned-install-dirs.md): throwing would loop reconnects forever where node-pty can't build.
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(true)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('rebuilds unloadable native deps and recovers before first relay launch', async () => {
const conn = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'missing', repairProbe: 'ok' }))
await deployAndLaunchRelay(conn)
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const failedProbeIdx = execCalls.findIndex((c) => c.includes('require("node-pty")'))
const rebuildIdx = execCalls.findIndex((c) => c.includes('npm rebuild'))
const repairedProbeIdx = execCalls.findIndex(
(c, index) => index > rebuildIdx && c.includes('require("node-pty")')
)
expect(rebuildIdx).toBeGreaterThan(failedProbeIdx)
expect(execCalls[rebuildIdx]).toContain('--ignore-scripts=false')
expect(repairedProbeIdx).toBeGreaterThan(rebuildIdx)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
})
it('propagates an SSH-channel failure from the post-rebuild re-probe', async () => {
// Why: rebuild degrades gracefully, but the post-rebuild probe must surface transport death, else a dead channel finalizes a half-repaired install.
const conn = makeMockConnection(sftpCapture)
feed([
...makeStagedFirstInstallExecPrefix(),
'', // npm install native deps
'', // chmod prebuilds
'MISSING\n', // first probe: require() fails
'', // cat probe stderr
'', // rm probe stderr
'', // npm rebuild native deps
'', // chmod prebuilds after rebuild
{ reject: 'SSH channel closed during native deps re-probe' } // re-probe rejects
])
await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/SSH channel closed/)
// Rebuild failure is swallowed; a re-probe transport failure must not be.
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('aborts an in-progress native install and releases its lock at deploy timeout', async () => {
vi.useFakeTimers()
try {
const conn = makeMockConnection(sftpCapture)
feed(makeStagedFirstInstallExecPrefix())
let installSignal: AbortSignal | undefined
vi.mocked(execCommand).mockImplementationOnce((_conn, command, options) => {
expect(command).toContain('npm install')
installSignal = options?.signal
return new Promise<string>((_resolve, reject) => {
installSignal?.addEventListener('abort', () => reject(installSignal?.reason), {
once: true
})
})
})
const promise = deployAndLaunchRelay(conn).catch((err: Error) => err)
await vi.waitFor(() => expect(installSignal).toBeDefined())
await vi.advanceTimersByTimeAsync(RELAY_DEPLOY_TIMEOUT_MS)
const result = await promise
expect(result).toBeInstanceOf(Error)
expect((result as Error).message).toBe(
`Relay deployment timed out after ${RELAY_DEPLOY_TIMEOUT_MS / 1000}s`
)
expect(installSignal?.aborted).toBe(true)
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
} finally {
vi.useRealTimers()
}
})
it('lets a probe SSH-channel failure bubble up rather than silently mapping to MISSING', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: 'ok',
probe: { reject: 'SSH channel closed unexpectedly' }
})
)
await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/SSH channel/)
// Pin the rejection to the PROBE call so slot-ordering drift can't pass this test via a different failure path.
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const probeCallIdx = execCalls.findIndex((c) => c.includes('require("node-pty")'))
const npmInstallIdx = execCalls.findIndex(
(c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher')
)
expect(probeCallIdx, 'probe must have been invoked').toBeGreaterThanOrEqual(0)
// Probe must come strictly after npm install, else it probes an empty dir and doesn't represent the real-world race.
expect(probeCallIdx).toBeGreaterThan(npmInstallIdx)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
// Channel failure must not be conflated with "node-pty missing" or "npm install failed".
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false)
expect(warnMessages.some((m) => m.includes('[ssh-relay][NATIVE-DEPS-INSTALL-FAIL]'))).toBe(
false
)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
// Lock must be released so a future reconnect can retry.
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('throws (rather than warns MISSING) when the install dir vanishes between npm install and probe', async () => {
const conn = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'dir-gone' }))
// Why: cd-failure short-circuits the probe's && so it rejects instead of resolving MISSING; conflating "dir gone" with "node-pty missing" strands the user in degraded mode.
await expect(deployAndLaunchRelay(conn)).rejects.toThrow(/cd:/)
// Pin the rejection to the probe slot so a refactor moving probe before npm install can't pass this test for the wrong reason.
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const probeIdx = execCalls.findIndex((c) => c.includes('require("node-pty")'))
const npmInstallIdx = execCalls.findIndex(
(c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher')
)
expect(probeIdx).toBeGreaterThan(npmInstallIdx)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('uses `node -e require()` rather than `test -d` so unloadable installs are caught', async () => {
const conn = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'ok' }))
await deployAndLaunchRelay(conn)
const probeCmds = vi
.mocked(execCommand)
.mock.calls.map(([, c]) => c)
.filter((c) => c.includes(`require("node-pty")`))
// Why: probe must require('node-pty') via the node binary; a weaker test -d passes even when the native binding load is broken.
expect(probeCmds.length).toBeGreaterThan(0)
expect(probeCmds[0]).toMatch(/node['"]?\s+-e/)
// Pin order npm install → chmod prebuilds → probe: chmod-after-probe breaks spawn-helper bits; probe-before-install tests an empty dir.
const all = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const npmIdx = all.findIndex(
(c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher')
)
const chmodPrebuildsIdx = all.findIndex(
(c) => c.includes('spawn-helper') && c.includes('chmod +x')
)
const probeIdx = all.findIndex((c) => c.includes('require("node-pty")'))
expect(npmIdx).toBeGreaterThanOrEqual(0)
expect(chmodPrebuildsIdx).toBeGreaterThan(npmIdx)
expect(probeIdx).toBeGreaterThan(chmodPrebuildsIdx)
// Hold the install lock through launch, then release it exactly once.
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('matches the sentinel even with bashrc/MOTD noise prefixed to probe stdout', async () => {
const conn = makeMockConnection(sftpCapture)
// Why: noisy .bashrc/MOTD can prefix probe stdout; production uses .includes(PROBE_OK) so pre-sentinel noise still resolves OK.
feed(
makeExecResponses({
npmInstall: 'ok',
probe: 'ok',
probeStdoutOverride: 'Welcome to Acme Corp\nLast login: ...\nORCA-NPTY-PROBE-OK\n'
})
)
await deployAndLaunchRelay(conn)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(false)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
})
it('detects MISSING even when the shell prepends noise before the MISSING token', async () => {
const conn = makeMockConnection(sftpCapture)
feed(
makeExecResponses({
npmInstall: 'ok',
probe: 'missing',
probeStdoutOverride: '(node:1234) [DEP0040] DeprecationWarning: ...\nMISSING\n'
})
)
await deployAndLaunchRelay(conn)
// Absence of PROBE_OK triggers the warn regardless of surrounding noise; finalize still runs by design.
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(true)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('keeps Windows node-pty probe failures non-fatal by checking LASTEXITCODE', async () => {
vi.mocked(parseUnameToRelayPlatform).mockReturnValueOnce('win32-x64')
vi.mocked(resolveRemoteNodePath).mockResolvedValueOnce('C:/Program Files/nodejs/node.exe')
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Windows AMD64',
'C:\\Users\\u',
'', // bounded stale-stage recovery
'__ORCA_UPLOAD_STAGE_SLOT__.sftp-namespace-00000000000000000000000000000000:slot-0',
'__ORCA_UPLOAD_STAGE_PROMOTION__.sftp-namespace-00000000000000000000000000000000:PROMOTED',
'', // npm install native deps
'MISSING\n', // native process exit normalized by PowerShell command
'', // npm rebuild native deps
'MISSING\n', // rebuilt native process still cannot load
'', // clean stage root
'', // no persisted active pipe marker
'WAITING', // initial pipe probe
'', // publish the per-launch credential
'', // WMI relay launch
'READY', // readiness poll
'' // persist active pipe marker
])
await deployAndLaunchRelay(conn)
const probeCommand =
vi
.mocked(execCommand)
.mock.calls.map(([, c]) => c)
.find((command) => decodePowerShellCommand(command)?.includes('require(\\"node-pty\\")')) ??
''
const probeScript = decodePowerShellCommand(probeCommand) ?? ''
expect(probeScript).toContain('$LASTEXITCODE -ne 0')
expect(probeScript).toContain("'MISSING'")
expect(probeScript).toContain('loadNativeModule')
const npmScripts = vi
.mocked(execCommand)
.mock.calls.map(([, command]) => decodePowerShellCommand(command) ?? '')
.filter((script) => script.includes('npm install') || script.includes('npm rebuild'))
expect(npmScripts).toHaveLength(2)
expect(npmScripts.every((script) => script.includes('--ignore-scripts=false'))).toBe(true)
expect(
npmScripts.every((script) =>
script.includes('if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }')
)
).toBe(true)
expect(
vi
.mocked(execCommand)
.mock.calls.some(([, command]) => command.includes('.npty-probe.stderr'))
).toBe(false)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('[ssh-relay][NPTY-MISSING]'))).toBe(true)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
})
it('includes the platform tuple in NPTY-MISSING and native install failure logs', async () => {
// Platform tuple lets bug reports be triaged for prebuild availability without asking the user for their arch.
const conn = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'missing' }))
await deployAndLaunchRelay(conn)
const missingMsgs = warnSpy.mock.calls
.map((args) => String(args[0] ?? ''))
.filter((m) => m.includes('[ssh-relay][NPTY-MISSING]'))
expect(missingMsgs.length).toBeGreaterThan(0)
expect(missingMsgs[0]).toContain('linux-x64')
})
it('writes an idempotent package.json (same bytes on every install)', async () => {
// First install run.
const conn1 = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'ok' }))
await deployAndLaunchRelay(conn1)
const firstPath = sftpCapture.paths.find((p) => p.endsWith('/package.json')) as string
const first = sftpCapture.contents[firstPath]
// Reset capture, run again as if it were a fresh install of the same dir.
sftpCapture.paths.length = 0
for (const k of Object.keys(sftpCapture.contents)) {
delete sftpCapture.contents[k]
}
for (const k of Object.keys(sftpCapture.execCallCountAtWrite)) {
delete sftpCapture.execCallCountAtWrite[k]
}
vi.mocked(execCommand).mockReset().mockResolvedValue('')
const conn2 = makeMockConnection(sftpCapture)
feed(makeExecResponses({ npmInstall: 'ok', probe: 'ok' }))
await deployAndLaunchRelay(conn2)
const secondPath = sftpCapture.paths.find((p) => p.endsWith('/package.json')) as string
const second = sftpCapture.contents[secondPath]
expect(second).toBe(first)
})
it('repairs an existing complete relay dir that is missing @parcel/watcher', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'ORCA-NATIVE-DEPS-MISSING:@parcel/watcher\nMISSING', // first probe before lock
'ORCA-NATIVE-DEPS-MISSING:@parcel/watcher\nMISSING', // re-probe after lock
'', // SFTP-namespace install-owner marker (repair)
'', // npm install native deps
'', // chmod prebuilds
'ORCA-NPTY-PROBE-OK\n',
'', // rm probe stderr
'DEAD',
'', // publish the per-launch credential
'READY'
])
await deployAndLaunchRelay(conn)
expect(vi.mocked(tryAcquireRelayRepairLock)).toHaveBeenCalledTimes(1)
expect(vi.mocked(tryAcquireRelayRepairLock).mock.calls[0]?.[3]?.signal).toBeInstanceOf(
AbortSignal
)
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(
execCalls.some(
(c) => c.includes('npm install') && c.includes('node-pty') && c.includes('@parcel/watcher')
)
).toBe(true)
const installCommand = execCalls.find((c) => c.includes('npm install')) ?? ''
expect(installCommand).toContain('node_modules/@parcel/watcher')
expect(installCommand).toContain("-name 'watcher-*'")
expect(installCommand).not.toContain("rm -rf 'node_modules/node-pty'")
})
it('keeps the caller resets when a repair reconnect has to drop node-pty', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed(makeRepairToolchainSkipExecResponses())
await expect(deployAndLaunchRelay(conn)).resolves.toBeDefined()
expect(vi.mocked(finalizeInstall)).toHaveBeenCalledTimes(1)
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
const reinstall = execCalls.findLast((c) => c.includes('npm install')) ?? ''
expect(reinstall).not.toContain('node-pty@')
expect(reinstall).toContain("rm -rf 'node_modules/node-pty'")
// Dropping the repair's own resets here leaves npm calling the broken watcher up to date.
expect(reinstall).toContain("rm -rf 'node_modules/@parcel/watcher'")
expect(reinstall).toContain("-name 'watcher-*'")
})
it('launches an already-installed relay in degraded mode when repair throws', async () => {
// Why: a repair failure on a completed dir must not block the connection — relay still serves fs/git/preflight; next reconnect retries.
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'MISSING', // health probe: require() fails
'MISSING', // re-probe after lock
'', // SFTP-namespace install-owner marker (repair)
{ reject: 'npm ERR! network ETIMEDOUT' }, // npm install fails (offline)
'DEAD',
'', // remote credential generation after degraded repair
'READY'
])
// Deploy must resolve (degraded), not reject.
await deployAndLaunchRelay(conn)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes('launching degraded'))).toBe(true)
})
it('retains the repair lock when remote command termination is unconfirmed', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
vi.mocked(execCommand)
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
.mockResolvedValueOnce('/home/u')
.mockResolvedValueOnce('MISSING')
.mockResolvedValueOnce('MISSING')
.mockResolvedValueOnce('') // SFTP-namespace install-owner marker (repair)
.mockRejectedValueOnce(
Object.assign(new Error('npm termination was not confirmed'), {
sshChannelCloseConfirmed: false
})
)
.mockResolvedValueOnce('DEAD')
.mockResolvedValueOnce('') // remote credential generation after degraded repair
.mockResolvedValueOnce('READY')
await deployAndLaunchRelay(conn)
expect(vi.mocked(abandonInstall)).not.toHaveBeenCalled()
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((message) => message.includes('launching degraded'))).toBe(true)
})
it('retains the first-install lock when an aborted npm install has unconfirmed teardown', async () => {
vi.useFakeTimers()
try {
const conn = makeMockConnection(sftpCapture)
feed(makeStagedFirstInstallExecPrefix())
let installSignal: AbortSignal | undefined
vi.mocked(execCommand).mockImplementationOnce((_conn, command, options) => {
expect(command).toContain('npm install')
installSignal = options?.signal
return new Promise<string>((_resolve, reject) => {
installSignal?.addEventListener(
'abort',
() => {
// Mirrors execCommand's bounded close grace when ssh2 never confirms npm stopped.
setTimeout(
() =>
reject(
Object.assign(new Error('npm teardown remained unconfirmed'), {
sshChannelCloseConfirmed: false
})
),
5_000
)
},
{ once: true }
)
})
})
const deploy = deployAndLaunchRelay(conn).catch((err: Error) => err)
await vi.waitFor(() => expect(installSignal).toBeDefined())
await vi.advanceTimersByTimeAsync(RELAY_DEPLOY_TIMEOUT_MS)
await vi.advanceTimersByTimeAsync(5_000)
const result = await deploy
expect(result).toBeInstanceOf(Error)
expect((result as Error).message).toContain('Relay deployment timed out')
expect(vi.mocked(abandonInstall)).not.toHaveBeenCalled()
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
} finally {
vi.useRealTimers()
}
})
it('does not finalize or release a first-install lock after unconfirmed rebuild teardown', async () => {
const conn = makeMockConnection(sftpCapture)
feed([
...makeStagedFirstInstallExecPrefix(),
'', // npm install
'', // chmod prebuilds
'MISSING',
'rebuild diagnostics',
'' // remove probe diagnostics
])
vi.mocked(execCommand).mockRejectedValueOnce(
Object.assign(new Error('rebuild termination was not confirmed'), {
sshChannelCloseConfirmed: false
})
)
await expect(deployAndLaunchRelay(conn)).rejects.toThrow(
'rebuild termination was not confirmed'
)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).not.toHaveBeenCalled()
})
it('retains the first-install lock when an aborted rebuild has unconfirmed teardown', async () => {
vi.useFakeTimers()
try {
const conn = makeMockConnection(sftpCapture)
feed([
...makeStagedFirstInstallExecPrefix(),
'', // npm install
'', // chmod prebuilds
'MISSING',
'rebuild diagnostics',
'' // remove probe diagnostics
])
let rebuildSignal: AbortSignal | undefined
vi.mocked(execCommand).mockImplementationOnce((_conn, command, options) => {
expect(command).toContain('npm rebuild')
rebuildSignal = options?.signal
return new Promise<string>((_resolve, reject) => {
rebuildSignal?.addEventListener(
'abort',
() => {
setTimeout(
() =>
reject(
Object.assign(new Error('rebuild teardown remained unconfirmed'), {
sshChannelCloseConfirmed: false
})
),
5_000
)
},
{ once: true }
)
})
})
const deploy = deployAndLaunchRelay(conn).catch((err: Error) => err)
await vi.waitFor(() => expect(rebuildSignal).toBeDefined())
await vi.advanceTimersByTimeAsync(RELAY_DEPLOY_TIMEOUT_MS)
await vi.advanceTimersByTimeAsync(5_000)
const result = await deploy
expect(result).toBeInstanceOf(Error)
expect((result as Error).message).toContain('Relay deployment timed out')
expect(vi.mocked(abandonInstall)).not.toHaveBeenCalled()
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
} finally {
vi.useRealTimers()
}
})
it.each(['busy', 'error'] as const)('launches degraded when lock is %s', async (lockResult) => {
// Why: lock contention/wedge must not block a completed relay from launching in degraded mode.
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
vi.mocked(tryAcquireRelayRepairLock).mockResolvedValueOnce(lockResult)
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'MISSING',
'DEAD',
'', // remote credential generation without a namespace marker
'READY'
])
await deployAndLaunchRelay(conn)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).not.toHaveBeenCalled()
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.some((c) => c.includes('npm install'))).toBe(false)
const warnMessages = warnSpy.mock.calls.map((args) => String(args[0] ?? ''))
expect(warnMessages.some((m) => m.includes(`repair lock is ${lockResult}`))).toBe(true)
})
it('loads native bindings when checking whether a completed relay needs repair', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'ORCA-NATIVE-DEPS-OK',
'', // launch namespace marker
'DEAD',
'', // publish the per-launch credential
'READY'
])
await deployAndLaunchRelay(conn)
const healthProbe = vi
.mocked(execCommand)
.mock.calls.map(([, c]) => c)
.find((c) => c.includes('ORCA-NATIVE-DEPS-OK'))
expect(healthProbe).toContain('require("node-pty")')
expect(healthProbe).toContain('loadNativeModule')
expect(healthProbe).toContain('require("@parcel/watcher")')
expect(healthProbe).not.toContain('require.resolve')
})
it('does not mutate an existing relay dir when required native deps are present', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'ORCA-NATIVE-DEPS-OK',
'', // launch namespace marker
'DEAD',
'', // publish the per-launch credential
'READY'
])
await deployAndLaunchRelay(conn)
expect(vi.mocked(acquireInstallLock)).not.toHaveBeenCalled()
expect(vi.mocked(tryAcquireRelayRepairLock)).toHaveBeenCalledTimes(1)
expect(vi.mocked(finalizeInstall)).not.toHaveBeenCalled()
expect(vi.mocked(abandonInstall)).toHaveBeenCalledTimes(1)
const execCalls = vi.mocked(execCommand).mock.calls.map(([, c]) => c)
expect(execCalls.some((c) => c.includes('npm install'))).toBe(false)
})
})