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
407 lines
12 KiB
Python
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
|