Files
orca/src/cli/index-serve-command.test.ts
T
OrcaWinandOrcaWin 225f940453 test: strengthen CLI expectations and consolidate repeated scenarios (#32976)
* test: strengthen CLI refusals and consolidate serve validation

* test: consolidate terminal creation and strengthen restore output

* test: pin dashboard telemetry clock away from midnight

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 17:11:16 -07:00

213 lines
6.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const {
callMock,
runtimeClientConstructorMock,
serveOrcaAppMock,
getDefaultUserDataPathMock,
addEnvironmentFromPairingCodeMock,
listEnvironmentsMock,
spawnMock
} = vi.hoisted(() => ({
callMock: vi.fn(),
runtimeClientConstructorMock: vi.fn(),
serveOrcaAppMock: vi.fn(),
getDefaultUserDataPathMock: vi.fn(() => '/tmp/orca-user-data'),
addEnvironmentFromPairingCodeMock: vi.fn(),
listEnvironmentsMock: vi.fn(),
spawnMock: vi.fn()
}))
vi.mock('./runtime-client', async () => {
const { createRuntimeClientModuleMock } = await import('./index-test-harness.js')
return createRuntimeClientModuleMock({
callMock,
runtimeClientConstructorMock,
serveOrcaAppMock,
getDefaultUserDataPathMock
})
})
vi.mock('./runtime/environments', () => ({
addEnvironmentFromPairingCode: addEnvironmentFromPairingCodeMock,
listEnvironments: listEnvironmentsMock,
removeEnvironment: vi.fn(),
resolveEnvironment: vi.fn()
}))
vi.mock('child_process', async () => {
const { createChildProcessModuleMock } = await import('./index-test-harness.js')
return createChildProcessModuleMock(spawnMock)
})
import { main } from './index'
import { useWorktreeAwarenessEnvironment } from './index-test-harness'
describe('orca cli worktree awareness', () => {
const priorExitCode = process.exitCode
beforeEach(() => {
process.exitCode = undefined
})
afterEach(() => {
process.exitCode = priorExitCode
})
useWorktreeAwarenessEnvironment({
callMock,
serveOrcaAppMock,
getDefaultUserDataPathMock,
addEnvironmentFromPairingCodeMock,
listEnvironmentsMock,
spawnMock
})
it('starts a foreground headless server through `serve`', async () => {
serveOrcaAppMock.mockResolvedValue(17)
process.env.ORCA_ENVIRONMENT = 'stale-env'
await main(
['serve', '--json', '--port', '6768', '--pairing-address', '100.64.1.20', '--no-pairing'],
'/tmp/repo'
)
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
json: true,
port: '6768',
pairingAddress: '100.64.1.20',
noPairing: true,
mobilePairing: false,
grantDesktopControl: false,
recipeJson: false,
projectRoot: null
})
expect(process.exitCode).toBe(17)
expect(callMock).not.toHaveBeenCalled()
})
it.skipIf(process.platform === 'win32')(
'turns on supervision only for an explicit --supervise, and never with --recipe-json',
async () => {
serveOrcaAppMock.mockResolvedValue(0)
await main(['serve', '--supervise', '--no-pairing'], '/tmp/repo')
expect(serveOrcaAppMock).toHaveBeenLastCalledWith(
expect.objectContaining({ supervise: true, noPairing: true })
)
await main(['serve', '--no-pairing'], '/tmp/repo')
expect(serveOrcaAppMock.mock.lastCall?.[0]).not.toHaveProperty('supervise')
serveOrcaAppMock.mockClear()
const error = vi.spyOn(console, 'error').mockImplementation(() => {})
await main(
['serve', '--supervise', '--recipe-json', '--project-root', '/workspace/repo'],
'/tmp/repo'
)
expect(serveOrcaAppMock).not.toHaveBeenCalled()
expect(error).toHaveBeenCalledExactlyOnceWith(
'--recipe-json starts a detached server and returns; it cannot be combined with --supervise.'
)
expect(process.exitCode).toBe(1)
}
)
it('forwards the pairing-time desktop-control grant', async () => {
serveOrcaAppMock.mockResolvedValue(0)
await main(['serve', '--grant-desktop-control'], '/tmp/repo')
expect(serveOrcaAppMock).toHaveBeenCalledWith(
expect.objectContaining({ grantDesktopControl: true, mobilePairing: false })
)
})
it('starts a foreground headless server with mobile pairing enabled', async () => {
serveOrcaAppMock.mockResolvedValue(0)
await main(
['serve', '--pairing-address', '100.64.1.20', '--mobile-pairing', '--json'],
'/tmp/repo'
)
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
json: true,
port: null,
pairingAddress: '100.64.1.20',
noPairing: false,
mobilePairing: true,
grantDesktopControl: false,
recipeJson: false,
projectRoot: null
})
})
it('starts a recipe JSON headless server for VM recipes', async () => {
serveOrcaAppMock.mockResolvedValue(0)
await main(
[
'serve',
'--pairing-address',
'wss://sandbox.example.com',
'--project-root',
'/workspace/repo',
'--recipe-json'
],
'/tmp/repo'
)
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
json: false,
port: null,
pairingAddress: 'wss://sandbox.example.com',
noPairing: false,
mobilePairing: false,
grantDesktopControl: false,
recipeJson: true,
projectRoot: '/workspace/repo'
})
})
it.each([
{
name: 'recipe JSON output without a project root',
args: ['serve', '--recipe-json'],
message: 'Recipe JSON output requires --project-root.'
},
{
name: 'recipe JSON output with mobile pairing',
args: ['serve', '--recipe-json', '--project-root', '/workspace/repo', '--mobile-pairing'],
message: 'Recipe JSON output requires runtime pairing; remove --mobile-pairing.'
},
{
name: 'contradictory pairing flags',
args: ['serve', '--mobile-pairing', '--no-pairing'],
message: 'Use either --mobile-pairing or --no-pairing, not both.'
},
{
name: 'an invalid port',
args: ['serve', '--port', 'not-a-port'],
message: 'Invalid --port value: not-a-port'
},
{
name: 'a valueless port',
args: ['serve', '--port'],
message: 'Missing value for --port.'
}
])('rejects $name before launching the app', async ({ args, message }) => {
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(console, 'error').mockImplementation(() => {})
await main([...args, '--json'], '/tmp/repo')
expect(serveOrcaAppMock).not.toHaveBeenCalled()
expect(logSpy).toHaveBeenCalledTimes(1)
expect(JSON.parse(logSpy.mock.calls[0][0])).toEqual({
id: 'local',
ok: false,
error: { code: 'invalid_argument', message },
_meta: { runtimeId: null }
})
expect(process.exitCode).toBe(1)
})
})