fix: resolve mypy errors, fix arxiv test session isolation, and add premerge script
- Remove unused type:ignore comment in scholars.py - Fix 51 mypy errors across test files (Any return types, cast(), protocol stubs) - Fix arxiv unit test failures caused by session isolation (patch_session_factory fixture) - Fix integration test rate-limit bleed between search and create - Add scripts/premerge.sh for local CI verification - Add docs/website/.docusaurus/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
49cf7034b2
commit
87aa89b58c
17 changed files with 129 additions and 28 deletions
17
tests/unit/services/domains/arxiv/conftest.py
Normal file
17
tests/unit/services/domains/arxiv/conftest.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_session_factory(db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Patch get_session_factory in arxiv modules to use the test's DB engine.
|
||||
|
||||
Without this, the implementation creates its own engine via get_session_factory()
|
||||
which may not share transaction visibility with the test's db_session fixture.
|
||||
"""
|
||||
factory = async_sessionmaker(db_session.bind, expire_on_commit=False)
|
||||
monkeypatch.setattr("app.services.arxiv.rate_limit.get_session_factory", lambda: factory)
|
||||
monkeypatch.setattr("app.services.arxiv.cache.get_session_factory", lambda: factory)
|
||||
return factory
|
||||
|
|
@ -147,8 +147,8 @@ async def test_client_coalesces_concurrent_identical_search_requests() -> None:
|
|||
async def test_client_logs_cache_hit_and_miss(
|
||||
db_session: AsyncSession,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
patch_session_factory,
|
||||
) -> None:
|
||||
_ = db_session
|
||||
calls = {"count": 0}
|
||||
logged: list[dict[str, object]] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -10,7 +10,9 @@ from app.services.arxiv.types import ArxivEntry, ArxivFeed, ArxivOpenSearchMeta
|
|||
from app.settings import settings
|
||||
|
||||
|
||||
def _item(*, title: str = "A Test Paper", scholar_label: str = "Ada Lovelace") -> SimpleNamespace:
|
||||
def _item(*, title: str = "A Test Paper", scholar_label: str = "Ada Lovelace") -> Any:
|
||||
from types import SimpleNamespace
|
||||
|
||||
return SimpleNamespace(title=title, scholar_label=scholar_label)
|
||||
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ async def test_http_gateway_returns_none_when_disabled(monkeypatch: pytest.Monke
|
|||
fake_client = FakeClient()
|
||||
object.__setattr__(settings, "arxiv_enabled", False)
|
||||
try:
|
||||
gateway = arxiv_gateway.HttpArxivGateway(client=fake_client)
|
||||
gateway = arxiv_gateway.HttpArxivGateway(client=fake_client) # type: ignore[arg-type]
|
||||
result = await gateway.discover_arxiv_id_for_publication(item=_item())
|
||||
assert result is None
|
||||
assert fake_client.calls == 0
|
||||
|
|
@ -102,7 +104,7 @@ async def test_http_gateway_uses_client_search_for_discovery() -> None:
|
|||
)
|
||||
|
||||
fake_client = FakeClient()
|
||||
gateway = arxiv_gateway.HttpArxivGateway(client=fake_client)
|
||||
gateway = arxiv_gateway.HttpArxivGateway(client=fake_client) # type: ignore[arg-type]
|
||||
result = await gateway.discover_arxiv_id_for_publication(
|
||||
item=_item(title="My Paper", scholar_label="Ada Lovelace"),
|
||||
request_email="user@example.com",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from app.settings import settings
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_arxiv_rate_limit_respects_cooldown(db_session: AsyncSession) -> None:
|
||||
async def test_arxiv_rate_limit_respects_cooldown(db_session: AsyncSession, patch_session_factory) -> None:
|
||||
db_session.add(
|
||||
ArxivRuntimeState(
|
||||
state_key=ARXIV_RUNTIME_STATE_KEY,
|
||||
|
|
@ -37,7 +37,7 @@ async def test_arxiv_rate_limit_respects_cooldown(db_session: AsyncSession) -> N
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_arxiv_rate_limit_persists_cooldown_after_429(db_session: AsyncSession) -> None:
|
||||
async def test_arxiv_rate_limit_persists_cooldown_after_429(db_session: AsyncSession, patch_session_factory) -> None:
|
||||
previous_interval = settings.arxiv_min_interval_seconds
|
||||
previous_cooldown = settings.arxiv_rate_limit_cooldown_seconds
|
||||
object.__setattr__(settings, "arxiv_min_interval_seconds", 0.0)
|
||||
|
|
@ -62,7 +62,7 @@ async def test_arxiv_rate_limit_persists_cooldown_after_429(db_session: AsyncSes
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_arxiv_rate_limit_serializes_concurrent_calls(db_session: AsyncSession) -> None:
|
||||
async def test_arxiv_rate_limit_serializes_concurrent_calls(db_session: AsyncSession, patch_session_factory) -> None:
|
||||
previous_interval = settings.arxiv_min_interval_seconds
|
||||
previous_cooldown = settings.arxiv_rate_limit_cooldown_seconds
|
||||
object.__setattr__(settings, "arxiv_min_interval_seconds", 0.2)
|
||||
|
|
@ -90,6 +90,7 @@ async def test_arxiv_rate_limit_serializes_concurrent_calls(db_session: AsyncSes
|
|||
async def test_arxiv_rate_limit_logs_request_scheduled_and_completed(
|
||||
db_session: AsyncSession,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
patch_session_factory,
|
||||
) -> None:
|
||||
logged: list[dict[str, object]] = []
|
||||
|
||||
|
|
@ -107,8 +108,8 @@ async def test_arxiv_rate_limit_logs_request_scheduled_and_completed(
|
|||
|
||||
assert scheduled
|
||||
assert completed
|
||||
assert float(scheduled[0]["wait_seconds"]) >= 0.0
|
||||
assert int(completed[0]["status_code"]) == 200
|
||||
assert float(str(scheduled[0]["wait_seconds"])) >= 0.0
|
||||
assert int(str(completed[0]["status_code"])) == 200
|
||||
assert completed[0]["source_path"] == "search"
|
||||
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ async def test_arxiv_rate_limit_logs_request_scheduled_and_completed(
|
|||
async def test_arxiv_rate_limit_logs_cooldown_activation(
|
||||
db_session: AsyncSession,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
patch_session_factory,
|
||||
) -> None:
|
||||
logged_warning: list[dict[str, object]] = []
|
||||
previous_interval = settings.arxiv_min_interval_seconds
|
||||
|
|
@ -140,11 +142,11 @@ async def test_arxiv_rate_limit_logs_cooldown_activation(
|
|||
cooldown_events = [entry for entry in logged_warning if entry.get("event") == "arxiv.cooldown_activated"]
|
||||
assert cooldown_events
|
||||
assert cooldown_events[0]["source_path"] == "lookup_ids"
|
||||
assert float(cooldown_events[0]["cooldown_remaining_seconds"]) > 0.0
|
||||
assert float(str(cooldown_events[0]["cooldown_remaining_seconds"])) > 0.0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_arxiv_cooldown_status_reads_active_cooldown(db_session: AsyncSession) -> None:
|
||||
async def test_get_arxiv_cooldown_status_reads_active_cooldown(db_session: AsyncSession, patch_session_factory) -> None:
|
||||
now_utc = datetime(2026, 2, 26, 13, 0, tzinfo=UTC)
|
||||
existing = await db_session.get(ArxivRuntimeState, ARXIV_RUNTIME_STATE_KEY)
|
||||
if existing is None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue