backend/tests/test_health_overview_provider_state.py
Rama master · réplica de lectura del repositorio original.
"""The health page must not keep a third definition of "provider is fine".
Forgejo #17: on one install `/api/v1/health` reported "degraded (23/29
active)", `/api/v1/providers` listed 6 unhealthy, and the health page showed
"PROVIDERS DEGRADED 0" next to "No provider activity recorded yet". All three
numbers were correct — they answered different questions under nearly the
same name.
The health overview computed degraded itself: breaker not closed, or
auto-disabled, or at least 10 searches with under 5% hits. It therefore
missed a provider failing call after call (consecutive failures), which the
classifier behind /api/v1/providers has flagged since 1.14.0. And a provider
that has never been asked anything came out identical to a healthy one, so
"nothing has run yet" was indistinguishable from "everything is fine".
So: one classifier for both surfaces, and activity reported separately from
health.
"""
from datetime import UTC, datetime
def _add_provider_stats(app, name, **columns):
from db.models.providers import ProviderStats
from extensions import db
with app.app_context():
db.session.add(ProviderStats(provider_name=name, updated_at=datetime.now(UTC), **columns))
db.session.commit()
def _providers(client):
resp = client.get("/api/v1/health/library")
assert resp.status_code == 200
body = resp.get_json()
return {p["provider"]: p for p in body["providers"]}, body["totals"]
def test_a_provider_failing_call_after_call_counts_as_degraded(client):
"""The gap the old rule left: failures without an open breaker.
Nine searches, none successful, nine consecutive failures — under the old
rule that is neither breaker-open nor auto-disabled nor "10+ searches", so
it read as healthy on the very page meant to surface it.
"""
_add_provider_stats(
client.application,
"failingprov",
total_searches=9,
successful_searches=0,
consecutive_failures=9,
)
providers, _ = _providers(client)
assert providers["failingprov"]["degraded"] is True
assert providers["failingprov"]["status_reason"] == "consecutive_failures"
def test_a_provider_nobody_has_asked_is_not_called_degraded(client):
"""Never asked is not the same as broken — but it must be visible."""
_add_provider_stats(client.application, "freshprov", total_searches=0, successful_searches=0)
providers, totals = _providers(client)
assert providers["freshprov"]["degraded"] is False
assert providers["freshprov"]["status_reason"] == "no_activity"
assert totals["providers_no_activity"] >= 1
def test_the_established_verdicts_still_hold(client):
"""The rules that already worked keep working."""
app = client.application
_add_provider_stats(app, "goodprov", total_searches=100, successful_searches=60)
_add_provider_stats(app, "deadprov", total_searches=50, successful_searches=1)
_add_provider_stats(app, "offprov", total_searches=5, successful_searches=5, auto_disabled=1)
providers, _ = _providers(client)
assert providers["goodprov"]["degraded"] is False
assert providers["deadprov"]["degraded"] is True
assert providers["deadprov"]["status_reason"] == "low_hit_rate"
assert providers["offprov"]["degraded"] is True
assert providers["offprov"]["status_reason"] == "auto_disabled"
def test_totals_separate_health_from_activity(client):
"""The two numbers the report saw side by side must be distinguishable."""
app = client.application
_add_provider_stats(app, "busy_ok", total_searches=80, successful_searches=40)
_add_provider_stats(app, "never_asked", total_searches=0, successful_searches=0)
_add_provider_stats(
app, "broken", total_searches=4, successful_searches=0, consecutive_failures=5
)
_, totals = _providers(client)
assert totals["providers_degraded"] == 1
assert totals["providers_no_activity"] == 1