Ctrl K
ComunidadGuías…

backend/tests/test_health_overview_provider_state.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""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