Files
orca/src/cli/runtime-client.test.ts
T
2026-05-04 13:08:27 -07:00

343 lines
11 KiB
TypeScript

import { mkdtempSync, writeFileSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { createServer, type Socket } from 'net'
import { afterEach, describe, expect, it } from 'vitest'
import { RuntimeClient, RuntimeRpcFailureError } from './runtime-client'
const servers = new Set<ReturnType<typeof createServer>>()
const sockets = new Set<Socket>()
afterEach(async () => {
for (const socket of sockets) {
socket.destroy()
}
sockets.clear()
await Promise.all(
[...servers].map(
(server) =>
new Promise<void>((resolve) => {
server.close(() => resolve())
})
)
)
servers.clear()
})
function writeMetadata(
userDataPath: string,
endpoint: string,
authToken = 'token',
pid = 123
): void {
writeFileSync(
join(userDataPath, 'orca-runtime.json'),
JSON.stringify({
runtimeId: 'runtime-1',
pid,
transports: [
{
kind: 'unix',
endpoint
}
],
authToken,
startedAt: 1
}),
'utf8'
)
}
function findUnusedPid(seed = 200_000): number {
// Why: the stale-bootstrap test must point metadata at a definitely-dead
// process. Hard-coding a small PID is host-dependent and flakes when that
// PID happens to be alive on the machine running the suite.
let pid = Math.max(seed, process.pid + 10_000)
while (pid < 2_000_000) {
try {
process.kill(pid, 0)
pid += 1
} catch {
return pid
}
}
return 2_000_000
}
// Why: these tests create Unix domain socket servers in temp directories.
// Windows does not support Unix domain sockets in the same way, causing
// EACCES errors on listen(), so the suite is skipped on that platform.
describe.skipIf(process.platform === 'win32')('RuntimeClient', () => {
it('returns the full RPC envelope for successful calls', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', (data) => {
const request = JSON.parse(String(data).trim()) as { id: string }
socket.write(
`${JSON.stringify({
id: request.id,
ok: true,
result: { running: true },
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 500)
const response = await client.call<{ running: boolean }>('status.get')
expect(response).toMatchObject({
ok: true,
result: { running: true },
_meta: { runtimeId: 'runtime-1' }
})
expect(response.id).toBeTruthy()
})
it('reports not_running when no runtime metadata exists', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const client = new RuntimeClient(userDataPath, 100)
const status = await client.getCliStatus()
expect(status.result).toEqual({
app: {
running: false,
pid: null
},
runtime: {
state: 'not_running',
reachable: false,
runtimeId: null
},
graph: {
state: 'not_running'
}
})
})
it('reports stale_bootstrap when bootstrap artifacts exist but no runtime is reachable', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
writeMetadata(userDataPath, join(userDataPath, 'missing.sock'), 'token', findUnusedPid())
const client = new RuntimeClient(userDataPath, 100)
const status = await client.getCliStatus()
expect(status.result.runtime.state).toBe('stale_bootstrap')
expect(status.result.runtime.reachable).toBe(false)
})
it('reports graph_not_ready when the runtime is reachable but graph is unavailable', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', (data) => {
const request = JSON.parse(String(data).trim()) as { id: string }
socket.write(
`${JSON.stringify({
id: request.id,
ok: true,
result: {
runtimeId: 'runtime-1',
rendererGraphEpoch: 0,
graphStatus: 'unavailable',
authoritativeWindowId: null,
liveTabCount: 0,
liveLeafCount: 0
},
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 100)
const status = await client.getCliStatus()
expect(status.result.runtime.state).toBe('graph_not_ready')
expect(status.result.graph.state).toBe('unavailable')
})
it('openOrca succeeds immediately when the runtime is already reachable', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', (data) => {
const request = JSON.parse(String(data).trim()) as { id: string }
socket.write(
`${JSON.stringify({
id: request.id,
ok: true,
result: {
runtimeId: 'runtime-1',
rendererGraphEpoch: 0,
graphStatus: 'ready',
authoritativeWindowId: 1,
liveTabCount: 1,
liveLeafCount: 1
},
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 100)
const status = await client.openOrca(100)
expect(status.result.runtime.state).toBe('ready')
expect(status.result.runtime.reachable).toBe(true)
})
it('times out if the runtime never responds', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
// Why: keep the socket open without replying so the client timeout path
// is exercised against a real hung runtime connection.
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 25)
await expect(client.call('status.get')).rejects.toMatchObject({
code: 'runtime_timeout'
})
})
it('allows a per-call timeout override for long runtime requests', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', (data) => {
const request = JSON.parse(String(data).trim()) as { id: string }
setTimeout(() => {
socket.write(
`${JSON.stringify({
id: request.id,
ok: true,
result: { satisfied: true },
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
}, 40)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 25)
const response = await client.call<{ satisfied: boolean }>('terminal.wait', undefined, {
timeoutMs: 250
})
expect(response.result).toEqual({ satisfied: true })
})
it('preserves structured runtime failures', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', (data) => {
const request = JSON.parse(String(data).trim()) as { id: string }
socket.write(
`${JSON.stringify({
id: request.id,
ok: false,
error: { code: 'selector_not_found', message: 'selector_not_found' },
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 100)
await expect(client.call('worktree.show')).rejects.toBeInstanceOf(RuntimeRpcFailureError)
await expect(client.call('worktree.show')).rejects.toMatchObject({
response: {
ok: false,
_meta: { runtimeId: 'runtime-1' }
}
})
})
it('rejects invalid runtime response frames', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', () => {
socket.write('not json\n')
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 100)
await expect(client.call('status.get')).rejects.toMatchObject({
code: 'invalid_runtime_response'
})
})
it('rejects mismatched response ids from the runtime', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-client-'))
const endpoint = join(userDataPath, 'runtime.sock')
const server = createServer((socket) => {
sockets.add(socket)
socket.once('close', () => sockets.delete(socket))
socket.once('data', () => {
socket.write(
`${JSON.stringify({
id: 'not-the-request-id',
ok: true,
result: { running: true },
_meta: { runtimeId: 'runtime-1' }
})}\n`
)
})
})
servers.add(server)
await new Promise<void>((resolve) => server.listen(endpoint, resolve))
writeMetadata(userDataPath, endpoint)
const client = new RuntimeClient(userDataPath, 100)
await expect(client.call('status.get')).rejects.toMatchObject({
code: 'invalid_runtime_response'
})
})
})