"""Продуктовые счётчики (#3471): числа бизнеса рядом с техническими метриками. Проверяется то же, ради чего вообще заведён `test_metrics.py` — не «метрика существует», а что она не может взорвать кардинальность. Источник счётчиков — реальные `event_type` из `user_events` (миграция 184) плюс две ручки без собственного audit-события (suggest, PDF). Метки везде — фиксированный литерал из места вызова (outcome/found/channel/result), никогда значение из запроса (username, адрес, estimate_id), поэтому тест фиксирует именно НАБОР меток, а не факт роста счётчика на единицу — рост уже проверен паттерном `test_metrics.py`. """ from __future__ import annotations from prometheus_client import REGISTRY, generate_latest from app.observability import metrics as m def _labelnames(counter: object) -> tuple[str, ...]: return tuple(counter._labelnames) # type: ignore[attr-defined] def test_estimates_counter_has_bounded_outcome_label() -> None: """Только исход расчёта, никогда адрес/estimate_id — те дали бы ряд на заявку.""" assert _labelnames(m.ESTIMATES) == ("outcome",) def test_address_suggestions_counter_has_bounded_found_label() -> None: """found — да/нет, не сам адрес и не количество результатов (unbounded).""" assert _labelnames(m.ADDRESS_SUGGESTIONS) == ("found",) def test_reports_exported_counter_has_no_labels() -> None: """У «Меры» один формат отчёта (PDF оценки) — без лейбла, дублировать нечего.""" assert _labelnames(m.REPORTS_EXPORTED) == () def test_leads_counter_has_no_labels() -> None: assert _labelnames(m.LEADS) == () def test_support_messages_counter_has_bounded_channel_label() -> None: """channel — ровно два значения (web/anon), не thread_id и не username.""" assert _labelnames(m.SUPPORT_MESSAGES) == ("channel",) def test_logins_counter_has_bounded_result_label() -> None: """result — исход попытки, не username (иначе ряд на каждый аккаунт).""" assert _labelnames(m.LOGINS) == ("result",) def test_product_counters_survive_a_realistic_sequence() -> None: """Инкременты по реальным меткам видны в экспозиции и не мешают друг другу. Значения сравниваются приращением: реестр `prometheus_client` глобален на процесс, и абсолютное число зависит от порядка запуска тестов. """ def _value(name: str, labels: dict[str, str]) -> float: return REGISTRY.get_sample_value(name, labels) or 0.0 before_ok = _value("mera_estimates_total", {"outcome": "ok"}) before_insufficient = _value("mera_estimates_total", {"outcome": "insufficient_data"}) before_found = _value("mera_address_suggestions_total", {"found": "yes"}) before_web = _value("mera_support_messages_total", {"channel": "web"}) before_login_ok = _value("mera_logins_total", {"result": "success"}) m.ESTIMATES.labels(outcome="ok").inc() m.ESTIMATES.labels(outcome="insufficient_data").inc() m.ADDRESS_SUGGESTIONS.labels(found="yes").inc() m.SUPPORT_MESSAGES.labels(channel="web").inc() m.LOGINS.labels(result="success").inc() m.LEADS.inc() m.REPORTS_EXPORTED.inc() assert _value("mera_estimates_total", {"outcome": "ok"}) - before_ok == 1.0 assert ( _value("mera_estimates_total", {"outcome": "insufficient_data"}) - before_insufficient == 1.0 ) assert _value("mera_address_suggestions_total", {"found": "yes"}) - before_found == 1.0 assert _value("mera_support_messages_total", {"channel": "web"}) - before_web == 1.0 assert _value("mera_logins_total", {"result": "success"}) - before_login_ok == 1.0 body = generate_latest(REGISTRY).decode() for metric in ( "mera_estimates_total", "mera_address_suggestions_total", "mera_reports_exported_total", "mera_leads_total", "mera_support_messages_total", "mera_logins_total", ): assert metric in body