Files
orca/src/main/daemon/daemon-server-kill-attribution.test.ts
T
Neil 087b895524 refactor(daemon): split oversized PTY services (#16160)
* refactor(daemon): split oversized PTY services

* revert(daemon): restore merge-base session listing and canceled-spawn behavior

Two behavior changes rode along with the file-splitting refactor:

- listLiveTerminalHostSessions dropped sessions with isTerminating, not just
  dead ones, hiding sessions the merge base still advertised.
- spawnAndPublishSession called session.beginTermination() before publishing a
  canceled spawn into the host map.

Both hunks are reverted to the merge base; the refactor is untouched.
2026-08-24 21:18:11 -07:00

128 lines
4.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { DaemonServer } from './daemon-server'
import { SessionNotFoundError, type DaemonRequest } from './types'
type DaemonServerPrivate = {
host: { kill: (sessionId: string, opts?: { immediate?: boolean }) => void | Promise<void> }
preparations: {
pending: Map<string, Set<{ canceled: boolean; clientId: string }>>
}
requestRouter: {
route(clientId: string, request: DaemonRequest): Promise<unknown>
}
}
describe('daemon kill attribution', () => {
let server: DaemonServer
let dir: string
afterEach(async () => {
await server?.shutdown()
rmSync(dir, { recursive: true, force: true })
})
it('records the requesting control client in the session-killed log', async () => {
dir = mkdtempSync(join(tmpdir(), 'daemon-kill-attribution-'))
const killLog = { log: vi.fn(), close: vi.fn() }
server = new DaemonServer({
socketPath: join(dir, 'daemon.sock'),
tokenPath: join(dir, 'daemon.token'),
log: killLog,
spawnSubprocess: () => {
throw new Error('not used')
}
})
const daemon = server as unknown as DaemonServerPrivate
let finishKill!: () => void
const killFinished = new Promise<void>((resolve) => {
finishKill = resolve
})
vi.spyOn(daemon.host, 'kill').mockReturnValue(killFinished)
const request = daemon.requestRouter.route('control-42', {
id: 'kill-1',
type: 'kill',
payload: { sessionId: 'agent-session', immediate: true }
})
await vi.waitFor(() => expect(daemon.host.kill).toHaveBeenCalledOnce())
expect(killLog.log).not.toHaveBeenCalledWith('session-killed', expect.anything())
finishKill()
await request
expect(killLog.log).toHaveBeenCalledWith('session-killed', {
sessionId: 'agent-session',
immediate: true,
clientId: 'control-42'
})
})
it('attributes a failed kill without claiming the session was killed', async () => {
dir = mkdtempSync(join(tmpdir(), 'daemon-kill-attribution-'))
const killLog = { log: vi.fn(), close: vi.fn() }
server = new DaemonServer({
socketPath: join(dir, 'daemon.sock'),
tokenPath: join(dir, 'daemon.token'),
log: killLog,
spawnSubprocess: () => {
throw new Error('not used')
}
})
const daemon = server as unknown as DaemonServerPrivate
vi.spyOn(daemon.host, 'kill').mockRejectedValue(new Error('kill refused'))
await expect(
daemon.requestRouter.route('control-42', {
id: 'kill-1',
type: 'kill',
payload: { sessionId: 'agent-session', immediate: true }
})
).rejects.toThrow('kill refused')
expect(killLog.log).toHaveBeenCalledWith('session-kill-failed', {
sessionId: 'agent-session',
immediate: true,
clientId: 'control-42'
})
expect(killLog.log).not.toHaveBeenCalledWith('session-killed', expect.anything())
})
it('attributes a tolerated kill after canceling a pending spawn', async () => {
dir = mkdtempSync(join(tmpdir(), 'daemon-kill-attribution-'))
const killLog = { log: vi.fn(), close: vi.fn() }
server = new DaemonServer({
socketPath: join(dir, 'daemon.sock'),
tokenPath: join(dir, 'daemon.token'),
log: killLog,
spawnSubprocess: () => {
throw new Error('not used')
}
})
const daemon = server as unknown as DaemonServerPrivate
const pendingPreparation = {
canceled: false,
controller: new AbortController(),
clientId: 'control-42'
}
daemon.preparations.pending.set('agent-session', new Set([pendingPreparation]))
vi.spyOn(daemon.host, 'kill').mockRejectedValue(new SessionNotFoundError('agent-session'))
await daemon.requestRouter.route('control-42', {
id: 'kill-1',
type: 'kill',
payload: { sessionId: 'agent-session', immediate: true }
})
expect(pendingPreparation.canceled).toBe(true)
expect(killLog.log).toHaveBeenCalledWith('session-killed', {
sessionId: 'agent-session',
immediate: true,
clientId: 'control-42'
})
expect(killLog.log).not.toHaveBeenCalledWith('session-kill-failed', expect.anything())
})
})