/** * Frontend hook tests for trading reports. * * Tests useReports and useReport hooks with MSW mocks. * Requirements validated: 5.4, 5.5 */ import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; import { type ReactNode, createElement } from 'react'; import { describe, expect, it } from 'vitest'; import { useReports, useReport } from '../api/hooks'; import { server } from './mocks/server'; const mockReportList = [ { id: 'rpt-1', report_type: 'daily', period_start: '2025-01-15', period_end: '2025-01-15', validation_status: 'passed', generated_at: '2025-01-15T21:30:00Z', }, { id: 'rpt-2', report_type: 'weekly', period_start: '2025-01-13', period_end: '2025-01-17', validation_status: 'warnings', generated_at: '2025-01-18T10:00:00Z', }, ]; const mockReportDetail = { id: 'rpt-1', report_type: 'daily', period_start: '2025-01-15', period_end: '2025-01-15', validation_status: 'passed', generated_at: '2025-01-15T21:30:00Z', created_at: '2025-01-15T21:30:05Z', report_data: { pnl: { realized_pnl: 125.5, unrealized_pnl: -30.2 }, executive_summary: 'Test executive summary', }, }; function createWrapper() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, }, }); return function Wrapper({ children }: { children: ReactNode }) { return createElement(QueryClientProvider, { client: queryClient }, children); }; } describe('useReports', () => { it('fetches report list with default params', async () => { server.use( http.get('/api/reports', () => HttpResponse.json(mockReportList)), ); const { result } = renderHook(() => useReports(), { wrapper: createWrapper(), }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toHaveLength(2); expect(result.current.data![0].id).toBe('rpt-1'); expect(result.current.data![0].report_type).toBe('daily'); expect(result.current.data![1].report_type).toBe('weekly'); }); it('passes query params for filtering', async () => { let capturedUrl = ''; server.use( http.get('/api/reports', ({ request }) => { capturedUrl = request.url; return HttpResponse.json([mockReportList[0]]); }), ); const { result } = renderHook( () => useReports({ report_type: 'daily', limit: 10 }), { wrapper: createWrapper() }, ); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(capturedUrl).toContain('report_type=daily'); expect(capturedUrl).toContain('limit=10'); expect(result.current.data).toHaveLength(1); }); it('handles error state', async () => { server.use( http.get('/api/reports', () => new HttpResponse(null, { status: 500 }), ), ); const { result } = renderHook(() => useReports(), { wrapper: createWrapper(), }); await waitFor(() => expect(result.current.isError).toBe(true)); }); }); describe('useReport', () => { it('fetches single report by id', async () => { server.use( http.get('/api/reports/rpt-1', () => HttpResponse.json(mockReportDetail), ), ); const { result } = renderHook(() => useReport('rpt-1'), { wrapper: createWrapper(), }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data!.id).toBe('rpt-1'); expect(result.current.data!.report_data).toBeDefined(); expect(result.current.data!.report_data.pnl).toBeDefined(); expect(result.current.data!.created_at).toBe('2025-01-15T21:30:05Z'); }); it('does not fetch when id is undefined', async () => { const { result } = renderHook(() => useReport(undefined), { wrapper: createWrapper(), }); // Should stay in idle/loading state without fetching expect(result.current.isFetching).toBe(false); }); it('handles 404 error', async () => { server.use( http.get('/api/reports/nonexistent', () => new HttpResponse(null, { status: 404 }), ), ); const { result } = renderHook(() => useReport('nonexistent'), { wrapper: createWrapper(), }); await waitFor(() => expect(result.current.isError).toBe(true)); }); });