test: strengthen relay, persistence, and benchmark checks (#33563)

* test: strengthen relay, persistence, and benchmark checks

* test: prune fixed journal wait allowance

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-25 09:00:13 -07:00
committed by GitHub
co-authored by OrcaWin
parent 6c9c7b0fdb
commit 09873c1442
14 changed files with 259 additions and 135 deletions
-1
View File
@@ -165,7 +165,6 @@
1 test-no-real-time-wait:mobile/src/terminal/terminal-web-document-mount.test.ts
1 test-no-real-time-wait:mobile/src/terminal/terminal-webview-tap-routing.test.ts
1 test-no-real-time-wait:mobile/src/transport/cellular-handshake-stall-real-socket.test.ts
1 test-no-real-time-wait:mobile/src/transport/mobile-relay-pairing-journal-store.test.ts
1 test-no-real-time-wait:mobile/src/transport/mobile-relay-runtime-failover.test.ts
4 test-no-real-time-wait:mobile/src/transport/rpc-client-live-recovery.test.ts
1 test-no-real-time-wait:mobile/src/transport/rpc-operation-barrier.test.ts
@@ -1,6 +1,7 @@
#!/usr/bin/env node
// Compare per-comparison localeCompare options with hoisted Intl.Collator on Source Control refresh; assert identical sorted output before timing.
import { execFileSync } from 'node:child_process'
import { build } from 'esbuild'
import { performance } from 'node:perf_hooks'
import { fileURLToPath } from 'node:url'
@@ -26,26 +27,40 @@ function conflictRank(entry) {
return 2
}
// Pre-fix: resolves a collator per comparison.
// Per-call baseline preserves production's locale and deterministic numeric ties.
function compareBefore(a, b) {
return (
conflictRank(a) - conflictRank(b) || a.path.localeCompare(b.path, undefined, { numeric: true })
conflictRank(a) - conflictRank(b) ||
a.path.localeCompare(b.path, 'en', { numeric: true }) ||
(a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
)
}
// Post-fix: one hoisted collator, mirroring source-control-status-sort.ts.
const collator = new Intl.Collator(undefined, { numeric: true })
function compareAfter(a, b) {
return conflictRank(a) - conflictRank(b) || collator.compare(a.path, b.path)
}
const { outputFiles } = await build({
entryPoints: [
fileURLToPath(
new URL(
'../../src/renderer/src/components/right-sidebar/source-control-status-sort.ts',
import.meta.url
)
)
],
bundle: true,
platform: 'node',
format: 'esm',
write: false
})
const { compareGitStatusEntries: compareAfter } = await import(
`data:text/javascript;base64,${Buffer.from(outputFiles[0].text).toString('base64')}`
)
// Use real byte-sorted Git paths: shuffled input inflates win over nearly sorted production arrays.
const REPO_PATHS = execFileSync('git', ['ls-files'], {
const REPO_PATHS = execFileSync('git', ['ls-files', '-z'], {
cwd: fileURLToPath(new URL('../..', import.meta.url)),
maxBuffer: 256 * 1024 * 1024
})
.toString()
.split('\n')
.split('\0')
.filter(Boolean)
function makeEntries(count) {
@@ -88,7 +103,7 @@ for (const count of [3, 17, 26, 63, 308, 1000]) {
const entries = makeEntries(count)
const before = [...entries].sort(compareBefore).map((entry) => entry.path)
const after = [...entries].sort(compareAfter).map((entry) => entry.path)
if (before.join('\n') !== after.join('\n')) {
if (before.length !== after.length || before.some((path, index) => path !== after[index])) {
throw new Error(`sort order differs at ${count} files`)
}
const beforeMs = measure(compareBefore, entries)
@@ -11,20 +11,7 @@ assert(
sources.every((source) => source.includes('export function buildRemovedSshTargetCleanupPatch'))
)
async function load(source, instrument = false) {
if (instrument) {
const spread = '...nextTabsByWorktree'
assert.equal(source.split(spread).length, 2)
source = source.replace(spread, '...countTabMapCopy(nextTabsByWorktree)')
source += `
export const tabMapCopies = { count: 0, entries: 0 };
function countTabMapCopy(map) {
tabMapCopies.count++;
tabMapCopies.entries += Reflect.ownKeys(map).length;
return map;
}
`
}
async function load(source) {
source += "\nexport { toAppSshPtyId } from '../../../../shared/ssh-pty-id';"
const result = await build({
entryPoints: [entry],
@@ -62,7 +49,7 @@ function emptyState() {
tabsByWorktree: {},
ptyIdsByTabId: {},
lastKnownRelayPtyIdByTabId: {},
pendingCodexPaneRestartIds: {},
pendingPaneAccountRestarts: {},
codexRestartNoticeByPtyId: {},
deferredSshSessionIdsByTabId: {},
pendingReconnectPtyIdByTabId: {},
@@ -72,6 +59,7 @@ function emptyState() {
sshTargetLabels: new Map(),
sshTargetGenerations: new Map(),
portForwardsByConnection: {},
unrestoredPortForwardsByConnection: {},
detectedPortsByConnection: {},
sshCredentialQueue: []
}
@@ -201,7 +189,7 @@ for (let iteration = 0; iteration < 3000; iteration++) {
state.pendingReconnectPtyIdByTabId[id] = toAppSshPtyId(owner, 'reconnect')
}
if (ptyId) {
state.pendingCodexPaneRestartIds[ptyId] = true
state.pendingPaneAccountRestarts[ptyId] = 'codex'
state.codexRestartNoticeByPtyId[ptyId] = {
previousAccountLabel: 'old',
nextAccountLabel: 'new'
@@ -227,35 +215,13 @@ for (let iteration = 0; iteration < 3000; iteration++) {
}
console.log('3,000 frozen-state full-patch / identity differential cases passed')
// Instrument only the copy site for counts; timing arms above remain uninstrumented.
const counted = await Promise.all(sources.map((source) => load(source, true)))
for (const stride of [1, 10, 0]) {
const state = timedState(100, stride, false)
const expectedCopies = stride === 0 ? [0, 0] : [100 / stride, 1]
counted.forEach((module, index) => {
module.tabMapCopies.count = 0
module.tabMapCopies.entries = 0
assert.deepEqual(
module.buildRemovedSshTargetCleanupPatch(state, 'removed'),
arms[index](state, 'removed')
)
assert.deepEqual(module.tabMapCopies, {
count: expectedCopies[index],
entries: expectedCopies[index] * 100
})
})
console.log(
JSON.stringify({ stride, copiedEntries: counted.map((module) => module.tabMapCopies.entries) })
)
}
function sample(arm, state, repeats) {
function sample(arm, state, repeats, expectedChanged) {
const start = performance.now()
let changed = 0
for (let i = 0; i < repeats; i++) {
changed += arm(state, 'removed') !== null ? 1 : 0
}
assert(changed === 0 || changed === repeats)
assert.equal(changed, expectedChanged ? repeats : 0)
return (performance.now() - start) / repeats
}
@@ -281,19 +247,22 @@ for (const [count, stride, catalog, tabsPerWorkspace] of [
[500, 1, true, 4]
]) {
const state = timedState(count, stride, catalog, tabsPerWorkspace)
compare(state, 'removed')
const expectedChanged = compare(state, 'removed')[0] !== null
for (const arm of arms) {
const until = performance.now() + 80
while (performance.now() < until) {
sample(arm, state, 1)
sample(arm, state, 1, expectedChanged)
}
}
const repeats = Math.max(1, Math.min(20000, Math.ceil(40 / sample(arms[0], state, 1))))
const repeats = Math.max(
1,
Math.min(20000, Math.ceil(40 / sample(arms[0], state, 1, expectedChanged)))
)
/** @type {number[][]} */
const samples = [[], []]
for (let pair = 0; pair < 8; pair++) {
for (const index of pair % 2 ? [1, 0] : [0, 1]) {
samples[index].push(sample(arms[index], state, repeats))
samples[index].push(sample(arms[index], state, repeats, expectedChanged))
}
}
const median = samples.map((values) => {
@@ -72,10 +72,6 @@ function dependencies(journal: MobileRelayDirectUpgradeJournal | null = null) {
describe('existing direct pairing relay upgrade', () => {
it('persists pending material before install and publishes only after committed status', async () => {
const deps = dependencies()
let journal: MobileRelayDirectUpgradeJournal | null = null
deps.writeJournal.mockImplementation(async (next) => {
journal = next
})
const client = clientWith([
success({ v: 1, relay }),
success({
@@ -105,13 +101,34 @@ describe('existing direct pairing relay upgrade', () => {
const result = await upgradeDirectMobileRelay({ client, host, dependencies: deps })
expect(journal).not.toBeNull()
expect(deps.writeJournal).toHaveBeenCalledOnce()
const journal = deps.writeJournal.mock.calls[0]?.[0]
if (!journal) {
throw new Error('Upgrade did not persist pending material')
}
expect(client.sendRequest).toHaveBeenNthCalledWith(1, 'pairing.getEndpoints', {
installReqId: journal.reqId
})
expect(client.sendRequest).toHaveBeenNthCalledWith(3, 'pairing.getEndpoints', {
installReqId: journal.reqId
})
expect(deps.writeBundle).toHaveBeenCalledWith({
v: 1,
hostId: host.id,
deviceToken: host.deviceToken,
current: {
token: journal.pendingResumeToken,
hash: journal.pendingResumeTokenHash,
version: 1,
expiresAt: 9_999_999
}
})
expect(deps.writeJournal.mock.invocationCallOrder[0]).toBeLessThan(
vi.mocked(client.sendRequest).mock.invocationCallOrder[0]!
)
expect(client.sendRequest).toHaveBeenNthCalledWith(2, 'pairing.provisionRelay', {
reqId: journal!.reqId,
newResumeTokenHash: journal!.pendingResumeTokenHash
reqId: journal.reqId,
newResumeTokenHash: journal.pendingResumeTokenHash
})
expect(deps.writeBundle).toHaveBeenCalledBefore(deps.setRelayRouting)
expect(deps.setRelayRouting).toHaveBeenCalledWith(host.id, relay)
@@ -73,7 +73,7 @@ describe('pairing invite director resolution', () => {
type: 'relay-moved',
v: 1,
cellUrl: 'https://relay-c2.onorca.dev',
assignmentEpoch: 7,
assignmentEpoch: 8,
targetFromCell: true
})
})
@@ -114,6 +114,15 @@ describe('mobile relay pairing journal store', () => {
expect(asyncStorage.setItem.mock.invocationCallOrder[0]).toBeLessThan(
secureStore.setItemAsync.mock.invocationCallOrder[0]!
)
expect(asyncStorage.setItem).toHaveBeenCalledWith(
'orca:mobile-relay:pairing-journal:v1',
JSON.stringify(journal.metadata)
)
expect(secureStore.setItemAsync).toHaveBeenCalledWith(
'orca.mobile-relay.pairing-journal.v1',
JSON.stringify(journal.secrets),
expect.objectContaining({ keychainAccessible: 'WHEN_UNLOCKED_THIS_DEVICE_ONLY' })
)
expect(metadataRaw).not.toContain(offer.deviceToken)
expect(metadataRaw).not.toContain(offer.relay.inviteToken)
expect(metadataRaw).not.toContain(journal.secrets.pendingResumeToken)
@@ -238,12 +247,17 @@ describe('mobile relay pairing journal store', () => {
const secretReadGate = new Promise<void>((resolve) => {
releaseSecretRead = resolve
})
let enteredSecretRead = () => {}
const secretReadStarted = new Promise<void>((resolve) => {
enteredSecretRead = resolve
})
secureStore.getItemAsync.mockImplementationOnce(async () => {
enteredSecretRead()
await secretReadGate
return secretRaw
})
const loading = loadMobileRelayPairingJournal()
await vi.waitFor(() => expect(secureStore.getItemAsync).toHaveBeenCalled())
await secretReadStarted
const replacement = createMobileRelayPairingJournal({
offer: offer as PairingOffer & { relay: NonNullable<PairingOffer['relay']> },
@@ -379,33 +393,38 @@ describe('mobile relay pairing journal store', () => {
})
it('makes a superseding scan wait for a publication in progress, and refuses a stale claim', async () => {
const created = createMobileRelayPairingJournal({
offer: offer as PairingOffer & { relay: NonNullable<PairingOffer['relay']> },
hostId: 'host-1',
hostName: 'Blue Whale',
randomBytes: (length) => new Uint8Array(length).fill(10)
})
await saveMobileRelayPairingJournal(created)
releaseMobileRelayPairingJournal(created.metadata.journalId)
const release = await claimMobileRelayPairingJournalPublication(created.metadata.journalId)
const replacement = createMobileRelayPairingJournal({
offer: offer as PairingOffer & { relay: NonNullable<PairingOffer['relay']> },
hostId: 'host-2',
hostName: 'Red Panda',
randomBytes: (length) => new Uint8Array(length).fill(12)
})
let saved = false
const save = saveMobileRelayPairingJournal(replacement).then(() => (saved = true))
await new Promise((resolve) => setTimeout(resolve, 10))
expect(saved).toBe(false)
vi.useFakeTimers()
try {
const created = createMobileRelayPairingJournal({
offer: offer as PairingOffer & { relay: NonNullable<PairingOffer['relay']> },
hostId: 'host-1',
hostName: 'Blue Whale',
randomBytes: (length) => new Uint8Array(length).fill(10)
})
await saveMobileRelayPairingJournal(created)
releaseMobileRelayPairingJournal(created.metadata.journalId)
const release = await claimMobileRelayPairingJournalPublication(created.metadata.journalId)
const replacement = createMobileRelayPairingJournal({
offer: offer as PairingOffer & { relay: NonNullable<PairingOffer['relay']> },
hostId: 'host-2',
hostName: 'Red Panda',
randomBytes: (length) => new Uint8Array(length).fill(12)
})
let saved = false
const save = saveMobileRelayPairingJournal(replacement).then(() => (saved = true))
await vi.advanceTimersByTimeAsync(0)
expect(saved).toBe(false)
await clearMobileRelayPairingJournal(created.metadata.journalId)
release()
await save
await expect(loadMobileRelayPairingJournal()).resolves.toEqual(replacement)
await expect(
claimMobileRelayPairingJournalPublication(created.metadata.journalId)
).rejects.toThrow(/stale/)
await clearMobileRelayPairingJournal(created.metadata.journalId)
release()
await save
await expect(loadMobileRelayPairingJournal()).resolves.toEqual(replacement)
await expect(
claimMobileRelayPairingJournalPublication(created.metadata.journalId)
).rejects.toThrow(/stale/)
} finally {
vi.useRealTimers()
}
})
it('self-heals an undecryptable Android secret so the next QR scan can pair', async () => {
@@ -53,7 +53,7 @@ describe('mobile relay resume director', () => {
)
await expect(
resolveMobileRelayEndpoint({ relay, resumeToken: 'A'.repeat(43), fetchImpl: badTarget })
).rejects.toThrow()
).rejects.toThrow(/cellUrl/)
const oversized = vi.fn(
async () =>
@@ -62,6 +62,24 @@ describe('mobile relay resume director', () => {
await expect(
resolveMobileRelayEndpoint({ relay, resumeToken: 'A'.repeat(43), fetchImpl: oversized })
).rejects.toThrow(/too large/)
const oversizedBody =
' '.repeat(16 * 1024) +
JSON.stringify({
v: 1,
cellUrl: 'https://relay-c2.onorca.dev',
assignmentEpoch: 8,
leaseExpiresAt: 1
})
for (const headers of [new Headers(), new Headers({ 'content-length': '1' })]) {
await expect(
resolveMobileRelayEndpoint({
relay,
resumeToken: 'A'.repeat(43),
fetchImpl: async () => new Response(oversizedBody, { headers })
})
).rejects.toThrow(/too large/)
}
})
it('carries the bounded-overload Retry-After off a rejected resolve', async () => {
@@ -263,7 +263,7 @@ describe('relay runtime recovery without direct connectivity', () => {
expect(openRelay).toHaveBeenCalledTimes(2)
expect(openRelay).toHaveBeenLastCalledWith(
relay,
expect.objectContaining({ version: 3 }),
expect.objectContaining({ token: 'token-v3'.padEnd(43, 'A'), version: 3 }),
expect.any(String),
expect.any(Function)
)
@@ -351,7 +351,7 @@ describe('relay runtime recovery without direct connectivity', () => {
expect(deps.openRelay).toHaveBeenCalledOnce()
expect(deps.openRelay).toHaveBeenLastCalledWith(
relay,
expect.objectContaining({ version: 2 }),
expect.objectContaining({ token: 'token-v2'.padEnd(43, 'A'), version: 2 }),
expect.any(String),
expect.any(Function)
)
@@ -380,7 +380,7 @@ describe('relay runtime recovery without direct connectivity', () => {
expect(openRelay).toHaveBeenCalledTimes(2)
expect(openRelay).toHaveBeenLastCalledWith(
relay,
expect.objectContaining({ version: 1 }),
expect.objectContaining({ token: 'token-v1'.padEnd(43, 'A'), version: 1 }),
expect.any(String),
expect.any(Function)
)
@@ -524,6 +524,7 @@ describe('failover with a real direct rpc-client', () => {
await supervisor.start()
await vi.advanceTimersByTimeAsync(3_000)
expect(DeadSocket.constructed).toContain(DIRECT_ENDPOINT)
expect(deps.openRelay).toHaveBeenCalled()
expect(logical.getActivePath()).toBe('relay')
supervisor.stop()
@@ -548,6 +549,7 @@ describe('failover with a real direct rpc-client', () => {
await started
await vi.advanceTimersByTimeAsync(3_000)
expect(DeadSocket.constructed).toContain(DIRECT_ENDPOINT)
expect(deps.openRelay).toHaveBeenCalled()
expect(logical.getActivePath()).toBe('relay')
supervisor.stop()
+69 -17
View File
@@ -90,6 +90,7 @@ describe('pairing keychain', () => {
const rotated = secureStoreMock.setItemAsync.mock.calls.at(-1)!
expect(serviceOf(rotated[2] as Options)).toBe('orca.pairing.v1')
expect(rotated[0]).toBe(TOKEN_KEY)
expect(rotated[1]).toBe('token')
expect(generationRecord).toBe('1')
})
@@ -209,6 +210,7 @@ describe('pairing keychain', () => {
serviceOf(options) === 'orca.pairing.v1' ? 'token' : null
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
})
it('reads through the rotated service once a rotation has been committed', async () => {
@@ -218,6 +220,7 @@ describe('pairing keychain', () => {
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('rotated-token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
})
it('falls back to a retired service so rotation does not orphan a still-readable token', async () => {
@@ -227,6 +230,7 @@ describe('pairing keychain', () => {
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('legacy-token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
expect(secureStoreMock.getItemAsync).toHaveBeenCalledTimes(3)
})
@@ -241,6 +245,7 @@ describe('pairing keychain', () => {
})
await expect(readPairingKeychainItem(TOKEN_KEY)).rejects.toBe(currentError)
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
expect(secureStoreMock.getItemAsync).toHaveBeenCalledTimes(1)
})
@@ -280,11 +285,13 @@ describe('pairing keychain', () => {
// Treat Android’s indistinguishable absent/undecryptable null as absent so callers can clean orphans.
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBeNull()
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
expect(secureStoreMock.getItemAsync).toHaveBeenCalledTimes(1)
// Keep presence so later reads cannot resurrect superseded legacy secrets.
expect(presenceRecord).toBe('1')
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBeNull()
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
expect(secureStoreMock.getItemAsync).toHaveBeenCalledTimes(2)
})
@@ -295,6 +302,7 @@ describe('pairing keychain', () => {
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('legacy-token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
})
it('probes every bounded service when the generation record is temporarily unreadable', async () => {
@@ -309,6 +317,7 @@ describe('pairing keychain', () => {
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('rotated-token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
expect(secureStoreMock.getItemAsync).toHaveBeenCalledTimes(7)
})
@@ -328,10 +337,13 @@ describe('pairing keychain', () => {
await deletePairingKeychainItem(TOKEN_KEY)
const services = secureStoreMock.deleteItemAsync.mock.calls.map((call) =>
serviceOf(call[1] as Options)
)
expect(services).toEqual(['orca.pairing.v2', 'orca.pairing.v1', undefined])
expect(
secureStoreMock.deleteItemAsync.mock.calls.map(([key, options]) => [key, serviceOf(options)])
).toEqual([
[TOKEN_KEY, 'orca.pairing.v2'],
[TOKEN_KEY, 'orca.pairing.v1'],
[TOKEN_KEY, undefined]
])
expect(asyncStorageMock.removeItem).toHaveBeenCalledWith(TOKEN_PRESENCE_KEY)
})
@@ -345,25 +357,49 @@ describe('pairing keychain', () => {
})
await expect(deletePairingKeychainItem(TOKEN_KEY)).rejects.toBe(deleteError)
expect(secureStoreMock.deleteItemAsync).toHaveBeenCalledTimes(3)
expect(
secureStoreMock.deleteItemAsync.mock.calls.map(([key, options]) => [key, serviceOf(options)])
).toEqual([
[TOKEN_KEY, 'orca.pairing.v2'],
[TOKEN_KEY, 'orca.pairing.v1'],
[TOKEN_KEY, undefined]
])
expect(asyncStorageMock.removeItem).not.toHaveBeenCalled()
})
it('serializes writes that share the global generation', async () => {
let releaseFirst!: () => void
const firstWrite = new Promise<void>((resolve) => {
releaseFirst = resolve
vi.useFakeTimers()
const entered = Promise.withResolvers<void>()
const held = Promise.withResolvers<void>()
const order: string[] = []
secureStoreMock.setItemAsync.mockImplementation(async (key: string, value: string) => {
order.push(`start:${key}:${value}`)
if (key === TOKEN_KEY) {
entered.resolve()
await held.promise
}
order.push(`finish:${key}:${value}`)
})
secureStoreMock.setItemAsync.mockImplementationOnce(async () => firstWrite)
try {
const first = writePairingKeychainItem(TOKEN_KEY, 'first')
const second = writePairingKeychainItem(`${TOKEN_KEY}.second`, 'second')
await entered.promise
await vi.advanceTimersByTimeAsync(0)
expect(order).toEqual([`start:${TOKEN_KEY}:first`])
const first = writePairingKeychainItem(TOKEN_KEY, 'first')
const second = writePairingKeychainItem(`${TOKEN_KEY}.second`, 'second')
await vi.waitFor(() => expect(secureStoreMock.setItemAsync).toHaveBeenCalledTimes(1))
held.resolve()
await Promise.all([first, second])
releaseFirst()
await Promise.all([first, second])
expect(secureStoreMock.setItemAsync).toHaveBeenCalledTimes(2)
expect(order).toEqual([
`start:${TOKEN_KEY}:first`,
`finish:${TOKEN_KEY}:first`,
`start:${TOKEN_KEY}.second:second`,
`finish:${TOKEN_KEY}.second:second`
])
} finally {
held.resolve()
vi.useRealTimers()
}
})
it.each(['not-a-number', '', ' ', '0:pending'])(
@@ -375,6 +411,7 @@ describe('pairing keychain', () => {
)
await expect(readPairingKeychainItem(TOKEN_KEY)).resolves.toBe('rotated-token')
expect(secureStoreMock.getItemAsync).toHaveBeenLastCalledWith(TOKEN_KEY, expect.any(Object))
}
)
@@ -397,7 +434,22 @@ describe('pairing keychain', () => {
await deletePairingKeychainItem(TOKEN_KEY)
expect(secureStoreMock.deleteItemAsync).toHaveBeenCalledTimes(9)
expect(
secureStoreMock.deleteItemAsync.mock.calls.map(([key, options]) => [
key,
serviceOf(options)
])
).toEqual([
[TOKEN_KEY, 'orca.pairing.v8'],
[TOKEN_KEY, 'orca.pairing.v7'],
[TOKEN_KEY, 'orca.pairing.v6'],
[TOKEN_KEY, 'orca.pairing.v5'],
[TOKEN_KEY, 'orca.pairing.v4'],
[TOKEN_KEY, 'orca.pairing.v3'],
[TOKEN_KEY, 'orca.pairing.v2'],
[TOKEN_KEY, 'orca.pairing.v1'],
[TOKEN_KEY, undefined]
])
}
)
@@ -291,7 +291,10 @@ describe('recovering pairing relay candidate', () => {
})
await expect(candidate.sendRequest('status.get')).resolves.toEqual(success())
expect(resolveDirector).toHaveBeenCalledOnce()
expect(resolveDirector).toHaveBeenCalledExactlyOnceWith({
...journal.metadata.relay,
inviteToken: journal.secrets.inviteToken
})
})
it('bounds director recovery and applies full jitter to failures and target retries', async () => {
@@ -47,7 +47,10 @@ describe('served relay pairing recovery', () => {
})
await expect(candidate.sendRequest('status.get')).resolves.toMatchObject({ ok: true })
expect(resolveDirector).toHaveBeenCalledOnce()
expect(resolveDirector).toHaveBeenCalledExactlyOnceWith({
...journal.metadata.relay,
inviteToken: journal.secrets.inviteToken
})
expect(persistMove).toHaveBeenCalledWith(resolvedRelay)
})
@@ -45,6 +45,7 @@ function makeRuntimeWithSlowFirstWrite() {
const firstWriteDurable = new Promise<void>((resolve) => {
releaseFirstWrite = resolve
})
const firstWriteEntered = Promise.withResolvers<void>()
let writes = 0
const applyLayoutCommand = vi.fn(
async (
@@ -59,6 +60,7 @@ function makeRuntimeWithSlowFirstWrite() {
}
writes += 1
if (writes === 1) {
firstWriteEntered.resolve()
await firstWriteDurable
}
return { ok: true, result: {} }
@@ -66,7 +68,7 @@ function makeRuntimeWithSlowFirstWrite() {
)
const runtime = new OrcaRuntimeService(Object.assign(runtimeStore, { applyLayoutCommand }))
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
return { runtime, getSession, releaseFirstWrite }
return { runtime, getSession, releaseFirstWrite, firstWriteEntered: firstWriteEntered.promise }
}
function sleepCapture(updatedAt: number): WorktreeSleepResumeCapture {
@@ -98,11 +100,11 @@ function sleepCapture(updatedAt: number): WorktreeSleepResumeCapture {
describe('a queued hook status and a workspace sleep', () => {
it('keeps the sleep’s record when a status accepted before the sleep is written after it', async () => {
const { runtime, getSession, releaseFirstWrite } = makeRuntimeWithSlowFirstWrite()
const { runtime, getSession, releaseFirstWrite, firstWriteEntered } =
makeRuntimeWithSlowFirstWrite()
runtime.recordHookStatusResume(codexRow())
await vi.waitFor(() =>
expect(getSession().sleepingAgentSessionsByPaneKey?.[PANE_KEY]).toBeDefined()
)
await firstWriteEntered
expect(getSession().sleepingAgentSessionsByPaneKey?.[PANE_KEY]).toBeDefined()
runtime.recordHookStatusResume(codexRow({ state: 'blocked', receivedAt: 11 }))
const slept = runtime['sleepResumeRecords'].persist(TEST_WORKTREE_ID, sleepCapture(11), [
@@ -120,11 +122,11 @@ describe('a queued hook status and a workspace sleep', () => {
})
it('keeps a window sleep’s record when a status accepted before it is written after it', async () => {
const { runtime, getSession, releaseFirstWrite } = makeRuntimeWithSlowFirstWrite()
const { runtime, getSession, releaseFirstWrite, firstWriteEntered } =
makeRuntimeWithSlowFirstWrite()
runtime.recordHookStatusResume(codexRow())
await vi.waitFor(() =>
expect(getSession().sleepingAgentSessionsByPaneKey?.[PANE_KEY]).toBeDefined()
)
await firstWriteEntered
expect(getSession().sleepingAgentSessionsByPaneKey?.[PANE_KEY]).toBeDefined()
runtime.recordHookStatusResume(codexRow({ state: 'blocked', receivedAt: 11 }))
const [record] = sleepCapture(11).get(PTY_ID)!.records
@@ -38,7 +38,7 @@ describe('HostAccountSignInFlows', () => {
const flows = new HostAccountSignInFlows(runner.run, 'linux')
const started = flows.start('device-a', 'claude')
expect(started.phase).toBe('starting')
await vi.waitFor(() => expect(runner.run).toHaveBeenCalledOnce())
expect(runner.run).toHaveBeenCalledOnce()
runner.channel().onPrompt({ ...PROMPT, needsCode: true })
const waiting = await flows.wait('device-a', started.flowId, started.revision)
@@ -77,7 +77,7 @@ describe('HostAccountSignInFlows', () => {
const runner = controllableRunner()
const flows = new HostAccountSignInFlows(runner.run, 'linux')
const { flowId } = flows.start('device-a', 'claude')
await vi.waitFor(() => expect(runner.run).toHaveBeenCalledOnce())
expect(runner.run).toHaveBeenCalledOnce()
expect((await flows.cancel('device-a', flowId)).phase).toBe('cancelled')
expect(runner.channel().signal.aborted).toBe(true)
await expect(runner.channel().nextCode()).rejects.toThrow()
@@ -115,7 +115,7 @@ describe('HostAccountSignInFlows', () => {
const runner = controllableRunner()
const flows = new HostAccountSignInFlows(runner.run, 'linux')
const { flowId, revision } = flows.start('device-a', 'claude')
await vi.waitFor(() => expect(runner.run).toHaveBeenCalledOnce())
expect(runner.run).toHaveBeenCalledOnce()
runner
.channel()
.onPrompt({ url: 'https://evil.test/?code=true', userCode: null, needsCode: true })
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import { RpcDispatcher } from './rpc/dispatcher'
import type { RpcRequest } from './rpc/core'
@@ -46,10 +46,12 @@ function graph(ptyId: string | null, extraLeafPtyId?: string) {
function makeHost(options: { livePtyIds: Set<string> }) {
const writes: string[] = []
const inputWritten = Promise.withResolvers<void>()
const runtime = new OrcaRuntimeService(null)
runtime.setPtyController({
write: (_ptyId, data) => {
writes.push(data)
inputWritten.resolve()
return true
},
kill: () => true,
@@ -57,7 +59,7 @@ function makeHost(options: { livePtyIds: Set<string> }) {
hasPty: (ptyId) => options.livePtyIds.has(ptyId)
})
runtime.attachWindow(1)
return { runtime, writes }
return { runtime, writes, inputWritten: inputWritten.promise }
}
async function handleFor(runtime: OrcaRuntimeService): Promise<string> {
@@ -67,6 +69,9 @@ async function handleFor(runtime: OrcaRuntimeService): Promise<string> {
function startMultiplex(runtime: OrcaRuntimeService) {
const binaryFrames: StreamFrame[] = []
const controlRegistered = Promise.withResolvers<void>()
const subscribed = Promise.withResolvers<void>()
const writeUnavailable = Promise.withResolvers<void>()
const handlers = new Map<number, (frame: StreamFrame) => void>()
const messages: { result?: { type?: string; streamId?: number } }[] = []
const request: RpcRequest = {
@@ -77,18 +82,29 @@ function startMultiplex(runtime: OrcaRuntimeService) {
}
void new RpcDispatcher({ runtime, methods: TERMINAL_METHODS }).dispatchStreaming(
request,
(message) => messages.push(JSON.parse(message)),
(message) => {
messages.push(JSON.parse(message))
if (messages.at(-1)?.result?.type === 'subscribed') {
subscribed.resolve()
}
},
{
connectionId: 'paired-client',
sendBinary: (bytes: Uint8Array<ArrayBufferLike>) => {
const frame = decodeTerminalStreamFrame(bytes)
if (frame) {
binaryFrames.push(frame)
if (frame.opcode === TerminalStreamOpcode.WriteUnavailable) {
writeUnavailable.resolve()
}
}
return true
},
registerBinaryStreamHandler: (streamId: number, handler: (frame: StreamFrame) => void) => {
handlers.set(streamId, handler)
if (streamId === 0) {
controlRegistered.resolve()
}
return () => handlers.delete(streamId)
}
}
@@ -97,17 +113,26 @@ function startMultiplex(runtime: OrcaRuntimeService) {
handlers.get(streamId)?.(
decodeTerminalStreamFrame(encodeTerminalStreamFrame({ opcode, streamId, seq: 0, payload }))!
)
return { binaryFrames, handlers, messages, send }
return {
binaryFrames,
handlers,
messages,
send,
controlRegistered: controlRegistered.promise,
subscribed: subscribed.promise,
writeUnavailable: writeUnavailable.promise
}
}
describe('a host pane remounting onto its same live PTY (#23759)', () => {
it('keeps the handle a paired client is typing into', async () => {
const { runtime, writes } = makeHost({ livePtyIds: new Set([PTY_ID]) })
const { runtime, writes, inputWritten } = makeHost({ livePtyIds: new Set([PTY_ID]) })
runtime.syncWindowGraph(1, graph(PTY_ID))
const handle = await handleFor(runtime)
const client = startMultiplex(runtime)
await vi.waitFor(() => expect(client.handlers.has(0)).toBe(true))
await client.controlRegistered
expect(client.handlers.has(0)).toBe(true)
client.send(
0,
TerminalStreamOpcode.Subscribe,
@@ -118,16 +143,16 @@ describe('a host pane remounting onto its same live PTY (#23759)', () => {
capabilities: { writeUnavailable: 1 }
})
)
await vi.waitFor(() =>
expect(client.messages.some((message) => message.result?.type === 'subscribed')).toBe(true)
)
await client.subscribed
expect(client.messages.some((message) => message.result?.type === 'subscribed')).toBe(true)
runtime.syncWindowGraph(1, graph(null))
runtime.syncWindowGraph(1, graph(PTY_ID))
expect(await handleFor(runtime)).toBe(handle)
client.send(1, TerminalStreamOpcode.Input, encodeTerminalStreamText('ls\r'))
await vi.waitFor(() => expect(writes).toEqual(['ls\r']))
await Promise.race([inputWritten, client.writeUnavailable])
expect(writes).toEqual(['ls\r'])
expect(
client.binaryFrames.some((frame) => frame.opcode === TerminalStreamOpcode.WriteUnavailable)
).toBe(false)