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

367 lines
13 KiB
Python

"""
Integration tests for download_content tool in MCP strategy.
"""
from unittest.mock import MagicMock, patch
from local_deep_research.content_fetcher import ContentFetcher
from local_deep_research.research_library.downloaders.base import DownloadResult
class TestMCPDownloadContentTool:
"""Test download_content tool integration with MCP strategy."""
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_execute_download_content_success(self, mock_get_downloader):
"""Test successful content download via MCP strategy executor."""
# Setup mock
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=b"# Research Paper\n\nThis is the abstract of the paper discussing important findings.",
is_success=True,
)
mock_downloader.get_metadata.return_value = {
"title": "Important Research Paper",
"author": "Dr. Smith",
}
mock_get_downloader.return_value = mock_downloader
# Import and test the executor pattern
from local_deep_research.content_fetcher import ContentFetcher
fetcher = ContentFetcher()
result = fetcher.fetch(
"https://arxiv.org/abs/2301.12345",
max_length=5000,
)
assert result["status"] == "success"
assert "abstract" in result["content"].lower()
assert result["source_type"] == "arXiv"
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_execute_download_content_with_truncation(
self, mock_get_downloader
):
"""Test content truncation for very long papers."""
# Create a very long paper content
long_content = "Introduction. " * 5000 # ~70k characters
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=long_content.encode("utf-8"),
is_success=True,
)
mock_downloader.get_metadata.return_value = {}
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch(
"https://arxiv.org/abs/2301.12345",
max_length=1000,
)
assert result["status"] == "success"
assert len(result["content"]) < 2000 # Should be truncated
assert "truncated" in result["content"].lower()
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_execute_download_content_paper_not_found(
self, mock_get_downloader
):
"""Test handling when paper is not available."""
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=None,
is_success=False,
skip_reason="Paper not found or requires subscription",
)
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch("https://example.com/paywalled-paper")
assert result["status"] == "error"
assert (
"subscription" in result["error"].lower()
or "not found" in result["error"].lower()
)
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
@patch(
"local_deep_research.content_fetcher.fetcher.policy_aware_validate_url",
return_value=True,
)
def test_execute_download_content_timeout(
self, mock_validate, mock_get_downloader
):
"""Test handling of timeout during download."""
mock_downloader = MagicMock()
mock_downloader.download_with_result.side_effect = TimeoutError(
"Connection timed out"
)
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch("https://slow-server.example.com/paper")
assert result["status"] == "error"
assert (
"timed out" in result["error"].lower()
or "timeout" in result["error"].lower()
)
class TestDownloadContentWithRealURLPatterns:
"""Test with real-world URL patterns."""
def test_arxiv_url_variants(self):
"""Test various arXiv URL formats are recognized."""
fetcher = ContentFetcher()
# Standard abstract
info = fetcher.get_url_info("https://arxiv.org/abs/2301.12345")
assert info["url_type"] == "arxiv"
assert info["extracted_id"] == "2301.12345"
# With version
info = fetcher.get_url_info("https://arxiv.org/abs/2301.12345v3")
assert info["url_type"] == "arxiv"
assert info["extracted_id"] == "2301.12345v3"
# PDF link
info = fetcher.get_url_info("https://arxiv.org/pdf/2301.12345.pdf")
assert info["url_type"] == "arxiv"
# HTML version
info = fetcher.get_url_info("https://arxiv.org/html/2301.12345")
assert info["url_type"] == "arxiv"
# ar5iv (HTML rendering)
info = fetcher.get_url_info("https://ar5iv.org/abs/2301.12345")
assert info["url_type"] == "arxiv"
# Old format (pre-2007)
info = fetcher.get_url_info("https://arxiv.org/abs/hep-th/9901001")
assert info["url_type"] == "arxiv"
def test_pubmed_url_variants(self):
"""Test various PubMed URL formats."""
fetcher = ContentFetcher()
# Standard PubMed
info = fetcher.get_url_info("https://pubmed.ncbi.nlm.nih.gov/12345678")
assert info["url_type"] == "pubmed"
assert info["extracted_id"] == "12345678"
# With trailing slash
info = fetcher.get_url_info("https://pubmed.ncbi.nlm.nih.gov/12345678/")
assert info["url_type"] == "pubmed"
# Old format
info = fetcher.get_url_info(
"https://www.ncbi.nlm.nih.gov/pubmed/12345678"
)
assert info["url_type"] == "pubmed"
def test_pmc_url_variants(self):
"""Test various PMC URL formats."""
fetcher = ContentFetcher()
# Standard PMC
info = fetcher.get_url_info(
"https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1234567/"
)
assert info["url_type"] == "pmc"
assert info["extracted_id"] == "PMC1234567"
# Europe PMC
info = fetcher.get_url_info("https://europepmc.org/article/PMC/1234567")
assert info["url_type"] == "pmc"
def test_semantic_scholar_url_variants(self):
"""Test Semantic Scholar URL formats."""
fetcher = ContentFetcher()
# With paper title in URL
info = fetcher.get_url_info(
"https://www.semanticscholar.org/paper/Attention-Is-All-You-Need-Vaswani-Shazeer/abcdef1234567890abcdef1234567890abcdef12"
)
assert info["url_type"] == "semantic_scholar"
# API URL
info = fetcher.get_url_info(
"https://api.semanticscholar.org/v1/paper/abcd1234"
)
assert info["url_type"] == "semantic_scholar"
def test_biorxiv_medrxiv_url_variants(self):
"""Test bioRxiv and medRxiv URL formats."""
fetcher = ContentFetcher()
# bioRxiv
info = fetcher.get_url_info(
"https://www.biorxiv.org/content/10.1101/2021.01.01.123456v1"
)
assert info["url_type"] == "biorxiv"
# medRxiv
info = fetcher.get_url_info(
"https://www.medrxiv.org/content/10.1101/2021.01.01.123456v1"
)
assert info["url_type"] == "medrxiv"
def test_doi_url_variants(self):
"""Test DOI URL formats."""
fetcher = ContentFetcher()
# doi.org
info = fetcher.get_url_info("https://doi.org/10.1038/nature12373")
assert info["url_type"] == "doi"
assert "10.1038" in info["extracted_id"]
# dx.doi.org
info = fetcher.get_url_info(
"https://dx.doi.org/10.1126/science.1234567"
)
assert info["url_type"] == "doi"
class TestDownloadContentEdgeCases:
"""Test edge cases and error handling."""
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_empty_url(self, mock_get_downloader):
"""Test handling of empty URL."""
fetcher = ContentFetcher()
# Empty URL should still try to classify and fetch
info = fetcher.get_url_info("")
assert info["url_type"] == "html" # Falls back to HTML
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_malformed_url(self, mock_get_downloader):
"""Test handling of malformed URL."""
mock_downloader = MagicMock()
mock_downloader.download_with_result.side_effect = ValueError(
"Invalid URL"
)
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch("not-a-valid-url")
assert result["status"] == "error"
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_binary_pdf_content(self, mock_get_downloader):
"""Test handling of binary PDF content that can't be decoded as UTF-8."""
# Simulate PDF bytes that fail UTF-8 decoding
pdf_bytes = b"%PDF-1.4\x00\x01\x02\x03binary content"
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=pdf_bytes,
is_success=True,
)
mock_downloader.get_metadata.return_value = {}
mock_get_downloader.return_value = mock_downloader
# Mock the PDF text extraction to return extracted text
with patch(
"local_deep_research.research_library.downloaders.base.BaseDownloader.extract_text_from_pdf"
) as mock_extract:
mock_extract.return_value = "Extracted PDF text content"
fetcher = ContentFetcher()
result = fetcher.fetch("https://example.com/paper.pdf")
# Should attempt PDF extraction
assert result["status"] == "success" or result["status"] == "error"
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_rate_limited_response(self, mock_get_downloader):
"""Test handling of rate-limited responses."""
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=None,
is_success=False,
skip_reason="Rate limited - too many requests (429)",
)
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch("https://arxiv.org/abs/2301.12345")
assert result["status"] == "error"
assert "rate" in result["error"].lower() or "429" in result["error"]
@patch(
"local_deep_research.content_fetcher.fetcher.ContentFetcher._get_downloader"
)
def test_subscription_required(self, mock_get_downloader):
"""Test handling of paywalled content."""
mock_downloader = MagicMock()
mock_downloader.download_with_result.return_value = DownloadResult(
content=None,
is_success=False,
skip_reason="Subscription required to access full text",
)
mock_get_downloader.return_value = mock_downloader
fetcher = ContentFetcher()
result = fetcher.fetch("https://nature.com/articles/paywalled")
assert result["status"] == "error"
assert "subscription" in result["error"].lower()
class TestDownloadContentCaching:
"""Test downloader caching behavior."""
def test_downloader_reuse(self):
"""Test that downloaders are cached and reused."""
fetcher = ContentFetcher()
# Get HTML downloader twice
d1 = fetcher._get_downloader(
__import__(
"local_deep_research.content_fetcher.url_classifier",
fromlist=["URLType"],
).URLType.HTML
)
d2 = fetcher._get_downloader(
__import__(
"local_deep_research.content_fetcher.url_classifier",
fromlist=["URLType"],
).URLType.HTML
)
# Should be the same instance
assert d1 is d2
def test_different_downloaders_for_different_types(self):
"""Test that different URL types get different downloaders."""
from local_deep_research.content_fetcher.url_classifier import URLType
fetcher = ContentFetcher()
html_downloader = fetcher._get_downloader(URLType.HTML)
# Other downloaders may not be available, but HTML should always work
assert html_downloader is not None