Files
orca/mobile/src/tasks/worktree-create-capability.test.ts
T
Brennan Benson e719ef1a57 fix(mobile): survive connection-migration cutovers during worktree create (#9234)
* fix(mobile): survive connection-migration cutovers during worktree create

A worktree.create in flight when the mobile transport migrates (relay/direct
hand-off on shoddy cellular, relay lease rotation, relay recovery) rejects with
"RPC interrupted by connection migration" even though the host completed it —
leaving the Create Workspace modal stuck while the worktree exists on desktop.
A naive retry hits a name collision and spawns a duplicate.

Mirror the existing mobile terminal-create idempotency: worktree.create now
accepts an optional clientMutationId that the host dedupes (in-flight + brief
post-success TTL), and mobile mints one key per candidate name and re-issues
the create on a cutover so the retry reconciles instead of duplicating.

* fix(mobile): gate worktree cutover replay by capability

* fix(mobile): await worktree replay capability
2026-07-17 15:56:43 -07:00

58 lines
1.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client'
import { readNewWorktreeRuntimeCapabilities } from './worktree-create-capability'
function statusClient(outcomes: Array<'cutover' | 'error' | string[]>): RpcClient {
let call = 0
return {
sendRequest: async () => {
const outcome = outcomes[Math.min(call, outcomes.length - 1)]!
call += 1
if (outcome === 'cutover') {
throw new LogicalClientCutoverError()
}
if (outcome === 'error') {
throw new Error('offline')
}
return {
id: '1',
ok: true,
result: { capabilities: outcome },
_meta: { runtimeId: 'r' }
}
}
} as unknown as RpcClient
}
describe('readNewWorktreeRuntimeCapabilities', () => {
it('reads task and idempotent-create support from status.get', async () => {
await expect(
readNewWorktreeRuntimeCapabilities(
statusClient([['mobile.tasks.v1', 'worktree.create-idempotency.v1']])
)
).resolves.toEqual({
tasksSupported: true,
idempotentWorktreeCreateSupported: true
})
})
it('retries the safe status probe after a connection cutover', async () => {
await expect(
readNewWorktreeRuntimeCapabilities(
statusClient(['cutover', ['worktree.create-idempotency.v1']])
)
).resolves.toEqual({
tasksSupported: false,
idempotentWorktreeCreateSupported: true
})
})
it('fails closed when capability detection is unavailable', async () => {
await expect(readNewWorktreeRuntimeCapabilities(statusClient(['error']))).resolves.toEqual({
tasksSupported: false,
idempotentWorktreeCreateSupported: false
})
})
})