Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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