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 { buildWorktree, okFixture } from './test-fixtures' 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 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 { worktree: { ...buildWorktree('/tmp/api', 'api'), id: kind === 'folder' ? 'folder:folder1' : 'repo::/tmp/api', name: 'api', kind, repo: 'repo' }, references: references.map((item) => ({ ...item, key: getWorkspaceReferenceIdentity(item), selected: true })) } } function mockCalls( before = listResult(), after = before, capabilities = [WORKTREE_LINKED_ITEMS_DELTA_RUNTIME_CAPABILITY] ) { let lists = 0 callMock.mockImplementation(async (method: string) => { if (method === 'status.get') { return okFixture('status', { capabilities }) } if (method === 'reference.list') { return okFixture('list', lists++ === 0 ? before : after) } if (method === 'reference.find') { return okFixture('find', { kind: 'reference_matches', matches: [], truncated: false }) } return okFixture('mutation', { worktree: before.worktree }) }) vi.spyOn(console, 'log').mockImplementation(() => {}) vi.spyOn(console, 'error').mockImplementation(() => {}) } describe('reference CLI', () => { const priorExitCode = process.exitCode beforeEach(() => { process.exitCode = undefined }) afterEach(() => { process.exitCode = priorExitCode }) useWorktreeAwarenessEnvironment({ callMock, serveOrcaAppMock, getDefaultUserDataPathMock, addEnvironmentFromPairingCodeMock, listEnvironmentsMock, spawnMock }) it('adds several URLs through the existing delta merge and never invents origins', async () => { mockCalls(listResult([]), listResult([parsedPR, parsedTask])) await main(['reference', 'add', PR, TASK, '--worktree', 'name:api', '--json'], '/tmp/api') expect(callMock).toHaveBeenCalledWith('worktree.set', { worktree: 'id:repo::/tmp/api', linkedItemsBase: [], linkedItems: [parsedPR, parsedTask], linkedItemsSelectionChanged: false }) 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 () => { mockCalls() await main(['reference', 'add', PR, 'github:pr:4', '--worktree', 'name:api'], '/tmp/api') expect(callMock).not.toHaveBeenCalled() expect(console.error).toHaveBeenCalled() }) 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).toHaveBeenCalledExactlyOnceWith('status.get') expect(console.error).toHaveBeenCalledWith(expect.stringContaining('cannot merge')) }) it('duplicate adds preserve current metadata and produce a no-op', async () => { 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: PR_KEY, operation: 'add', changed: false, reason: 'already_linked' } ]) }) 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', PR_KEY, '--worktree', 'folder:folder1'], '/tmp/api') expect(callMock).toHaveBeenCalledWith('folderWorkspace.update', { folderWorkspaceId: 'folder1', updates: { linkedItemsBase: [parsedPR, parsedTask], linkedItems: [parsedTask], linkedItemsSelectionChanged: false } }) }) it('removes a URL without touching the same review number in another repository', async () => { 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( 'worktree.set', expect.objectContaining({ linkedItemsBase: [parsedPR, other], linkedItems: [other] }) ) }) const localTwin = { ...parsedPR, title: 'Local', taskSourceContext: { kind: 'task-source' as const, provider: 'github' as const, projectId: 'p1', hostId: 'local' as const } } const remoteTwin = { ...parsedPR, title: 'Remote', taskSourceContext: { ...localTwin.taskSourceContext, hostId: 'runtime:other' as const } } it('keeps same-URL twins with different source contexts on an unrelated add', async () => { mockCalls(listResult([localTwin, remoteTwin])) await main(['reference', 'add', TASK, '--worktree', 'name:api'], '/tmp/api') expect(callMock).toHaveBeenCalledWith( 'worktree.set', expect.objectContaining({ linkedItemsBase: [localTwin, remoteTwin], linkedItems: [localTwin, remoteTwin, parsedTask] }) ) }) it('removing a URL removes every twin that shares its identity', async () => { mockCalls(listResult([localTwin, parsedTask, remoteTwin]), listResult([parsedTask])) await main(['reference', 'remove', PR, '--worktree', 'name:api'], '/tmp/api') expect(callMock).toHaveBeenCalledWith( 'worktree.set', expect.objectContaining({ linkedItemsBase: [localTwin, parsedTask, remoteTwin], linkedItems: [parsedTask] }) ) }) it('removes by the host-provided key even when the local identity differs', async () => { const before = listResult([parsedPR, parsedTask]) before.references[0].key = 'host-v2:pr-key' mockCalls(before, listResult([parsedTask])) await main( ['reference', 'remove', '--key', 'host-v2:pr-key', '--worktree', 'name:api', '--json'], '/tmp/api' ) expect(callMock).toHaveBeenCalledWith( 'worktree.set', expect.objectContaining({ linkedItemsBase: [parsedPR, parsedTask], linkedItems: [parsedTask] }) ) expect(JSON.parse(vi.mocked(console.log).mock.calls[0][0]).result.changes).toEqual([ { key: 'host-v2:pr-key', operation: 'remove', changed: true } ]) }) it('removing an absent URL is a no-op', async () => { mockCalls() await main(['reference', 'remove', TASK, '--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.changes[0]).toMatchObject({ changed: false, reason: 'not_linked' }) }) it('rejects remote current without resolving the local cwd on the paired host', async () => { mockCalls() await main( ['reference', 'find', 'STA-1234', '--worktree', 'current', '--pairing-code', 'remote'], '/tmp/api' ) expect(callMock).not.toHaveBeenCalled() expect(console.error).toHaveBeenCalledWith(expect.stringContaining('remote host')) }) it('forwards local caller path context so WSL selectors resolve without a scan', async () => { mockCalls() const cwd = String.raw`\\wsl$\Ubuntu\home\me\repo` await main(['reference', 'find', 'STA-1234', '--worktree', 'path:/home/me/repo'], cwd) expect(callMock).toHaveBeenCalledExactlyOnceWith( 'reference.find', expect.objectContaining({ worktree: 'path:/home/me/repo', cwd }) ) }) it('passes explicit paired-host selectors unchanged', async () => { mockCalls() await main( ['reference', 'find', 'STA-1234', '--worktree', 'path:/srv/api', '--pairing-code', 'remote'], '/tmp/api' ) expect(callMock).toHaveBeenCalledExactlyOnceWith( 'reference.find', expect.objectContaining({ worktree: 'path:/srv/api' }) ) expect(callMock.mock.calls[0][1]).not.toHaveProperty('cwd') }) it('uses metadata current lookup instead of worktree.list or Git scanning', async () => { mockCalls() await main( [ 'reference', 'find', 'STA-1234', '--worktree', 'current', '--include-archived', '--limit', '10' ], '/tmp/api/subdir' ) expect(callMock).toHaveBeenCalledExactlyOnceWith('reference.find', { query: 'STA-1234', worktree: 'current', cwd: '/tmp/api/subdir', repo: undefined, includeArchived: true, limit: 10 }) }) it.each([ { 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, '--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 () => { mockCalls() await main( [ 'worktree', 'create', '--name', 'fix', '--repo', 'id:repo', '--no-parent', '--reference', PR, '--reference', TASK ], '/tmp/api' ) expect(callMock).toHaveBeenCalledWith( 'worktree.create', expect.objectContaining({ linkedItems: [parsedPR, parsedTask] }) ) }) it.each(['pr', 'issue', 'linear-issue', 'gitlab-issue', 'gitlab-mr'])( 'rejects create --reference mixed with --%s', async (flag) => { mockCalls() await main( [ 'worktree', 'create', '--name', 'fix', '--repo', 'id:repo', '--no-parent', '--reference', PR, `--${flag}`, '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) } ) // Why: hosts built before reference.* still advertise the delta capability. it.each([ [['reference', 'list', '--worktree', 'name:api']], [['reference', 'find', '--query', 'STA-1234']], [['reference', 'add', TASK, '--worktree', 'name:api']] ])('names the version gap when the host lacks reference RPCs: %j', async (argv) => { mockCalls() const { RuntimeClientError } = await import('./runtime/types.js') const base = callMock.getMockImplementation() callMock.mockImplementation(async (method: string, ...rest: unknown[]) => { if (method.startsWith('reference.')) { throw new RuntimeClientError('method_not_found', `Unknown method: ${method}`) } return base?.(method, ...rest) }) 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) }) it('validates create flags locally before probing reference capability', async () => { mockCalls() await main( ['worktree', 'create', '--repo', 'id:repo', '--no-parent', '--reference', PR], '/tmp/api' ) expect(callMock).not.toHaveBeenCalledWith('status.get', expect.anything()) expect(callMock).not.toHaveBeenCalledWith('status.get') expect(console.error).toHaveBeenCalledWith(expect.stringContaining('--name')) }) })