remove legacy in-memory search state fallback

This commit is contained in:
Justin Visser 2026-02-19 22:31:26 +01:00
parent d7404abf18
commit 1ce85304e3
17 changed files with 766 additions and 248 deletions

View file

@ -19,6 +19,8 @@ from app.settings import settings
RESET_SQL = text(
"""
TRUNCATE TABLE
author_search_cache_entries,
author_search_runtime_state,
ingestion_queue_items,
scholar_publications,
crawl_runs,

View file

@ -11,10 +11,12 @@ EXPECTED_TABLES = {
"scholar_publications",
"crawl_runs",
"ingestion_queue_items",
"author_search_runtime_state",
"author_search_cache_entries",
}
EXPECTED_ENUMS = {"run_status", "run_trigger_type"}
EXPECTED_REVISION = "20260219_0009"
EXPECTED_REVISION = "20260219_0010"
@pytest.mark.integration

View file

@ -32,3 +32,22 @@ def test_rate_limiter_enforces_windowed_attempt_budget() -> None:
clock["now"] = 11.0
assert limiter.check(key).allowed
def test_rate_limiter_check_does_not_create_or_retain_empty_keys() -> None:
clock = {"now": 0.0}
limiter = SlidingWindowRateLimiter(
max_attempts=2,
window_seconds=10,
now=lambda: clock["now"],
)
key = "127.0.0.1:never-failed@example.com"
assert limiter.check(key).allowed
assert key not in limiter._attempts
limiter.record_failure(key)
assert key in limiter._attempts
clock["now"] = 11.0
assert limiter.check(key).allowed
assert key not in limiter._attempts

View file

@ -1,11 +1,14 @@
from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.services import scholars as scholar_service
from app.services.scholar_parser import ParseState
from app.services.scholar_source import FetchResult
pytestmark = [pytest.mark.integration, pytest.mark.db]
class StubScholarSource:
def __init__(self, fetch_results: list[FetchResult]) -> None:
@ -21,13 +24,6 @@ class StubScholarSource:
return self._fetch_results[index]
@pytest.fixture(autouse=True)
def reset_author_search_runtime_state() -> None:
scholar_service._reset_author_search_runtime_state_for_tests()
yield
scholar_service._reset_author_search_runtime_state_for_tests()
def _ok_author_search_fetch() -> FetchResult:
body = (
"<html><body>"
@ -74,11 +70,14 @@ def _network_timeout_fetch() -> FetchResult:
@pytest.mark.asyncio
async def test_search_author_candidates_serves_cached_response_for_same_query() -> None:
async def test_search_author_candidates_serves_cached_response_for_same_query(
db_session: AsyncSession,
) -> None:
source = StubScholarSource([_ok_author_search_fetch()])
first = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Ada Lovelace",
limit=10,
network_error_retries=0,
@ -94,6 +93,7 @@ async def test_search_author_candidates_serves_cached_response_for_same_query()
)
second = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Ada Lovelace",
limit=10,
network_error_retries=0,
@ -117,11 +117,14 @@ async def test_search_author_candidates_serves_cached_response_for_same_query()
@pytest.mark.asyncio
async def test_search_author_candidates_trips_cooldown_after_blocked_responses() -> None:
async def test_search_author_candidates_trips_cooldown_after_blocked_responses(
db_session: AsyncSession,
) -> None:
source = StubScholarSource([_blocked_author_search_fetch()])
first = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Blocked Query",
limit=10,
network_error_retries=0,
@ -137,6 +140,7 @@ async def test_search_author_candidates_trips_cooldown_after_blocked_responses()
)
second = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Blocked Query",
limit=10,
network_error_retries=0,
@ -161,11 +165,14 @@ async def test_search_author_candidates_trips_cooldown_after_blocked_responses()
@pytest.mark.asyncio
async def test_search_author_candidates_emits_cooldown_alert_warning_after_threshold() -> None:
async def test_search_author_candidates_emits_cooldown_alert_warning_after_threshold(
db_session: AsyncSession,
) -> None:
source = StubScholarSource([_blocked_author_search_fetch()])
_ = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Blocked Query",
limit=10,
network_error_retries=0,
@ -182,6 +189,7 @@ async def test_search_author_candidates_emits_cooldown_alert_warning_after_thres
)
second = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Blocked Query",
limit=10,
network_error_retries=0,
@ -202,7 +210,9 @@ async def test_search_author_candidates_emits_cooldown_alert_warning_after_thres
@pytest.mark.asyncio
async def test_search_author_candidates_adds_retry_threshold_warning() -> None:
async def test_search_author_candidates_adds_retry_threshold_warning(
db_session: AsyncSession,
) -> None:
source = StubScholarSource(
[
_network_timeout_fetch(),
@ -213,6 +223,7 @@ async def test_search_author_candidates_adds_retry_threshold_warning() -> None:
result = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Ada Lovelace",
limit=10,
network_error_retries=2,
@ -234,11 +245,14 @@ async def test_search_author_candidates_adds_retry_threshold_warning() -> None:
@pytest.mark.asyncio
async def test_search_author_candidates_can_be_disabled_by_configuration() -> None:
async def test_search_author_candidates_can_be_disabled_by_configuration(
db_session: AsyncSession,
) -> None:
source = StubScholarSource([_ok_author_search_fetch()])
parsed = await scholar_service.search_author_candidates(
source=source,
db_session=db_session,
query="Ada Lovelace",
limit=5,
network_error_retries=0,

View file

@ -0,0 +1,13 @@
from app.services.scholar_source import _build_profile_url
def test_build_profile_url_includes_pagesize_for_initial_page() -> None:
url = _build_profile_url(
scholar_id="abcDEF123456",
cstart=0,
pagesize=100,
)
assert "user=abcDEF123456" in url
assert "pagesize=100" in url
assert "cstart=" not in url