mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 00:02:41 +00:00
test: replace daemon fallback type assertions
This commit is contained in:
@@ -11,6 +11,7 @@ import type {
|
||||
MockSpawnerHandle,
|
||||
NetConnectStubs
|
||||
} from './daemon-init-mock-types'
|
||||
import type { DaemonLauncher } from './daemon-spawner'
|
||||
|
||||
export type { MockAdapter, MockSpawner } from './daemon-init-mock-types'
|
||||
|
||||
@@ -58,7 +59,7 @@ export function createDaemonInitModuleFactories(state: DaemonInitMockState) {
|
||||
// Why: both fakes are annotated with constructor types so the exported factories widen to
|
||||
// MockSpawner/MockAdapter instead of leaking their private fields into declaration emit.
|
||||
const MockDaemonSpawner: MockSpawnerConstructor = class MockDaemonSpawner {
|
||||
readonly launcher: unknown
|
||||
readonly launcher: DaemonLauncher
|
||||
readonly ensureRunning: Mock
|
||||
readonly resetHandle: Mock
|
||||
readonly resetRespawnWindow: Mock
|
||||
@@ -66,7 +67,7 @@ export function createDaemonInitModuleFactories(state: DaemonInitMockState) {
|
||||
readonly getHandle: Mock
|
||||
private socketCounter: number
|
||||
private handle: MockSpawnerHandle | null
|
||||
constructor(opts: { runtimeDir: string; launcher: unknown }) {
|
||||
constructor(opts: { runtimeDir: string; launcher: DaemonLauncher }) {
|
||||
this.launcher = opts.launcher
|
||||
this.socketCounter = 0
|
||||
this.handle = null
|
||||
|
||||
@@ -216,10 +216,7 @@ describe('daemon-init: runRestartDaemon (7-step sequence)', () => {
|
||||
const mod = await importFresh()
|
||||
await mod.initDaemonPtyProvider(undefined, { macosLoginSessionWatch: true })
|
||||
|
||||
const launcher = spawnerInstances[0].launcher as (
|
||||
socketPath: string,
|
||||
tokenPath: string
|
||||
) => Promise<{ mode?: string; shutdown(): Promise<void> }>
|
||||
const launcher = spawnerInstances[0].launcher
|
||||
getMacDaemonTccAttributionHealthMock.mockResolvedValueOnce('severed')
|
||||
getDaemonLaunchIdentityMock.mockReturnValueOnce(identity)
|
||||
// Why: live sessions must veto replacement — the Settings surface owns the remedy instead.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import type { DaemonLauncher } from './daemon-spawner'
|
||||
|
||||
/** Fake DaemonSpawner instance every mocked `new DaemonSpawner()` records. */
|
||||
export type MockSpawner = {
|
||||
@@ -7,7 +8,7 @@ export type MockSpawner = {
|
||||
resetRespawnWindow: Mock
|
||||
shutdown: Mock
|
||||
getHandle: Mock
|
||||
launcher: unknown
|
||||
launcher: DaemonLauncher
|
||||
}
|
||||
|
||||
/** Fake DaemonPtyAdapter instance every mocked `new DaemonPtyAdapter()` records. */
|
||||
@@ -40,7 +41,7 @@ export type MockAdapter = {
|
||||
|
||||
export type MockSpawnerConstructor = new (opts: {
|
||||
runtimeDir: string
|
||||
launcher: unknown
|
||||
launcher: DaemonLauncher
|
||||
}) => MockSpawner
|
||||
|
||||
export type MockAdapterConstructor = new (opts: MockAdapter['options']) => MockAdapter
|
||||
|
||||
@@ -359,8 +359,11 @@ describe('daemon-init: runRestartDaemon (7-step sequence)', () => {
|
||||
getMacDaemonTccAttributionHealthMock.mockResolvedValue('severed')
|
||||
|
||||
const { DegradedDaemonPtyProvider } = await import('./degraded-daemon-pty-provider')
|
||||
const provider = mod.getDaemonProvider() as InstanceType<typeof DegradedDaemonPtyProvider>
|
||||
const provider = mod.getDaemonProvider()
|
||||
expect(provider).toBeInstanceOf(DegradedDaemonPtyProvider)
|
||||
if (!(provider instanceof DegradedDaemonPtyProvider)) {
|
||||
throw new Error('Expected degraded daemon provider')
|
||||
}
|
||||
|
||||
await expect(provider.recoverFreshSpawnRouting()).resolves.toBe(false)
|
||||
expect(provider.routesFreshSpawnsToLocalProvider).toBe(true)
|
||||
|
||||
@@ -2,27 +2,27 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const {
|
||||
inProcessProvider,
|
||||
installedProvider,
|
||||
degradedInstances,
|
||||
isDaemonRestartInFlightMock,
|
||||
getDaemonProviderMock,
|
||||
getLocalPtyProviderMock,
|
||||
replaceDaemonProviderMock,
|
||||
rebindLocalProviderListenersMock,
|
||||
checkDaemonHealthMock,
|
||||
getMacDaemonTccAttributionHealthMock
|
||||
} = vi.hoisted(() => {
|
||||
const inProcessProvider = { kind: 'in-process' }
|
||||
const installedProvider = { kind: 'installed-daemon-adapter' }
|
||||
const degradedInstances: {
|
||||
opts: Record<string, unknown>
|
||||
discover: ReturnType<typeof vi.fn>
|
||||
dispose: ReturnType<typeof vi.fn>
|
||||
}[] = []
|
||||
return {
|
||||
inProcessProvider,
|
||||
installedProvider,
|
||||
degradedInstances: [] as {
|
||||
opts: Record<string, unknown>
|
||||
discover: ReturnType<typeof vi.fn>
|
||||
dispose: ReturnType<typeof vi.fn>
|
||||
}[],
|
||||
degradedInstances,
|
||||
isDaemonRestartInFlightMock: vi.fn(() => false),
|
||||
getDaemonProviderMock: vi.fn((): unknown => installedProvider),
|
||||
getDaemonProviderMock: vi.fn<() => unknown>(),
|
||||
getLocalPtyProviderMock: vi.fn<() => unknown>(),
|
||||
replaceDaemonProviderMock: vi.fn(),
|
||||
rebindLocalProviderListenersMock: vi.fn(),
|
||||
checkDaemonHealthMock: vi.fn(async () => 'healthy'),
|
||||
@@ -34,7 +34,7 @@ vi.mock('../ipc/pty', () => ({
|
||||
getInProcessPtyProvider: () => inProcessProvider,
|
||||
// Why the installed provider here: this is what the real registry answers after install, and
|
||||
// what the degraded fallback must never be.
|
||||
getLocalPtyProvider: () => installedProvider,
|
||||
getLocalPtyProvider: getLocalPtyProviderMock,
|
||||
rebindLocalProviderListeners: rebindLocalProviderListenersMock
|
||||
}))
|
||||
vi.mock('./daemon-restart-state', () => ({ isDaemonRestartInFlight: isDaemonRestartInFlightMock }))
|
||||
@@ -70,15 +70,19 @@ import {
|
||||
createSeveredDaemonRecoveryProbe,
|
||||
degradeInstalledProviderForSeveredDaemon
|
||||
} from './daemon-severed-attribution-degrade'
|
||||
import type { DaemonPtyAdapter } from './daemon-pty-adapter'
|
||||
import { DaemonPtyAdapter } from './daemon-pty-adapter'
|
||||
|
||||
const installedAdapter = installedProvider as unknown as DaemonPtyAdapter
|
||||
const installedAdapter = new DaemonPtyAdapter({
|
||||
socketPath: '/runtime/daemon.sock',
|
||||
tokenPath: '/runtime/token'
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
degradedInstances.length = 0
|
||||
vi.clearAllMocks()
|
||||
isDaemonRestartInFlightMock.mockReturnValue(false)
|
||||
getDaemonProviderMock.mockReturnValue(installedProvider)
|
||||
getDaemonProviderMock.mockReturnValue(installedAdapter)
|
||||
getLocalPtyProviderMock.mockReturnValue(installedAdapter)
|
||||
checkDaemonHealthMock.mockResolvedValue('healthy')
|
||||
getMacDaemonTccAttributionHealthMock.mockResolvedValue('intact')
|
||||
})
|
||||
@@ -106,8 +110,8 @@ describe('degradeInstalledProviderForSeveredDaemon', () => {
|
||||
expect(degradedInstances).toHaveLength(1)
|
||||
const [degraded] = degradedInstances
|
||||
expect(degraded.opts.fallback).toBe(inProcessProvider)
|
||||
expect(degraded.opts.fallback).not.toBe(installedProvider)
|
||||
expect(degraded.opts.current).toBe(installedProvider)
|
||||
expect(degraded.opts.fallback).not.toBe(installedAdapter)
|
||||
expect(degraded.opts.current).toBe(installedAdapter)
|
||||
expect(degraded.opts.legacy).toEqual([])
|
||||
expect(replaceDaemonProviderMock).toHaveBeenCalledTimes(1)
|
||||
expect(rebindLocalProviderListenersMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { DegradedDaemonFreshSpawnRouter } from './degraded-daemon-fresh-spawn-routing'
|
||||
import type { IPtyProvider } from '../providers/types'
|
||||
import { createPtyProviderTestDouble } from '../providers/pty-provider-test-double'
|
||||
|
||||
describe('degraded fresh-spawn transitions', () => {
|
||||
function setup(probe: () => Promise<boolean>) {
|
||||
const current = { spawn: vi.fn(async () => ({ id: 'daemon' })) } as unknown as IPtyProvider
|
||||
const fallback = { spawn: vi.fn(async () => ({ id: 'local' })) } as unknown as IPtyProvider
|
||||
const current = createPtyProviderTestDouble('daemon')
|
||||
const fallback = createPtyProviderTestDouble('local')
|
||||
return {
|
||||
current,
|
||||
fallback,
|
||||
|
||||
@@ -11,6 +11,24 @@ import {
|
||||
import { DegradedDaemonPtyProvider } from '../daemon/degraded-daemon-pty-provider'
|
||||
import type { DaemonPtyAdapter } from '../daemon/daemon-pty-adapter'
|
||||
|
||||
function isDaemonAdapter(
|
||||
provider: ReturnType<typeof getLocalPtyProvider>
|
||||
): provider is DaemonPtyAdapter {
|
||||
return typeof provider.onWriteUnavailable === 'function'
|
||||
}
|
||||
|
||||
function getSpawnResultId(result: unknown): string {
|
||||
if (
|
||||
typeof result !== 'object' ||
|
||||
result === null ||
|
||||
!('id' in result) ||
|
||||
typeof result.id !== 'string'
|
||||
) {
|
||||
throw new Error('Expected PTY spawn result')
|
||||
}
|
||||
return result.id
|
||||
}
|
||||
|
||||
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
|
||||
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
|
||||
vi.mock('node-pty', () => import('./pty-ipc-mock-registry').then((m) => m.nodePtyModuleMock()))
|
||||
@@ -61,10 +79,14 @@ describe('configured in-process fallback lifecycle', () => {
|
||||
|
||||
function setup() {
|
||||
const daemon = installObservableDaemonTestProvider()
|
||||
getLocalPtyProvider().onWriteUnavailable = vi.fn(() => () => {})
|
||||
const current = getLocalPtyProvider()
|
||||
current.onWriteUnavailable = vi.fn(() => () => {})
|
||||
if (!isDaemonAdapter(current)) {
|
||||
throw new Error('Expected daemon adapter test double')
|
||||
}
|
||||
const fallback = getInProcessPtyProvider()
|
||||
const provider = new DegradedDaemonPtyProvider({
|
||||
current: getLocalPtyProvider() as DaemonPtyAdapter,
|
||||
current,
|
||||
legacy: [],
|
||||
fallback
|
||||
})
|
||||
@@ -88,9 +110,7 @@ describe('configured in-process fallback lifecycle', () => {
|
||||
it('delivers local and daemon output and natural exits exactly once', async () => {
|
||||
const { daemon, provider, runtime, process } = setup()
|
||||
try {
|
||||
const result = (await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 })) as {
|
||||
id: string
|
||||
}
|
||||
const id = getSpawnResultId(await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 }))
|
||||
process.emitData('abc')
|
||||
expect(runtime.onPtyData).toHaveBeenCalledTimes(1)
|
||||
process.emitExit()
|
||||
@@ -101,7 +121,7 @@ describe('configured in-process fallback lifecycle', () => {
|
||||
expect(runtime.onPtyExit).toHaveBeenCalledTimes(2)
|
||||
expect(mainWindow.webContents.send).toHaveBeenCalledWith(
|
||||
'pty:exit',
|
||||
expect.objectContaining({ id: result.id })
|
||||
expect.objectContaining({ id })
|
||||
)
|
||||
} finally {
|
||||
provider.disposeProviderOnly()
|
||||
@@ -111,13 +131,11 @@ describe('configured in-process fallback lifecycle', () => {
|
||||
it('destroys fallback handles on app quit without stopping daemon sessions', async () => {
|
||||
const { daemon, fallback, provider, process } = setup()
|
||||
try {
|
||||
const result = (await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 })) as {
|
||||
id: string
|
||||
}
|
||||
const id = getSpawnResultId(await handlers.get('pty:spawn')!(null, { cols: 80, rows: 24 }))
|
||||
const kill = process.proc.kill
|
||||
killAllPty()
|
||||
expect(kill).toHaveBeenCalledOnce()
|
||||
expect(fallback.hasPty(result.id)).toBe(false)
|
||||
expect(fallback.hasPty(id)).toBe(false)
|
||||
expect(daemon.shutdown).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
provider.disposeProviderOnly()
|
||||
@@ -131,8 +149,8 @@ describe('configured in-process fallback lifecycle', () => {
|
||||
const kill = process.proc.kill
|
||||
const reload = () => {
|
||||
for (const [name, callback] of mainWindow.webContents.on.mock.calls) {
|
||||
if (name === 'did-finish-load') {
|
||||
;(callback as () => void)()
|
||||
if (name === 'did-finish-load' && typeof callback === 'function') {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { LocalPtyProvider } from '../../../providers/local-pty-provider'
|
||||
import { createPtyProviderTestDouble } from '../../../providers/pty-provider-test-double'
|
||||
import { configureLocalPtyProvider } from './local-configure'
|
||||
import { getInProcessPtyProvider, getLocalPtyProvider, setLocalPtyProvider } from './registry'
|
||||
|
||||
@@ -12,7 +13,7 @@ afterEach(() => {
|
||||
|
||||
describe('configureLocalPtyProvider', () => {
|
||||
it('configures the in-process provider once when it is also the installed one', () => {
|
||||
const inProcess = getInProcessPtyProvider() as LocalPtyProvider
|
||||
const inProcess = getInProcessPtyProvider()
|
||||
expect(getLocalPtyProvider()).toBe(inProcess)
|
||||
const configure = vi.spyOn(inProcess, 'configure')
|
||||
|
||||
@@ -25,9 +26,9 @@ describe('configureLocalPtyProvider', () => {
|
||||
// Why: the daemon lands before handlers register, so this is the production order. Degraded
|
||||
// routing spawns fresh terminals on the in-process provider, which must carry hook env and
|
||||
// runtime callbacks even though it is no longer what IPC routes through.
|
||||
const daemonTopology = { isDegraded: false } as never
|
||||
const daemonTopology = createPtyProviderTestDouble('daemon')
|
||||
setLocalPtyProvider(daemonTopology)
|
||||
const inProcess = getInProcessPtyProvider() as LocalPtyProvider
|
||||
const inProcess = getInProcessPtyProvider()
|
||||
const configure = vi.spyOn(inProcess, 'configure')
|
||||
|
||||
configureLocalPtyProvider({ trustedTerminalHandleEnv: new Set() })
|
||||
@@ -40,7 +41,7 @@ describe('configureLocalPtyProvider', () => {
|
||||
const installed = new LocalPtyProvider()
|
||||
setLocalPtyProvider(installed)
|
||||
const installedConfigure = vi.spyOn(installed, 'configure')
|
||||
const inProcessConfigure = vi.spyOn(getInProcessPtyProvider() as LocalPtyProvider, 'configure')
|
||||
const inProcessConfigure = vi.spyOn(getInProcessPtyProvider(), 'configure')
|
||||
|
||||
configureLocalPtyProvider({ trustedTerminalHandleEnv: new Set() })
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { vi, type Mock } from 'vitest'
|
||||
import { settledWriteStub } from './settled-pty-write-stub'
|
||||
import type { IPtyProvider, PtySpawnOptions, PtySpawnResult } from './types'
|
||||
|
||||
export type PtyProviderTestDouble = IPtyProvider & {
|
||||
spawn: Mock<(opts: PtySpawnOptions) => Promise<PtySpawnResult>>
|
||||
}
|
||||
|
||||
export function createPtyProviderTestDouble(id: string): PtyProviderTestDouble {
|
||||
return {
|
||||
spawn: vi.fn(async () => ({ id })),
|
||||
attach: vi.fn(async () => {}),
|
||||
write: vi.fn(),
|
||||
writeWithSettlement: vi.fn(settledWriteStub()),
|
||||
resize: vi.fn(),
|
||||
shutdown: vi.fn(async () => {}),
|
||||
sendSignal: vi.fn(async () => {}),
|
||||
getCwd: vi.fn(async () => ''),
|
||||
getInitialCwd: vi.fn(async () => ''),
|
||||
clearBuffer: vi.fn(async () => {}),
|
||||
acknowledgeDataEvent: vi.fn(),
|
||||
hasChildProcesses: vi.fn(async () => false),
|
||||
getForegroundProcess: vi.fn(async () => null),
|
||||
serialize: vi.fn(async () => '{}'),
|
||||
revive: vi.fn(async () => {}),
|
||||
listProcesses: vi.fn(async () => []),
|
||||
getDefaultShell: vi.fn(async () => '/bin/zsh'),
|
||||
getProfiles: vi.fn(async () => []),
|
||||
onData: vi.fn(() => () => {}),
|
||||
onReplay: vi.fn(() => () => {}),
|
||||
onExit: vi.fn(() => () => {})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user