Files
orca/src/main/daemon/daemon-spawner.test.ts
T
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00

175 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { mkdtempSync, rmSync } from 'node:fs'
import {
DaemonSpawner,
getDaemonPidPath,
getDaemonSocketPath,
getDaemonTokenPath
} from './daemon-spawner'
import { startDaemon, type DaemonHandle } from './daemon-main'
import { DaemonClient } from './client'
import type { SubprocessHandle } from './session'
import { PROTOCOL_VERSION } from './types'
function createTestDir(): string {
return mkdtempSync(join(tmpdir(), 'daemon-spawner-test-'))
}
function createMockSubprocess(): SubprocessHandle {
let onExitCb: ((code: number) => void) | null = null
return {
pid: 88888,
getForegroundProcess: vi.fn(() => null),
write: vi.fn(),
resize: vi.fn(),
kill: vi.fn(() => setTimeout(() => onExitCb?.(0), 5)),
forceKill: vi.fn(),
signal: vi.fn(),
onData(_cb: (data: string) => void) {},
onExit(cb: (code: number) => void) {
onExitCb = cb
},
dispose: vi.fn()
}
}
describe('DaemonSpawner', () => {
let dir: string
let spawner: DaemonSpawner
let activeDaemons: DaemonHandle[]
beforeEach(() => {
dir = createTestDir()
activeDaemons = []
})
afterEach(async () => {
await spawner?.shutdown()
for (const d of activeDaemons) {
await d.shutdown().catch(() => {})
}
rmSync(dir, { recursive: true, force: true })
})
function createSpawner(): DaemonSpawner {
spawner = new DaemonSpawner({
runtimeDir: dir,
launcher: async (socketPath, tokenPath) => {
const handle = await startDaemon({
socketPath,
tokenPath,
spawnSubprocess: () => createMockSubprocess()
})
activeDaemons.push(handle)
return { shutdown: () => handle.shutdown() }
}
})
return spawner
}
describe('ensureRunning', () => {
it('uses protocol-scoped socket and token paths', () => {
const socketPath = getDaemonSocketPath(dir)
const tokenPath = getDaemonTokenPath(dir)
const pidPath = getDaemonPidPath(dir)
if (process.platform === 'win32') {
expect(socketPath).toContain(`orca-terminal-host-v${PROTOCOL_VERSION}`)
} else {
expect(socketPath).toBe(join(dir, `daemon-v${PROTOCOL_VERSION}.sock`))
}
expect(tokenPath).toBe(join(dir, `daemon-v${PROTOCOL_VERSION}.token`))
expect(pidPath).toBe(join(dir, `daemon-v${PROTOCOL_VERSION}.pid`))
})
it('starts daemon and returns connection info', async () => {
const s = createSpawner()
const info = await s.ensureRunning()
if (process.platform === 'win32') {
expect(info.socketPath).toContain(`orca-terminal-host-v${PROTOCOL_VERSION}`)
} else {
expect(info.socketPath).toContain(dir)
}
expect(info.tokenPath).toContain(dir)
})
it('returns same info on subsequent calls', async () => {
const s = createSpawner()
const info1 = await s.ensureRunning()
const info2 = await s.ensureRunning()
expect(info1.socketPath).toBe(info2.socketPath)
expect(info1.tokenPath).toBe(info2.tokenPath)
})
it('daemon is connectable after ensureRunning', async () => {
const s = createSpawner()
const info = await s.ensureRunning()
const client = new DaemonClient({
socketPath: info.socketPath,
tokenPath: info.tokenPath
})
await client.ensureConnected()
expect(client.isConnected()).toBe(true)
client.disconnect()
})
it('daemon can create sessions', async () => {
const s = createSpawner()
const info = await s.ensureRunning()
const client = new DaemonClient({
socketPath: info.socketPath,
tokenPath: info.tokenPath
})
await client.ensureConnected()
const result = await client.request<{ isNew: boolean }>('createOrAttach', {
sessionId: 'test-session',
cols: 80,
rows: 24
})
expect(result.isNew).toBe(true)
client.disconnect()
})
})
describe('shutdown', () => {
it('stops the daemon', async () => {
const s = createSpawner()
const info = await s.ensureRunning()
await s.shutdown()
const client = new DaemonClient({
socketPath: info.socketPath,
tokenPath: info.tokenPath
})
await expect(client.ensureConnected()).rejects.toThrow()
})
it('can be called when daemon is not running', async () => {
const s = createSpawner()
await expect(s.shutdown()).resolves.toBeUndefined()
})
it('allows re-start after shutdown', async () => {
const s = createSpawner()
await s.ensureRunning()
await s.shutdown()
const info = await s.ensureRunning()
const client = new DaemonClient({
socketPath: info.socketPath,
tokenPath: info.tokenPath
})
await client.ensureConnected()
expect(client.isConnected()).toBe(true)
client.disconnect()
})
})
})