mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* refactor(providers): delete the SSH git and filesystem providers and relay-only wiring * refactor(ssh): drop relay-protocol exports only the deleted stream readers used --------- Co-authored-by: m4air <m4air@Mac.localdomain>
243 lines
7.6 KiB
TypeScript
243 lines
7.6 KiB
TypeScript
import path from 'node:path'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
handlers,
|
|
store,
|
|
REPO_PATH,
|
|
WORKTREE_FEATURE_PATH,
|
|
getStatusMock,
|
|
getBranchCompareMock,
|
|
getBranchDiffMock,
|
|
listWorktreesMock,
|
|
resetFilesystemIpcMocks
|
|
} from './filesystem-test-harness'
|
|
|
|
vi.mock('electron', async () => (await import('./filesystem-test-harness')).electronMock)
|
|
vi.mock('fs/promises', async () => (await import('./filesystem-test-harness')).fsPromisesMock)
|
|
vi.mock(
|
|
'../wsl-unc-delete',
|
|
async () => (await import('./filesystem-test-harness')).wslUncDeleteMock
|
|
)
|
|
vi.mock(
|
|
'../crash-reporting/crash-breadcrumb-store',
|
|
async () => (await import('./filesystem-test-harness')).crashBreadcrumbMock
|
|
)
|
|
vi.mock(
|
|
'../git/status',
|
|
async () => (await import('./filesystem-test-harness')).gitStatusModuleMock
|
|
)
|
|
vi.mock(
|
|
'../git/check-ignored-paths',
|
|
async () => (await import('./filesystem-test-harness')).gitIgnoredPathsMock
|
|
)
|
|
vi.mock('../git/worktree', async () => (await import('./filesystem-test-harness')).gitWorktreeMock)
|
|
vi.mock(
|
|
'../providers/ssh-filesystem-dispatch',
|
|
async () => (await import('./filesystem-test-harness')).sshFilesystemDispatchMock
|
|
)
|
|
vi.mock(
|
|
'../text-generation/commit-message-text-generation',
|
|
async () => (await import('./filesystem-test-harness')).textGenerationModuleMock
|
|
)
|
|
vi.mock(
|
|
'../text-generation/pull-request-context',
|
|
async () => (await import('./filesystem-test-harness')).pullRequestContextMock
|
|
)
|
|
vi.mock(
|
|
'../source-control/pull-request-template',
|
|
async () => (await import('./filesystem-test-harness')).pullRequestTemplateMock
|
|
)
|
|
vi.mock(
|
|
'../source-control/pull-request-linked-issue',
|
|
async () => (await import('./filesystem-test-harness')).pullRequestLinkedIssueMock
|
|
)
|
|
|
|
import { registerFilesystemHandlers } from './filesystem'
|
|
import { invalidateAuthorizedRootsCache } from './registered-worktree-roots-cache'
|
|
|
|
describe('registerFilesystemHandlers', () => {
|
|
beforeEach(() => {
|
|
resetFilesystemIpcMocks()
|
|
// Reset module-level auth cache so each test starts with a fresh dirty
|
|
// flag — prevents stale worktree data from a prior test's cache rebuild.
|
|
invalidateAuthorizedRootsCache()
|
|
})
|
|
|
|
it('routes branch compare queries through the git compare helper', async () => {
|
|
getBranchCompareMock.mockResolvedValue({
|
|
summary: {
|
|
baseRef: 'origin/main',
|
|
baseOid: 'base-oid',
|
|
compareRef: 'main',
|
|
headOid: 'head-oid',
|
|
mergeBase: 'merge-base-oid',
|
|
changedFiles: 1,
|
|
status: 'ready'
|
|
},
|
|
entries: [{ path: 'src/file.ts', status: 'modified' }]
|
|
})
|
|
|
|
registerFilesystemHandlers(store as never)
|
|
|
|
await handlers.get('git:branchCompare')!(null, {
|
|
worktreePath: WORKTREE_FEATURE_PATH,
|
|
baseRef: 'origin/main',
|
|
admissionTier: 'background'
|
|
})
|
|
|
|
expect(getBranchCompareMock).toHaveBeenCalledWith(WORKTREE_FEATURE_PATH, 'origin/main', {
|
|
admissionTier: 'background'
|
|
})
|
|
})
|
|
|
|
it('allows git operations on worktrees outside repo/workspace roots', async () => {
|
|
// Linked worktrees can live anywhere on disk (e.g. ~/.codex/worktrees/).
|
|
// As long as the path matches a worktree reported by `git worktree list`
|
|
// for a registered repo, it should be allowed — the security boundary is
|
|
// worktree registration, not directory containment.
|
|
const externalWorktreePath = path.resolve('/external/worktrees/feature')
|
|
listWorktreesMock.mockResolvedValue([
|
|
{
|
|
path: REPO_PATH,
|
|
head: 'abc',
|
|
branch: 'refs/heads/main',
|
|
isBare: false,
|
|
isMainWorktree: true
|
|
},
|
|
{
|
|
path: externalWorktreePath,
|
|
head: 'def',
|
|
branch: 'refs/heads/feature',
|
|
isBare: false,
|
|
isMainWorktree: false
|
|
}
|
|
])
|
|
|
|
getBranchCompareMock.mockResolvedValue({
|
|
summary: {
|
|
baseRef: 'origin/main',
|
|
baseOid: 'base-oid',
|
|
compareRef: 'feature',
|
|
headOid: 'head-oid',
|
|
mergeBase: 'merge-base-oid',
|
|
changedFiles: 0,
|
|
status: 'ready'
|
|
},
|
|
entries: []
|
|
})
|
|
|
|
registerFilesystemHandlers(store as never)
|
|
|
|
await handlers.get('git:branchCompare')!(null, {
|
|
worktreePath: externalWorktreePath,
|
|
baseRef: 'origin/main'
|
|
})
|
|
|
|
expect(getBranchCompareMock).toHaveBeenCalledWith(externalWorktreePath, 'origin/main', {})
|
|
})
|
|
|
|
it('rejects branchCompare for a worktree added after cache was built, then succeeds after invalidation', async () => {
|
|
// Reproduces the bug where CLI-created worktrees fail with
|
|
// "Access denied: unknown repository or worktree path" because the
|
|
// filesystem-auth cache was not invalidated after creation.
|
|
const cliWorktreePath = path.resolve('/external/cli-created-worktree')
|
|
|
|
// Step 1: register handlers and trigger initial cache build with only
|
|
// the original worktree in the listing.
|
|
registerFilesystemHandlers(store as never)
|
|
|
|
// Warm the cache by calling a git operation on the existing worktree.
|
|
getStatusMock.mockResolvedValue({ entries: [] })
|
|
await handlers.get('git:status')!(null, { worktreePath: WORKTREE_FEATURE_PATH })
|
|
|
|
// Step 2: simulate the CLI creating a new worktree — git now lists it,
|
|
// but the auth cache is stale.
|
|
listWorktreesMock.mockResolvedValue([
|
|
{
|
|
path: WORKTREE_FEATURE_PATH,
|
|
head: 'abc',
|
|
branch: '',
|
|
isBare: false,
|
|
isMainWorktree: false
|
|
},
|
|
{
|
|
path: cliWorktreePath,
|
|
head: 'def',
|
|
branch: 'refs/heads/cli-feature',
|
|
isBare: false,
|
|
isMainWorktree: false
|
|
}
|
|
])
|
|
|
|
// Step 3: branchCompare on the new worktree should fail — this is the
|
|
// exact error the user reported.
|
|
await expect(
|
|
handlers.get('git:branchCompare')!(null, {
|
|
worktreePath: cliWorktreePath,
|
|
baseRef: 'origin/main'
|
|
})
|
|
).rejects.toThrow('Access denied: unknown repository or worktree path')
|
|
|
|
// Step 4: invalidate the cache (what our fix does after CLI create).
|
|
invalidateAuthorizedRootsCache()
|
|
|
|
// Step 5: the same branchCompare should now succeed.
|
|
getBranchCompareMock.mockResolvedValue({
|
|
summary: {
|
|
baseRef: 'origin/main',
|
|
baseOid: 'base-oid',
|
|
compareRef: 'cli-feature',
|
|
headOid: 'head-oid',
|
|
mergeBase: 'merge-base-oid',
|
|
changedFiles: 0,
|
|
status: 'ready'
|
|
},
|
|
entries: []
|
|
})
|
|
|
|
await handlers.get('git:branchCompare')!(null, {
|
|
worktreePath: cliWorktreePath,
|
|
baseRef: 'origin/main'
|
|
})
|
|
|
|
expect(getBranchCompareMock).toHaveBeenCalledWith(cliWorktreePath, 'origin/main', {})
|
|
})
|
|
|
|
it('routes branch diff queries through the pinned branch diff helper', async () => {
|
|
getBranchDiffMock.mockResolvedValue({
|
|
kind: 'text',
|
|
originalContent: 'left',
|
|
modifiedContent: 'right',
|
|
originalIsBinary: false,
|
|
modifiedIsBinary: false
|
|
})
|
|
|
|
registerFilesystemHandlers(store as never)
|
|
|
|
await handlers.get('git:branchDiff')!(null, {
|
|
worktreePath: WORKTREE_FEATURE_PATH,
|
|
compare: {
|
|
baseRef: 'origin/main',
|
|
baseOid: 'base-oid',
|
|
headOid: 'head-oid',
|
|
mergeBase: 'merge-base-oid'
|
|
},
|
|
filePath: 'src/file.ts',
|
|
oldPath: 'src/old-file.ts'
|
|
})
|
|
|
|
// Why: validateGitRelativeFilePath uses path.relative() which produces
|
|
// platform-specific separators (backslashes on Windows).
|
|
expect(getBranchDiffMock).toHaveBeenCalledWith(
|
|
WORKTREE_FEATURE_PATH,
|
|
{
|
|
headOid: 'head-oid',
|
|
mergeBase: 'merge-base-oid',
|
|
filePath: path.join('src', 'file.ts'),
|
|
oldPath: path.join('src', 'old-file.ts')
|
|
},
|
|
{ admissionTier: 'interactive' }
|
|
)
|
|
})
|
|
})
|