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
405 lines
14 KiB
Python
405 lines
14 KiB
Python
"""
|
|
Tests for web/services/pdf_extraction_service.py
|
|
|
|
Tests cover:
|
|
- PDFExtractionService.extract_text_and_metadata()
|
|
- PDFExtractionService.extract_batch()
|
|
- get_pdf_extraction_service() singleton
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
|
|
class TestExtractTextAndMetadata:
|
|
"""Tests for extract_text_and_metadata method."""
|
|
|
|
def test_extract_text_and_metadata_success(self):
|
|
"""Test successful text extraction from PDF."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page = MagicMock()
|
|
mock_page.extract_text.return_value = "Extracted text from page 1"
|
|
mock_pdf.pages = [mock_page]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "test.pdf"
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["text"] == "Extracted text from page 1"
|
|
assert result["pages"] == 1
|
|
assert result["filename"] == "test.pdf"
|
|
assert result["size"] == len(mock_pdf_content)
|
|
assert result["error"] is None
|
|
|
|
def test_extract_text_and_metadata_multiple_pages(self):
|
|
"""Test extraction from multi-page PDF."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page1 = MagicMock()
|
|
mock_page1.extract_text.return_value = "Page 1 text"
|
|
mock_page2 = MagicMock()
|
|
mock_page2.extract_text.return_value = "Page 2 text"
|
|
mock_page3 = MagicMock()
|
|
mock_page3.extract_text.return_value = "Page 3 text"
|
|
mock_pdf.pages = [mock_page1, mock_page2, mock_page3]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "multipage.pdf"
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert "Page 1 text" in result["text"]
|
|
assert "Page 2 text" in result["text"]
|
|
assert "Page 3 text" in result["text"]
|
|
assert result["pages"] == 3
|
|
|
|
def test_extract_text_and_metadata_no_text(self):
|
|
"""Test extraction when PDF has no extractable text."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page = MagicMock()
|
|
mock_page.extract_text.return_value = ""
|
|
mock_pdf.pages = [mock_page]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "empty.pdf"
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert result["text"] == ""
|
|
assert "No extractable text found" in result["error"]
|
|
|
|
def test_extract_text_and_metadata_whitespace_only(self):
|
|
"""Test extraction when PDF has only whitespace."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page = MagicMock()
|
|
mock_page.extract_text.return_value = " \n\t "
|
|
mock_pdf.pages = [mock_page]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "whitespace.pdf"
|
|
)
|
|
|
|
assert result["success"] is False
|
|
|
|
def test_extract_text_and_metadata_page_returns_none(self):
|
|
"""Test extraction when a page returns None."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page1 = MagicMock()
|
|
mock_page1.extract_text.return_value = "Page 1"
|
|
mock_page2 = MagicMock()
|
|
mock_page2.extract_text.return_value = None
|
|
mock_pdf.pages = [mock_page1, mock_page2]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "partial.pdf"
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["text"] == "Page 1"
|
|
assert result["pages"] == 2
|
|
|
|
def test_extract_text_and_metadata_exception(self):
|
|
"""Test extraction when pdfplumber raises exception."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"invalid pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdfplumber.open.side_effect = Exception("Invalid PDF")
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "invalid.pdf"
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert result["text"] == ""
|
|
assert result["pages"] == 0
|
|
assert "Failed to extract text from PDF" in result["error"]
|
|
|
|
def test_extract_text_and_metadata_strips_text(self):
|
|
"""Test that extracted text is stripped."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
mock_pdf_content = b"fake pdf content"
|
|
|
|
with patch(
|
|
"local_deep_research.web.services.pdf_extraction_service.pdfplumber"
|
|
) as mock_pdfplumber:
|
|
mock_pdf = MagicMock()
|
|
mock_page = MagicMock()
|
|
mock_page.extract_text.return_value = " Text with spaces "
|
|
mock_pdf.pages = [mock_page]
|
|
mock_pdf.__enter__ = Mock(return_value=mock_pdf)
|
|
mock_pdf.__exit__ = Mock(return_value=False)
|
|
mock_pdfplumber.open.return_value = mock_pdf
|
|
|
|
result = PDFExtractionService.extract_text_and_metadata(
|
|
mock_pdf_content, "test.pdf"
|
|
)
|
|
|
|
assert result["text"] == "Text with spaces"
|
|
|
|
|
|
class TestExtractBatch:
|
|
"""Tests for extract_batch method."""
|
|
|
|
def test_extract_batch_single_file_success(self):
|
|
"""Test batch extraction with single successful file."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
files_data = [{"content": b"pdf1", "filename": "file1.pdf"}]
|
|
|
|
with patch.object(
|
|
PDFExtractionService,
|
|
"extract_text_and_metadata",
|
|
return_value={
|
|
"text": "Extracted",
|
|
"pages": 1,
|
|
"size": 4,
|
|
"filename": "file1.pdf",
|
|
"success": True,
|
|
"error": None,
|
|
},
|
|
):
|
|
result = PDFExtractionService.extract_batch(files_data)
|
|
|
|
assert result["total_files"] == 1
|
|
assert result["successful"] == 1
|
|
assert result["failed"] == 0
|
|
assert len(result["results"]) == 1
|
|
assert len(result["errors"]) == 0
|
|
|
|
def test_extract_batch_multiple_files_success(self):
|
|
"""Test batch extraction with multiple successful files."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
files_data = [
|
|
{"content": b"pdf1", "filename": "file1.pdf"},
|
|
{"content": b"pdf2", "filename": "file2.pdf"},
|
|
{"content": b"pdf3", "filename": "file3.pdf"},
|
|
]
|
|
|
|
with patch.object(
|
|
PDFExtractionService,
|
|
"extract_text_and_metadata",
|
|
return_value={
|
|
"text": "Extracted",
|
|
"pages": 1,
|
|
"size": 4,
|
|
"filename": "test.pdf",
|
|
"success": True,
|
|
"error": None,
|
|
},
|
|
):
|
|
result = PDFExtractionService.extract_batch(files_data)
|
|
|
|
assert result["total_files"] == 3
|
|
assert result["successful"] == 3
|
|
assert result["failed"] == 0
|
|
|
|
def test_extract_batch_with_failures(self):
|
|
"""Test batch extraction with some failures."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
files_data = [
|
|
{"content": b"pdf1", "filename": "good.pdf"},
|
|
{"content": b"pdf2", "filename": "bad.pdf"},
|
|
]
|
|
|
|
def mock_extract(content, filename):
|
|
if filename == "good.pdf":
|
|
return {
|
|
"text": "Extracted",
|
|
"pages": 1,
|
|
"size": 4,
|
|
"filename": filename,
|
|
"success": True,
|
|
"error": None,
|
|
}
|
|
return {
|
|
"text": "",
|
|
"pages": 0,
|
|
"size": 4,
|
|
"filename": filename,
|
|
"success": False,
|
|
"error": "Failed to extract",
|
|
}
|
|
|
|
with patch.object(
|
|
PDFExtractionService,
|
|
"extract_text_and_metadata",
|
|
side_effect=mock_extract,
|
|
):
|
|
result = PDFExtractionService.extract_batch(files_data)
|
|
|
|
assert result["total_files"] == 2
|
|
assert result["successful"] == 1
|
|
assert result["failed"] == 1
|
|
assert len(result["errors"]) == 1
|
|
assert "bad.pdf" in result["errors"][0]
|
|
|
|
def test_extract_batch_empty_list(self):
|
|
"""Test batch extraction with empty list."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
result = PDFExtractionService.extract_batch([])
|
|
|
|
assert result["total_files"] == 0
|
|
assert result["successful"] == 0
|
|
assert result["failed"] == 0
|
|
assert len(result["results"]) == 0
|
|
|
|
def test_extract_batch_all_failures(self):
|
|
"""Test batch extraction when all files fail."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
files_data = [
|
|
{"content": b"pdf1", "filename": "fail1.pdf"},
|
|
{"content": b"pdf2", "filename": "fail2.pdf"},
|
|
]
|
|
|
|
with patch.object(
|
|
PDFExtractionService,
|
|
"extract_text_and_metadata",
|
|
return_value={
|
|
"text": "",
|
|
"pages": 0,
|
|
"size": 4,
|
|
"filename": "fail.pdf",
|
|
"success": False,
|
|
"error": "Failed",
|
|
},
|
|
):
|
|
result = PDFExtractionService.extract_batch(files_data)
|
|
|
|
assert result["total_files"] == 2
|
|
assert result["successful"] == 0
|
|
assert result["failed"] == 2
|
|
assert len(result["errors"]) == 2
|
|
|
|
|
|
class TestGetPdfExtractionService:
|
|
"""Tests for get_pdf_extraction_service singleton."""
|
|
|
|
def test_returns_pdf_extraction_service_instance(self):
|
|
"""Test that function returns PDFExtractionService instance."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
get_pdf_extraction_service,
|
|
PDFExtractionService,
|
|
)
|
|
|
|
service = get_pdf_extraction_service()
|
|
|
|
assert isinstance(service, PDFExtractionService)
|
|
|
|
def test_returns_same_instance(self):
|
|
"""Test that function returns the same singleton instance."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
get_pdf_extraction_service,
|
|
)
|
|
|
|
service1 = get_pdf_extraction_service()
|
|
service2 = get_pdf_extraction_service()
|
|
|
|
assert service1 is service2
|
|
|
|
|
|
class TestPDFExtractionServiceClass:
|
|
"""Tests for PDFExtractionService class."""
|
|
|
|
def test_class_has_static_methods(self):
|
|
"""Test that class has required static methods."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
assert hasattr(PDFExtractionService, "extract_text_and_metadata")
|
|
assert hasattr(PDFExtractionService, "extract_batch")
|
|
assert callable(PDFExtractionService.extract_text_and_metadata)
|
|
assert callable(PDFExtractionService.extract_batch)
|
|
|
|
def test_instance_can_be_created(self):
|
|
"""Test that PDFExtractionService can be instantiated."""
|
|
from local_deep_research.web.services.pdf_extraction_service import (
|
|
PDFExtractionService,
|
|
)
|
|
|
|
service = PDFExtractionService()
|
|
|
|
assert service is not None
|