feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { AuthenticatedSseHub, type SseConnectionState } from '../src/services/sseHub'
|
||||
import { useAuthStore } from '../src/stores/auth'
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
const auth = useAuthStore()
|
||||
auth.initialized = true
|
||||
auth.applySession({
|
||||
accessToken: 'test-access-token',
|
||||
expiresAt: '2026-07-31T12:00:00.000Z',
|
||||
user: {
|
||||
id: 'bao',
|
||||
email: 'bao@nexus.local',
|
||||
displayName: 'Bao',
|
||||
role: 'owner',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('authenticated SSE hub', () => {
|
||||
it('backs off with jitter after a normal EOF instead of reconnecting immediately', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.5)
|
||||
const fetchMock = vi.fn(async () => new Response('', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
}))
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const states: SseConnectionState[] = []
|
||||
const hub = new AuthenticatedSseHub()
|
||||
const unsubscribe = hub.subscribe('/api/v1/events', () => undefined, {
|
||||
onStateChange: state => states.push(state),
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||||
expect(states).toContain('open')
|
||||
expect(states.at(-1)).toBe('reconnecting')
|
||||
|
||||
await vi.advanceTimersByTimeAsync(999)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
unsubscribe()
|
||||
hub.closeAll()
|
||||
})
|
||||
|
||||
it('reconnects a stalled stream when the heartbeat budget expires', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.5)
|
||||
const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
init?.signal?.addEventListener('abort', () => {
|
||||
controller.error(init.signal?.reason ?? new Error('aborted'))
|
||||
}, { once: true })
|
||||
},
|
||||
})
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
})
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const states: SseConnectionState[] = []
|
||||
const hub = new AuthenticatedSseHub()
|
||||
const unsubscribe = hub.subscribe('/api/v1/events', () => undefined, {
|
||||
onStateChange: state => states.push(state),
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(45_000)
|
||||
expect(states).toContain('error')
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
unsubscribe()
|
||||
hub.closeAll()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user