Files
orca/src/main/ipc/repos-remote.test.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

735 lines
25 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { EventEmitter } from 'node:events'
import type * as GitRunner from '../git/runner'
import type * as RepoModule from '../git/repo'
const { reposMocks, moduleMocks } = await vi.hoisted(async () => {
const moduleMocks = await import('./repos-remote-test-harness')
return { reposMocks: moduleMocks.createReposIpcMocks(), moduleMocks }
})
vi.mock('electron', () => moduleMocks.electronModuleMock(reposMocks))
vi.mock('../git/repo', async (importOriginal) =>
moduleMocks.gitRepoModuleMock(await importOriginal<typeof RepoModule>())
)
vi.mock('../git/runner', async (importOriginal) =>
moduleMocks.gitRunnerModuleMock(reposMocks, await importOriginal<typeof GitRunner>())
)
vi.mock('../git/worktree', () => moduleMocks.gitWorktreeModuleMock(reposMocks))
vi.mock('./registered-worktree-roots-cache', () =>
moduleMocks.registeredWorktreeRootsCacheModuleMock(reposMocks)
)
vi.mock('../worktree-root-preparation', () =>
moduleMocks.worktreeRootPreparationModuleMock(reposMocks)
)
vi.mock('../providers/ssh-git-dispatch', () => moduleMocks.sshGitDispatchModuleMock(reposMocks))
vi.mock('../providers/ssh-filesystem-dispatch', () =>
moduleMocks.sshFilesystemDispatchModuleMock(reposMocks)
)
vi.mock('./ssh', () => moduleMocks.sshModuleMock(reposMocks))
vi.mock('../ssh/ssh-target-registry', () => moduleMocks.sshModuleMock(reposMocks))
import { registerRepoHandlers } from './repos'
import { clearGitCapabilityStateForTests } from '../git/git-capability-state'
import { resetSshProviderAuthorities } from '../ssh/ssh-provider-authority'
import { DEFAULT_REPO_BADGE_COLOR } from '../../shared/constants'
import { clearSubmodulePathsCacheForTests } from '../git/status'
import { createRepoHandlerHarness, waitForAssertion } from './repos-remote-test-harness'
const {
handleMock,
mockStore,
mockGitProvider,
mockFilesystemProvider,
mockMultiplexer,
gitExecFileAsyncMock,
gitSpawnMock,
prepareLocalWorktreeRootForRepoMock
} = reposMocks
beforeEach(() => {
clearGitCapabilityStateForTests()
resetSshProviderAuthorities()
})
describe('repos:addRemote', () => {
const { handlers, mockWindow, captureHandlers } = createRepoHandlerHarness()
beforeEach(() => {
captureHandlers(handleMock)
mockStore.getRepos.mockReset().mockReturnValue([])
mockStore.addRepo.mockReset()
mockStore.removeProject.mockReset()
mockStore.getSshTarget.mockReset()
mockStore.updateRepo.mockReset()
mockGitProvider.isGitRepoAsync.mockReset()
mockGitProvider.isGitRepoAsync.mockResolvedValue({ isRepo: true, rootPath: null })
mockGitProvider.exec.mockReset()
mockGitProvider.exec.mockResolvedValue({ stdout: '', stderr: '' })
mockGitProvider.clone.mockReset()
mockGitProvider.clone.mockResolvedValue({ stdout: '', stderr: '' })
mockGitProvider.getHostPlatform.mockReset()
mockGitProvider.getHostPlatform.mockReturnValue({
relayPlatform: 'linux-x64',
os: 'linux',
arch: 'x64',
pathFlavor: 'posix',
commandDialect: 'posix',
pathSeparator: '/',
pathDelimiter: ':'
})
mockFilesystemProvider.stat.mockReset()
mockFilesystemProvider.stat.mockRejectedValue(new Error('not found'))
mockFilesystemProvider.createDirNoClobber.mockReset()
mockFilesystemProvider.createDirNoClobber.mockResolvedValue(undefined)
mockFilesystemProvider.deletePath.mockReset()
mockFilesystemProvider.deletePath.mockResolvedValue(undefined)
mockMultiplexer.request.mockReset()
mockMultiplexer.notify.mockReset()
gitSpawnMock.mockReset()
gitExecFileAsyncMock.mockReset().mockResolvedValue({ stdout: '', stderr: '' })
clearSubmodulePathsCacheForTests()
prepareLocalWorktreeRootForRepoMock.mockReset().mockResolvedValue(undefined)
gitSpawnMock.mockImplementation(() => {
const proc = new EventEmitter() as EventEmitter & { stderr: EventEmitter }
proc.stderr = new EventEmitter()
setImmediate(() => proc.emit('close', 0, null))
return proc
})
mockWindow.webContents.send.mockReset()
registerRepoHandlers(mockWindow as never, mockStore as never)
})
it('registers the repos:addRemote handler', () => {
expect(handlers.has('repos:addRemote')).toBe(true)
})
it('registers the repos:cloneRemote handler', () => {
expect(handlers.has('repos:cloneRemote')).toBe(true)
})
it('registers the repos:createRemote handler', () => {
expect(handlers.has('repos:createRemote')).toBe(true)
})
it('creates a remote repo with connectionId', async () => {
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/user/project',
connectionId: 'conn-1',
kind: 'git',
displayName: 'project',
badgeColor: DEFAULT_REPO_BADGE_COLOR,
projectHostSetupMethod: 'imported-existing-folder'
})
)
expect(result).toHaveProperty('repo.id')
expect(result).toHaveProperty('repo.connectionId', 'conn-1')
expect(result).not.toHaveProperty('repo.externalWorktreeVisibility')
})
it('uses custom displayName when provided', async () => {
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project',
displayName: 'My Server Repo'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
displayName: 'My Server Repo',
path: '/home/user/project'
})
)
expect(result).toHaveProperty('repo.displayName', 'My Server Repo')
})
it('clones a repo on an SSH target and registers the cloned path', async () => {
const result = await handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
expect(mockFilesystemProvider.createDir).toHaveBeenCalledWith('/home/user')
expect(mockGitProvider.clone).toHaveBeenCalledWith(
['clone', '--progress', '--', 'https://github.com/stablyai/orca.git', 'orca'],
'/home/user',
expect.objectContaining({
signal: expect.any(AbortSignal),
timeoutMs: 10 * 60_000,
onProgress: expect.any(Function)
})
)
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/user/orca',
connectionId: 'conn-1',
kind: 'git',
displayName: 'orca',
badgeColor: DEFAULT_REPO_BADGE_COLOR
})
)
expect(mockMultiplexer.notify).toHaveBeenCalledWith('session.registerRoot', {
rootPath: '/home/user/orca'
})
expect(result).toHaveProperty('path', '/home/user/orca')
expect(result).toHaveProperty('connectionId', 'conn-1')
})
it('forwards SSH clone progress through the existing clone progress event', async () => {
mockGitProvider.clone.mockImplementationOnce(
async (
_args: string[],
_cwd: string,
options?: { onProgress?: (progress: { phase: string; percent: number }) => void }
) => {
options?.onProgress?.({ phase: 'Receiving objects', percent: 42 })
return { stdout: '', stderr: '' }
}
)
await handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
expect(mockWindow.webContents.send).toHaveBeenCalledWith('repos:clone-progress', {
phase: 'Receiving objects',
percent: 42
})
})
it('returns an existing SSH repo instead of cloning the same target again', async () => {
const existing = {
id: 'existing-id',
path: '/home/user/orca',
connectionId: 'conn-1',
displayName: 'orca',
badgeColor: '#fff',
addedAt: 1000,
kind: 'git'
}
mockStore.getRepos.mockReturnValue([existing])
const result = await handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
expect(result).toBe(existing)
expect(mockGitProvider.clone).not.toHaveBeenCalled()
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('upgrades an existing SSH folder repo after cloning into that path', async () => {
const existing = {
id: 'existing-folder',
path: '/home/user/orca',
connectionId: 'conn-1',
displayName: 'orca',
badgeColor: '#fff',
addedAt: 1000,
kind: 'folder'
}
const updated = { ...existing, kind: 'git' }
mockStore.getRepos.mockReturnValue([existing])
mockStore.updateRepo.mockReturnValue(updated)
const result = await handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
expect(mockGitProvider.clone).toHaveBeenCalledWith(
['clone', '--progress', '--', 'https://github.com/stablyai/orca.git', 'orca'],
'/home/user',
expect.objectContaining({
signal: expect.any(AbortSignal),
timeoutMs: 10 * 60_000,
onProgress: expect.any(Function)
})
)
expect(mockStore.updateRepo).toHaveBeenCalledWith('existing-folder', {
kind: 'git',
projectHostSetupMethod: 'cloned'
})
expect(mockStore.addRepo).not.toHaveBeenCalled()
expect(result).toBe(updated)
})
it('does not delete a fresh SSH clone target after git clone fails', async () => {
mockGitProvider.clone.mockRejectedValueOnce(new Error('repository not found'))
mockFilesystemProvider.stat.mockRejectedValueOnce(new Error('not found'))
await expect(
handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
).rejects.toThrow('repository not found')
expect(mockFilesystemProvider.deletePath).not.toHaveBeenCalled()
})
it('rejects concurrent SSH clones to the same destination', async () => {
let releaseClone!: () => void
mockGitProvider.clone.mockImplementationOnce(
async () =>
new Promise<{ stdout: string; stderr: string }>((resolve) => {
releaseClone = () => resolve({ stdout: '', stderr: '' })
})
)
const firstClone = handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
await waitForAssertion(() => expect(mockGitProvider.clone).toHaveBeenCalledTimes(1))
await expect(
handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
).rejects.toThrow('A clone is already in progress for this SSH destination')
releaseClone()
await firstClone
})
it('resolves SSH clone destinations under home before validating the path', async () => {
mockMultiplexer.request.mockResolvedValueOnce({ resolvedPath: '/home/ubuntu/projects' })
await handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '~/projects'
})
expect(mockMultiplexer.request).toHaveBeenCalledWith('session.resolveHome', {
path: '~/projects'
})
expect(mockGitProvider.clone).toHaveBeenCalledWith(
['clone', '--progress', '--', 'https://github.com/stablyai/orca.git', 'orca'],
'/home/ubuntu/projects',
expect.any(Object)
)
})
it('does not clean up a pre-existing SSH clone target after git clone fails', async () => {
mockGitProvider.clone.mockRejectedValueOnce(new Error('destination already exists'))
mockFilesystemProvider.stat.mockResolvedValueOnce({ type: 'directory', size: 0, mtime: 0 })
await expect(
handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
).rejects.toThrow('destination already exists')
expect(mockFilesystemProvider.deletePath).not.toHaveBeenCalled()
})
it('aborts an active SSH clone and reports the abort without deleting pre-existing targets', async () => {
mockFilesystemProvider.stat.mockResolvedValueOnce({ type: 'directory', size: 0, mtime: 0 })
mockGitProvider.clone.mockImplementationOnce(
async (_args: string[], _cwd: string, options?: { signal?: AbortSignal }) =>
new Promise<{ stdout: string; stderr: string }>((_resolve, reject) => {
options?.signal?.addEventListener('abort', () => reject(new Error('aborted by test')))
})
)
const clonePromise = handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/home/user'
})
await waitForAssertion(() => expect(mockGitProvider.clone).toHaveBeenCalledTimes(1))
await handlers.get('repos:cloneAbort')!(null, undefined)
await expect(clonePromise).rejects.toThrow('Clone aborted')
const options = mockGitProvider.clone.mock.calls[0][2] as { signal: AbortSignal }
expect(options.signal.aborted).toBe(true)
expect(mockFilesystemProvider.deletePath).not.toHaveBeenCalled()
})
it('rejects SSH clone destinations that are not absolute host paths', async () => {
await expect(
handlers.get('repos:cloneRemote')!(null, {
connectionId: 'conn-1',
url: 'https://github.com/stablyai/orca.git',
destination: 'relative/path'
})
).rejects.toThrow('Clone destination must be an absolute path on the SSH host')
expect(mockGitProvider.clone).not.toHaveBeenCalled()
})
it('creates a new git project on an SSH target', async () => {
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '/home/user',
name: 'created',
kind: 'git'
})
expect(mockFilesystemProvider.createDirNoClobber).toHaveBeenCalledWith('/home/user/created')
expect(mockGitProvider.exec).toHaveBeenCalledWith(['init'], '/home/user/created')
expect(mockGitProvider.exec).toHaveBeenCalledWith(
['commit', '--allow-empty', '-m', 'Initial commit'],
'/home/user/created'
)
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/user/created',
connectionId: 'conn-1',
kind: 'git',
displayName: 'created'
})
)
expect(result).toHaveProperty('repo.path', '/home/user/created')
expect(result).toHaveProperty('repo.connectionId', 'conn-1')
})
it('resolves SSH create parents under home before validating the path', async () => {
mockMultiplexer.request.mockResolvedValueOnce({ resolvedPath: '/home/ubuntu/projects' })
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '~/projects',
name: 'created',
kind: 'folder'
})
expect(mockMultiplexer.request).toHaveBeenCalledWith('session.resolveHome', {
path: '~/projects'
})
expect(mockFilesystemProvider.createDirNoClobber).toHaveBeenCalledWith(
'/home/ubuntu/projects/created'
)
expect(result).toHaveProperty('repo.path', '/home/ubuntu/projects/created')
})
it('creates a new folder project on an SSH target without git init', async () => {
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '/home/user',
name: 'notes',
kind: 'folder'
})
expect(mockFilesystemProvider.createDirNoClobber).toHaveBeenCalledWith('/home/user/notes')
expect(mockGitProvider.exec).not.toHaveBeenCalled()
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/user/notes',
connectionId: 'conn-1',
kind: 'folder',
displayName: 'notes'
})
)
expect(result).toHaveProperty('repo.kind', 'folder')
})
it('rejects SSH create parent paths that are not absolute host paths', async () => {
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: 'relative/path',
name: 'created',
kind: 'git'
})
expect(result).toEqual({ error: 'Parent directory must be an absolute path on the SSH host' })
expect(mockFilesystemProvider.createDirNoClobber).not.toHaveBeenCalled()
expect(mockGitProvider.exec).not.toHaveBeenCalled()
})
it('rejects non-empty existing SSH create targets', async () => {
mockFilesystemProvider.stat.mockResolvedValueOnce({ type: 'directory', size: 0, mtime: 0 })
mockFilesystemProvider.readDir.mockResolvedValueOnce([
{ name: 'package.json', isDirectory: false, isSymlink: false }
])
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '/home/user',
name: 'created',
kind: 'git'
})
expect(result).toEqual({
error: '"created" already exists at this location and is not empty.'
})
expect(mockFilesystemProvider.createDirNoClobber).not.toHaveBeenCalled()
expect(mockGitProvider.exec).not.toHaveBeenCalled()
})
it('removes a newly created SSH directory when git init fails', async () => {
mockGitProvider.exec.mockRejectedValueOnce(new Error('git init failed'))
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '/home/user',
name: 'created',
kind: 'git'
})
expect(result).toEqual({ error: 'Failed to initialize git repository: git init failed' })
expect(mockFilesystemProvider.deletePath).toHaveBeenCalledWith('/home/user/created', true)
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('preserves an existing empty SSH directory and removes only .git when commit fails', async () => {
mockFilesystemProvider.stat.mockResolvedValueOnce({ type: 'directory', size: 0, mtime: 0 })
mockFilesystemProvider.readDir.mockResolvedValueOnce([])
mockGitProvider.exec
.mockResolvedValueOnce({ stdout: '', stderr: '' })
.mockRejectedValueOnce(new Error('Please tell me who you are'))
const result = await handlers.get('repos:createRemote')!(null, {
connectionId: 'conn-1',
parentPath: '/home/user',
name: 'created',
kind: 'git'
})
expect(result).toEqual({
error:
'Git author identity is not configured on the SSH host. Run `git config --global user.name "Your Name"` and `git config --global user.email "you@example.com"` on that host, then try again.'
})
expect(mockFilesystemProvider.deletePath).toHaveBeenCalledWith('/home/user/created/.git', true)
expect(mockFilesystemProvider.deletePath).not.toHaveBeenCalledWith('/home/user/created', true)
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('returns existing repo if same connectionId and path already added', async () => {
const existing = {
id: 'existing-id',
path: '/home/user/project',
connectionId: 'conn-1',
displayName: 'project',
badgeColor: '#fff',
addedAt: 1000,
kind: 'git'
}
mockStore.getRepos.mockReturnValue([existing])
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project'
})
expect(result).toEqual({ repo: existing })
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('throws when SSH connection is not found', async () => {
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'unknown-conn',
remotePath: '/home/user/project'
})
expect(result).toEqual({ error: 'SSH connection "unknown-conn" not found or not connected' })
})
it('throws when remote path is not a git repo', async () => {
mockGitProvider.isGitRepoAsync.mockResolvedValueOnce({ isRepo: false, rootPath: null })
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/documents'
})
expect(result).toEqual({ error: 'Not a valid git repository: /home/user/documents' })
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('adds as folder when kind is explicitly set', async () => {
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/documents',
kind: 'folder'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
kind: 'folder',
path: '/home/user/documents',
badgeColor: DEFAULT_REPO_BADGE_COLOR
})
)
expect(result).toHaveProperty('repo.kind', 'folder')
})
it('uses rootPath from git detection when available', async () => {
mockGitProvider.isGitRepoAsync.mockResolvedValueOnce({
isRepo: true,
rootPath: '/home/user/project'
})
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project/src'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
kind: 'git',
path: '/home/user/project'
})
)
expect(result).toHaveProperty('repo.path', '/home/user/project')
})
it('uses the resolved git root basename for the default remote display name', async () => {
mockGitProvider.isGitRepoAsync.mockResolvedValueOnce({
isRepo: true,
rootPath: '/home/user/project'
})
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project/src'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/user/project',
displayName: 'project'
})
)
expect(result).toHaveProperty('repo.displayName', 'project')
})
it('derives default remote display names from Windows path separators', async () => {
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: 'C:\\Users\\alice\\project',
kind: 'folder'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: 'C:\\Users\\alice\\project',
displayName: 'project'
})
)
expect(result).toHaveProperty('repo.displayName', 'project')
})
it('notifies renderer when remote repo is added', async () => {
await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/project'
})
expect(mockWindow.webContents.send).toHaveBeenCalledWith('repos:changed')
})
it('resolves ~ to absolute path via relay and uses SSH target label', async () => {
mockMultiplexer.request.mockResolvedValueOnce({ resolvedPath: '/home/ubuntu' })
mockStore.getSshTarget.mockReturnValueOnce({
id: 'conn-1',
label: 'ubuntu-box',
host: '192.168.1.100',
port: 22,
username: 'user'
})
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '~'
})
expect(mockMultiplexer.request).toHaveBeenCalledWith('session.resolveHome', { path: '~' })
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
displayName: 'ubuntu-box',
path: '/home/ubuntu'
})
)
expect(result).toHaveProperty('repo.displayName', 'ubuntu-box')
expect(result).toHaveProperty('repo.path', '/home/ubuntu')
})
it('resolves ~/subdir to absolute path via relay', async () => {
mockMultiplexer.request.mockResolvedValueOnce({ resolvedPath: '/home/ubuntu/subdir' })
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '~/subdir'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
path: '/home/ubuntu/subdir',
displayName: 'subdir'
})
)
expect(result).toHaveProperty('repo.path', '/home/ubuntu/subdir')
})
it('returns an existing SSH repo when a selected subdirectory resolves to the repo root', async () => {
const existing = {
id: 'existing-id',
path: '/home/user/orca',
connectionId: 'conn-1',
displayName: 'orca',
badgeColor: '#fff',
addedAt: 1000,
kind: 'git'
}
mockStore.getRepos.mockReturnValue([existing])
mockGitProvider.isGitRepoAsync.mockResolvedValueOnce({
isRepo: true,
rootPath: '/home/user/orca'
})
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '/home/user/orca/src'
})
expect(result).toEqual({ repo: existing })
expect(mockStore.addRepo).not.toHaveBeenCalled()
})
it('ignores SSH target label when custom displayName is provided', async () => {
mockMultiplexer.request.mockResolvedValueOnce({ resolvedPath: '/home/ubuntu' })
mockStore.getSshTarget.mockReturnValueOnce({
id: 'conn-1',
label: 'ubuntu-box',
host: '192.168.1.100',
port: 22,
username: 'user'
})
const result = await handlers.get('repos:addRemote')!(null, {
connectionId: 'conn-1',
remotePath: '~',
displayName: 'My Home'
})
expect(mockStore.addRepo).toHaveBeenCalledWith(
expect.objectContaining({
displayName: 'My Home',
path: '/home/ubuntu'
})
)
expect(result).toHaveProperty('repo.displayName', 'My Home')
})
})