mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
test(worktree): cover retirement runtime contracts
This commit is contained in:
@@ -25,8 +25,7 @@ export function useRetiredWorktreeNames(
|
||||
}
|
||||
let cancelled = false
|
||||
void client
|
||||
// limit 1 because only the retirement map is wanted; the rows are incidental.
|
||||
.sendRequest('worktree.list', { repo: `id:${repoId}`, limit: 1 })
|
||||
.sendRequest('worktree.listRetiredNames', { repo: `id:${repoId}` })
|
||||
.then((response) => {
|
||||
if (cancelled) {
|
||||
return
|
||||
|
||||
@@ -158,7 +158,10 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
|
||||
getSettings: vi.fn(),
|
||||
getWorktreeMeta: vi.fn(),
|
||||
setWorktreeMeta: vi.fn(),
|
||||
removeWorktreeMeta: vi.fn()
|
||||
removeWorktreeMeta: vi.fn(),
|
||||
addRetiredWorktreeName: vi.fn(),
|
||||
getRetiredWorktreeNames: vi.fn(),
|
||||
mergeRetiredWorktreeNames: vi.fn()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -200,6 +203,9 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
|
||||
store.getWorktreeMeta.mockReset()
|
||||
store.setWorktreeMeta.mockReset()
|
||||
store.removeWorktreeMeta.mockReset()
|
||||
store.addRetiredWorktreeName.mockReset()
|
||||
store.getRetiredWorktreeNames.mockReset()
|
||||
store.mergeRetiredWorktreeNames.mockReset()
|
||||
|
||||
for (const key of Object.keys(handlers)) {
|
||||
delete handlers[key]
|
||||
@@ -236,6 +242,7 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
|
||||
})
|
||||
resolveSetupRunnerShellMock.mockReturnValue(undefined)
|
||||
store.getWorktreeMeta.mockReturnValue(undefined)
|
||||
store.getRetiredWorktreeNames.mockReturnValue([])
|
||||
store.setWorktreeMeta.mockReturnValue({})
|
||||
resolveLocalGitUsernameMock.mockResolvedValue('')
|
||||
getDefaultBaseRefMock.mockReturnValue('origin/main')
|
||||
@@ -302,6 +309,7 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
|
||||
false
|
||||
)
|
||||
expect(resolveLocalGitUsernameMock).not.toHaveBeenCalled()
|
||||
expect(store.addRetiredWorktreeName).toHaveBeenCalledWith('repo-1', 'improve-dashboard')
|
||||
expect(store.setWorktreeMeta).toHaveBeenCalledWith(
|
||||
'repo-1::C:/workspaces/improve-dashboard',
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -316,7 +316,10 @@ describe('registerWorktreeHandlers', () => {
|
||||
removeWorktreeLineage: vi.fn(),
|
||||
getAllWorkspaceLineage: vi.fn(),
|
||||
getFolderWorkspaces: vi.fn(),
|
||||
getProjectGroups: vi.fn()
|
||||
getProjectGroups: vi.fn(),
|
||||
addRetiredWorktreeName: vi.fn(),
|
||||
getRetiredWorktreeNames: vi.fn(),
|
||||
mergeRetiredWorktreeNames: vi.fn()
|
||||
}
|
||||
let runtimeStub: {
|
||||
resolveRemoteTrackingBase: ReturnType<typeof vi.fn>
|
||||
@@ -400,6 +403,9 @@ describe('registerWorktreeHandlers', () => {
|
||||
store.getAllWorkspaceLineage,
|
||||
store.getFolderWorkspaces,
|
||||
store.getProjectGroups,
|
||||
store.addRetiredWorktreeName,
|
||||
store.getRetiredWorktreeNames,
|
||||
store.mergeRetiredWorktreeNames,
|
||||
killAllProcessesForWorktreeMock,
|
||||
clearProviderPtyStateMock,
|
||||
getLocalPtyProviderMock,
|
||||
@@ -448,6 +454,7 @@ describe('registerWorktreeHandlers', () => {
|
||||
})
|
||||
store.getWorktreeMeta.mockReturnValue(undefined)
|
||||
store.getAllWorktreeMeta.mockReturnValue({})
|
||||
store.getRetiredWorktreeNames.mockReturnValue([])
|
||||
store.setWorktreeMeta.mockReturnValue({})
|
||||
store.getProjectHostSetups.mockReturnValue([
|
||||
{
|
||||
|
||||
@@ -1331,6 +1331,9 @@ const store = {
|
||||
addedAt: 1
|
||||
}
|
||||
],
|
||||
addRetiredWorktreeName: () => {},
|
||||
getRetiredWorktreeNames: () => [],
|
||||
mergeRetiredWorktreeNames: () => false,
|
||||
addRepo: () => {},
|
||||
updateRepo: (id: string, updates: Record<string, unknown>) =>
|
||||
({
|
||||
@@ -4048,12 +4051,13 @@ describe('OrcaRuntimeService', () => {
|
||||
})
|
||||
|
||||
it('does not reuse stale in-flight worktree scans after creating a worktree', async () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const addRetiredWorktreeName = vi.fn()
|
||||
const runtime = new OrcaRuntimeService({ ...store, addRetiredWorktreeName })
|
||||
const staleScan = deferred<typeof MOCK_GIT_WORKTREES>()
|
||||
const createdWorktree = {
|
||||
path: '/tmp/workspaces/cache-race',
|
||||
path: '/tmp/workspaces/nautilus',
|
||||
head: 'def',
|
||||
branch: 'cache-race',
|
||||
branch: 'nautilus',
|
||||
isBare: false,
|
||||
isMainWorktree: false
|
||||
}
|
||||
@@ -4067,7 +4071,7 @@ describe('OrcaRuntimeService', () => {
|
||||
const staleLookup = runtime.showManagedWorktree(TEST_WORKTREE_ID)
|
||||
const result = await runtime.createManagedWorktree({
|
||||
repoSelector: 'id:repo-1',
|
||||
name: 'cache-race'
|
||||
name: 'nautilus'
|
||||
})
|
||||
const freshLookup = runtime.showManagedWorktree(result.worktree.id)
|
||||
|
||||
@@ -4083,6 +4087,7 @@ describe('OrcaRuntimeService', () => {
|
||||
worktrees: expect.arrayContaining([expect.objectContaining({ path: createdWorktree.path })])
|
||||
}
|
||||
)
|
||||
expect(addRetiredWorktreeName).toHaveBeenCalledWith(TEST_REPO_ID, 'nautilus')
|
||||
})
|
||||
|
||||
it('creates additional workspace metadata for folder-mode repos through runtime create', async () => {
|
||||
|
||||
@@ -21091,6 +21091,22 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
}
|
||||
|
||||
async listRetiredWorktreeNames(repoSelector: string): Promise<Record<string, string[]>> {
|
||||
const store = this.store
|
||||
if (!store?.getRetiredWorktreeNames) {
|
||||
return {}
|
||||
}
|
||||
const repo = await this.resolveRepoSelector(repoSelector)
|
||||
if (store.mergeRetiredWorktreeNames) {
|
||||
try {
|
||||
await ensureRetiredWorktreeNamesBackfilled(store, repo, store.getSettings())
|
||||
} catch (error) {
|
||||
console.warn(`[runtime] retirement backfill failed for repo ${repo.id}:`, error)
|
||||
}
|
||||
}
|
||||
return this.collectRetiredNamesForRepos(new Set([repo.id]))
|
||||
}
|
||||
|
||||
/** Why: scoped to the repos in the page plus any explicitly requested one, so the payload cannot
|
||||
* grow with the number of repos on the host; a client only suggests names for repos it sees. */
|
||||
private collectRetiredNamesForRepos(repoIds: Set<string>): Record<string, string[]> {
|
||||
|
||||
@@ -25,6 +25,23 @@ const passthroughDedupe = <T>(_repo: string, _id: string | undefined, run: () =>
|
||||
run()
|
||||
|
||||
describe('worktree RPC methods', () => {
|
||||
it('returns retired names without exposing the full worktree listing', async () => {
|
||||
const runtime = {
|
||||
getRuntimeId: () => 'test-runtime',
|
||||
listRetiredWorktreeNames: vi.fn().mockResolvedValue({ 'repo-1': ['nautilus'] })
|
||||
} as unknown as OrcaRuntimeService
|
||||
|
||||
const response = await new RpcDispatcher({ runtime, methods: WORKTREE_METHODS }).dispatch(
|
||||
makeRequest('worktree.listRetiredNames', { repo: 'id:repo-1' })
|
||||
)
|
||||
|
||||
expect(runtime.listRetiredWorktreeNames).toHaveBeenCalledWith('id:repo-1')
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { retiredNamesByRepo: { 'repo-1': ['nautilus'] } }
|
||||
})
|
||||
})
|
||||
|
||||
it('routes mobile session-only activation without notifying desktop clients', async () => {
|
||||
const runtime = {
|
||||
getRuntimeId: () => 'test-runtime',
|
||||
|
||||
@@ -43,6 +43,13 @@ export const WORKTREE_METHODS: RpcMethod[] = [
|
||||
params: WorktreeListParams,
|
||||
handler: async (params, { runtime }) => runtime.listManagedWorktrees(params.repo, params.limit)
|
||||
}),
|
||||
defineMethod({
|
||||
name: 'worktree.listRetiredNames',
|
||||
params: WorktreeDetectedListParams,
|
||||
handler: async (params, { runtime }) => ({
|
||||
retiredNamesByRepo: await runtime.listRetiredWorktreeNames(params.repo)
|
||||
})
|
||||
}),
|
||||
defineMethod({
|
||||
name: 'worktree.detectedList',
|
||||
params: WorktreeDetectedListParams,
|
||||
@@ -289,18 +296,11 @@ export const WORKTREE_METHODS: RpcMethod[] = [
|
||||
defineMethod({
|
||||
name: 'worktree.forceDeleteBranch',
|
||||
params: WorktreeForceDeleteBranch,
|
||||
handler: async (params, { runtime }) =>
|
||||
params.hostId
|
||||
? runtime.forceDeletePreservedBranch(
|
||||
params.worktree,
|
||||
params.branchName,
|
||||
params.expectedHead,
|
||||
params.hostId
|
||||
)
|
||||
: runtime.forceDeletePreservedBranch(
|
||||
params.worktree,
|
||||
params.branchName,
|
||||
params.expectedHead
|
||||
)
|
||||
handler: async (params, { runtime }) => {
|
||||
const deletionArgs = [params.worktree, params.branchName, params.expectedHead] as const
|
||||
return params.hostId
|
||||
? runtime.forceDeletePreservedBranch(...deletionArgs, params.hostId)
|
||||
: runtime.forceDeletePreservedBranch(...deletionArgs)
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
@@ -429,6 +429,7 @@ const MOBILE_RPC_METHOD_ALLOWLIST = new Set([
|
||||
'worktree.activate',
|
||||
'worktree.create',
|
||||
'worktree.forceDeleteBranch',
|
||||
'worktree.listRetiredNames',
|
||||
'worktree.prefetchCreateBase',
|
||||
'worktree.ps',
|
||||
'worktree.show',
|
||||
|
||||
@@ -137,16 +137,23 @@ export function getRetiredWorktreeNamesByRepo(
|
||||
if (isFolderRepo(candidate)) {
|
||||
continue
|
||||
}
|
||||
const retiredNames = getRetiredNames(candidate.id)
|
||||
if (retiredNames.length === 0) {
|
||||
continue
|
||||
}
|
||||
const collisionKey = getRetirementCollisionKey(candidate, settings)
|
||||
let names = namesByCollisionKey.get(collisionKey)
|
||||
if (!names) {
|
||||
names = new Set()
|
||||
namesByCollisionKey.set(collisionKey, names)
|
||||
}
|
||||
for (const name of getRetiredNames(candidate.id)) {
|
||||
for (const name of retiredNames) {
|
||||
names.add(name)
|
||||
}
|
||||
}
|
||||
if (namesByCollisionKey.size === 0) {
|
||||
return {}
|
||||
}
|
||||
const requestedRepoIds = new Set(repoIds)
|
||||
const byRepo: Record<string, string[]> = {}
|
||||
for (const repo of repos) {
|
||||
|
||||
Reference in New Issue
Block a user