|
|
"""Unit tests for bioRxiv tool.""" |
|
|
|
|
|
import pytest |
|
|
import respx |
|
|
from httpx import Response |
|
|
|
|
|
from src.tools.biorxiv import BioRxivTool |
|
|
from src.utils.models import Evidence |
|
|
|
|
|
|
|
|
@pytest.fixture |
|
|
def mock_biorxiv_response(): |
|
|
"""Mock bioRxiv API response.""" |
|
|
return { |
|
|
"collection": [ |
|
|
{ |
|
|
"doi": "10.1101/2024.01.15.24301234", |
|
|
"title": "Metformin repurposing for Alzheimer's disease: a systematic review", |
|
|
"authors": "Smith, John; Jones, Alice; Brown, Bob", |
|
|
"date": "2024-01-15", |
|
|
"category": "neurology", |
|
|
"abstract": "Background: Metformin has shown neuroprotective effects. " |
|
|
"We conducted a systematic review of metformin's potential " |
|
|
"for Alzheimer's disease treatment.", |
|
|
}, |
|
|
{ |
|
|
"doi": "10.1101/2024.01.10.24301111", |
|
|
"title": "COVID-19 vaccine efficacy study", |
|
|
"authors": "Wilson, C", |
|
|
"date": "2024-01-10", |
|
|
"category": "infectious diseases", |
|
|
"abstract": "This study evaluates COVID-19 vaccine efficacy.", |
|
|
}, |
|
|
], |
|
|
"messages": [{"status": "ok", "count": 2}], |
|
|
} |
|
|
|
|
|
|
|
|
class TestBioRxivTool: |
|
|
"""Tests for BioRxivTool.""" |
|
|
|
|
|
def test_tool_name(self): |
|
|
"""Tool should have correct name.""" |
|
|
tool = BioRxivTool() |
|
|
assert tool.name == "biorxiv" |
|
|
|
|
|
def test_default_server_is_medrxiv(self): |
|
|
"""Default server should be medRxiv for medical relevance.""" |
|
|
tool = BioRxivTool() |
|
|
assert tool.server == "medrxiv" |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_returns_evidence(self, mock_biorxiv_response): |
|
|
"""Search should return Evidence objects.""" |
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
return_value=Response(200, json=mock_biorxiv_response) |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
results = await tool.search("metformin alzheimer", max_results=5) |
|
|
|
|
|
assert len(results) == 1 |
|
|
assert isinstance(results[0], Evidence) |
|
|
assert results[0].citation.source == "biorxiv" |
|
|
assert "metformin" in results[0].citation.title.lower() |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_filters_by_keywords(self, mock_biorxiv_response): |
|
|
"""Search should filter papers by query keywords.""" |
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
return_value=Response(200, json=mock_biorxiv_response) |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
|
|
|
|
|
|
results = await tool.search("metformin") |
|
|
assert len(results) == 1 |
|
|
assert "metformin" in results[0].citation.title.lower() |
|
|
|
|
|
|
|
|
results = await tool.search("covid vaccine") |
|
|
assert len(results) == 1 |
|
|
assert "covid" in results[0].citation.title.lower() |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_marks_as_preprint(self, mock_biorxiv_response): |
|
|
"""Evidence content should note it's a preprint.""" |
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
return_value=Response(200, json=mock_biorxiv_response) |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
results = await tool.search("metformin") |
|
|
|
|
|
assert "PREPRINT" in results[0].content |
|
|
assert "Not peer-reviewed" in results[0].content |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_empty_results(self): |
|
|
"""Search should handle empty results gracefully.""" |
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
return_value=Response(200, json={"collection": [], "messages": []}) |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
results = await tool.search("xyznonexistent") |
|
|
|
|
|
assert results == [] |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_api_error(self): |
|
|
"""Search should raise SearchError on API failure.""" |
|
|
from src.utils.exceptions import SearchError |
|
|
|
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
return_value=Response(500, text="Internal Server Error") |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
|
|
|
with pytest.raises(SearchError): |
|
|
await tool.search("metformin") |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
@respx.mock |
|
|
async def test_search_network_error(self): |
|
|
"""Search should raise SearchError on network failure.""" |
|
|
import httpx |
|
|
|
|
|
from src.utils.exceptions import SearchError |
|
|
|
|
|
respx.get(url__startswith="https://api.biorxiv.org/details").mock( |
|
|
side_effect=httpx.RequestError("Network connection failed") |
|
|
) |
|
|
|
|
|
tool = BioRxivTool() |
|
|
|
|
|
with pytest.raises(SearchError) as exc_info: |
|
|
await tool.search("metformin") |
|
|
|
|
|
assert "connection failed" in str(exc_info.value) |
|
|
|
|
|
def test_extract_terms(self): |
|
|
"""Should extract meaningful search terms.""" |
|
|
tool = BioRxivTool() |
|
|
|
|
|
terms = tool._extract_terms("metformin for Alzheimer's disease") |
|
|
|
|
|
assert "metformin" in terms |
|
|
assert "alzheimer" in terms |
|
|
assert "disease" in terms |
|
|
assert "for" not in terms |
|
|
assert "the" not in terms |
|
|
|
|
|
|
|
|
class TestBioRxivIntegration: |
|
|
"""Integration tests (marked for separate run).""" |
|
|
|
|
|
@pytest.mark.integration |
|
|
@pytest.mark.asyncio |
|
|
async def test_real_api_call(self): |
|
|
"""Test actual API call (requires network).""" |
|
|
tool = BioRxivTool(days=30) |
|
|
results = await tool.search("diabetes", max_results=3) |
|
|
|
|
|
|
|
|
|
|
|
assert isinstance(results, list) |
|
|
if results: |
|
|
r = results[0] |
|
|
assert isinstance(r, Evidence) |
|
|
assert r.citation.source == "biorxiv" |
|
|
|