7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
516 lines
16 KiB
Python
516 lines
16 KiB
Python
"""
|
|
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
|
|
|
|

|
|
|
|
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 = (
|
|
"<h1>Title</h1><p>Content</p>"
|
|
)
|
|
|
|
html = mock_service._markdown_to_html("# Title\n\nContent")
|
|
|
|
assert "<h1>" 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
|