mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 16:02:22 +00:00
156 lines
6.1 KiB
TypeScript
156 lines
6.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { listRegisteredPtysMock } = vi.hoisted(() => ({
|
|
listRegisteredPtysMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../memory/pty-registry', () => ({
|
|
listRegisteredPtys: listRegisteredPtysMock
|
|
}))
|
|
|
|
import { killAllProcessesForWorktree } from './worktree-teardown'
|
|
import type { IPtyProvider } from '../providers/types'
|
|
|
|
function createProviderStub(
|
|
listProcesses: () => Promise<{ id: string; cwd: string; title: string }[]>
|
|
): IPtyProvider {
|
|
return {
|
|
spawn: vi.fn(),
|
|
attach: vi.fn(),
|
|
write: vi.fn(),
|
|
resize: vi.fn(),
|
|
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
sendSignal: vi.fn(),
|
|
getCwd: vi.fn(),
|
|
getInitialCwd: vi.fn(),
|
|
clearBuffer: vi.fn(),
|
|
acknowledgeDataEvent: vi.fn(),
|
|
hasChildProcesses: vi.fn(),
|
|
getForegroundProcess: vi.fn(),
|
|
serialize: vi.fn(),
|
|
revive: vi.fn(),
|
|
listProcesses: vi.fn(listProcesses),
|
|
getDefaultShell: vi.fn(),
|
|
getProfiles: vi.fn(),
|
|
onData: vi.fn().mockReturnValue(() => {}),
|
|
onReplay: vi.fn().mockReturnValue(() => {}),
|
|
onExit: vi.fn().mockReturnValue(() => {})
|
|
} as unknown as IPtyProvider
|
|
}
|
|
|
|
describe('killAllProcessesForWorktree', () => {
|
|
beforeEach(() => {
|
|
listRegisteredPtysMock.mockReset()
|
|
})
|
|
|
|
it('reaches daemon sessions and registry entries without a runtime', async () => {
|
|
// Simulate headless-CLI: no renderer, so `runtime` is undefined.
|
|
const localProvider = createProviderStub(async () => [
|
|
{ id: 'w1@@abcd1234', cwd: '/tmp/w1', title: 'shell' },
|
|
{ id: 'w2@@efef5678', cwd: '/tmp/w2', title: 'shell' }
|
|
])
|
|
listRegisteredPtysMock.mockReturnValue([
|
|
{ ptyId: 'w1-registry-1', worktreeId: 'w1', sessionId: null, paneKey: null, pid: 100 },
|
|
{ ptyId: 'w2-registry-2', worktreeId: 'w2', sessionId: null, paneKey: null, pid: 101 }
|
|
])
|
|
|
|
const result = await killAllProcessesForWorktree('w1', { localProvider })
|
|
|
|
expect(result.runtimeStopped).toBe(0)
|
|
expect(result.providerStopped).toBe(1)
|
|
expect(result.registryStopped).toBe(1)
|
|
|
|
expect(localProvider.shutdown).toHaveBeenCalledWith('w1@@abcd1234', { immediate: true })
|
|
expect(localProvider.shutdown).toHaveBeenCalledWith('w1-registry-1', { immediate: true })
|
|
expect(localProvider.shutdown).not.toHaveBeenCalledWith('w2@@efef5678', { immediate: true })
|
|
expect(localProvider.shutdown).not.toHaveBeenCalledWith('w2-registry-2', { immediate: true })
|
|
})
|
|
|
|
it('skips the daemon prefix sweep safely when the provider uses numeric ids', async () => {
|
|
// LocalPtyProvider shape: numeric ids that cannot match `${worktreeId}@@`.
|
|
const localProvider = createProviderStub(async () => [
|
|
{ id: '1', cwd: '/tmp/w1', title: 'shell' },
|
|
{ id: '2', cwd: '/tmp/w2', title: 'shell' }
|
|
])
|
|
listRegisteredPtysMock.mockReturnValue([
|
|
{ ptyId: '1', worktreeId: 'w1', sessionId: null, paneKey: null, pid: 200 }
|
|
])
|
|
|
|
const result = await killAllProcessesForWorktree('w1', { localProvider })
|
|
|
|
// Prefix sweep must kill nothing; registry sweep must still fire.
|
|
expect(result.providerStopped).toBe(0)
|
|
expect(result.registryStopped).toBe(1)
|
|
expect(localProvider.shutdown).toHaveBeenCalledWith('1', { immediate: true })
|
|
expect(localProvider.shutdown).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('best-effort: swallows errors from listProcesses and shutdown', async () => {
|
|
const localProvider = createProviderStub(() => Promise.reject(new Error('boom')))
|
|
listRegisteredPtysMock.mockReturnValue([
|
|
{ ptyId: 'x', worktreeId: 'w1', sessionId: null, paneKey: null, pid: 10 }
|
|
])
|
|
;(localProvider.shutdown as unknown as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
new Error('already dead')
|
|
)
|
|
|
|
const result = await killAllProcessesForWorktree('w1', { localProvider })
|
|
|
|
// listProcesses rejected → provider sweep returns 0; registry shutdown
|
|
// rejected → counted as not-killed (registry sweep currently swallows).
|
|
expect(result.providerStopped).toBe(0)
|
|
expect(result.registryStopped).toBe(0)
|
|
})
|
|
|
|
it('does not carry state between successive calls with distinct providers', async () => {
|
|
// Guards against a future refactor that memoises provider or registry
|
|
// reads inside the helper.
|
|
const providerA = createProviderStub(async () => [
|
|
{ id: 'w1@@aaaa', cwd: '/tmp', title: 'shell' }
|
|
])
|
|
const providerB = createProviderStub(async () => [
|
|
{ id: 'w1@@bbbb', cwd: '/tmp', title: 'shell' }
|
|
])
|
|
listRegisteredPtysMock.mockReturnValue([])
|
|
|
|
const r1 = await killAllProcessesForWorktree('w1', { localProvider: providerA })
|
|
expect(providerA.shutdown).toHaveBeenCalledWith('w1@@aaaa', { immediate: true })
|
|
expect(providerB.shutdown).not.toHaveBeenCalled()
|
|
expect(r1.providerStopped).toBe(1)
|
|
|
|
const r2 = await killAllProcessesForWorktree('w1', { localProvider: providerB })
|
|
expect(providerB.shutdown).toHaveBeenCalledWith('w1@@bbbb', { immediate: true })
|
|
expect(providerB.shutdown).toHaveBeenCalledTimes(1)
|
|
expect(r2.providerStopped).toBe(1)
|
|
})
|
|
|
|
it('invokes runtime.stopTerminalsForWorktree when runtime is provided', async () => {
|
|
const stopTerminalsForWorktree = vi.fn().mockResolvedValue({ stopped: 3 })
|
|
const runtime = {
|
|
stopTerminalsForWorktree
|
|
} as unknown as Parameters<typeof killAllProcessesForWorktree>[1]['runtime']
|
|
|
|
const localProvider = createProviderStub(async () => [])
|
|
listRegisteredPtysMock.mockReturnValue([])
|
|
|
|
const result = await killAllProcessesForWorktree('w1', { runtime, localProvider })
|
|
|
|
expect(stopTerminalsForWorktree).toHaveBeenCalledWith('w1')
|
|
expect(result.runtimeStopped).toBe(3)
|
|
})
|
|
|
|
it('tolerates runtime.stopTerminalsForWorktree throwing (headless assertGraphReady reject)', async () => {
|
|
const stopTerminalsForWorktree = vi.fn().mockRejectedValue(new Error('graph not ready'))
|
|
const runtime = {
|
|
stopTerminalsForWorktree
|
|
} as unknown as Parameters<typeof killAllProcessesForWorktree>[1]['runtime']
|
|
|
|
const localProvider = createProviderStub(async () => [])
|
|
listRegisteredPtysMock.mockReturnValue([])
|
|
|
|
const result = await killAllProcessesForWorktree('w1', { runtime, localProvider })
|
|
|
|
expect(result.runtimeStopped).toBe(0)
|
|
})
|
|
})
|