mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
314 lines
8.8 KiB
TypeScript
314 lines
8.8 KiB
TypeScript
import { 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 { buildWorktree, okFixture, queueFixtures, worktreeListFixture } from './test-fixtures'
|
|
import { useWorktreeAwarenessEnvironment } from './index-test-harness'
|
|
|
|
describe('orca cli worktree awareness', () => {
|
|
useWorktreeAwarenessEnvironment({
|
|
callMock,
|
|
serveOrcaAppMock,
|
|
getDefaultUserDataPathMock,
|
|
addEnvironmentFromPairingCodeMock,
|
|
listEnvironmentsMock,
|
|
spawnMock
|
|
})
|
|
|
|
it('opts into setup and activation when worktree.create runs hooks', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
|
|
okFixture('req_create', {
|
|
worktree: buildWorktree('/tmp/repo/feature', 'feature', 'abc', 'repo-1')
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'feature', '--run-hooks', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', {
|
|
repo: 'id:repo-1',
|
|
name: 'feature',
|
|
baseBranch: undefined,
|
|
linkedIssue: undefined,
|
|
comment: undefined,
|
|
runHooks: true,
|
|
activate: true,
|
|
// Why: the CLI pairs as a runtime device but has no viewer, so --activate must
|
|
// stay an explicit all-surface reveal rather than caller-scoped navigation.
|
|
navigation: 'all',
|
|
parentWorktree: undefined,
|
|
cwdParentWorktree: 'id:repo-1::/tmp/repo',
|
|
noParent: false,
|
|
callerTerminalHandle: undefined,
|
|
cliProvenanceRequest: {}
|
|
})
|
|
})
|
|
|
|
it('starts an agent worktree in the background unless activation is explicit', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
|
|
okFixture('req_create', {
|
|
worktree: buildWorktree('/tmp/repo/agent-task', 'agent-task', 'abc', 'repo-1'),
|
|
lineage: null,
|
|
warnings: []
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'worktree',
|
|
'create',
|
|
'--repo',
|
|
'id:repo-1',
|
|
'--name',
|
|
'agent-task',
|
|
'--agent',
|
|
'codex',
|
|
'--prompt',
|
|
'hi',
|
|
'--setup',
|
|
'run',
|
|
'--json'
|
|
],
|
|
'/tmp/repo/src'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', {
|
|
repo: 'id:repo-1',
|
|
name: 'agent-task',
|
|
baseBranch: undefined,
|
|
linkedIssue: undefined,
|
|
comment: undefined,
|
|
runHooks: false,
|
|
activate: false,
|
|
setupDecision: 'run',
|
|
parentWorktree: undefined,
|
|
cwdParentWorktree: 'id:repo-1::/tmp/repo',
|
|
noParent: false,
|
|
callerTerminalHandle: undefined,
|
|
cliProvenanceRequest: {},
|
|
startupAgent: 'codex',
|
|
startupPrompt: 'hi'
|
|
})
|
|
})
|
|
|
|
it('infers the repo and honors explicit activation on worktree.create', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
|
|
okFixture('req_create', {
|
|
worktree: buildWorktree('/tmp/repo/agent-task', 'agent-task', 'abc', 'repo-1'),
|
|
lineage: null,
|
|
warnings: []
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'worktree',
|
|
'create',
|
|
'--name',
|
|
'agent-task',
|
|
'--agent',
|
|
'codex',
|
|
'--prompt',
|
|
'hi',
|
|
'--activate',
|
|
'--json'
|
|
],
|
|
'/tmp/repo/src'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', {
|
|
repo: 'id:repo-1',
|
|
name: 'agent-task',
|
|
baseBranch: undefined,
|
|
linkedIssue: undefined,
|
|
comment: undefined,
|
|
runHooks: false,
|
|
activate: true,
|
|
navigation: 'all',
|
|
parentWorktree: undefined,
|
|
cwdParentWorktree: 'id:repo-1::/tmp/repo',
|
|
noParent: false,
|
|
callerTerminalHandle: undefined,
|
|
cliProvenanceRequest: {},
|
|
startupAgent: 'codex',
|
|
startupPrompt: 'hi'
|
|
})
|
|
})
|
|
|
|
it('rejects prompt without agent on worktree.create', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--prompt', 'hi', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'--prompt requires --agent'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects unknown agents on worktree.create', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--agent', 'wat', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: 'invalid_argument',
|
|
message: 'Unknown TUI agent "wat"'
|
|
}
|
|
})
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects agent, prompt, and setup flags without values on worktree.create', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--agent'],
|
|
'/tmp/repo'
|
|
)
|
|
expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false)
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Missing value for --agent'
|
|
)
|
|
|
|
callMock.mockClear()
|
|
logSpy.mockClear()
|
|
errSpy.mockClear()
|
|
process.exitCode = priorExitCode
|
|
|
|
await main(
|
|
[
|
|
'worktree',
|
|
'create',
|
|
'--repo',
|
|
'id:repo-1',
|
|
'--name',
|
|
'child',
|
|
'--agent',
|
|
'codex',
|
|
'--prompt'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false)
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Missing value for --prompt'
|
|
)
|
|
|
|
callMock.mockClear()
|
|
logSpy.mockClear()
|
|
errSpy.mockClear()
|
|
process.exitCode = priorExitCode
|
|
|
|
await main(
|
|
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--setup'],
|
|
'/tmp/repo'
|
|
)
|
|
expect(callMock.mock.calls.some(([method]) => method === 'worktree.create')).toBe(false)
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Missing value for --setup'
|
|
)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects contradictory setup flags on worktree.create', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
[
|
|
'worktree',
|
|
'create',
|
|
'--repo',
|
|
'id:repo-1',
|
|
'--name',
|
|
'child',
|
|
'--run-hooks',
|
|
'--setup',
|
|
'skip',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Choose either --run-hooks or --setup run'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
})
|