remove legacy in-memory search state fallback
This commit is contained in:
parent
d7404abf18
commit
1ce85304e3
17 changed files with 766 additions and 248 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
13
tests/unit/test_scholar_source.py
Normal file
13
tests/unit/test_scholar_source.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue