backend/tests/test_issue_201_status_reason.py
Rama master · réplica de lectura del repositorio original.
"""#201 (core) — say WHY a provider is unhealthy, machine-readably.
The UI mapped every ``healthy: false`` onto one label, "Unreachable". A
provider that simply has no account configured therefore read as a network
problem, which sends you looking in the wrong place: on the reporting install
`titlovi` displayed "Unreachable" while the real answer was "no credentials
stored".
The backend already distinguishes the cases internally and then flattens them
into a free-text ``message`` the UI cannot branch on. ``status_reason`` carries
the same distinction in a form the frontend can map, and gives #200/#197
something to key off later.
"""
import pytest
from providers.manager_status_mixin import _classify_health, _uninitialized_reason
class TestClassifyHealth:
def test_a_working_provider(self):
healthy, _msg, reason = _classify_health(
auto_disabled=False,
cb_state="closed",
consecutive_failures=0,
total_searches=100,
results=40,
downloads=9,
)
assert (healthy, reason) == (True, "ok")
def test_auto_disabled_wins_over_everything(self):
healthy, _msg, reason = _classify_health(
auto_disabled=True,
cb_state="open",
consecutive_failures=9,
total_searches=0,
results=0,
downloads=0,
)
assert (healthy, reason) == (False, "auto_disabled")
def test_open_circuit(self):
healthy, _msg, reason = _classify_health(
auto_disabled=False,
cb_state="open",
consecutive_failures=0,
total_searches=10,
results=5,
downloads=1,
)
assert (healthy, reason) == (False, "circuit_open")
def test_consecutive_failures(self):
healthy, msg, reason = _classify_health(
auto_disabled=False,
cb_state="closed",
consecutive_failures=4,
total_searches=10,
results=5,
downloads=1,
)
assert (healthy, reason) == (False, "consecutive_failures")
assert "4" in msg, "the number belongs in the message the user reads"
def test_answers_but_never_delivers(self):
"""The #198 verdict must be its own reason, not folded into the
network-problem bucket."""
healthy, msg, reason = _classify_health(
auto_disabled=False,
cb_state="closed",
consecutive_failures=0,
total_searches=1666,
results=0,
downloads=0,
)
assert (healthy, reason) == (False, "no_results")
assert "1666" in msg
def test_never_delivering_does_not_outrank_a_real_failure(self):
_healthy, _msg, reason = _classify_health(
auto_disabled=False,
cb_state="closed",
consecutive_failures=5,
total_searches=1666,
results=0,
downloads=0,
)
assert reason == "consecutive_failures"
class _Settings:
def __init__(self, **kw):
for k, v in kw.items():
setattr(self, k, v)
class TestUninitializedReason:
def test_a_required_field_left_empty_is_not_a_network_problem(self):
fields = [
{"key": "titlovi_username", "required": True},
{"key": "titlovi_password", "required": True},
]
msg, reason = _uninitialized_reason(
fields, _Settings(titlovi_username="", titlovi_password="")
)
assert reason == "no_credentials"
assert "credential" in msg.lower()
def test_partially_filled_still_counts_as_missing(self):
fields = [
{"key": "titlovi_username", "required": True},
{"key": "titlovi_password", "required": True},
]
msg, reason = _uninitialized_reason(
fields, _Settings(titlovi_username="dennis", titlovi_password="")
)
assert reason == "no_credentials"
assert msg
def test_optional_fields_do_not_trigger_it(self):
fields = [{"key": "some_option", "required": False}]
_msg, reason = _uninitialized_reason(fields, _Settings(some_option=""))
assert reason == "not_initialized"
def test_all_credentials_present_means_something_else_went_wrong(self):
fields = [{"key": "jimaku_api_key", "required": True}]
_msg, reason = _uninitialized_reason(fields, _Settings(jimaku_api_key="abc"))
assert reason == "not_initialized"
def test_no_config_fields_at_all(self):
_msg, reason = _uninitialized_reason([], _Settings())
assert reason == "not_initialized"
class TestPayloadCarriesTheReason:
"""A pure function nobody reads is worth nothing — the status payload has
to actually carry the field."""
def test_status_dict_includes_status_reason(self, app_ctx, monkeypatch):
from providers import ProviderManager
manager = ProviderManager()
statuses = manager.get_provider_status()
assert statuses, "no providers registered"
for s in statuses:
assert "status_reason" in s, f"{s['name']} has no status_reason"
@pytest.mark.parametrize(
"reason",
[
"ok",
"auto_disabled",
"circuit_open",
"consecutive_failures",
"no_results",
"no_credentials",
"not_initialized",
],
)
def test_every_reason_is_declared_for_the_frontend(self, reason):
"""Guards the trap that has cost us twice: a new backend enum value
with no frontend counterpart renders as a blank or a wrong label."""
from providers.manager_status_mixin import STATUS_REASONS
assert reason in STATUS_REASONS