Files
orca/src/cli/index.test.ts
T
Jinwoo-HandOrca cd4483c49b feat(orchestration): QoL bundle — preamble visibility, status enum, dispatch cross-ref, inbox --full
Addresses four items from ORCHESTRATOR_FEEDBACK:

- #5 preamble visibility: `dispatch-show --preamble` regenerates the preamble
  text for a task; `dispatch --inject --dry-run` previews without mutating
  state; `dispatch --return-preamble` echoes the injected preamble in the JSON
  response so coordinators can audit what a worker received.
- #6 status enum validation: CLI rejects unknown `task-update --status` values
  with `invalid status '<x>', expected one of: pending, ready, dispatched,
  completed, failed, blocked` before the RPC's generic Zod message. Valid
  statuses are listed under Notes in `task-update --help`.
- #13 task-list dispatch cross-ref: `task-list --json` now includes
  `assignee_handle` and `dispatch_id` for tasks in status=dispatched via a
  read-only LEFT JOIN on dispatch_contexts. Non-dispatched rows keep their
  legacy shape so existing consumers are unaffected.
- #14 inbox body visibility: `inbox --full` prints body + payload verbatim;
  default output is unchanged (id/from/to/subject only).

No DB migrations; join-only change on dispatch_contexts so the sibling
preamble PR's `last_heartbeat_at` column addition will not conflict.

Co-authored-by: Orca <help@stably.ai>
2026-05-04 04:27:04 -04:00

270 lines
8.0 KiB
TypeScript

import path from 'path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const callMock = vi.fn()
vi.mock('./runtime-client', () => {
class RuntimeClient {
call = callMock
getCliStatus = vi.fn()
openOrca = vi.fn()
}
class RuntimeClientError extends Error {
readonly code: string
constructor(code: string, message: string) {
super(message)
this.code = code
}
}
class RuntimeRpcFailureError extends RuntimeClientError {
readonly response: unknown
constructor(response: unknown) {
super('runtime_error', 'runtime_error')
this.response = response
}
}
return {
RuntimeClient,
RuntimeClientError,
RuntimeRpcFailureError
}
})
import {
buildCurrentWorktreeSelector,
COMMAND_SPECS,
main,
normalizeWorktreeSelector
} from './index'
import { buildWorktree, okFixture, queueFixtures, worktreeListFixture } from './test-fixtures'
describe('COMMAND_SPECS collision check', () => {
it('has no duplicate command paths', () => {
const seen = new Set<string>()
for (const spec of COMMAND_SPECS) {
const key = spec.path.join(' ')
expect(seen.has(key), `Duplicate COMMAND_SPECS path: "${key}"`).toBe(false)
seen.add(key)
}
})
})
describe('orca cli worktree awareness', () => {
const originalTerminalHandle = process.env.ORCA_TERMINAL_HANDLE
const originalUserDataPath = process.env.ORCA_USER_DATA_PATH
beforeEach(() => {
callMock.mockReset()
})
afterEach(() => {
vi.restoreAllMocks()
if (originalTerminalHandle === undefined) {
delete process.env.ORCA_TERMINAL_HANDLE
} else {
process.env.ORCA_TERMINAL_HANDLE = originalTerminalHandle
}
if (originalUserDataPath === undefined) {
delete process.env.ORCA_USER_DATA_PATH
} else {
process.env.ORCA_USER_DATA_PATH = originalUserDataPath
}
})
it('builds the current worktree selector from cwd', () => {
expect(buildCurrentWorktreeSelector('/tmp/repo/feature')).toBe(
`path:${path.resolve('/tmp/repo/feature')}`
)
})
it('normalizes active/current worktree selectors to cwd', () => {
const resolved = path.resolve('/tmp/repo/feature')
expect(normalizeWorktreeSelector('active', '/tmp/repo/feature')).toBe(`path:${resolved}`)
expect(normalizeWorktreeSelector('current', '/tmp/repo/feature')).toBe(`path:${resolved}`)
expect(normalizeWorktreeSelector('branch:feature/foo', '/tmp/repo/feature')).toBe(
'branch:feature/foo'
)
})
it('shows the enclosing worktree for `worktree current`', async () => {
queueFixtures(
callMock,
worktreeListFixture([buildWorktree('/tmp/repo/feature', 'feature/foo')]),
okFixture('req_1', {
worktree: {
id: 'repo::/tmp/repo/feature',
branch: 'feature/foo',
path: '/tmp/repo/feature'
}
})
)
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
await main(['worktree', 'current', '--json'], '/tmp/repo/feature/src')
expect(callMock).toHaveBeenNthCalledWith(1, 'worktree.list', { limit: 10_000 })
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.show', {
worktree: `path:${path.resolve('/tmp/repo/feature')}`
})
expect(logSpy).toHaveBeenCalledTimes(1)
})
it('uses cwd when active is passed to worktree.set', async () => {
queueFixtures(
callMock,
worktreeListFixture([
buildWorktree('/tmp/repo', 'main', 'aaa'),
buildWorktree('/tmp/repo/feature', 'feature/foo')
]),
okFixture('req_1', {
worktree: {
id: 'repo::/tmp/repo/feature',
branch: 'feature/foo',
path: '/tmp/repo/feature',
comment: 'hello'
}
})
)
vi.spyOn(console, 'log').mockImplementation(() => {})
await main(
['worktree', 'set', '--worktree', 'active', '--comment', 'hello', '--json'],
'/tmp/repo/feature/src'
)
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.set', {
worktree: `path:${path.resolve('/tmp/repo/feature')}`,
displayName: undefined,
linkedIssue: undefined,
comment: 'hello'
})
})
it('uses the resolved enclosing worktree for other worktree consumers', async () => {
queueFixtures(
callMock,
worktreeListFixture([buildWorktree('/tmp/repo/feature', 'feature/foo')]),
okFixture('req_show', {
worktree: {
id: 'repo::/tmp/repo/feature',
branch: 'feature/foo',
path: '/tmp/repo/feature'
}
})
)
vi.spyOn(console, 'log').mockImplementation(() => {})
await main(['worktree', 'show', '--worktree', 'current', '--json'], '/tmp/repo/feature/src')
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.show', {
worktree: `path:${path.resolve('/tmp/repo/feature')}`
})
})
it('formats group orchestration sends in text mode', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_sender'
callMock.mockResolvedValueOnce({
id: 'req_send',
ok: true,
result: {
messages: [{ id: 'msg_1' }, { id: 'msg_2' }],
recipients: 2
},
_meta: {
runtimeId: 'runtime-1'
}
})
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
await main(['orchestration', 'send', '--to', '@all', '--subject', 'hello'], '/tmp/repo')
expect(callMock).toHaveBeenCalledWith('orchestration.send', {
from: 'term_sender',
to: '@all',
subject: 'hello',
body: undefined,
type: undefined,
priority: undefined,
threadId: undefined,
payload: undefined,
devMode: false
})
expect(logSpy).toHaveBeenCalledWith('Sent 2 messages to 2 recipients')
})
it('rejects unknown task-update status with an enum-aware error', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_coord'
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
const priorExitCode = process.exitCode
await main(
['orchestration', 'task-update', '--id', 'task_x', '--status', 'complete'],
'/tmp/repo'
)
const output = [...errSpy.mock.calls, ...logSpy.mock.calls]
.flat()
.map((v) => (typeof v === 'string' ? v : JSON.stringify(v)))
.join('\n')
expect(output).toContain("invalid status 'complete'")
expect(output).toContain('pending, ready, dispatched, completed, failed, blocked')
expect(callMock).not.toHaveBeenCalled()
expect(process.exitCode).toBe(1)
// Reset exitCode so subsequent tests don't inherit the failure.
process.exitCode = priorExitCode
errSpy.mockRestore()
})
it('passes dev mode to injected orchestration dispatches', async () => {
process.env.ORCA_TERMINAL_HANDLE = 'term_sender'
process.env.ORCA_USER_DATA_PATH = '/tmp/orca-dev'
callMock.mockResolvedValueOnce({
id: 'req_dispatch',
ok: true,
result: {
dispatch: { id: 'ctx_1', task_id: 'task_1', status: 'dispatched' }
},
_meta: {
runtimeId: 'runtime-1'
}
})
vi.spyOn(console, 'log').mockImplementation(() => {})
await main(
['orchestration', 'dispatch', '--task', 'task_1', '--to', 'term_worker', '--inject'],
'/tmp/repo'
)
expect(callMock).toHaveBeenCalledWith('orchestration.dispatch', {
task: 'task_1',
to: 'term_worker',
from: 'term_sender',
inject: true,
devMode: true
})
})
it('uses the resolved enclosing worktree for terminal consumers', async () => {
queueFixtures(
callMock,
worktreeListFixture([buildWorktree('/tmp/repo/feature', 'feature/foo')]),
okFixture('req_term', { terminals: [], totalCount: 0, truncated: false })
)
vi.spyOn(console, 'log').mockImplementation(() => {})
await main(['terminal', 'list', '--worktree', 'active', '--json'], '/tmp/repo/feature/src')
expect(callMock).toHaveBeenNthCalledWith(2, 'terminal.list', {
worktree: `path:${path.resolve('/tmp/repo/feature')}`,
limit: undefined
})
})
})