import { describe, it, expect, vi } from 'vitest'; import { PromptSummaryService, extractFirstSentence, extractHeadings, type LlmSummaryGenerator, } from '../../src/services/prompt-summary.service.js'; describe('extractFirstSentence', () => { it('extracts first sentence from plain text', () => { const result = extractFirstSentence('This is the first sentence. And this is the second.', 20); expect(result).toBe('This is the first sentence.'); }); it('truncates to maxWords', () => { const long = 'word '.repeat(30).trim(); const result = extractFirstSentence(long, 5); expect(result).toBe('word word word word word...'); }); it('skips markdown headings to find content', () => { const content = '# Title\n\n## Subtitle\n\nActual content here. More text.'; expect(extractFirstSentence(content, 20)).toBe('Actual content here.'); }); it('falls back to first heading if no content lines', () => { const content = '# Only Headings\n## Nothing Else'; expect(extractFirstSentence(content, 20)).toBe('Only Headings'); }); it('strips markdown formatting', () => { const content = 'This has **bold** and *italic* and `code` and [link](http://example.com).'; expect(extractFirstSentence(content, 20)).toBe('This has bold and italic and code and link.'); }); it('handles empty content', () => { expect(extractFirstSentence('', 20)).toBe(''); expect(extractFirstSentence(' ', 20)).toBe(''); }); it('handles content with no sentence boundary', () => { const content = 'No period at the end'; expect(extractFirstSentence(content, 20)).toBe('No period at the end'); }); it('handles exclamation and question marks', () => { expect(extractFirstSentence('Is this a question? Yes it is.', 20)).toBe('Is this a question?'); expect(extractFirstSentence('Watch out! Be careful.', 20)).toBe('Watch out!'); }); }); describe('extractHeadings', () => { it('extracts all levels of markdown headings', () => { const content = '# H1\n## H2\n### H3\nSome text\n#### H4'; expect(extractHeadings(content)).toEqual(['H1', 'H2', 'H3', 'H4']); }); it('returns empty array for content without headings', () => { expect(extractHeadings('Just plain text\nMore text')).toEqual([]); }); it('handles empty content', () => { expect(extractHeadings('')).toEqual([]); }); it('trims heading text', () => { const content = '# Spaced Heading \n## Another '; expect(extractHeadings(content)).toEqual(['Spaced Heading', 'Another']); }); }); describe('PromptSummaryService', () => { it('uses regex fallback when no LLM', async () => { const service = new PromptSummaryService(null); const result = await service.generateSummary('# Overview\n\nThis is a test document. It has content.\n\n## Section One\n\n## Section Two'); expect(result.summary).toBe('This is a test document.'); expect(result.chapters).toEqual(['Overview', 'Section One', 'Section Two']); }); it('uses LLM when available', async () => { const mockLlm: LlmSummaryGenerator = { generate: vi.fn(async () => ({ summary: 'LLM-generated summary', chapters: ['LLM Chapter 1'], })), }; const service = new PromptSummaryService(mockLlm); const result = await service.generateSummary('Some content'); expect(result.summary).toBe('LLM-generated summary'); expect(result.chapters).toEqual(['LLM Chapter 1']); expect(mockLlm.generate).toHaveBeenCalledWith('Some content'); }); it('falls back to regex on LLM failure', async () => { const mockLlm: LlmSummaryGenerator = { generate: vi.fn(async () => { throw new Error('LLM unavailable'); }), }; const service = new PromptSummaryService(mockLlm); const result = await service.generateSummary('Fallback content here. Second sentence.'); expect(result.summary).toBe('Fallback content here.'); expect(mockLlm.generate).toHaveBeenCalled(); }); it('generateWithRegex works directly', () => { const service = new PromptSummaryService(null); const result = service.generateWithRegex('# Title\n\nContent line. More.\n\n## Chapter A\n\n## Chapter B'); expect(result.summary).toBe('Content line.'); expect(result.chapters).toEqual(['Title', 'Chapter A', 'Chapter B']); }); });