|
| 1 | +import { describe, it, expect, beforeEach, vi } from 'vitest'; |
| 2 | +import { renderHook, waitFor, act } from '@testing-library/react'; |
| 3 | +import { useSessionFull } from '@/state/useSessionFull'; |
| 4 | +import type { SessionFullBundle } from '@/api/types'; |
| 5 | +import { MockEventSource } from '../_helpers/MockEventSource'; |
| 6 | + |
| 7 | +const bundle: SessionFullBundle = { |
| 8 | + session: { |
| 9 | + id: 'SES-1', status: 'in_progress', |
| 10 | + created_at: 't0', updated_at: 't0', deleted_at: null, |
| 11 | + agents_run: [], tool_calls: [], findings: {}, |
| 12 | + pending_intervention: null, user_inputs: [], |
| 13 | + parent_session_id: null, dedup_rationale: null, |
| 14 | + extra_fields: {}, version: 1, |
| 15 | + }, |
| 16 | + agents_run: [], tool_calls: [], events: [], |
| 17 | + agent_definitions: {}, vm_seq: 0, |
| 18 | +}; |
| 19 | + |
| 20 | +describe('useSessionFull', () => { |
| 21 | + beforeEach(() => { |
| 22 | + MockEventSource.reset(); |
| 23 | + // @ts-expect-error global override |
| 24 | + global.EventSource = MockEventSource; |
| 25 | + global.fetch = vi.fn().mockResolvedValue( |
| 26 | + new Response(JSON.stringify(bundle), { |
| 27 | + status: 200, |
| 28 | + headers: { 'content-type': 'application/json' }, |
| 29 | + }), |
| 30 | + ); |
| 31 | + }); |
| 32 | + |
| 33 | + it('fetches bootstrap then sets state.session.id', async () => { |
| 34 | + const { result } = renderHook(() => useSessionFull('SES-1')); |
| 35 | + expect(result.current.isLoading).toBe(true); |
| 36 | + await waitFor(() => expect(result.current.isLoading).toBe(false)); |
| 37 | + expect(result.current.state.session?.id).toBe('SES-1'); |
| 38 | + expect(result.current.error).toBeNull(); |
| 39 | + }); |
| 40 | + |
| 41 | + it('opens SSE stream after bootstrap and applies events', async () => { |
| 42 | + const { result } = renderHook(() => useSessionFull('SES-1')); |
| 43 | + await waitFor(() => expect(result.current.isLoading).toBe(false)); |
| 44 | + await waitFor(() => expect(MockEventSource.lastInstance()).toBeDefined()); |
| 45 | + expect(MockEventSource.lastInstance()!.url).toBe('/api/v1/sessions/SES-1/events'); |
| 46 | + |
| 47 | + act(() => { |
| 48 | + MockEventSource.lastInstance()!.emit( |
| 49 | + JSON.stringify({ seq: 1, kind: 'status_changed', payload: { status: 'resolved' }, ts: 't1' }), |
| 50 | + ); |
| 51 | + }); |
| 52 | + await waitFor(() => expect(result.current.state.session?.status).toBe('resolved')); |
| 53 | + }); |
| 54 | + |
| 55 | + it('captures fetch error in error state', async () => { |
| 56 | + global.fetch = vi.fn().mockResolvedValue( |
| 57 | + new Response( |
| 58 | + JSON.stringify({ error: { code: 'not_found', message: 'gone', details: {} } }), |
| 59 | + { status: 404, headers: { 'content-type': 'application/json' } }, |
| 60 | + ), |
| 61 | + ); |
| 62 | + const { result } = renderHook(() => useSessionFull('SES-x')); |
| 63 | + await waitFor(() => expect(result.current.error).not.toBeNull()); |
| 64 | + expect(result.current.error?.code).toBe('not_found'); |
| 65 | + expect(result.current.isLoading).toBe(false); |
| 66 | + }); |
| 67 | +}); |
0 commit comments