scholarr/tests/unit/test_run_safety.py
Justin Visser bf04c77aa9 ci: add ruff linting and mypy type checking
Add ruff and mypy to dev dependencies with configuration in pyproject.toml.
Add a lint CI job that runs ruff check, ruff format --check, and mypy.
Auto-fix import sorting and formatting across the codebase. Exclude
alembic/versions from linting (auto-generated migrations). Ignore B008
(FastAPI Depends pattern) and RUF001 (unicode in user-facing strings).

21 ruff lint errors and 50 mypy errors remain for manual review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:11:41 +01:00

107 lines
4.2 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime, timedelta
from app.db.models import UserSetting
from app.services.domains.ingestion import safety as run_safety
def test_apply_run_safety_outcome_triggers_blocked_cooldown() -> None:
settings = UserSetting(user_id=1, scrape_safety_state={})
now = datetime(2026, 2, 19, 20, 0, tzinfo=UTC)
safety_state, reason = run_safety.apply_run_safety_outcome(
settings,
run_id=12,
blocked_failure_count=2,
network_failure_count=0,
blocked_failure_threshold=1,
network_failure_threshold=2,
blocked_cooldown_seconds=600,
network_cooldown_seconds=300,
now_utc=now,
)
assert reason == run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD
assert safety_state["cooldown_active"] is True
assert safety_state["cooldown_reason"] == run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD
assert safety_state["cooldown_remaining_seconds"] == 600
assert safety_state["counters"]["cooldown_entry_count"] == 1
assert safety_state["counters"]["last_blocked_failure_count"] == 2
assert safety_state["counters"]["last_evaluated_run_id"] == 12
def test_clear_expired_cooldown() -> None:
now = datetime(2026, 2, 19, 20, 0, tzinfo=UTC)
settings = UserSetting(
user_id=1,
scrape_safety_state={"blocked_start_count": 3},
scrape_cooldown_until=now - timedelta(seconds=5),
scrape_cooldown_reason=run_safety.COOLDOWN_REASON_NETWORK_FAILURE_THRESHOLD,
)
changed = run_safety.clear_expired_cooldown(settings, now_utc=now)
safety_state = run_safety.get_safety_state_payload(settings, now_utc=now)
assert changed is True
assert safety_state["cooldown_active"] is False
assert safety_state["cooldown_reason"] is None
assert safety_state["counters"]["blocked_start_count"] == 3
def test_apply_run_safety_outcome_triggers_network_cooldown() -> None:
settings = UserSetting(user_id=1, scrape_safety_state={})
now = datetime(2026, 2, 19, 20, 0, tzinfo=UTC)
safety_state, reason = run_safety.apply_run_safety_outcome(
settings,
run_id=21,
blocked_failure_count=0,
network_failure_count=3,
blocked_failure_threshold=2,
network_failure_threshold=1,
blocked_cooldown_seconds=900,
network_cooldown_seconds=300,
now_utc=now,
)
assert reason == run_safety.COOLDOWN_REASON_NETWORK_FAILURE_THRESHOLD
assert safety_state["cooldown_active"] is True
assert safety_state["cooldown_reason"] == run_safety.COOLDOWN_REASON_NETWORK_FAILURE_THRESHOLD
assert safety_state["cooldown_remaining_seconds"] == 300
assert safety_state["counters"]["cooldown_entry_count"] == 1
assert safety_state["counters"]["last_network_failure_count"] == 3
assert safety_state["counters"]["last_evaluated_run_id"] == 21
def test_register_cooldown_blocked_start_increments_counter() -> None:
now = datetime(2026, 2, 19, 20, 0, tzinfo=UTC)
settings = UserSetting(
user_id=1,
scrape_safety_state={"blocked_start_count": 1},
scrape_cooldown_until=now + timedelta(minutes=5),
scrape_cooldown_reason=run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD,
)
safety_state = run_safety.register_cooldown_blocked_start(settings, now_utc=now)
assert safety_state["cooldown_active"] is True
assert safety_state["cooldown_reason"] == run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD
assert safety_state["counters"]["blocked_start_count"] == 2
def test_get_safety_event_context_contains_structured_fields() -> None:
now = datetime(2026, 2, 19, 20, 0, tzinfo=UTC)
settings = UserSetting(
user_id=1,
scrape_safety_state={"cooldown_entry_count": 4},
scrape_cooldown_until=now + timedelta(minutes=10),
scrape_cooldown_reason=run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD,
)
event_context = run_safety.get_safety_event_context(settings, now_utc=now)
assert event_context["cooldown_active"] is True
assert event_context["cooldown_reason"] == run_safety.COOLDOWN_REASON_BLOCKED_FAILURE_THRESHOLD
assert int(event_context["cooldown_remaining_seconds"]) == 600
assert event_context["safety_counters"]["cooldown_entry_count"] == 4