"""Tests for codex.sources.semanticscholar.""" from __future__ import annotations import httpx import pytest from codex.models import Citation from codex.sources import semanticscholar # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _SAMPLE_REFS_RESPONSE = { "data": [ { "citedPaper": { "paperId": "abc123", "externalIds": {"DOI": "10.1000/xyz1"}, }, "contexts": ["This approach was first described in [1]."], }, { "citedPaper": { "paperId": "def456", "externalIds": {"ArXiv": "2301.07041"}, }, "contexts": [], }, ] } _SAMPLE_RECS_RESPONSE = { "recommendedPapers": [ {"paperId": "rec_id_1"}, {"paperId": "rec_id_2"}, {"paperId": "rec_id_3"}, ] } # --------------------------------------------------------------------------- # fetch_references # --------------------------------------------------------------------------- def test_fetch_references_returns_citations(monkeypatch: pytest.MonkeyPatch) -> None: """fetch_references maps API response to Citation objects correctly.""" def mock_get(url: str, params: dict[str, object] | None = None) -> httpx.Response: return httpx.Response(200, json=_SAMPLE_REFS_RESPONSE) monkeypatch.setattr(semanticscholar, "_get", mock_get) citations = semanticscholar.fetch_references("abc123") assert len(citations) == 2 assert all(isinstance(c, Citation) for c in citations) # First citation uses DOI and has context assert citations[0].citing_id == "abc123" assert citations[0].cited_id == "10.1000/xyz1" assert citations[0].context == "This approach was first described in [1]." # Second citation falls back to ArXiv ID, no context assert citations[1].citing_id == "abc123" assert citations[1].cited_id == "2301.07041" assert citations[1].context is None def test_fetch_references_404_returns_empty(monkeypatch: pytest.MonkeyPatch) -> None: """A 404 response should return an empty list.""" def mock_get(url: str, params: dict[str, object] | None = None) -> httpx.Response: raise httpx.HTTPStatusError( "Not Found", request=httpx.Request("GET", url), response=httpx.Response(404, request=httpx.Request("GET", url)), ) monkeypatch.setattr(semanticscholar, "_get", mock_get) result = semanticscholar.fetch_references("nonexistent") assert result == [] # --------------------------------------------------------------------------- # fetch_recommendations # --------------------------------------------------------------------------- def test_fetch_recommendations_returns_list_of_str( monkeypatch: pytest.MonkeyPatch, ) -> None: """fetch_recommendations returns a flat list of paperId strings.""" def mock_get(url: str, params: dict[str, object] | None = None) -> httpx.Response: return httpx.Response(200, json=_SAMPLE_RECS_RESPONSE) monkeypatch.setattr(semanticscholar, "_get", mock_get) result = semanticscholar.fetch_recommendations("abc123", limit=3) assert isinstance(result, list) assert len(result) == 3 assert all(isinstance(r, str) for r in result) assert result == ["rec_id_1", "rec_id_2", "rec_id_3"] def test_fetch_recommendations_404_returns_empty( monkeypatch: pytest.MonkeyPatch, ) -> None: """A 404 response should return an empty list.""" def mock_get(url: str, params: dict[str, object] | None = None) -> httpx.Response: raise httpx.HTTPStatusError( "Not Found", request=httpx.Request("GET", url), response=httpx.Response(404, request=httpx.Request("GET", url)), ) monkeypatch.setattr(semanticscholar, "_get", mock_get) result = semanticscholar.fetch_recommendations("nonexistent") assert result == []