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

407 lines
12 KiB
Python

"""
Fixtures for research_library tests.
Provides mocked database sessions, HTTP responses, and test data.
"""
import hashlib
import uuid
from contextlib import contextmanager
from typing import Generator
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from local_deep_research.database.models import Base
from local_deep_research.database.models.library import (
Collection,
Document,
DocumentCollection,
DocumentStatus,
SourceType,
)
# ============== Database Fixtures ==============
@pytest.fixture
def library_engine():
"""Create an in-memory SQLite database with all library models."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture
def library_session(library_engine) -> Generator[Session, None, None]:
"""Create a database session for library testing."""
SessionLocal = sessionmaker(bind=library_engine)
session = SessionLocal()
yield session
session.close()
@pytest.fixture
def mock_source_type(library_session) -> SourceType:
"""Create a mock SourceType for research downloads."""
source_type = SourceType(
id=str(uuid.uuid4()),
name="research_download",
display_name="Research Download",
description="Downloaded from research sources",
icon="fas fa-download",
)
library_session.add(source_type)
library_session.commit()
return source_type
@pytest.fixture
def mock_upload_source_type(library_session) -> SourceType:
"""Create a mock SourceType for user uploads."""
source_type = SourceType(
id=str(uuid.uuid4()),
name="user_upload",
display_name="User Upload",
description="Uploaded by user",
icon="fas fa-upload",
)
library_session.add(source_type)
library_session.commit()
return source_type
@pytest.fixture
def mock_collection(library_session) -> Collection:
"""Create a default Library collection."""
collection = Collection(
id=str(uuid.uuid4()),
name="Library",
description="Default library collection for research downloads",
is_default=True,
collection_type="default_library",
)
library_session.add(collection)
library_session.commit()
return collection
@pytest.fixture
def mock_user_collection(library_session) -> Collection:
"""Create a user-defined collection."""
collection = Collection(
id=str(uuid.uuid4()),
name="Test Collection",
description="A test collection for user uploads",
is_default=False,
collection_type="user_collection",
)
library_session.add(collection)
library_session.commit()
return collection
@pytest.fixture
def mock_document(
library_session, mock_source_type, mock_collection
) -> Document:
"""Create a mock completed document with text content."""
doc_content = "This is the full text content of a test research paper."
doc = Document(
id=str(uuid.uuid4()),
source_type_id=mock_source_type.id,
document_hash=hashlib.sha256(doc_content.encode()).hexdigest(),
original_url="https://arxiv.org/abs/2301.12345",
file_path="pdfs/arxiv_2301.12345.pdf",
file_size=1024000,
file_type="pdf",
mime_type="application/pdf",
title="Test Paper on Quantum Computing",
text_content=doc_content,
status=DocumentStatus.COMPLETED,
doi="10.1234/test.2024.001",
arxiv_id="2301.12345",
)
library_session.add(doc)
library_session.commit()
# Link document to collection
doc_coll = DocumentCollection(
document_id=doc.id,
collection_id=mock_collection.id,
indexed=False,
)
library_session.add(doc_coll)
library_session.commit()
return doc
@pytest.fixture
def mock_pending_document(library_session, mock_source_type) -> Document:
"""Create a mock pending document (not yet downloaded)."""
doc = Document(
id=str(uuid.uuid4()),
source_type_id=mock_source_type.id,
document_hash=hashlib.sha256(b"pending").hexdigest(),
original_url="https://arxiv.org/abs/2401.99999",
file_size=0,
file_type="pdf",
status=DocumentStatus.PENDING,
title="Pending Download Paper",
)
library_session.add(doc)
library_session.commit()
return doc
# ============== HTTP Response Mocking ==============
@pytest.fixture
def mock_http_response(mocker):
"""Factory for creating mock HTTP responses."""
def _create_response(
status_code=200,
content=b"",
headers=None,
json_data=None,
text=None,
):
response = mocker.Mock()
response.status_code = status_code
response.content = content
response.headers = headers or {"content-type": "application/pdf"}
response.ok = status_code < 400
if json_data is not None:
response.json.return_value = json_data
response.text = str(json_data)
elif text is not None:
response.text = text
else:
response.text = (
content.decode() if isinstance(content, bytes) else str(content)
)
return response
return _create_response
@pytest.fixture
def mock_pdf_content():
"""Return minimal valid PDF bytes for testing."""
# Minimal valid PDF structure
return b"""%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer << /Size 4 /Root 1 0 R >>
startxref
193
%%EOF"""
@pytest.fixture
def mock_arxiv_api_response():
"""Mock arXiv API XML response for metadata."""
return """<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:arxiv="http://arxiv.org/schemas/atom">
<entry>
<title>Test Paper on Quantum Computing</title>
<id>http://arxiv.org/abs/2301.12345</id>
<summary>This is a test paper abstract about quantum computing advancements.</summary>
<published>2023-01-15T00:00:00Z</published>
<author><name>John Doe</name></author>
<author><name>Jane Smith</name></author>
<arxiv:category term="quant-ph"/>
<arxiv:category term="cs.AI"/>
</entry>
</feed>"""
@pytest.fixture
def mock_pubmed_elink_response():
"""Mock PubMed NCBI elink API response."""
return {
"linksets": [{"linksetdbs": [{"dbto": "pmc", "links": ["9876543"]}]}]
}
@pytest.fixture
def mock_europe_pmc_response():
"""Mock Europe PMC API JSON response."""
return {
"resultList": {
"result": [
{
"id": "12345678",
"pmcid": "PMC9876543",
"isOpenAccess": "Y",
"hasPDF": "Y",
"journalTitle": "Nature Medicine",
"title": "Test Medical Research Paper",
}
]
}
}
@pytest.fixture
def mock_semantic_scholar_paper_response():
"""Mock Semantic Scholar paper API response."""
return {
"paperId": "a" * 40,
"title": "Deep Learning for Natural Language Processing",
"openAccessPdf": {
"url": "https://arxiv.org/pdf/2301.12345.pdf",
"status": "GREEN",
},
}
@pytest.fixture
def mock_openalex_work_response():
"""Mock OpenAlex work API response."""
return {
"id": "https://openalex.org/W123456789",
"open_access": {"is_oa": True, "oa_status": "gold"},
"best_oa_location": {
"pdf_url": "https://example.com/paper.pdf",
"landing_page_url": "https://example.com/paper",
},
}
@pytest.fixture
def mock_biorxiv_page_html():
"""Mock bioRxiv HTML page for metadata extraction."""
return """<!DOCTYPE html>
<html>
<head>
<meta name="DC.Title" content="Novel CRISPR Gene Editing Technique">
<meta name="DC.Creator" content="Alice Johnson, Bob Williams">
<meta name="DC.Description" content="We present a novel approach to gene editing using CRISPR-Cas9...">
</head>
<body></body>
</html>"""
# ============== Service Mocking ==============
@pytest.fixture
def mock_settings_manager(mocker):
"""Mock settings manager for download service."""
mock = mocker.Mock()
mock.get_setting.side_effect = lambda key, default=None: {
"research_library.storage_path": "/tmp/test_library",
"search.engine.web.semantic_scholar.api_key": None,
"local_search_embedding_model": "all-MiniLM-L6-v2",
"local_search_embedding_provider": "sentence_transformers",
"local_search_chunk_size": 1000,
"local_search_chunk_overlap": 200,
}.get(key, default)
return mock
@pytest.fixture
def temp_library_dir(tmp_path):
"""Create temporary library directory structure."""
library_path = tmp_path / "library"
library_path.mkdir()
(library_path / "pdfs").mkdir()
(library_path / "uploads").mkdir()
return library_path
@pytest.fixture
def mock_embedding_manager(mocker):
"""Mock LocalEmbeddingManager to avoid loading actual models."""
mock = mocker.Mock()
mock.embeddings = mocker.Mock()
mock.embeddings.embed_query.return_value = [0.1] * 384
mock.embeddings.embed_documents.return_value = [[0.1] * 384]
mock._store_chunks_to_db.return_value = ["chunk_1", "chunk_2"]
mock._delete_chunks_from_db.return_value = 5
return mock
@pytest.fixture
def mock_rate_tracker(mocker):
"""Mock AdaptiveRateLimitTracker to avoid rate limit delays."""
mock = mocker.Mock()
mock.apply_rate_limit.return_value = 0.0 # No wait
mock.record_outcome.return_value = None
return mock
# ============== Session Context Mocking ==============
@pytest.fixture
def mock_db_session_context(library_session, mocker):
"""Mock get_user_db_session context manager to return test session."""
@contextmanager
def _mock_session(*args, **kwargs):
yield library_session
mocker.patch(
"local_deep_research.database.session_context.get_user_db_session",
_mock_session,
)
return library_session
# ============== Downloader Test Helpers ==============
@pytest.fixture
def mock_requests_session(mocker, mock_pdf_content):
"""Mock requests.Session for all downloaders."""
mock_session = mocker.Mock()
mock_session.headers = {"User-Agent": "Test Agent"}
# Default successful PDF response
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.content = mock_pdf_content
mock_response.headers = {"content-type": "application/pdf"}
mock_response.ok = True
mock_session.get.return_value = mock_response
return mock_session
@pytest.fixture
def create_downloader_with_mock_session(
mocker, mock_requests_session, mock_rate_tracker
):
"""Factory to create a downloader with mocked session and rate tracker."""
def _create(downloader_class, **kwargs):
# Patch the session and rate tracker
mocker.patch.object(
downloader_class,
"__init__",
lambda self, timeout=30: None,
)
downloader = downloader_class.__new__(downloader_class)
downloader.timeout = kwargs.get("timeout", 30)
downloader.session = mock_requests_session
downloader.rate_tracker = mock_rate_tracker
return downloader
return _create