Files
orca/src/main/browser/browser-client-page-unavailability.test.ts
T
Neil 60b8eb4a6f fix(browser): keep in-app browser logins when a WSL or managed SSH host re-pairs (#29610)
Client-hosted browser pages for a server Orca deployed (WSL hosts today; managed
SSH desktop tabs use the SSH route) now derive their partition from the
environment and the server's public key, not the pairing revision and device id
that rotate on every re-pair. Existing jars are adopted, and each binding keeps
the shipping build's fingerprint (with the stable one as an alias) so a
downgraded build still opens the same jar.
2026-10-20 19:00:06 -07:00

158 lines
5.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import type { BrowserClientHostCommandEvent } from '../../shared/browser-client-host-protocol'
import type { BrowserRoutePageGuestIdentity } from './browser-route-page-authority'
import { BrowserClientPageCommandExecutor } from './browser-client-page-command-executor'
const partition = `persist:orca-browser-v1-${'a'.repeat(64)}`
const registration = {
partition,
browserPageId: 'page-a',
pageHostGeneration: 7,
rendererWebContentsId: 11,
webContentsId: 41
}
describe('browser client page unavailability', () => {
it('fences an exact unavailable guest generation and requests reconciliation once', async () => {
const { executor, onPageUnavailable, reportUnavailable } = harness()
await executor.handle(createCommand(), new AbortController().signal)
reportUnavailable({ ...registration, pageHostGeneration: 6 })
expect(onPageUnavailable).not.toHaveBeenCalled()
reportUnavailable(registration)
reportUnavailable(registration)
expect(onPageUnavailable).toHaveBeenCalledOnce()
expect(onPageUnavailable).toHaveBeenCalledWith('page-a', 7)
expect(executor.snapshotPageInventory()).toEqual([
expect.objectContaining({ browserPageId: 'page-a', state: 'outcomeUnknown' })
])
})
it('closes an unavailable page after its owning app renderer is gone', async () => {
const { executor, renderer, reportUnavailable, setRendererCurrent } = harness()
await executor.handle(createCommand(), new AbortController().signal)
setRendererCurrent(false)
reportUnavailable(registration)
await expect(executor.handle(closeCommand(), new AbortController().signal)).resolves.toEqual({
status: 'completed'
})
expect(renderer.retirePage).not.toHaveBeenCalled()
expect(executor.snapshotPageInventory()).toEqual([])
})
it('releases the page availability watch when the page retires', async () => {
const { executor, releaseAvailabilityWatch } = harness()
await executor.handle(createCommand(), new AbortController().signal)
expect(releaseAvailabilityWatch).not.toHaveBeenCalled()
await expect(executor.retirePage('page-a', 7)).resolves.toBe(true)
expect(releaseAvailabilityWatch).toHaveBeenCalledOnce()
})
})
function harness() {
let rendererCurrent = true
let availabilityListener: ((page: BrowserRoutePageGuestIdentity) => void) | null = null
const releaseAvailabilityWatch = vi.fn(() => {
availabilityListener = null
})
const renderer = {
rendererWebContentsId: 11,
isCurrent: vi.fn(() => rendererCurrent),
mountPage: vi.fn(async () => ({ webContentsId: 41 })),
retirePage: vi.fn(async () => {})
}
const onPageUnavailable = vi.fn()
const executor = new BrowserClientPageCommandExecutor({
orcaProfileId: 'profile-a',
authorityConnectionIdentity: 'authority-a',
pairingAuthorityConnectionIdentity: 'pairing-authority-a',
legacyAuthorityConnectionIdentity: 'legacy-authority-a',
storageScope: 'a'.repeat(64),
retainNetworkRoute: vi.fn(async () => ({
key: 'execution-host-a',
executionHostIdentity: 'execution-host-record-a',
legacyExecutionHostIdentity: 'legacy-execution-host-record-a',
proxyEndpoint: { host: '127.0.0.1' as const, port: 43123 },
release: vi.fn()
})),
selectRenderer: () => renderer,
routeSessions: {
preparePage: vi.fn(async () => ({ partition, release: vi.fn() }))
},
routeWebContents: {
claimGuestLifecycle: vi.fn((page: BrowserRoutePageGuestIdentity) => ({
registration: page,
guestAuthority: Symbol('guest'),
whenDestroyed: Promise.resolve(),
isCurrent: () => true
})),
registerGuest: vi.fn(() => true),
grantNavigation: vi.fn(() => true),
revokeNavigation: vi.fn(() => true),
navigateGuest: vi.fn(async () => true),
beginGuestRetirement: vi.fn(() => Promise.resolve()),
watchPageAvailability: vi.fn((_browserPageId, listener) => {
availabilityListener = listener
return releaseAvailabilityWatch
})
},
executeAutomation: vi.fn(async () => ({})),
retireAutomation: vi.fn(async () => {}),
guestBinding: { bind: vi.fn(), release: vi.fn() },
onPageUnavailable
})
return {
executor,
onPageUnavailable,
releaseAvailabilityWatch,
renderer,
reportUnavailable: (page: BrowserRoutePageGuestIdentity) => availabilityListener?.(page),
setRendererCurrent: (value: boolean) => {
rendererCurrent = value
}
}
}
function createCommand(): BrowserClientHostCommandEvent {
return {
type: 'command',
authorityRuntimeId: 'runtime-a',
authorityEpoch: 'epoch-a',
browserHostClientId: 'client-a',
browserHostGeneration: 3,
pageCommandProtocolVersion: 1,
browserPageId: 'page-a',
pageHostGeneration: 7,
commandSequence: 1,
commandId: 'create-page-a',
command: {
type: 'createPage',
browserProfileId: 'profile-a',
executionHostKey: 'execution-host-a'
}
}
}
function closeCommand(): BrowserClientHostCommandEvent {
return {
...createCommand(),
pageReconciliationProtocolVersion: 1,
commandSequence: 2,
commandId: 'close-page-a',
command: {
type: 'closePage',
targetAuthority: {
authorityRuntimeId: 'runtime-a',
authorityEpoch: 'epoch-a',
browserHostClientId: 'client-a',
browserHostGeneration: 3,
pageHostGeneration: 7
}
}
}
}