scholarr/tests/unit/test_unpaywall_resolution.py
2026-02-26 12:54:19 +01:00

142 lines
5.1 KiB
Python

from __future__ import annotations
from dataclasses import replace
from datetime import datetime, timezone
import pytest
from app.services.domains.publications.types import PublicationListItem
from app.services.domains.publication_identifiers.types import DisplayIdentifier
from app.services.domains.unpaywall import application as unpaywall_app
class _DummyAsyncClient:
def __init__(self, *args, **kwargs) -> None:
self.args = args
self.kwargs = kwargs
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return None
def _item(publication_id: int) -> PublicationListItem:
return PublicationListItem(
publication_id=publication_id,
scholar_profile_id=1,
scholar_label="Shinya Yamanaka",
title="Induction of pluripotent stem cells",
year=2007,
citation_count=1000,
venue_text="Cell",
pub_url="https://doi.org/10.1016/j.cell.2007.11.019",
display_identifier=None,
pdf_url=None,
is_read=False,
first_seen_at=datetime.now(timezone.utc),
is_new_in_latest_run=True,
)
def test_publication_doi_uses_stored_value_when_metadata_has_no_doi() -> None:
item = replace(
_item(99),
pub_url="https://scholar.google.com/citations?view_op=view_citation&citation_for_view=abc:123",
venue_text="Cell 130 (5), 2007",
display_identifier=DisplayIdentifier(
kind="doi",
value="10.1016/j.cell.2007.11.019",
label="DOI",
url=None,
confidence_score=1.0,
),
)
assert unpaywall_app._publication_doi(item) == "10.1016/j.cell.2007.11.019"
def test_publication_doi_prefers_explicit_metadata_doi_over_stored_value() -> None:
item = replace(
_item(100),
pub_url="https://doi.org/10.2000/fresh-value",
venue_text="Cell",
display_identifier=DisplayIdentifier(
kind="doi",
value="10.1000/stale-value",
label="DOI",
url=None,
confidence_score=1.0,
),
)
assert unpaywall_app._publication_doi(item) == "10.2000/fresh-value"
@pytest.mark.asyncio
async def test_unpaywall_resolve_prefers_direct_pdf_without_landing_crawl(monkeypatch: pytest.MonkeyPatch) -> None:
payload = {
"doi": "10.1016/j.cell.2007.11.019",
"best_oa_location": {
"url_for_pdf": "https://oa.example.org/article.pdf",
"url": "https://oa.example.org/landing",
},
}
async def _fake_resolve_item_payload(**_kwargs):
return payload, False, "10.1016/j.cell.2007.11.019"
async def _fail_crawl(_client, *, page_url: str):
raise AssertionError(f"unexpected landing crawl: {page_url}")
monkeypatch.setattr(unpaywall_app, "_resolve_item_payload", _fake_resolve_item_payload)
monkeypatch.setattr(unpaywall_app, "resolve_pdf_from_landing_page", _fail_crawl)
monkeypatch.setattr("httpx.AsyncClient", _DummyAsyncClient)
resolved = await unpaywall_app.resolve_publication_oa_metadata([_item(1)], request_email="user@example.com")
assert resolved == {1: ("10.1016/j.cell.2007.11.019", "https://oa.example.org/article.pdf")}
@pytest.mark.asyncio
async def test_unpaywall_resolve_crawls_landing_page_when_pdf_url_is_not_direct(
monkeypatch: pytest.MonkeyPatch,
) -> None:
payload = {
"doi": "10.1016/j.cell.2007.11.019",
"best_oa_location": {
"url_for_pdf": "https://oa.example.org/view?paper=42",
"url": "https://oa.example.org/landing/42",
},
}
crawled_pages: list[str] = []
async def _fake_resolve_item_payload(**_kwargs):
return payload, False, "10.1016/j.cell.2007.11.019"
async def _fake_crawl(_client, *, page_url: str):
crawled_pages.append(page_url)
return "https://oa.example.org/files/paper-42.pdf"
monkeypatch.setattr(unpaywall_app, "_resolve_item_payload", _fake_resolve_item_payload)
monkeypatch.setattr(unpaywall_app, "resolve_pdf_from_landing_page", _fake_crawl)
monkeypatch.setattr("httpx.AsyncClient", _DummyAsyncClient)
resolved = await unpaywall_app.resolve_publication_oa_metadata([_item(2)], request_email="user@example.com")
assert resolved == {2: ("10.1016/j.cell.2007.11.019", "https://oa.example.org/files/paper-42.pdf")}
assert "https://oa.example.org/landing/42" in crawled_pages
@pytest.mark.asyncio
async def test_unpaywall_preserves_crossref_doi_when_unpaywall_has_no_record(
monkeypatch: pytest.MonkeyPatch,
) -> None:
async def _fake_resolve_item_payload(**_kwargs):
return None, True, "10.2000/crossref-only"
monkeypatch.setattr(unpaywall_app, "_resolve_item_payload", _fake_resolve_item_payload)
monkeypatch.setattr("httpx.AsyncClient", _DummyAsyncClient)
outcomes = await unpaywall_app.resolve_publication_oa_outcomes([_item(3)], request_email="user@example.com")
outcome = outcomes[3]
assert outcome.doi == "10.2000/crossref-only"
assert outcome.pdf_url is None
assert outcome.failure_reason == unpaywall_app.FAILURE_NO_RECORD
assert outcome.used_crossref is True