""" Extended Tests for PDF Service Phase 19: Socket & Real-time Services - PDF Service Tests Tests PDF generation and extraction functionality. """ import pytest from datetime import datetime, UTC from unittest.mock import patch, MagicMock class TestPDFGeneration: """Tests for PDF generation functionality""" @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_from_markdown(self, mock_service_cls): """Test PDF generation from markdown""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 mock content" markdown = "# Test Title\n\nThis is a test paragraph." result = mock_service.markdown_to_pdf(markdown) assert result.startswith(b"%PDF") @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_with_images(self, mock_service_cls): """Test PDF generation with embedded images""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with images" markdown = """ # Report with Image ![Chart](data:image/png;base64,iVBORw0KGgo=) This is a caption. """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_with_tables(self, mock_service_cls): """Test PDF generation with tables""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with tables" markdown = """ # Report with Table | Column 1 | Column 2 | |----------|----------| | Value 1 | Value 2 | | Value 3 | Value 4 | """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_with_code_blocks(self, mock_service_cls): """Test PDF generation with code blocks""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with code" markdown = """ # Code Example ```python def hello(): print("Hello, World!") ``` """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_with_math(self, mock_service_cls): """Test PDF generation with math expressions""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with math" markdown = """ # Math Example The quadratic formula is: $x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}$ """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_unicode_content(self, mock_service_cls): """Test PDF generation with unicode content""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with unicode" markdown = """ # Unicode Test Chinese: 中文测试 Japanese: 日本語テスト Emoji: 🔬📊📈 """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_large_document(self, mock_service_cls): """Test PDF generation for large documents""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 large document" # Generate large markdown content sections = [ f"## Section {i}\n\nContent for section {i}.\n\n" for i in range(100) ] markdown = "# Large Document\n\n" + "".join(sections) result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_page_layout(self, mock_service_cls): """Test PDF page layout settings""" mock_service = MagicMock() mock_service._get_page_settings.return_value = { "size": "A4", "margins": { "top": "1.5cm", "bottom": "1.5cm", "left": "1.5cm", "right": "1.5cm", }, } settings = mock_service._get_page_settings() assert settings["size"] == "A4" @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_headers_footers(self, mock_service_cls): """Test PDF headers and footers""" mock_service = MagicMock() mock_service._add_headers_footers.return_value = True result = mock_service._add_headers_footers( "Test Report", {"page_numbers": True} ) assert result is True @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_table_of_contents(self, mock_service_cls): """Test PDF table of contents generation""" mock_service = MagicMock() mock_service._generate_toc.return_value = """ ## Table of Contents 1. [Section 1](#section-1) 2. [Section 2](#section-2) """ toc = mock_service._generate_toc(["Section 1", "Section 2"]) assert "Section 1" in toc @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_hyperlinks(self, mock_service_cls): """Test PDF hyperlink preservation""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with links" markdown = """ # Links Test Visit [Example](https://example.com) for more information. """ result = mock_service.markdown_to_pdf(markdown) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_metadata(self, mock_service_cls): """Test PDF metadata embedding""" mock_service = MagicMock() mock_service.markdown_to_pdf.return_value = b"%PDF-1.4 with metadata" result = mock_service.markdown_to_pdf( "# Test", metadata={ "title": "Test Report", "author": "Test Author", "created": datetime.now(UTC).isoformat(), }, ) assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_compression(self, mock_service_cls): """Test PDF compression""" mock_service = MagicMock() mock_service._compress_pdf.return_value = b"%PDF-1.4 compressed" original = b"%PDF-1.4 original content" * 1000 compressed = mock_service._compress_pdf(original) # Compressed should be smaller or equal assert len(compressed) <= len(original) @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_error_recovery(self, mock_service_cls): """Test error recovery during PDF generation""" mock_service = MagicMock() mock_service.markdown_to_pdf.side_effect = [ Exception("First attempt failed"), b"%PDF-1.4 success on retry", ] # First call fails with pytest.raises(Exception): mock_service.markdown_to_pdf("# Test") # Second call succeeds result = mock_service.markdown_to_pdf("# Test") assert result is not None @patch("local_deep_research.web.services.pdf_service.PDFService") def test_generate_pdf_timeout_handling(self, mock_service_cls): """Test timeout handling during generation""" mock_service = MagicMock() mock_service._generate_with_timeout.return_value = { "success": True, "pdf": b"%PDF-1.4", } result = mock_service._generate_with_timeout( "# Test", timeout_seconds=30 ) assert result["success"] is True class TestPDFExtraction: """Tests for PDF extraction functionality""" @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_text_from_pdf(self, mock_service_cls): """Test text extraction from PDF""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "Extracted text content", "pages": 5, "success": True, } result = mock_service.extract_text_and_metadata( b"%PDF-1.4 content", "test.pdf" ) assert result["success"] is True assert len(result["text"]) > 0 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_text_corrupted_pdf(self, mock_service_cls): """Test handling corrupted PDF""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "", "success": False, "error": "Invalid PDF format", } result = mock_service.extract_text_and_metadata( b"not a valid pdf", "corrupted.pdf" ) assert result["success"] is False assert "error" in result @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_text_encrypted_pdf(self, mock_service_cls): """Test handling encrypted PDF""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "", "success": False, "error": "PDF is encrypted", } result = mock_service.extract_text_and_metadata( b"%PDF-1.4 encrypted", "encrypted.pdf" ) assert result["success"] is False @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_text_scanned_pdf(self, mock_service_cls): """Test handling scanned (image-based) PDF""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "", "pages": 3, "success": True, "warning": "PDF appears to be image-based", } result = mock_service.extract_text_and_metadata( b"%PDF-1.4 scanned", "scanned.pdf" ) # May succeed but with empty or minimal text assert result["success"] is True @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_metadata_from_pdf(self, mock_service_cls): """Test metadata extraction""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "Content", "pages": 10, "size": 50000, "filename": "research.pdf", "success": True, } result = mock_service.extract_text_and_metadata( b"%PDF-1.4 content", "research.pdf" ) assert result["pages"] == 10 assert result["size"] == 50000 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_images_from_pdf(self, mock_service_cls): """Test image extraction from PDF""" mock_service = MagicMock() mock_service._extract_images.return_value = [ {"page": 1, "image": b"image1"}, {"page": 3, "image": b"image2"}, ] images = mock_service._extract_images(b"%PDF-1.4 with images") assert len(images) == 2 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_tables_from_pdf(self, mock_service_cls): """Test table extraction from PDF""" mock_service = MagicMock() mock_service._extract_tables.return_value = [ {"page": 1, "data": [["Header", "Value"], ["Row", "Data"]]} ] tables = mock_service._extract_tables(b"%PDF-1.4 with tables") assert len(tables) == 1 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_large_pdf_streaming(self, mock_service_cls): """Test streaming extraction for large PDFs""" mock_service = MagicMock() mock_service._extract_streaming.return_value = iter( [ {"page": 1, "text": "Page 1 text"}, {"page": 2, "text": "Page 2 text"}, ] ) pages = list(mock_service._extract_streaming(b"%PDF-1.4 large")) assert len(pages) == 2 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_pdf_page_selection(self, mock_service_cls): """Test extracting specific pages""" mock_service = MagicMock() mock_service._extract_pages.return_value = { "text": "Pages 5-10 content", "pages_extracted": [5, 6, 7, 8, 9, 10], } result = mock_service._extract_pages( b"%PDF-1.4 content", start_page=5, end_page=10 ) assert len(result["pages_extracted"]) == 6 @patch( "local_deep_research.web.services.pdf_extraction_service.PDFExtractionService" ) def test_extract_pdf_timeout_handling(self, mock_service_cls): """Test extraction timeout handling""" mock_service = MagicMock() mock_service.extract_text_and_metadata.return_value = { "text": "", "success": False, "error": "Extraction timeout", } result = mock_service.extract_text_and_metadata( b"%PDF-1.4 large complex pdf", "huge.pdf" ) # May fail due to timeout assert "error" in result or result["success"] is True class TestHTMLToMarkdown: """Tests for HTML to markdown conversion""" @patch("local_deep_research.web.services.pdf_service.PDFService") def test_markdown_to_html_conversion(self, mock_service_cls): """Test markdown to HTML conversion""" mock_service = MagicMock() mock_service._markdown_to_html.return_value = ( "

Title

Content

" ) html = mock_service._markdown_to_html("# Title\n\nContent") assert "

" in html @patch("local_deep_research.web.services.pdf_service.PDFService") def test_markdown_extensions(self, mock_service_cls): """Test markdown extensions are applied""" mock_service = MagicMock() mock_service._get_markdown_extensions.return_value = [ "tables", "fenced_code", "footnotes", "toc", ] extensions = mock_service._get_markdown_extensions() assert "tables" in extensions assert "fenced_code" in extensions class TestCSSGeneration: """Tests for CSS generation""" @patch("local_deep_research.web.services.pdf_service.PDFService") def test_default_css_generation(self, mock_service_cls): """Test default CSS is generated""" mock_service = MagicMock() mock_service._get_default_css.return_value = """ @page { size: A4; margin: 1.5cm; } body { font-family: Arial; font-size: 10pt; } """ css = mock_service._get_default_css() assert "@page" in css assert "A4" in css @patch("local_deep_research.web.services.pdf_service.PDFService") def test_custom_css_application(self, mock_service_cls): """Test custom CSS is applied""" mock_service = MagicMock() mock_service._apply_custom_css.return_value = """ body { font-size: 12pt; color: #333; } """ custom_css = "body { font-size: 12pt; color: #333; }" result = mock_service._apply_custom_css(custom_css) assert "12pt" in result class TestSingleton: """Tests for PDF service singleton""" def test_get_pdf_service_returns_instance(self): """Test get_pdf_service returns an instance""" from local_deep_research.web.services.pdf_service import get_pdf_service service = get_pdf_service() assert service is not None def test_get_pdf_service_singleton(self): """Test get_pdf_service returns same instance""" from local_deep_research.web.services.pdf_service import get_pdf_service service1 = get_pdf_service() service2 = get_pdf_service() assert service1 is service2