httpRealtimeRecovery.test.ts 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839
  1. import { afterEach, expect, it, vi } from 'vitest'
  2. import { HTTPRealtimeChannel } from '@/api/httpRealtime'
  3. const api = vi.hoisted(() => ({ request: vi.fn() }))
  4. vi.mock('@/api/client', async importOriginal => ({ ...await importOriginal<typeof import('@/api/client')>(), apiRequest: api.request }))
  5. afterEach(() => { vi.unstubAllGlobals(); vi.useRealTimers(); api.request.mockReset() })
  6. it('times out response headers on every reconnect and preserves the received cursor', async () => {
  7. vi.useFakeTimers()
  8. api.request.mockResolvedValue({ id: 'channel' })
  9. let firstStream!: ReadableStreamDefaultController<Uint8Array>
  10. const attempts: RequestInit[] = []
  11. vi.stubGlobal('fetch', vi.fn((_url: unknown, options: RequestInit) => {
  12. attempts.push(options)
  13. if (attempts.length > 1) return new Promise((_resolve, reject) => {
  14. options.signal?.addEventListener('abort', () => reject(new Error('headers timed out')), { once: true })
  15. })
  16. return Promise.resolve(new Response(new ReadableStream<Uint8Array>({ start(controller) {
  17. firstStream = controller
  18. controller.enqueue(new TextEncoder().encode('id: epoch:9\nevent: frame\ndata: {"params":{"type":"gateway.ready"}}\n\n'))
  19. } }), { headers: { 'content-type': 'text/event-stream' } }))
  20. }))
  21. const failed = vi.fn(), channel = new HTTPRealtimeChannel(async () => {}, failed)
  22. try {
  23. await channel.open('chat')
  24. firstStream.error(new Error('connection lost'))
  25. await vi.advanceTimersByTimeAsync(500)
  26. expect(attempts).toHaveLength(2)
  27. await vi.advanceTimersByTimeAsync(45_000)
  28. expect(attempts[1].signal?.aborted).toBe(true)
  29. await vi.advanceTimersByTimeAsync(1000)
  30. expect(attempts).toHaveLength(3)
  31. expect(attempts[2].headers).toMatchObject({ 'Last-Event-ID': 'epoch:9' })
  32. expect(failed).not.toHaveBeenCalled()
  33. } finally {
  34. channel.close()
  35. await vi.advanceTimersByTimeAsync(60_000)
  36. expect(attempts).toHaveLength(3)
  37. }
  38. })