mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
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>
This commit is contained in:
@@ -142,12 +142,12 @@ it('retains fork reports without requiring a Slack secret and redacts delivery e
|
||||
sendNotification({
|
||||
fetch: vi.fn().mockRejectedValue(new Error('fetch failed: https://example.test/slack'))
|
||||
})
|
||||
).rejects.toThrow('Slack dependency review notification could not be sent')
|
||||
).rejects.toThrow(/^Slack dependency review notification could not be sent$/)
|
||||
await expect(
|
||||
sendNotification({
|
||||
fetch: vi.fn().mockResolvedValue({ ok: false, status: 503 })
|
||||
})
|
||||
).rejects.toThrow('Slack dependency review notification returned HTTP 503')
|
||||
).rejects.toThrow(/^Slack dependency review notification returned HTTP 503$/)
|
||||
})
|
||||
|
||||
it('extracts release data while excluding executable package and Cargo configuration', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callMock: vi.fn(),
|
||||
@@ -31,10 +31,6 @@ import { buildWorktree, okFixture } from './test-fixtures'
|
||||
import { RuntimeClientError } from './runtime/types'
|
||||
import { JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY } from '../shared/jira/jira-agent-capability'
|
||||
import { WORKTREE_LINKED_ITEMS_DELTA_RUNTIME_CAPABILITY } from '../shared/workspace-attachment-capabilities'
|
||||
import {
|
||||
getWorkspaceReferenceIdentity,
|
||||
parseWorkspaceReferenceUrl
|
||||
} from '../shared/workspace-reference-identity'
|
||||
|
||||
const site = {
|
||||
id: 'site-1',
|
||||
@@ -56,9 +52,17 @@ const issue = {
|
||||
updatedAt: '2026-10-13',
|
||||
createdAt: '2026-10-12'
|
||||
}
|
||||
const attachment = { ...parseWorkspaceReferenceUrl(issue.url), title: issue.title }
|
||||
const jiraReference = {
|
||||
provider: 'jira' as const,
|
||||
type: 'issue' as const,
|
||||
number: 0,
|
||||
identifier: 'ENG-123',
|
||||
jiraIdentifier: 'ENG-123',
|
||||
url: issue.url
|
||||
}
|
||||
const attachment = { ...jiraReference, title: issue.title }
|
||||
const reviewUrl = 'https://gitlab.com/acme/api/-/merge_requests/12'
|
||||
const review = parseWorkspaceReferenceUrl(reviewUrl)
|
||||
const review = { provider: 'gitlab' as const, type: 'mr' as const, number: 12, url: reviewUrl }
|
||||
const workspace = { ...buildWorktree('/tmp/api', 'api'), kind: 'worktree', name: 'api' }
|
||||
const addArgs = ['reference', 'add', 'eng-123', '--worktree', 'name:api']
|
||||
const createArgs = [
|
||||
@@ -106,8 +110,16 @@ function expectNoWrite(): void {
|
||||
}
|
||||
|
||||
describe('Jira reference keys', () => {
|
||||
const priorExitCode = process.exitCode
|
||||
beforeEach(() => {
|
||||
process.exitCode = undefined
|
||||
})
|
||||
afterEach(() => {
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
useWorktreeAwarenessEnvironment(mocks)
|
||||
beforeEach(() => {
|
||||
mocks.runtimeClientConstructorMock.mockClear()
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
pairRuntimeEnvironment(mocks.listEnvironmentsMock, 'remote')
|
||||
@@ -123,6 +135,7 @@ describe('Jira reference keys', () => {
|
||||
expect(mocks.callMock).toHaveBeenCalledWith(
|
||||
'worktree.set',
|
||||
expect.objectContaining({
|
||||
worktree: 'id:repo::/tmp/api',
|
||||
linkedItemsBase: [],
|
||||
linkedItems: [attachment],
|
||||
linkedItemsSelectionChanged: false
|
||||
@@ -132,13 +145,18 @@ describe('Jira reference keys', () => {
|
||||
|
||||
it('uses the selected execution host for Jira lookup and workspace mutation', async () => {
|
||||
await main([...addArgs, '--environment', 'remote'], '/tmp/api')
|
||||
expect(mocks.runtimeClientConstructorMock).toHaveBeenCalledWith(undefined, 'remote')
|
||||
expect(mocks.runtimeClientConstructorMock).toHaveBeenCalledExactlyOnceWith(undefined, 'remote')
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('jira.lookupIssueSummary', {
|
||||
key: 'ENG-123',
|
||||
siteId: site.id
|
||||
})
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('reference.list', { worktree: 'name:api' })
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('worktree.set', expect.anything())
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('worktree.set', {
|
||||
worktree: 'id:repo::/tmp/api',
|
||||
linkedItemsBase: [],
|
||||
linkedItems: [attachment],
|
||||
linkedItemsSelectionChanged: false
|
||||
})
|
||||
})
|
||||
|
||||
it('refuses ambiguous sites rather than using activeSiteId', async () => {
|
||||
@@ -157,7 +175,12 @@ describe('Jira reference keys', () => {
|
||||
|
||||
it.each(['all', 'missing'])('rejects invalid explicit site %s before lookup', async (id) => {
|
||||
await main([...addArgs, '--reference-site', id], '/tmp/api')
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(console.error).toHaveBeenCalledExactlyOnceWith(
|
||||
id === 'all'
|
||||
? 'Choose one Jira site ID; --reference-site all is unsupported.'
|
||||
: 'No matching connected Jira site. Check orca jira connection and Orca Jira settings.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
expect(mocks.callMock).not.toHaveBeenCalledWith('jira.lookupIssueSummary', expect.anything())
|
||||
expectNoWrite()
|
||||
})
|
||||
@@ -182,7 +205,7 @@ describe('Jira reference keys', () => {
|
||||
expect(mocks.callMock).toHaveBeenCalledWith(
|
||||
'worktree.set',
|
||||
expect.objectContaining({
|
||||
linkedItems: [{ ...parseWorkspaceReferenceUrl(serverIssue.url), title: issue.title }]
|
||||
linkedItems: [{ ...attachment, url: serverIssue.url }]
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -198,7 +221,14 @@ describe('Jira reference keys', () => {
|
||||
])('rejects an absent or mismatched summary %#', async (summary) => {
|
||||
responses({ 'jira.lookupIssueSummary': summary })
|
||||
await main(addArgs, '/tmp/api')
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('jira.lookupIssueSummary', {
|
||||
key: 'ENG-123',
|
||||
siteId: site.id
|
||||
})
|
||||
expect(console.error).toHaveBeenCalledExactlyOnceWith(
|
||||
'Jira did not return issue ENG-123 with a title on the selected site.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
expectNoWrite()
|
||||
})
|
||||
|
||||
@@ -241,7 +271,13 @@ describe('Jira reference keys', () => {
|
||||
responses({
|
||||
'reference.list': {
|
||||
worktree: workspace,
|
||||
references: [{ ...stored, key: getWorkspaceReferenceIdentity(stored), selected: false }]
|
||||
references: [
|
||||
{
|
||||
...stored,
|
||||
key: '["jira","issue","example.atlassian.net","/browse/ENG-123"]',
|
||||
selected: false
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
await main([...addArgs, '--json'], '/tmp/api')
|
||||
@@ -273,6 +309,7 @@ describe('Jira reference keys', () => {
|
||||
it('resolves create references before workspace creation', async () => {
|
||||
await main(createArgs, '/tmp/api')
|
||||
const methods = mocks.callMock.mock.calls.map(([method]) => method)
|
||||
expect(methods).toContain('jira.lookupIssueSummary')
|
||||
expect(methods.indexOf('jira.lookupIssueSummary')).toBeLessThan(
|
||||
methods.indexOf('worktree.create')
|
||||
)
|
||||
@@ -285,6 +322,18 @@ describe('Jira reference keys', () => {
|
||||
it('creates nothing when the Jira reference cannot resolve', async () => {
|
||||
responses({ 'jira.lookupIssueSummary': null })
|
||||
await main(createArgs, '/tmp/api')
|
||||
expect(mocks.callMock).toHaveBeenCalledWith('jira.lookupIssueSummary', {
|
||||
key: 'ENG-123',
|
||||
siteId: site.id
|
||||
})
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0])).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'jira_issue_unavailable',
|
||||
message: 'Jira did not return issue ENG-123 with a title on the selected site.'
|
||||
}
|
||||
})
|
||||
expect(process.exitCode).toBe(1)
|
||||
expectNoWrite()
|
||||
})
|
||||
|
||||
@@ -312,7 +361,18 @@ describe('Jira reference keys', () => {
|
||||
key: 'ENG-124',
|
||||
siteId: site.id
|
||||
})
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(
|
||||
mocks.callMock.mock.calls
|
||||
.filter(([method]) => method === 'jira.lookupIssueSummary')
|
||||
.map(([, params]) => params)
|
||||
).toEqual([
|
||||
{ key: 'ENG-123', siteId: site.id },
|
||||
{ key: 'ENG-124', siteId: site.id }
|
||||
])
|
||||
expect(console.error).toHaveBeenCalledExactlyOnceWith(
|
||||
'Jira did not return issue ENG-124 with a title on the selected site.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
expectNoWrite()
|
||||
})
|
||||
|
||||
@@ -328,11 +388,11 @@ describe('Jira reference keys', () => {
|
||||
it('keeps full Jira URLs usable without a saved Jira connection', async () => {
|
||||
responses({ 'status.get': { capabilities: [WORKTREE_LINKED_ITEMS_DELTA_RUNTIME_CAPABILITY] } })
|
||||
await main(['reference', 'add', issue.url, '--worktree', 'name:api'], '/tmp/api')
|
||||
expect(mocks.callMock).not.toHaveBeenCalledWith('jira.status')
|
||||
expect(mocks.callMock.mock.calls.map(([method]) => method)).not.toContain('jira.status')
|
||||
expect(mocks.callMock).toHaveBeenCalledWith(
|
||||
'worktree.set',
|
||||
expect.objectContaining({
|
||||
linkedItems: [parseWorkspaceReferenceUrl(issue.url)]
|
||||
linkedItems: [jiraReference]
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -341,7 +401,7 @@ describe('Jira reference keys', () => {
|
||||
responses({ 'status.get': { capabilities: [WORKTREE_LINKED_ITEMS_DELTA_RUNTIME_CAPABILITY] } })
|
||||
await main(addArgs, '/tmp/api')
|
||||
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Update Orca'))
|
||||
expect(mocks.callMock).not.toHaveBeenCalledWith('jira.status')
|
||||
expect(mocks.callMock.mock.calls.map(([method]) => method)).not.toContain('jira.status')
|
||||
expectNoWrite()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const {
|
||||
callMock,
|
||||
@@ -42,17 +42,24 @@ vi.mock('child_process', async () => {
|
||||
|
||||
import { main } from './index'
|
||||
import { buildWorktree, okFixture } from './test-fixtures'
|
||||
import {
|
||||
getWorkspaceReferenceIdentity,
|
||||
parseWorkspaceReferenceUrl
|
||||
} from '../shared/workspace-reference-identity'
|
||||
import { getWorkspaceReferenceIdentity } from '../shared/workspace-reference-identity'
|
||||
import type { WorkspaceAttachment } from '../shared/worktree/types'
|
||||
import { WORKTREE_LINKED_ITEMS_DELTA_RUNTIME_CAPABILITY } from '../shared/workspace-attachment-capabilities'
|
||||
import { useWorktreeAwarenessEnvironment } from './index-test-harness'
|
||||
|
||||
const PR = 'https://github.com/acme/api/pull/5123'
|
||||
const TASK = 'https://linear.app/acme/issue/STA-1234'
|
||||
const parsedPR = parseWorkspaceReferenceUrl(PR)
|
||||
const parsedTask = parseWorkspaceReferenceUrl(TASK)
|
||||
const PR_KEY = '["github","pr","github.com","/acme/api/pull/5123"]'
|
||||
const parsedPR: WorkspaceAttachment = { provider: 'github', type: 'pr', number: 5123, url: PR }
|
||||
const parsedTask: WorkspaceAttachment = {
|
||||
provider: 'linear',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
identifier: 'STA-1234',
|
||||
linearIdentifier: 'STA-1234',
|
||||
linearOrganizationUrlKey: 'acme',
|
||||
url: TASK
|
||||
}
|
||||
|
||||
function listResult(references = [parsedPR], kind: 'worktree' | 'folder' = 'worktree') {
|
||||
return {
|
||||
@@ -94,6 +101,13 @@ function mockCalls(
|
||||
}
|
||||
|
||||
describe('reference CLI', () => {
|
||||
const priorExitCode = process.exitCode
|
||||
beforeEach(() => {
|
||||
process.exitCode = undefined
|
||||
})
|
||||
afterEach(() => {
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
useWorktreeAwarenessEnvironment({
|
||||
callMock,
|
||||
serveOrcaAppMock,
|
||||
@@ -112,7 +126,14 @@ describe('reference CLI', () => {
|
||||
linkedItems: [parsedPR, parsedTask],
|
||||
linkedItemsSelectionChanged: false
|
||||
})
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0]).result.changes).toHaveLength(2)
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0]).result.changes).toEqual([
|
||||
{ key: PR_KEY, operation: 'add', changed: true },
|
||||
{
|
||||
key: '["linear","issue","linear.app","/acme/issue/STA-1234"]',
|
||||
operation: 'add',
|
||||
changed: true
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('validates every URL before any request', async () => {
|
||||
@@ -125,17 +146,20 @@ describe('reference CLI', () => {
|
||||
it('requires the delta capability even if the host supports linked items', async () => {
|
||||
mockCalls(listResult(), listResult(), ['worktree.linked-items.v1'])
|
||||
await main(['reference', 'add', TASK, '--worktree', 'name:api'], '/tmp/api')
|
||||
expect(callMock).toHaveBeenCalledTimes(1)
|
||||
expect(callMock).toHaveBeenCalledExactlyOnceWith('status.get')
|
||||
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('cannot merge'))
|
||||
})
|
||||
|
||||
it('duplicate adds preserve current metadata and produce a no-op', async () => {
|
||||
mockCalls()
|
||||
mockCalls(listResult([{ ...parsedPR, title: 'Saved title' }]))
|
||||
await main(['reference', 'add', PR, PR, '--worktree', 'name:api', '--json'], '/tmp/api')
|
||||
expect(callMock.mock.calls.some(([method]) => method === 'worktree.set')).toBe(false)
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0]).result.references[0].title).toBe(
|
||||
'Saved title'
|
||||
)
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0]).result.changes).toEqual([
|
||||
{
|
||||
key: getWorkspaceReferenceIdentity(parsedPR),
|
||||
key: PR_KEY,
|
||||
operation: 'add',
|
||||
changed: false,
|
||||
reason: 'already_linked'
|
||||
@@ -145,17 +169,7 @@ describe('reference CLI', () => {
|
||||
|
||||
it('removes only requested keys from folders via existing folder storage', async () => {
|
||||
mockCalls(listResult([parsedPR, parsedTask], 'folder'), listResult([parsedTask], 'folder'))
|
||||
await main(
|
||||
[
|
||||
'reference',
|
||||
'remove',
|
||||
'--key',
|
||||
getWorkspaceReferenceIdentity(parsedPR),
|
||||
'--worktree',
|
||||
'folder:folder1'
|
||||
],
|
||||
'/tmp/api'
|
||||
)
|
||||
await main(['reference', 'remove', '--key', PR_KEY, '--worktree', 'folder:folder1'], '/tmp/api')
|
||||
expect(callMock).toHaveBeenCalledWith('folderWorkspace.update', {
|
||||
folderWorkspaceId: 'folder1',
|
||||
updates: {
|
||||
@@ -167,7 +181,7 @@ describe('reference CLI', () => {
|
||||
})
|
||||
|
||||
it('removes a URL without touching the same review number in another repository', async () => {
|
||||
const other = parseWorkspaceReferenceUrl('https://github.com/acme/other/pull/5123')
|
||||
const other = { ...parsedPR, url: 'https://github.com/acme/other/pull/5123' }
|
||||
mockCalls(listResult([parsedPR, other]), listResult([other]))
|
||||
await main(['reference', 'remove', PR, '--worktree', 'name:api'], '/tmp/api')
|
||||
expect(callMock).toHaveBeenCalledWith(
|
||||
@@ -308,13 +322,21 @@ describe('reference CLI', () => {
|
||||
})
|
||||
|
||||
it.each([
|
||||
['5123'],
|
||||
['STA-1234', '--worktree', 'name:api', '--repo', 'id:repo'],
|
||||
['STA-1234', '--limit', '0']
|
||||
])('rejects invalid find arguments %j before querying', async (...args) => {
|
||||
{ args: ['5123'], message: 'Expected a full reference URL (https://…).' },
|
||||
{
|
||||
args: ['STA-1234', '--worktree', 'name:api', '--repo', 'id:repo'],
|
||||
message: 'Pass --worktree or --repo, not both.'
|
||||
},
|
||||
{ args: ['STA-1234', '--limit', '0'], message: 'Invalid positive integer for --limit' }
|
||||
])('rejects invalid find arguments $args before querying', async ({ args, message }) => {
|
||||
mockCalls()
|
||||
await main(['reference', 'find', ...args], '/tmp/api')
|
||||
await main(['reference', 'find', ...args, '--json'], '/tmp/api')
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0])).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'invalid_argument', message }
|
||||
})
|
||||
expect(process.exitCode).toBe(1)
|
||||
})
|
||||
|
||||
it('seeds repeated references in the create request before setup or agent startup', async () => {
|
||||
@@ -357,11 +379,17 @@ describe('reference CLI', () => {
|
||||
'--reference',
|
||||
PR,
|
||||
`--${flag}`,
|
||||
'4'
|
||||
'4',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/api'
|
||||
)
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0])).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: 'invalid_argument', message: `Cannot combine --reference with --${flag}.` }
|
||||
})
|
||||
expect(process.exitCode).toBe(1)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -380,13 +408,16 @@ describe('reference CLI', () => {
|
||||
}
|
||||
return base?.(method, ...rest)
|
||||
})
|
||||
const priorExitCode = process.exitCode
|
||||
await main(argv, '/tmp/api')
|
||||
const printed = vi.mocked(console.error).mock.calls.flat().join('\n')
|
||||
expect(printed).toContain('Update Orca on the execution host')
|
||||
expect(printed).not.toContain('Unknown method')
|
||||
await main([...argv, '--json'], '/tmp/api')
|
||||
expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0])).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'incompatible_runtime',
|
||||
message:
|
||||
'This Orca host does not support reference commands yet. Update Orca on the execution host.'
|
||||
}
|
||||
})
|
||||
expect(process.exitCode).toBe(1)
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('validates create flags locally before probing reference capability', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const {
|
||||
callMock,
|
||||
@@ -44,6 +44,13 @@ 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,
|
||||
@@ -54,7 +61,7 @@ describe('orca cli worktree awareness', () => {
|
||||
})
|
||||
|
||||
it('starts a foreground headless server through `serve`', async () => {
|
||||
serveOrcaAppMock.mockResolvedValue(0)
|
||||
serveOrcaAppMock.mockResolvedValue(17)
|
||||
process.env.ORCA_ENVIRONMENT = 'stale-env'
|
||||
|
||||
await main(
|
||||
@@ -62,7 +69,7 @@ describe('orca cli worktree awareness', () => {
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledWith({
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
|
||||
json: true,
|
||||
port: '6768',
|
||||
pairingAddress: '100.64.1.20',
|
||||
@@ -72,6 +79,8 @@ describe('orca cli worktree awareness', () => {
|
||||
recipeJson: false,
|
||||
projectRoot: null
|
||||
})
|
||||
expect(process.exitCode).toBe(17)
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
@@ -88,11 +97,16 @@ describe('orca cli worktree awareness', () => {
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,7 +128,7 @@ describe('orca cli worktree awareness', () => {
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledWith({
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
|
||||
json: true,
|
||||
port: null,
|
||||
pairingAddress: '100.64.1.20',
|
||||
@@ -141,7 +155,7 @@ describe('orca cli worktree awareness', () => {
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledWith({
|
||||
expect(serveOrcaAppMock).toHaveBeenCalledExactlyOnceWith({
|
||||
json: false,
|
||||
port: null,
|
||||
pairingAddress: 'wss://sandbox.example.com',
|
||||
@@ -153,86 +167,46 @@ describe('orca cli worktree awareness', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects recipe JSON output without a project root', async () => {
|
||||
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(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
await main(['serve', '--recipe-json'], '/tmp/repo')
|
||||
await main([...args, '--json'], '/tmp/repo')
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Recipe JSON output requires --project-root.'
|
||||
)
|
||||
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)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('rejects recipe JSON output with mobile pairing', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(
|
||||
['serve', '--recipe-json', '--project-root', '/workspace/repo', '--mobile-pairing'],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Recipe JSON output requires runtime pairing; remove --mobile-pairing.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('rejects contradictory serve pairing flags', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(['serve', '--mobile-pairing', '--no-pairing', '--json'], '/tmp/repo')
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Use either --mobile-pairing or --no-pairing, not both.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('rejects invalid serve ports before launching the app', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(['serve', '--port', 'not-a-port', '--json'], '/tmp/repo')
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Invalid --port value: not-a-port'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('rejects value-less serve ports before launching the app', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(['serve', '--port', '--json'], '/tmp/repo')
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Missing value for --port.'
|
||||
)
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const {
|
||||
callMock,
|
||||
@@ -53,6 +53,10 @@ describe('orca serve restore resume', () => {
|
||||
spawnMock
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
runtimeClientConstructorMock.mockClear()
|
||||
})
|
||||
|
||||
it('asks the local Orca to resume and never starts a server', async () => {
|
||||
process.env.ORCA_ENVIRONMENT = 'paired-env'
|
||||
callMock.mockResolvedValue({
|
||||
@@ -72,10 +76,13 @@ describe('orca serve restore resume', () => {
|
||||
await main(['serve', 'restore', 'resume'], '/tmp/repo')
|
||||
|
||||
expect(serveOrcaAppMock).not.toHaveBeenCalled()
|
||||
expect(callMock).toHaveBeenCalledWith('runtime.automaticWork.resume')
|
||||
expect(runtimeClientConstructorMock).toHaveBeenLastCalledWith(null, null)
|
||||
expect(logSpy.mock.calls[0]?.[0]).toContain('Automatic restores resumed')
|
||||
expect(logSpy.mock.calls[0]?.[0]).toContain('stopped unexpectedly')
|
||||
expect(callMock.mock.calls).toEqual([['runtime.automaticWork.resume']])
|
||||
expect(runtimeClientConstructorMock.mock.calls).toEqual([[null, null]])
|
||||
expect(logSpy.mock.calls).toEqual([
|
||||
[
|
||||
'Automatic restores resumed. They were paused at 1970-01-01T00:00:00.000Z because the terminal host stopped unexpectedly.'
|
||||
]
|
||||
])
|
||||
})
|
||||
|
||||
it('says so when nothing was paused, and when the host has no gate', async () => {
|
||||
@@ -95,8 +102,14 @@ describe('orca serve restore resume', () => {
|
||||
})
|
||||
await main(['serve', 'restore', 'resume'], '/tmp/repo')
|
||||
|
||||
expect(logSpy.mock.calls[0]?.[0]).toContain('were not paused')
|
||||
expect(logSpy.mock.calls[1]?.[0]).toContain('no automatic-restore gate')
|
||||
expect(callMock.mock.calls).toEqual([
|
||||
['runtime.automaticWork.resume'],
|
||||
['runtime.automaticWork.resume']
|
||||
])
|
||||
expect(logSpy.mock.calls).toEqual([
|
||||
['Automatic restores were not paused; nothing to resume.'],
|
||||
['This host has no automatic-restore gate to resume (its terminal host predates it).']
|
||||
])
|
||||
})
|
||||
|
||||
it('explains a terminal host that predates the command', async () => {
|
||||
@@ -108,6 +121,11 @@ describe('orca serve restore resume', () => {
|
||||
_meta: { runtimeId: 'runtime-1' }
|
||||
})
|
||||
await main(['serve', 'restore', 'resume'], '/tmp/repo')
|
||||
expect(logSpy.mock.calls[0]?.[0]).toContain('predates this command')
|
||||
expect(callMock.mock.calls).toEqual([['runtime.automaticWork.resume']])
|
||||
expect(logSpy.mock.calls).toEqual([
|
||||
[
|
||||
'The terminal host running now predates this command, so it cannot clear its pause. Run this again after the terminal host restarts. Until then, opening a sleeping terminal still resumes it.'
|
||||
]
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const {
|
||||
callMock,
|
||||
@@ -54,14 +54,114 @@ describe('orca cli worktree awareness', () => {
|
||||
spawnMock
|
||||
})
|
||||
|
||||
it('passes explicit focus through terminal.create', async () => {
|
||||
const originalExitCode = process.exitCode
|
||||
beforeEach(() => {
|
||||
process.exitCode = undefined
|
||||
runtimeClientConstructorMock.mockClear()
|
||||
})
|
||||
afterEach(() => {
|
||||
process.exitCode = originalExitCode
|
||||
})
|
||||
|
||||
it.each([
|
||||
[
|
||||
'passes explicit focus through terminal.create',
|
||||
{
|
||||
title: 'RUNNER',
|
||||
command: undefined,
|
||||
flags: ['--focus'],
|
||||
expected: { focus: true, presentation: 'focused' }
|
||||
}
|
||||
],
|
||||
[
|
||||
'keeps interactive Codex startup commands backgrounded unless focus is explicit',
|
||||
{
|
||||
title: 'Codex',
|
||||
command: 'codex',
|
||||
flags: [],
|
||||
expected: { focus: false, rendererBacked: true, activate: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'keeps explicit focus semantics when forcing Codex through the renderer path',
|
||||
{
|
||||
title: 'Codex',
|
||||
command: 'codex',
|
||||
flags: ['--focus'],
|
||||
expected: { focus: true, presentation: 'focused', rendererBacked: true, activate: true }
|
||||
}
|
||||
],
|
||||
[
|
||||
'does not force the visible terminal path for explicit Codex exec commands',
|
||||
{
|
||||
title: 'Codex exec',
|
||||
command: 'codex exec summarize',
|
||||
flags: [],
|
||||
expected: { focus: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'does not force the visible terminal path for Codex exec commands after global options',
|
||||
{
|
||||
title: 'Codex exec',
|
||||
command: 'codex -m gpt-5 --sandbox workspace-write exec summarize',
|
||||
flags: [],
|
||||
expected: { focus: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'does not force the visible terminal path for Codex review commands after long options',
|
||||
{
|
||||
title: 'Codex review',
|
||||
command: 'codex --model=gpt-5 --sandbox=workspace-write review',
|
||||
flags: [],
|
||||
expected: { focus: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'does not force the visible terminal path for Codex help commands',
|
||||
{
|
||||
title: 'Codex help',
|
||||
command: 'codex --help',
|
||||
flags: [],
|
||||
expected: { focus: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'keeps Codex prompts after global options backgrounded unless focus is explicit',
|
||||
{
|
||||
title: 'Codex prompt',
|
||||
command: 'codex -m gpt-5 "fix the flaky test"',
|
||||
flags: [],
|
||||
expected: { focus: false, rendererBacked: true, activate: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'keeps interactive Claude startup commands backgrounded unless focus is explicit',
|
||||
{
|
||||
title: 'Claude',
|
||||
command: 'claude',
|
||||
flags: [],
|
||||
expected: { focus: false, rendererBacked: true, activate: false }
|
||||
}
|
||||
],
|
||||
[
|
||||
'keeps Claude print commands on the background terminal path',
|
||||
{
|
||||
title: 'Claude print',
|
||||
command: 'claude -p "summarize"',
|
||||
flags: [],
|
||||
expected: { focus: false }
|
||||
}
|
||||
]
|
||||
] as const)('%s', async (_name, { title, command, flags, expected }) => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'RUNNER'
|
||||
title
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -74,20 +174,18 @@ describe('orca cli worktree awareness', () => {
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'RUNNER',
|
||||
'--focus',
|
||||
title,
|
||||
...(command === undefined ? [] : ['--command', command]),
|
||||
...flags,
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: undefined,
|
||||
title: 'RUNNER',
|
||||
focus: true,
|
||||
presentation: 'focused'
|
||||
})
|
||||
expect(callMock.mock.calls).toEqual([
|
||||
['terminal.create', { worktree: 'path:/tmp/repo/feature', command, title, ...expected }]
|
||||
])
|
||||
expect(process.exitCode).toBeUndefined()
|
||||
})
|
||||
|
||||
it('prints terminal.read fallback screen lines in json mode', async () => {
|
||||
@@ -126,340 +224,6 @@ describe('orca cli worktree awareness', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps interactive Codex startup commands backgrounded unless focus is explicit', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex',
|
||||
'--command',
|
||||
'codex',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex',
|
||||
title: 'Codex',
|
||||
focus: false,
|
||||
rendererBacked: true,
|
||||
activate: false
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps explicit focus semantics when forcing Codex through the renderer path', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex',
|
||||
'--command',
|
||||
'codex',
|
||||
'--focus',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex',
|
||||
title: 'Codex',
|
||||
focus: true,
|
||||
presentation: 'focused',
|
||||
rendererBacked: true,
|
||||
activate: true
|
||||
})
|
||||
})
|
||||
|
||||
it('does not force the visible terminal path for explicit Codex exec commands', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex exec'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex exec',
|
||||
'--command',
|
||||
'codex exec summarize',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex exec summarize',
|
||||
title: 'Codex exec',
|
||||
focus: false
|
||||
})
|
||||
})
|
||||
|
||||
it('does not force the visible terminal path for Codex exec commands after global options', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex exec'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex exec',
|
||||
'--command',
|
||||
'codex -m gpt-5 --sandbox workspace-write exec summarize',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex -m gpt-5 --sandbox workspace-write exec summarize',
|
||||
title: 'Codex exec',
|
||||
focus: false
|
||||
})
|
||||
})
|
||||
|
||||
it('does not force the visible terminal path for Codex review commands after long options', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex review'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex review',
|
||||
'--command',
|
||||
'codex --model=gpt-5 --sandbox=workspace-write review',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex --model=gpt-5 --sandbox=workspace-write review',
|
||||
title: 'Codex review',
|
||||
focus: false
|
||||
})
|
||||
})
|
||||
|
||||
it('does not force the visible terminal path for Codex help commands', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex help'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex help',
|
||||
'--command',
|
||||
'codex --help',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex --help',
|
||||
title: 'Codex help',
|
||||
focus: false
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps Codex prompts after global options backgrounded unless focus is explicit', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Codex prompt'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Codex prompt',
|
||||
'--command',
|
||||
'codex -m gpt-5 "fix the flaky test"',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'codex -m gpt-5 "fix the flaky test"',
|
||||
title: 'Codex prompt',
|
||||
focus: false,
|
||||
rendererBacked: true,
|
||||
activate: false
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps interactive Claude startup commands backgrounded unless focus is explicit', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Claude'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Claude',
|
||||
'--command',
|
||||
'claude',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'claude',
|
||||
title: 'Claude',
|
||||
focus: false,
|
||||
rendererBacked: true,
|
||||
activate: false
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps Claude print commands on the background terminal path', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_terminal_create', {
|
||||
terminal: {
|
||||
handle: 'term_1',
|
||||
worktreeId: 'repo-1::/tmp/repo/feature',
|
||||
title: 'Claude print'
|
||||
}
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
[
|
||||
'terminal',
|
||||
'create',
|
||||
'--worktree',
|
||||
'path:/tmp/repo/feature',
|
||||
'--title',
|
||||
'Claude print',
|
||||
'--command',
|
||||
'claude -p "summarize"',
|
||||
'--json'
|
||||
],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('terminal.create', {
|
||||
worktree: 'path:/tmp/repo/feature',
|
||||
command: 'claude -p "summarize"',
|
||||
title: 'Claude print',
|
||||
focus: false
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the resolved enclosing worktree for terminal consumers', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
@@ -532,7 +296,6 @@ describe('orca cli worktree awareness', () => {
|
||||
it('rejects implicit remote terminal create instead of resolving from client cwd', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(
|
||||
['terminal', 'create', '--pairing-code', 'remote-runtime', '--json'],
|
||||
@@ -540,12 +303,17 @@ describe('orca cli worktree awareness', () => {
|
||||
)
|
||||
|
||||
expect(callMock).not.toHaveBeenCalled()
|
||||
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
||||
'Remote terminal create requires --worktree'
|
||||
)
|
||||
expect(logSpy).toHaveBeenCalledTimes(1)
|
||||
expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'invalid_argument',
|
||||
message:
|
||||
'Remote terminal create requires --worktree because the client cwd cannot identify a server worktree.'
|
||||
}
|
||||
})
|
||||
expect(errSpy).not.toHaveBeenCalled()
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('sends explicit remote terminal create worktree selectors unchanged', async () => {
|
||||
@@ -602,7 +370,6 @@ describe('orca cli worktree awareness', () => {
|
||||
}
|
||||
})
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const priorExitCode = process.exitCode
|
||||
|
||||
await main(['terminal', 'wait', '--terminal', 'term_worker', '--for', 'tui-idle'], '/tmp/repo')
|
||||
|
||||
@@ -619,8 +386,6 @@ describe('orca cli worktree awareness', () => {
|
||||
)
|
||||
expect(logSpy.mock.calls.flat().join('\n')).toContain('blockedReason: codex-cwd-prompt')
|
||||
expect(process.exitCode).toBe(1)
|
||||
|
||||
process.exitCode = priorExitCode
|
||||
})
|
||||
|
||||
it('does not force remote Codex terminal creates through a local renderer path', async () => {
|
||||
|
||||
@@ -84,6 +84,10 @@ describe('orca terminal list host scope', () => {
|
||||
|
||||
await main(['terminal', 'list', '--json'], '/tmp/repo')
|
||||
|
||||
expect(callMock.mock.calls).toEqual([
|
||||
['terminal.list', { worktree: undefined, limit: undefined, includeVisualLayouts: false }]
|
||||
])
|
||||
|
||||
const printed = JSON.parse(String(logSpy.mock.calls[0]?.[0]))
|
||||
expect(printed.result.terminals[0].executionHostId).toBe('ssh:box-1')
|
||||
expect(printed.result.hostScope).toEqual({
|
||||
@@ -108,10 +112,14 @@ describe('orca terminal list host scope', () => {
|
||||
|
||||
await main(['terminal', 'list'], '/tmp/repo')
|
||||
|
||||
expect(callMock.mock.calls).toEqual([
|
||||
['terminal.list', { worktree: undefined, limit: undefined, includeVisualLayouts: true }]
|
||||
])
|
||||
|
||||
const printed = String(logSpy.mock.calls[0]?.[0])
|
||||
expect(printed).toContain('host=ssh:box-1')
|
||||
expect(printed).toContain('scope: ssh:box-1')
|
||||
expect(printed).toContain('not covered: local')
|
||||
expect(printed).toBe(
|
||||
'term_remote worker connected host=ssh:box-1 /remote/wt\npreview: <empty>\n\nscope: ssh:box-1 — not covered: local (reached through this runtime; no flag selects it)'
|
||||
)
|
||||
})
|
||||
|
||||
it('does not claim a local scope when the host never reported one', async () => {
|
||||
@@ -123,8 +131,13 @@ describe('orca terminal list host scope', () => {
|
||||
|
||||
await main(['terminal', 'list'], '/tmp/repo')
|
||||
|
||||
expect(callMock.mock.calls).toEqual([
|
||||
['terminal.list', { worktree: undefined, limit: undefined, includeVisualLayouts: true }]
|
||||
])
|
||||
|
||||
const printed = String(logSpy.mock.calls[0]?.[0])
|
||||
expect(printed).toContain('scope: unverifiable')
|
||||
expect(printed).not.toContain('scope: local')
|
||||
expect(printed).toBe(
|
||||
'No terminals listed.\nscope: unverifiable — this host does not report which hosts it lists'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('dashboard visible-use telemetry', () => {
|
||||
let visibility: DocumentVisibilityState
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.useFakeTimers({ now: new Date('2026-10-22T12:00:00Z') })
|
||||
track.mockReset().mockResolvedValue(true)
|
||||
getConsentState.mockReset().mockResolvedValue({ effective: 'enabled' })
|
||||
useAppStore.setState({
|
||||
|
||||
Reference in New Issue
Block a user