streamlined
This commit is contained in:
parent
d1dd3213dc
commit
ab1d29b203
23 changed files with 853 additions and 22 deletions
58
tests/unit/test_runs_summary.py
Normal file
58
tests/unit/test_runs_summary.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from app.services.runs import extract_run_summary
|
||||
|
||||
|
||||
def test_extract_run_summary_includes_extended_metrics() -> None:
|
||||
error_log = {
|
||||
"summary": {
|
||||
"succeeded_count": 3,
|
||||
"failed_count": 1,
|
||||
"partial_count": 2,
|
||||
"failed_state_counts": {"network_error": 1},
|
||||
"failed_reason_counts": {"network_timeout": 1},
|
||||
"scrape_failure_counts": {"network_error": 1},
|
||||
"retry_counts": {
|
||||
"retries_scheduled_count": 4,
|
||||
"scholars_with_retries_count": 2,
|
||||
"retry_exhausted_count": 1,
|
||||
},
|
||||
"alert_thresholds": {
|
||||
"blocked_failure_threshold": 1,
|
||||
"network_failure_threshold": 2,
|
||||
"retry_scheduled_threshold": 3,
|
||||
},
|
||||
"alert_flags": {
|
||||
"blocked_failure_threshold_exceeded": False,
|
||||
"network_failure_threshold_exceeded": True,
|
||||
"retry_scheduled_threshold_exceeded": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
summary = extract_run_summary(error_log)
|
||||
|
||||
assert summary["succeeded_count"] == 3
|
||||
assert summary["failed_count"] == 1
|
||||
assert summary["partial_count"] == 2
|
||||
assert summary["failed_state_counts"] == {"network_error": 1}
|
||||
assert summary["failed_reason_counts"] == {"network_timeout": 1}
|
||||
assert summary["scrape_failure_counts"] == {"network_error": 1}
|
||||
assert summary["retry_counts"]["retries_scheduled_count"] == 4
|
||||
assert summary["retry_counts"]["scholars_with_retries_count"] == 2
|
||||
assert summary["retry_counts"]["retry_exhausted_count"] == 1
|
||||
assert summary["alert_thresholds"]["retry_scheduled_threshold"] == 3
|
||||
assert summary["alert_flags"]["network_failure_threshold_exceeded"] is True
|
||||
|
||||
|
||||
def test_extract_run_summary_defaults_extended_metrics() -> None:
|
||||
summary = extract_run_summary({})
|
||||
|
||||
assert summary["scrape_failure_counts"] == {}
|
||||
assert summary["retry_counts"] == {
|
||||
"retries_scheduled_count": 0,
|
||||
"scholars_with_retries_count": 0,
|
||||
"retry_exhausted_count": 0,
|
||||
}
|
||||
assert summary["alert_thresholds"] == {}
|
||||
assert summary["alert_flags"] == {}
|
||||
|
|
@ -63,6 +63,16 @@ def _blocked_author_search_fetch() -> FetchResult:
|
|||
)
|
||||
|
||||
|
||||
def _network_timeout_fetch() -> FetchResult:
|
||||
return FetchResult(
|
||||
requested_url="https://scholar.google.com/citations?hl=en&view_op=search_authors&mauthors=ada",
|
||||
status_code=None,
|
||||
final_url=None,
|
||||
body="",
|
||||
error="timed out",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_author_candidates_serves_cached_response_for_same_query() -> None:
|
||||
source = StubScholarSource([_ok_author_search_fetch()])
|
||||
|
|
@ -150,6 +160,79 @@ async def test_search_author_candidates_trips_cooldown_after_blocked_responses()
|
|||
assert "author_search_cooldown_active" in second.warnings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_author_candidates_emits_cooldown_alert_warning_after_threshold() -> None:
|
||||
source = StubScholarSource([_blocked_author_search_fetch()])
|
||||
|
||||
_ = await scholar_service.search_author_candidates(
|
||||
source=source,
|
||||
query="Blocked Query",
|
||||
limit=10,
|
||||
network_error_retries=0,
|
||||
retry_backoff_seconds=0.0,
|
||||
search_enabled=True,
|
||||
cache_ttl_seconds=0,
|
||||
blocked_cache_ttl_seconds=0,
|
||||
cache_max_entries=64,
|
||||
min_interval_seconds=0.0,
|
||||
interval_jitter_seconds=0.0,
|
||||
cooldown_block_threshold=1,
|
||||
cooldown_seconds=300,
|
||||
cooldown_rejection_alert_threshold=1,
|
||||
)
|
||||
second = await scholar_service.search_author_candidates(
|
||||
source=source,
|
||||
query="Blocked Query",
|
||||
limit=10,
|
||||
network_error_retries=0,
|
||||
retry_backoff_seconds=0.0,
|
||||
search_enabled=True,
|
||||
cache_ttl_seconds=0,
|
||||
blocked_cache_ttl_seconds=0,
|
||||
cache_max_entries=64,
|
||||
min_interval_seconds=0.0,
|
||||
interval_jitter_seconds=0.0,
|
||||
cooldown_block_threshold=1,
|
||||
cooldown_seconds=300,
|
||||
cooldown_rejection_alert_threshold=1,
|
||||
)
|
||||
|
||||
assert second.state == ParseState.BLOCKED_OR_CAPTCHA
|
||||
assert "author_search_cooldown_alert_threshold_exceeded" in second.warnings
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_author_candidates_adds_retry_threshold_warning() -> None:
|
||||
source = StubScholarSource(
|
||||
[
|
||||
_network_timeout_fetch(),
|
||||
_network_timeout_fetch(),
|
||||
_ok_author_search_fetch(),
|
||||
]
|
||||
)
|
||||
|
||||
result = await scholar_service.search_author_candidates(
|
||||
source=source,
|
||||
query="Ada Lovelace",
|
||||
limit=10,
|
||||
network_error_retries=2,
|
||||
retry_backoff_seconds=0.0,
|
||||
search_enabled=True,
|
||||
cache_ttl_seconds=0,
|
||||
blocked_cache_ttl_seconds=0,
|
||||
cache_max_entries=64,
|
||||
min_interval_seconds=0.0,
|
||||
interval_jitter_seconds=0.0,
|
||||
cooldown_block_threshold=3,
|
||||
cooldown_seconds=300,
|
||||
retry_alert_threshold=2,
|
||||
)
|
||||
|
||||
assert result.state == ParseState.OK
|
||||
assert "author_search_retry_threshold_exceeded_2" in result.warnings
|
||||
assert source.calls == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_author_candidates_can_be_disabled_by_configuration() -> None:
|
||||
source = StubScholarSource([_ok_author_search_fetch()])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue