test(web): cover produced-file lifecycle edges

This commit is contained in:
ZiyaZhang
2026-08-10 09:25:55 -07:00
parent ee1a88c9f1
commit 4357be1565
5 changed files with 105 additions and 11 deletions

View File

@@ -192,8 +192,7 @@ export class ConnectionController {
}
/** Sink exception isolation: a business-layer throw is logged only, never affecting pump or reconnect semantics. */
private callSink(fn: (() => void) | undefined): void {
if (fn === undefined) return
private callSink(fn: () => void): void {
try {
fn()
} catch (error) {

View File

@@ -151,6 +151,42 @@ describe('connection client apply', () => {
}
})
it('retracts the host description while reconnecting and republishes the next generation', async () => {
;(globalThis as Win).location = { hostname: 'localhost', search: '?fixture' }
const handle = await mount()
const descriptions: Array<boolean | undefined> = []
const reconnectSnapshots: Array<boolean | undefined> = []
const stopDescription = handle.hostDescription.subscribe(() => {
descriptions.push(handle.hostDescription.getSnapshot()?.canOpenPath)
})
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const loop = handle.start({
onStateChange: (state) => {
if (state === 'reconnecting') {
reconnectSnapshots.push(handle.hostDescription.getSnapshot()?.canOpenPath)
}
},
}, { backoffBaseMs: 10, backoffFactor: 1, backoffMaxMs: 10, streamOpenTimeoutMs: 500 })
try {
await vi.waitFor(() => {
expect(handle.hostDescription.getSnapshot()?.canOpenPath).toBe(true)
})
const timing = (globalThis as Record<string, unknown>).__fxTiming as
| { breakStreams(): void }
| undefined
if (timing === undefined) throw new Error('fixture timing hooks missing')
timing.breakStreams()
await vi.waitFor(() => { expect(reconnectSnapshots).toEqual([undefined]) })
await vi.waitFor(() => { expect(descriptions).toEqual([true, undefined, true]) })
expect(handle.hostDescription.getSnapshot()?.canOpenPath).toBe(true)
} finally {
stopDescription()
loop.stop()
warnSpy.mockRestore()
}
})
it('WebApiClient keeps unary calls and respond on globalThis.fetch', async () => {
;(globalThis as Win).location = { hostname: 'localhost', search: '' }
const handle = await mount()

View File

@@ -180,6 +180,38 @@ describe('connection lifecycle', () => {
}
})
it('rejects a generation whose streams end during readiness and retries', async () => {
const api = new FakeApiClient()
const firstDescribe = deferred<Awaited<ReturnType<FakeApiClient['onDescribe']>>>()
let describeCalls = 0
api.onDescribe = () => {
describeCalls++
return describeCalls === 1
? firstDescribe.promise
: Promise.resolve(ok({ version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true }))
}
const states: ConnectionState[] = []
let connected = 0
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const controller = new ConnectionController(api, {
onConnected: () => { connected++ },
onStateChange: state => states.push(state),
}, FAST)
controller.start()
try {
await vi.waitFor(() => { expect(api.openMuxCount).toBe(1) })
api.endStreams()
firstDescribe.resolve(ok({ version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true }))
await vi.waitFor(() => { expect(describeCalls).toBe(2) })
await vi.waitFor(() => { expect(connected).toBe(1) })
expect(states).toEqual(['reconnecting', 'connected'])
} finally {
controller.stop()
warnSpy.mockRestore()
}
})
it('proceeds as connected via the timeout guard when a carrier never fires onOpen', async () => {
const api = new FakeApiClient()
api.suppressStreamOpen = true // misbehaving carrier: streams open but onOpen never fires