Files
stonks-oracle/frontend/src/test/reports.test.ts
T
Celes Renata bc077bfcc8 feat: trading feedback engine — periodic performance reports with AI summarization
- Migration 038: trading_reports table + report-summarizer agent seed
- 6 reporting modules: models, collector, sections, validator, summarizer, generator
- API endpoints: GET /api/reports (paginated, filterable), GET /api/reports/{id}
- Frontend hooks: useReports, useReport with TanStack Query
- Scheduler: daily (after 16:30 ET) and weekly (Saturday) report triggers
- Redis queue consumer for async report generation with retry/dedup
- 5 property-based tests (chunking, serialization, validation, accuracy, deltas)
- 109 unit/integration tests across all modules
- 6 frontend hook tests with MSW mocks
2026-05-01 22:13:09 +00:00

156 lines
4.3 KiB
TypeScript

/**
* 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));
});
});