import { describe, expect, it } from 'bun:test'; import type { SearchResult } from '@/lib/types'; import { extractJsonObject, parseCopilotResponse } from '@/lib/server/research-copilot-format'; function result(overrides: Partial = {}): SearchResult { return { chunkId: 1, documentId: 1, source: 'filings', sourceKind: 'filing_brief', sourceRef: '0001', title: '10-K brief', ticker: 'NVDA', accessionNumber: '0001', filingDate: '2026-02-18', citationLabel: 'NVDA · 0001 [1]', headingPath: null, chunkText: 'Demand stayed strong and margins expanded.', snippet: 'Demand stayed strong and margins expanded.', score: 0.9, vectorRank: 1, lexicalRank: 1, href: '/analysis/reports/NVDA/0001', ...overrides }; } describe('research copilot format helpers', () => { it('parses strict json responses with suggested actions', () => { const parsed = parseCopilotResponse(JSON.stringify({ answerMarkdown: 'Demand stayed strong [1]. The setup still looks constructive [2].', followUps: ['What disconfirms the bull case?', 'Which risks changed most?'], suggestedActions: [{ type: 'draft_memo_section', label: 'Use as thesis draft', section: 'thesis', contentMarkdown: 'Maintain a constructive stance while monitoring concentration.', citationIndexes: [1, 2] }] }), [result(), result({ chunkId: 2, citationLabel: 'NVDA · 0002 [2]', sourceRef: '0002' })], 'What changed?', 'thesis'); expect(parsed.citationIndexes).toEqual([1, 2]); expect(parsed.followUps).toHaveLength(2); expect(parsed.suggestedActions[0]?.type).toBe('draft_memo_section'); expect(parsed.suggestedActions[0]?.section).toBe('thesis'); }); it('falls back to plain text and default actions when json parsing fails', () => { const parsed = parseCopilotResponse( 'Plain text answer without json wrapper', [result(), result({ chunkId: 2, citationLabel: 'NVDA · 0002 [2]', sourceRef: '0002' })], 'Summarize the setup', null ); expect(parsed.answerMarkdown).toContain('Plain text answer'); expect(parsed.citationIndexes).toEqual([1, 2]); expect(parsed.suggestedActions.some((action) => action.type === 'draft_note')).toBe(true); expect(parsed.suggestedActions.some((action) => action.type === 'queue_research_brief')).toBe(true); }); it('extracts the first json object from fenced responses', () => { const extracted = extractJsonObject('```json\n{"answerMarkdown":"A [1]","followUps":[],"suggestedActions":[]}\n```'); expect(extracted).toBe('{"answerMarkdown":"A [1]","followUps":[],"suggestedActions":[]}'); }); });