From 091ac04b8c36905145b6902acb0d102d739e4050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sat, 25 Jul 2026 00:14:34 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(rollbar):=20opcjonalne=20wyciszenie=20?= =?UTF-8?q?b=C5=82=C4=99d=C3=B3w=20SMTP=20per=20instalacja?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Na bpp.umlub.pl serwer SMTP uczelni odrzuca poświadczenia (535 5.7.3 Authentication unsuccessful) — awaria po stronie administratorów poczty klienta, której nie naprawimy kodem. Efekt w Rollbarze: 60 wystąpień (#1554, #379), rosnące. Wyciszenie jest OPT-IN per instalacja przez DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS (domyślnie wyłączone), bo na pozostałych instalacjach niedziałająca poczta to realny problem — nie idą powiadomienia o zgłoszeniach publikacji do redaktorów i nikt tego nie zauważa, dopóki ktoś nie zapyta. Mechanizm: exception_level_filters pyrollbara z poziomem "ignored" (rollbar._is_ignored). Wyciszamy CAŁĄ rodzinę smtplib.SMTPException, bo awaria "poczta nie działa" ma wiele wcieleń, nie tylko 535 — ale nic poza nią (ConnectionRefusedError z Redisa czy PBN nadal leci). Budowanie listy filtrów wyodrębnione do django_bpp/rollbar_filters.py jako czysta funkcja — inaczej nie da się tego przetestować bez przeładowywania ustawień Django. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH --- .../+rollbar-wycisz-bledy-poczty.feature.rst | 6 ++ src/django_bpp/rollbar_filters.py | 33 +++++++++ src/django_bpp/settings/base.py | 9 +++ src/django_bpp/tests/test_rollbar_filters.py | 73 +++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst create mode 100644 src/django_bpp/rollbar_filters.py create mode 100644 src/django_bpp/tests/test_rollbar_filters.py diff --git a/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst b/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst new file mode 100644 index 000000000..80bf2b5f1 --- /dev/null +++ b/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst @@ -0,0 +1,6 @@ +Nowa zmienna środowiskowa ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS`` (domyślnie +wyłączona) pozwala wyciszyć w monitoringu błędów całą rodzinę błędów SMTP na +konkretnej instalacji. Do użycia tam, gdzie administratorzy poczty po stronie +klienta mają znaną awarię, której nie da się naprawić po naszej stronie — +zamiast zasypywać monitoring powtarzalnym zgłoszeniem. Na pozostałych +instalacjach błędy poczty są raportowane jak dotąd. diff --git a/src/django_bpp/rollbar_filters.py b/src/django_bpp/rollbar_filters.py new file mode 100644 index 000000000..1350ffb98 --- /dev/null +++ b/src/django_bpp/rollbar_filters.py @@ -0,0 +1,33 @@ +"""Filtry poziomów wyjątków dla Rollbara (``exception_level_filters``). + +pyrollbar pozwala przypisać klasie wyjątku poziom ``"ignored"`` — taki wyjątek +nie jest w ogóle wysyłany (``rollbar._is_ignored``). Używamy tego wyłącznie +tam, gdzie raport NIE niesie informacji: dana instalacja ma znaną, trwałą awarię +poza naszym kodem, a każde wystąpienie zakłada w Rollbarze osobny item. + +Wyodrębnione z ``settings.base`` jako czysta funkcja, żeby dało się to +przetestować bez przeładowywania ustawień Django. +""" + +from __future__ import annotations + +import smtplib + + +def zbuduj_exception_level_filters(*, ignoruj_bledy_poczty: bool = False): + """Zwraca listę par ``(klasa_wyjątku, poziom)`` dla ``ROLLBAR``. + + :param ignoruj_bledy_poczty: wycisza CAŁĄ rodzinę ``smtplib.SMTPException`` + (uwierzytelnianie, połączenie, odrzuceni odbiorcy). Włączane + per-instalacja przez ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS`` i tylko + tam, gdzie administratorzy poczty mają znaną awarię po swojej stronie, + a my nie mamy jak jej naprawić. Domyślnie WYŁĄCZONE — na pozostałych + instalacjach niedziałająca poczta to realny problem (nie idą + powiadomienia o zgłoszeniach publikacji) i chcemy o niej wiedzieć. + """ + filters: list[tuple[type[BaseException], str]] = [] + + if ignoruj_bledy_poczty: + filters.append((smtplib.SMTPException, "ignored")) + + return filters diff --git a/src/django_bpp/settings/base.py b/src/django_bpp/settings/base.py index 67d4589ca..b99fbac26 100644 --- a/src/django_bpp/settings/base.py +++ b/src/django_bpp/settings/base.py @@ -15,6 +15,7 @@ from bpp.util import slugify_function from django_bpp.channels_prefix import get_channels_prefix +from django_bpp.rollbar_filters import zbuduj_exception_level_filters from django_bpp.version import VERSION logger = logging.getLogger(__name__) @@ -160,6 +161,11 @@ def int_or_none(v): ROLLBAR_ACCESS_TOKEN=(str, None), # Publiczny token klienta (post_client_item) do frontendowego Rollbara. ROLLBAR_CLIENT_ACCESS_TOKEN=(str, ""), + # Wycisza w Rollbarze CAŁĄ rodzinę smtplib.SMTPException. Ustawiane PER + # INSTALACJA, domyślnie WYŁĄCZONE — szczegóły w django_bpp.rollbar_filters. + # Włączać wyłącznie tam, gdzie administratorzy poczty klienta mają znaną + # awarię po swojej stronie, której nie naprawimy kodem. + DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS=(bool, False), # # Prometheus # @@ -1764,6 +1770,9 @@ def iter_namespace(ns_pkg): re.compile(r"/favicon\.ico"), re.compile(r".*\{\{\s*clickURL\s*\}\}$"), ), + "exception_level_filters": zbuduj_exception_level_filters( + ignoruj_bledy_poczty=env("DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS"), + ), } # Publiczny token klienta (post_client_item) do frontendowego Rollbara. diff --git a/src/django_bpp/tests/test_rollbar_filters.py b/src/django_bpp/tests/test_rollbar_filters.py new file mode 100644 index 000000000..c8bc8f615 --- /dev/null +++ b/src/django_bpp/tests/test_rollbar_filters.py @@ -0,0 +1,73 @@ +"""Wyciszanie błędów poczty w Rollbarze jest opt-in per instalacja. + +Kontekst (Rollbar #1554, #379 — bpp.umlub.pl): serwer SMTP uczelni odrzuca +poświadczenia (``535 5.7.3 Authentication unsuccessful``). To awaria po +stronie administratorów poczty klienta, której nie naprawimy kodem, a każde +wystąpienie zakłada w Rollbarze osobny item. Wyciszamy — ale WYŁĄCZNIE na tej +instalacji, bo na pozostałych niedziałająca poczta to realny problem. +""" + +import smtplib + +import rollbar + +from django_bpp.rollbar_filters import zbuduj_exception_level_filters + + +def _poziom(filters, wyjatek): + """Odpytuje filtry tak, jak zrobi to pyrollbar przy raportowaniu.""" + for cls, poziom in filters: + if isinstance(wyjatek, cls): + return poziom + return None + + +def test_domyslnie_bledy_poczty_sa_raportowane(): + filters = zbuduj_exception_level_filters() + + assert _poziom(filters, smtplib.SMTPAuthenticationError(535, b"nope")) is None + + +def test_wlaczona_flaga_wycisza_bledy_uwierzytelniania_smtp(): + filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) + + assert _poziom(filters, smtplib.SMTPAuthenticationError(535, b"nope")) == "ignored" + + +def test_wlaczona_flaga_wycisza_cala_rodzine_bledow_smtp(): + """Awaria „poczta nie działa" ma wiele wcieleń, nie tylko 535.""" + filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) + + assert _poziom(filters, smtplib.SMTPConnectError(421, b"busy")) == "ignored" + assert _poziom(filters, smtplib.SMTPServerDisconnected("bye")) == "ignored" + + +def test_flaga_nie_wycisza_bledow_spoza_poczty(): + """Wyciszenie ma być chirurgiczne — nie zasłaniać niczego innego.""" + filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) + + assert _poziom(filters, ConnectionRefusedError("redis")) is None + assert _poziom(filters, ValueError("cokolwiek")) is None + + +def test_ustawienia_faktycznie_wpinaja_filtry_do_rollbara(): + """Sama funkcja nic nie da, jeśli nikt jej nie zawoła w ``settings``.""" + from django.conf import settings + + assert "exception_level_filters" in settings.ROLLBAR + # W testach flaga jest wyłączona → żadnych wyciszeń. To jest też asercja + # bezpieczeństwa: domyślna instalacja NIE gubi błędów. + assert settings.ROLLBAR["exception_level_filters"] == [] + + +def test_pyrollbar_faktycznie_honoruje_nasz_format_filtrow(monkeypatch): + """Kontrakt z pyrollbarem: nasze krotki muszą działać w ``_is_ignored``. + + Bez tego testu literówka w formacie (np. ``"ignore"`` zamiast + ``"ignored"``) przeszłaby niezauważona — filtry są danymi, nie kodem. + """ + filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) + monkeypatch.setitem(rollbar.SETTINGS, "exception_level_filters", filters) + + assert rollbar._is_ignored(smtplib.SMTPAuthenticationError(535, b"nope")) + assert not rollbar._is_ignored(ValueError("cokolwiek")) From 61f6813fc22f4175e2d060e2bc1a341a16cac39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sat, 25 Jul 2026 00:48:13 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(rollbar):=20zaw=C4=99=C5=BA=20wyciszeni?= =?UTF-8?q?e=20SMTP=20i=20popraw=20fa=C5=82szywe=20uzasadnienie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review obalił przesłankę, na której zbudowałem ten PR. 1. Twierdziłem, że "każde wystąpienie zakłada osobny item". NIEPRAWDA — sprawdzone w Rollbarze: item #379 ma total_occurrences=59, item #1554 ma 1. Czyli 60 wystąpień to DWA itemy, nie 60; grupowanie działa poprawnie. Drugi item odszczepił się tylko dlatego, że między wystąpieniami poszła podbitka pyrollbara 1.3.0 → 1.4.0, zmieniając hash. To jednak nadal jest argument za wyciszeniem w kodzie, tylko INNY: "mute" w interfejsie Rollbara jest per item i przepada przy zmianie hasha (jak wyżej), a wyciszenie w repo jest trwałe i widoczne w gicie — da się je znaleźć i cofnąć, gdy klient naprawi pocztę. Uzasadnienie przepisane na prawdziwe. 2. Zakres zawężony z całej rodziny smtplib.SMTPException do samego SMTPAuthenticationError. Poprzedni zakres wyciszał SMTPRecipientsRefused (zły adres w NASZEJ bazie) i SMTPSenderRefused (nasza konfiguracja DEFAULT_FROM_EMAIL) — jedno i drugie jest do naprawienia po naszej stronie. Przy okazji: "cała rodzina błędów poczty" i tak było nieosiągalne, bo odmowa połączenia TCP leci jako ConnectionRefusedError, który nie jest podklasą SMTPException. 3. Test "kontraktu z pyrollbarem" celował w rollbar._is_ignored — funkcję, która w pyrollbar 1.4.0 NIE JEST NIGDZIE WYWOŁYWANA (jedyne wystąpienie to jej definicja). Realne tłumienie idzie przez _filtered_level → events.on_exception_info → filters.basic.filter_by_level. Testy jadą teraz prawdziwą ścieżką, z przechwyceniem send_payload. Zweryfikowane mutacją: wyłączenie flagi, rozszerzenie do SMTPException i literówka w poziomie — każde łapane. 4. Zmienna dopisana do .env.example (przełącznik per instalacja, którego nie widać w .env.example, jest dla wdrażającego nieodkrywalny) oraz ostrzeżenie w docstringu, że przy DJANGO_BPP_HOSTNAMES działa na wszystkie uczelnie w procesie. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH --- .env.example | 9 ++ .../+rollbar-wycisz-bledy-poczty.feature.rst | 12 +- src/django_bpp/rollbar_filters.py | 39 ++++--- src/django_bpp/settings/base.py | 15 ++- src/django_bpp/tests/test_rollbar_filters.py | 106 +++++++++++------- 5 files changed, 115 insertions(+), 66 deletions(-) diff --git a/.env.example b/.env.example index 734c42e64..92d06d130 100644 --- a/.env.example +++ b/.env.example @@ -89,6 +89,15 @@ AUTH_LDAP_USER_SEARCH=ou=Pracownicy,dc=auth,dc=local # Bezpieczny do ujawnienia w przeglądarce. Pusty = front-end Rollbar wyłączony. ROLLBAR_CLIENT_ACCESS_TOKEN= +# Wycisza w Rollbarze smtplib.SMTPAuthenticationError (i TYLKO ją). Do włączenia +# na instalacji, gdzie administratorzy poczty klienta mają znaną, zgłoszoną +# awarię po swojej stronie. Ustawić także dla celery workera — wyjątek leci +# z djcelery_email, nie z requestu. +# NIE wycisza SMTPRecipientsRefused ani SMTPSenderRefused: te wskazują na NASZE +# dane/konfigurację. Przy DJANGO_BPP_HOSTNAMES działa na wszystkie uczelnie +# obsługiwane przez dany proces. +# DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS=1 + # # Docker exposed ports (defaults — odkomentuj, żeby zmienić) # diff --git a/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst b/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst index 80bf2b5f1..9269e6008 100644 --- a/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst +++ b/src/bpp/newsfragments/+rollbar-wycisz-bledy-poczty.feature.rst @@ -1,6 +1,6 @@ -Nowa zmienna środowiskowa ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS`` (domyślnie -wyłączona) pozwala wyciszyć w monitoringu błędów całą rodzinę błędów SMTP na -konkretnej instalacji. Do użycia tam, gdzie administratorzy poczty po stronie -klienta mają znaną awarię, której nie da się naprawić po naszej stronie — -zamiast zasypywać monitoring powtarzalnym zgłoszeniem. Na pozostałych -instalacjach błędy poczty są raportowane jak dotąd. +Nowa zmienna środowiskowa ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS`` +(domyślnie wyłączona) pozwala wyciszyć w monitoringu błędów zgłoszenia +o odrzuconych poświadczeniach serwera poczty na konkretnej instalacji. +Do użycia tam, gdzie administratorzy poczty po stronie klienta mają znaną +awarię, której nie da się naprawić po naszej stronie. Pozostałe błędy poczty +— w tym błędny adres odbiorcy czy nadawcy — są raportowane jak dotąd. diff --git a/src/django_bpp/rollbar_filters.py b/src/django_bpp/rollbar_filters.py index 1350ffb98..bcdf97351 100644 --- a/src/django_bpp/rollbar_filters.py +++ b/src/django_bpp/rollbar_filters.py @@ -1,9 +1,14 @@ """Filtry poziomów wyjątków dla Rollbara (``exception_level_filters``). pyrollbar pozwala przypisać klasie wyjątku poziom ``"ignored"`` — taki wyjątek -nie jest w ogóle wysyłany (``rollbar._is_ignored``). Używamy tego wyłącznie -tam, gdzie raport NIE niesie informacji: dana instalacja ma znaną, trwałą awarię -poza naszym kodem, a każde wystąpienie zakłada w Rollbarze osobny item. +nie jest w ogóle wysyłany. Używamy tego wyłącznie tam, gdzie awaria jest znana, +zgłoszona i leży POZA naszym kodem, więc raport nie niesie już informacji. + +Dlaczego kodem, a nie „mute" w interfejsie Rollbara: mute jest per item, a item +przepada przy zmianie hasha — wystarczyła podbitka pyrollbara 1.3.0 → 1.4.0, +żeby ten sam błąd SMTP odszczepił się w drugi item (#379 → #1554). Wyciszenie +w kodzie jest trwałe i, co ważniejsze, WIDOCZNE w repozytorium: da się je +znaleźć gitem i cofnąć, gdy klient naprawi pocztę. Wyodrębnione z ``settings.base`` jako czysta funkcja, żeby dało się to przetestować bez przeładowywania ustawień Django. @@ -14,20 +19,28 @@ import smtplib -def zbuduj_exception_level_filters(*, ignoruj_bledy_poczty: bool = False): +def zbuduj_exception_level_filters( + *, ignoruj_bledy_uwierzytelniania_smtp: bool = False +) -> list[tuple[type[BaseException], str]]: """Zwraca listę par ``(klasa_wyjątku, poziom)`` dla ``ROLLBAR``. - :param ignoruj_bledy_poczty: wycisza CAŁĄ rodzinę ``smtplib.SMTPException`` - (uwierzytelnianie, połączenie, odrzuceni odbiorcy). Włączane - per-instalacja przez ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS`` i tylko - tam, gdzie administratorzy poczty mają znaną awarię po swojej stronie, - a my nie mamy jak jej naprawić. Domyślnie WYŁĄCZONE — na pozostałych - instalacjach niedziałająca poczta to realny problem (nie idą - powiadomienia o zgłoszeniach publikacji) i chcemy o niej wiedzieć. + :param ignoruj_bledy_uwierzytelniania_smtp: wycisza ``SMTPAuthenticationError`` + — i TYLKO ją. Włączane per instalacja przez + ``DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS``, domyślnie WYŁĄCZONE. + + Celowo NIE wyciszamy całej rodziny ``smtplib.SMTPException``: + ``SMTPRecipientsRefused`` to zły adres w NASZEJ bazie, a + ``SMTPSenderRefused`` to nasza konfiguracja ``DEFAULT_FROM_EMAIL`` — + jedno i drugie jest do naprawienia po naszej stronie i chcemy o tym + wiedzieć także na instalacji z zepsutą pocztą. + + UWAGA: to ustawienie działa na CAŁY PROCES. Przy konfiguracji + wielotenantowej (``DJANGO_BPP_HOSTNAMES``) wyciszy błędy poczty + wszystkim uczelniom obsługiwanym przez ten proces. """ filters: list[tuple[type[BaseException], str]] = [] - if ignoruj_bledy_poczty: - filters.append((smtplib.SMTPException, "ignored")) + if ignoruj_bledy_uwierzytelniania_smtp: + filters.append((smtplib.SMTPAuthenticationError, "ignored")) return filters diff --git a/src/django_bpp/settings/base.py b/src/django_bpp/settings/base.py index b99fbac26..2b2ef4d33 100644 --- a/src/django_bpp/settings/base.py +++ b/src/django_bpp/settings/base.py @@ -161,11 +161,12 @@ def int_or_none(v): ROLLBAR_ACCESS_TOKEN=(str, None), # Publiczny token klienta (post_client_item) do frontendowego Rollbara. ROLLBAR_CLIENT_ACCESS_TOKEN=(str, ""), - # Wycisza w Rollbarze CAŁĄ rodzinę smtplib.SMTPException. Ustawiane PER - # INSTALACJA, domyślnie WYŁĄCZONE — szczegóły w django_bpp.rollbar_filters. - # Włączać wyłącznie tam, gdzie administratorzy poczty klienta mają znaną - # awarię po swojej stronie, której nie naprawimy kodem. - DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS=(bool, False), + # Wycisza w Rollbarze smtplib.SMTPAuthenticationError (i tylko ją). + # Ustawiane PER INSTALACJA, domyślnie WYŁĄCZONE — szczegóły i uzasadnienie + # zakresu w django_bpp.rollbar_filters. Włączać wyłącznie tam, gdzie + # administratorzy poczty klienta mają znaną, zgłoszoną awarię po swojej + # stronie, której nie naprawimy kodem. + DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS=(bool, False), # # Prometheus # @@ -1771,7 +1772,9 @@ def iter_namespace(ns_pkg): re.compile(r".*\{\{\s*clickURL\s*\}\}$"), ), "exception_level_filters": zbuduj_exception_level_filters( - ignoruj_bledy_poczty=env("DJANGO_BPP_ROLLBAR_IGNORE_SMTP_ERRORS"), + ignoruj_bledy_uwierzytelniania_smtp=env( + "DJANGO_BPP_ROLLBAR_IGNORE_SMTP_AUTH_ERRORS" + ), ), } diff --git a/src/django_bpp/tests/test_rollbar_filters.py b/src/django_bpp/tests/test_rollbar_filters.py index c8bc8f615..dcd1c90e4 100644 --- a/src/django_bpp/tests/test_rollbar_filters.py +++ b/src/django_bpp/tests/test_rollbar_filters.py @@ -1,53 +1,90 @@ -"""Wyciszanie błędów poczty w Rollbarze jest opt-in per instalacja. +"""Wyciszanie błędów uwierzytelniania SMTP w Rollbarze — opt-in per instalacja. -Kontekst (Rollbar #1554, #379 — bpp.umlub.pl): serwer SMTP uczelni odrzuca -poświadczenia (``535 5.7.3 Authentication unsuccessful``). To awaria po -stronie administratorów poczty klienta, której nie naprawimy kodem, a każde -wystąpienie zakłada w Rollbarze osobny item. Wyciszamy — ale WYŁĄCZNIE na tej -instalacji, bo na pozostałych niedziałająca poczta to realny problem. +Kontekst (Rollbar #379 i #1554 — bpp.umlub.pl): serwer SMTP uczelni odrzuca +poświadczenia (``535 5.7.3 Authentication unsuccessful``). To awaria po stronie +administratorów poczty klienta, której nie naprawimy kodem. Wyciszamy — ale +WYŁĄCZNIE na tej instalacji i WYŁĄCZNIE ten jeden wyjątek. """ import smtplib +import sys import rollbar from django_bpp.rollbar_filters import zbuduj_exception_level_filters -def _poziom(filters, wyjatek): - """Odpytuje filtry tak, jak zrobi to pyrollbar przy raportowaniu.""" - for cls, poziom in filters: - if isinstance(wyjatek, cls): - return poziom - return None +def _wyslane_payloady(monkeypatch, filters, wyjatek): + """Przepuszcza ``wyjatek`` przez PRAWDZIWĄ ścieżkę raportowania pyrollbara. + Świadomie NIE odpytujemy ``rollbar._is_ignored`` — ta funkcja nie jest + w pyrollbar 1.4.0 nigdzie wywoływana (jedyne wystąpienie to jej własna + definicja). Realne tłumienie idzie przez ``_filtered_level`` → + ``events.on_exception_info(level=...)`` → ``filters.basic.filter_by_level``. + Test odpytujący martwy kod dawałby fałszywą pewność dokładnie tam, gdzie + ma jej dostarczać. + """ + wyslane = [] + # `report_exc_info` nic nie robi, dopóki pyrollbar nie przejdzie `init()` + # — a w testach nie przechodzi (brak tokena). Inicjujemy więc jawnie, + # tokenem-atrapą, i przechwytujemy wysyłkę zamiast jej blokować. + monkeypatch.setattr(rollbar, "_initialized", True) + monkeypatch.setattr(rollbar, "send_payload", lambda p, t: wyslane.append(p)) + monkeypatch.setitem(rollbar.SETTINGS, "access_token", "atrapa") + monkeypatch.setitem(rollbar.SETTINGS, "exception_level_filters", filters) + monkeypatch.setitem(rollbar.SETTINGS, "handler", "blocking") + monkeypatch.setitem(rollbar.SETTINGS, "enabled", True) + + try: + raise wyjatek + except BaseException: + rollbar.report_exc_info(sys.exc_info()) + + return wyslane -def test_domyslnie_bledy_poczty_sa_raportowane(): - filters = zbuduj_exception_level_filters() - assert _poziom(filters, smtplib.SMTPAuthenticationError(535, b"nope")) is None +def test_domyslnie_bledy_smtp_sa_raportowane(monkeypatch): + wyslane = _wyslane_payloady( + monkeypatch, + zbuduj_exception_level_filters(), + smtplib.SMTPAuthenticationError(535, b"nope"), + ) + assert len(wyslane) == 1 -def test_wlaczona_flaga_wycisza_bledy_uwierzytelniania_smtp(): - filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) - assert _poziom(filters, smtplib.SMTPAuthenticationError(535, b"nope")) == "ignored" +def test_wlaczona_flaga_naprawde_nie_wysyla_bledu_uwierzytelniania(monkeypatch): + """Realna ścieżka wysyłki, nie sam kształt listy filtrów.""" + wyslane = _wyslane_payloady( + monkeypatch, + zbuduj_exception_level_filters(ignoruj_bledy_uwierzytelniania_smtp=True), + smtplib.SMTPAuthenticationError(535, b"nope"), + ) + assert wyslane == [] -def test_wlaczona_flaga_wycisza_cala_rodzine_bledow_smtp(): - """Awaria „poczta nie działa" ma wiele wcieleń, nie tylko 535.""" - filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) - assert _poziom(filters, smtplib.SMTPConnectError(421, b"busy")) == "ignored" - assert _poziom(filters, smtplib.SMTPServerDisconnected("bye")) == "ignored" +def test_flaga_nie_wycisza_bledow_poczty_wynikajacych_z_NASZYCH_danych(monkeypatch): + """``SMTPRecipientsRefused`` to zły adres w naszej bazie — chcemy wiedzieć. + + Zakres wyciszenia jest celowo wąski: awaria po stronie klienta to problem + uwierzytelniania, a nie każdy błąd poczty. Odrzucony odbiorca i odrzucony + nadawca wskazują na nasze dane/konfigurację i muszą być widoczne także na + instalacji z wyciszeniem. + """ + filters = zbuduj_exception_level_filters(ignoruj_bledy_uwierzytelniania_smtp=True) + + for wyjatek in ( + smtplib.SMTPRecipientsRefused({"zly@adres": (550, b"no such user")}), + smtplib.SMTPSenderRefused(553, b"bad sender", "bpp@example.com"), + ): + assert len(_wyslane_payloady(monkeypatch, filters, wyjatek)) == 1 -def test_flaga_nie_wycisza_bledow_spoza_poczty(): - """Wyciszenie ma być chirurgiczne — nie zasłaniać niczego innego.""" - filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) +def test_flaga_nie_wycisza_niczego_spoza_poczty(monkeypatch): + filters = zbuduj_exception_level_filters(ignoruj_bledy_uwierzytelniania_smtp=True) - assert _poziom(filters, ConnectionRefusedError("redis")) is None - assert _poziom(filters, ValueError("cokolwiek")) is None + assert len(_wyslane_payloady(monkeypatch, filters, ValueError("cokolwiek"))) == 1 def test_ustawienia_faktycznie_wpinaja_filtry_do_rollbara(): @@ -58,16 +95,3 @@ def test_ustawienia_faktycznie_wpinaja_filtry_do_rollbara(): # W testach flaga jest wyłączona → żadnych wyciszeń. To jest też asercja # bezpieczeństwa: domyślna instalacja NIE gubi błędów. assert settings.ROLLBAR["exception_level_filters"] == [] - - -def test_pyrollbar_faktycznie_honoruje_nasz_format_filtrow(monkeypatch): - """Kontrakt z pyrollbarem: nasze krotki muszą działać w ``_is_ignored``. - - Bez tego testu literówka w formacie (np. ``"ignore"`` zamiast - ``"ignored"``) przeszłaby niezauważona — filtry są danymi, nie kodem. - """ - filters = zbuduj_exception_level_filters(ignoruj_bledy_poczty=True) - monkeypatch.setitem(rollbar.SETTINGS, "exception_level_filters", filters) - - assert rollbar._is_ignored(smtplib.SMTPAuthenticationError(535, b"nope")) - assert not rollbar._is_ignored(ValueError("cokolwiek"))