Files
orca/src/main/ipc/cli.test.ts
T
Jinjing 34a9df7795 Fix wsl cli rc4 migration (#8471)
* fix(cli): migrate managed WSL launchers to native exe

* refactor(cli): reconcile managed WSL registrations

* fix(cli): harden WSL registration reconciliation

* fix(cli): serialize and harden WSL CLI registration lifecycle

- Cap per-distro concurrent repairs and add a filesystem lock so two
  Orca installs can't race the same distro's launcher/bridge files
- Skip re-adopting a legacy managed launcher when its bridge is user-
  owned, avoiding a doomed install retried on every startup
- Track per-distro reconciliation against launcher target + app
  version so unchanged registrations aren't re-probed each boot
- Replace command install with a single atomic rename instead of a
  move-based backup, closing a window where no orca-ide exists
- Remove the registry invalidate-on-persist-failure path now that
  writes are atomic and repair is disk-authoritative
- Add brief negative caching to WSL distro discovery to bound
  wsl.exe respawns on persistently broken hosts
- Extract keyed-promise-queue as a shared serialization primitive
2026-07-12 23:19:15 -07:00

149 lines
5.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
handlers,
ipcHandleMock,
wslInstallerMock,
recordInstalledMock,
recordRemovedMock,
getDefaultWslDistroMock
} = vi.hoisted(() => ({
handlers: new Map<string, (...args: unknown[]) => unknown>(),
ipcHandleMock: vi.fn(),
wslInstallerMock: vi.fn(),
recordInstalledMock: vi.fn(),
recordRemovedMock: vi.fn(),
getDefaultWslDistroMock: vi.fn()
}))
vi.mock('electron', () => ({ ipcMain: { handle: ipcHandleMock } }))
vi.mock('../cli/cli-installer', () => ({ CliInstaller: vi.fn() }))
vi.mock('../cli/wsl-cli-installer', () => ({ WslCliInstaller: wslInstallerMock }))
vi.mock('../cli/wsl-cli-registration-registry', () => ({
recordWslCliRegistrationInstalled: recordInstalledMock,
recordWslCliRegistrationRemoved: recordRemovedMock
}))
vi.mock('../persistence', () => ({ getCanonicalUserDataPath: () => '/canonical-user-data' }))
vi.mock('../startup/hydrate-shell-path', () => ({
hydrateShellPath: vi.fn(async () => ({ ok: false })),
mergePathSegments: vi.fn()
}))
vi.mock('../wsl', () => ({ getDefaultWslDistro: getDefaultWslDistroMock }))
import { registerCliHandlers } from './cli'
type WslHandler = (event: unknown, args?: { distro?: string | null }) => Promise<{ state: string }>
function getWslHandler(channel: string): WslHandler {
const handler = handlers.get(channel)
if (!handler) {
throw new Error(`Missing IPC handler: ${channel}`)
}
return handler as WslHandler
}
describe('WSL CLI registration IPC', () => {
beforeEach(() => {
handlers.clear()
ipcHandleMock.mockReset()
ipcHandleMock.mockImplementation(
(channel: string, handler: (...args: unknown[]) => unknown) => {
handlers.set(channel, handler)
}
)
wslInstallerMock.mockReset()
recordInstalledMock.mockReset().mockResolvedValue(undefined)
recordRemovedMock.mockReset().mockResolvedValue(undefined)
getDefaultWslDistroMock.mockReset().mockReturnValue('Ubuntu')
registerCliHandlers()
})
it('records a successful explicit-distro installation', async () => {
const install = vi.fn(async () => ({ state: 'installed' }))
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
return { install }
})
await expect(getWslHandler('cli:installWsl')({}, { distro: ' Debian ' })).resolves.toEqual({
state: 'installed'
})
expect(wslInstallerMock).toHaveBeenCalledWith({ distro: 'Debian' })
expect(recordInstalledMock).toHaveBeenCalledWith('/canonical-user-data', 'Debian')
})
it('records removal from the resolved default distro', async () => {
const remove = vi.fn(async () => ({ state: 'not_installed' }))
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
return { remove }
})
await expect(getWslHandler('cli:removeWsl')({})).resolves.toEqual({ state: 'not_installed' })
expect(wslInstallerMock).toHaveBeenCalledWith({ distro: 'Ubuntu' })
expect(recordRemovedMock).toHaveBeenCalledWith('/canonical-user-data', 'Ubuntu')
})
it('does not claim ownership when installation is not confirmed', async () => {
const install = vi.fn(async () => ({ state: 'unsupported' }))
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
return { install }
})
await expect(getWslHandler('cli:installWsl')({})).resolves.toEqual({ state: 'unsupported' })
expect(recordInstalledMock).not.toHaveBeenCalled()
})
it('keeps successful installation successful when advisory registry persistence fails', async () => {
const install = vi.fn(async () => ({ state: 'installed' }))
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
return { install }
})
recordInstalledMock.mockRejectedValueOnce(new Error('ENOSPC'))
await expect(getWslHandler('cli:installWsl')({})).resolves.toEqual({ state: 'installed' })
})
it('keeps successful removal successful when advisory registry persistence fails', async () => {
const remove = vi.fn(async () => ({ state: 'not_installed' }))
wslInstallerMock.mockImplementation(function MockWslCliInstaller() {
return { remove }
})
recordRemovedMock.mockRejectedValueOnce(new Error('EACCES'))
await expect(getWslHandler('cli:removeWsl')({})).resolves.toEqual({ state: 'not_installed' })
})
it('serializes a concurrent removal after installation and ownership persistence', async () => {
let finishInstall!: () => void
const install = vi.fn(
() =>
new Promise<{ state: 'installed' }>((resolve) => {
finishInstall = () => resolve({ state: 'installed' })
})
)
const remove = vi.fn(async () => ({ state: 'not_installed' as const }))
wslInstallerMock
.mockImplementationOnce(function MockInstallWslCliInstaller() {
return { install }
})
.mockImplementationOnce(function MockRemoveWslCliInstaller() {
return { remove }
})
const installation = getWslHandler('cli:installWsl')({}, { distro: 'Ubuntu' })
await vi.waitFor(() => expect(install).toHaveBeenCalledOnce())
const removal = getWslHandler('cli:removeWsl')({}, { distro: 'ubuntu' })
await Promise.resolve()
expect(remove).not.toHaveBeenCalled()
finishInstall()
await expect(installation).resolves.toEqual({ state: 'installed' })
await expect(removal).resolves.toEqual({ state: 'not_installed' })
expect(recordInstalledMock.mock.invocationCallOrder[0]).toBeLessThan(
remove.mock.invocationCallOrder[0]
)
expect(remove.mock.invocationCallOrder[0]).toBeLessThan(
recordRemovedMock.mock.invocationCallOrder[0]
)
})
})