| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
- import { ChatRpcSocket, GroupEventSocket } from '@/api/realtime'
- import { setApiCsrfToken } from '@/api/client'
- class FakeWebSocket extends EventTarget {
- static readonly CONNECTING = 0
- static readonly OPEN = 1
- static readonly CLOSING = 2
- static readonly CLOSED = 3
- static instances: FakeWebSocket[] = []
- readonly url: string
- readyState = FakeWebSocket.CONNECTING
- sent: string[] = []
- constructor(url: string) {
- super()
- this.url = url
- FakeWebSocket.instances.push(this)
- queueMicrotask(() => {
- this.readyState = FakeWebSocket.OPEN
- this.dispatchEvent(new Event('open'))
- })
- }
- send(data: string): void { this.sent.push(data) }
- close(code = 1000, reason = ''): void {
- this.readyState = FakeWebSocket.CLOSED
- this.dispatchEvent(new CloseEvent('close', { code, reason }))
- }
- message(value: unknown): void { this.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(value) })) }
- }
- beforeEach(() => {
- FakeWebSocket.instances = []
- setApiCsrfToken('csrf-test')
- vi.stubGlobal('WebSocket', FakeWebSocket)
- })
- afterEach(() => {
- vi.unstubAllGlobals()
- vi.restoreAllMocks()
- })
- function leaseFetch() {
- const mock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => new Response(JSON.stringify({ lease: 'lease-1' }), {
- status: 201, headers: { 'content-type': 'application/json' },
- }))
- vi.stubGlobal('fetch', mock)
- return mock
- }
- describe('raw realtime protocols', () => {
- it('waits for gateway.ready and correlates JSON-RPC responses', async () => {
- leaseFetch()
- const socket = new ChatRpcSocket()
- const events: string[] = []
- socket.onEvent(event => events.push(event.type))
- const connecting = socket.connect()
- await vi.waitFor(() => expect(FakeWebSocket.instances).toHaveLength(1))
- const transport = FakeWebSocket.instances[0]
- await Promise.resolve()
- expect(socket.state).toBe('connected')
- transport.message({ jsonrpc: '2.0', method: 'event', params: { type: 'gateway.ready', payload: {} } })
- await connecting
- expect(socket.state).toBe('ready')
- const response = socket.request('session.usage', { session_id: 'runtime-1' })
- const frame = JSON.parse(transport.sent[0])
- expect(frame).toMatchObject({ jsonrpc: '2.0', method: 'session.usage', params: { session_id: 'runtime-1' } })
- transport.message({ jsonrpc: '2.0', id: frame.id, result: { total_tokens: 42 } })
- await expect(response).resolves.toEqual({ total_tokens: 42 })
- expect(events).toEqual(['gateway.ready'])
- socket.close()
- })
- it('binds a group lease to epoch/cursor but exposes only the lease in WS query', async () => {
- const fetchMock = leaseFetch()
- const socket = new GroupEventSocket()
- await socket.connect({
- epoch: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
- cursor: 12,
- onEnvelope: () => undefined,
- onReset: () => undefined,
- })
- const request = fetchMock.mock.calls[0]?.[1]
- expect(request).toBeDefined()
- expect(JSON.parse(String(request!.body))).toEqual({
- channel: 'groups', epoch: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', cursor: 12,
- })
- const url = new URL(FakeWebSocket.instances[0].url)
- expect([...url.searchParams.keys()]).toEqual(['lease'])
- socket.close()
- })
- })
|