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({ 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() }) })