import { beforeAll, describe, expect, it, mock } from 'bun:test'; const TEST_USER_ID = 'copilot-api-user'; const mockGetSession = mock(async () => ({ id: 1, user_id: TEST_USER_ID, ticker: 'NVDA', title: 'NVDA copilot', selected_sources: ['documents', 'filings', 'research'], pinned_artifact_ids: [], created_at: '2026-03-14T00:00:00.000Z', updated_at: '2026-03-14T00:00:00.000Z', messages: [] })); const mockRunTurn = mock(async () => ({ session: { id: 1, user_id: TEST_USER_ID, ticker: 'NVDA', title: 'NVDA copilot', selected_sources: ['filings'], pinned_artifact_ids: [4], created_at: '2026-03-14T00:00:00.000Z', updated_at: '2026-03-14T00:00:01.000Z', messages: [] }, user_message: { id: 1, session_id: 1, user_id: TEST_USER_ID, role: 'user', content_markdown: 'What changed?', citations: [], follow_ups: [], suggested_actions: [], selected_sources: ['filings'], pinned_artifact_ids: [4], memo_section: 'thesis', created_at: '2026-03-14T00:00:00.000Z' }, assistant_message: { id: 2, session_id: 1, user_id: TEST_USER_ID, role: 'assistant', content_markdown: 'Demand stayed strong [1].', citations: [{ index: 1, label: 'NVDA ยท 0001 [1]', chunkId: 1, href: '/analysis/reports/NVDA/0001', source: 'filings', sourceKind: 'filing_brief', sourceRef: '0001', title: '10-K brief', ticker: 'NVDA', accessionNumber: '0001', filingDate: '2026-02-18', excerpt: 'Demand stayed strong.', artifactId: 5 }], follow_ups: ['What changed in risks?'], suggested_actions: [], selected_sources: ['filings'], pinned_artifact_ids: [4], memo_section: 'thesis', created_at: '2026-03-14T00:00:01.000Z' }, results: [] })); const mockGenerateBrief = mock(async () => ({ provider: 'test', model: 'test-model', bodyMarkdown: '# NVDA brief\n\nDemand held up.', evidence: [] })); const mockFindInFlightTask = mock(async () => null); const mockEnqueueTask = mock(async () => ({ id: 'task-1', user_id: TEST_USER_ID, task_type: 'research_brief', status: 'queued', stage: 'queued', stage_detail: 'Queued', stage_context: null, resource_key: 'research_brief:NVDA:update the thesis', notification_read_at: null, notification_silenced_at: null, priority: 55, payload: { ticker: 'NVDA', query: 'Update the thesis', sources: ['filings'] }, result: null, error: null, attempts: 0, max_attempts: 3, workflow_run_id: 'run-1', created_at: '2026-03-14T00:00:00.000Z', updated_at: '2026-03-14T00:00:00.000Z', finished_at: null })); function registerMocks() { mock.module('@/lib/server/auth-session', () => ({ requireAuthenticatedSession: async () => ({ session: { user: { id: TEST_USER_ID, email: 'copilot@example.com', name: 'Copilot API User', image: null } }, response: null }) })); mock.module('@/lib/server/repos/research-copilot', () => ({ getResearchCopilotSessionByTicker: mockGetSession })); mock.module('@/lib/server/research-copilot', () => ({ runResearchCopilotTurn: mockRunTurn, generateResearchBrief: mockGenerateBrief })); mock.module('@/lib/server/tasks', () => ({ enqueueTask: mockEnqueueTask, findInFlightTask: mockFindInFlightTask, getTaskById: mock(async () => null), getTaskQueueSnapshot: mock(async () => ({ items: [], stats: { queued: 0, running: 0, failed: 0 } })), getTaskTimeline: mock(async () => []), listRecentTasks: mock(async () => []), updateTaskNotification: mock(async () => null) })); } describe('research copilot api', () => { let app: { handle: (request: Request) => Promise }; beforeAll(async () => { mock.restore(); registerMocks(); ({ app } = await import('./app')); }); it('returns the ticker-scoped session payload', async () => { const response = await app.handle(new Request('http://localhost/api/research/copilot/session?ticker=nvda')); expect(response.status).toBe(200); const payload = await response.json() as { session: { ticker: string } }; expect(payload.session.ticker).toBe('NVDA'); expect(mockGetSession).toHaveBeenCalled(); }); it('returns turn responses with assistant citations', async () => { const response = await app.handle(new Request('http://localhost/api/research/copilot/turn', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ticker: 'nvda', query: 'What changed?', sources: ['filings'], pinnedArtifactIds: [4], memoSection: 'thesis' }) })); expect(response.status).toBe(200); const payload = await response.json() as { assistant_message: { citations: Array<{ artifactId: number | null }>; }; }; expect(payload.assistant_message.citations[0]?.artifactId).toBe(5); expect(mockRunTurn).toHaveBeenCalled(); }); it('queues research brief jobs with normalized ticker payloads', async () => { const response = await app.handle(new Request('http://localhost/api/research/copilot/job', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ticker: 'nvda', query: 'Update the thesis', sources: ['filings'] }) })); expect(response.status).toBe(200); const payload = await response.json() as { task: { task_type: string; payload: { ticker: string; }; }; }; expect(payload.task.task_type).toBe('research_brief'); expect(payload.task.payload.ticker).toBe('NVDA'); expect(mockFindInFlightTask).toHaveBeenCalledWith( TEST_USER_ID, 'research_brief', 'research_brief:NVDA:update the thesis' ); expect(mockEnqueueTask).toHaveBeenCalled(); }); });