"""Тесты для `_drop_basic_auth_noise` (before_send-фильтр, glitchtip-noise). Раньше форвардер слал КАЖДЫЙ basic_auth 401 (сканеры-боты, ломящиеся в закрытый basic_auth'ом gendsgn.ru) individual-событием в GlitchTip — 3 738 issue, 2 019 различных заголовков (remote_ip раздувал кардинальность), топя содержательный сигнал. `_drop_basic_auth_noise` дропает эти события НА ИСТОЧНИКЕ (before_send), но НЕ должен трогать unhandled-ошибки самого форвардера (реальный баг скрипта — `capture_exception` без `event_type`-тега, аналог "500 должен пройти"). """ from __future__ import annotations import os # DSN обязателен на module-level (`os.environ["GLITCHTIP_DSN"]`, fail-fast) — задаём # ДО импорта forwarder.py, иначе импорт падает KeyError. os.environ.setdefault("GLITCHTIP_DSN", "http://test@localhost/1") from forwarder import _BASIC_AUTH_EVENT_TYPES, _drop_basic_auth_noise def test_drops_individual_basic_auth_401() -> None: """emit_event() тегирует event_type=basic_auth_failed — 401 от бота-сканера, не ошибка сервиса, должен быть отброшен (return None).""" event = { "tags": {"event_type": "basic_auth_failed", "remote_ip": "95.165.147.218"}, "message": "basic_auth 401 — GET /wp-admin/install.php from 95.165.147.218", } assert _drop_basic_auth_noise(event, {}) is None def test_drops_basic_auth_storm_digest() -> None: """emit_digest() тегирует event_type=basic_auth_storm — тоже 401-класс, тоже не ошибка сервиса, дропаем.""" event = { "tags": {"event_type": "basic_auth_storm"}, "message": "basic_auth storm — 15 failed attempts in 60s", } assert _drop_basic_auth_noise(event, {}) is None def test_drops_when_tags_serialized_as_list_of_tuples() -> None: """Некоторые версии sentry_sdk сериализуют tags как list[tuple[str, str]] вместо dict — фильтр обязан поддерживать обе формы.""" event = {"tags": [("event_type", "basic_auth_failed")]} assert _drop_basic_auth_noise(event, {}) is None def test_passes_through_forwarder_own_crash() -> None: """500-аналог: unhandled exception самого форвардера (capture_exception в конце main(), реальный баг скрипта — напр. PermissionError на STATE_FILE) не несёт event_type-тег → должен пройти НЕТРОНУТЫМ, не быть молча проглоченным вместе с ботовым шумом.""" event = { "level": "error", "exception": {"values": [{"type": "PermissionError", "value": "denied"}]}, } out = _drop_basic_auth_noise(dict(event), {}) assert out == event def test_passes_through_event_without_tags() -> None: event: dict = {"message": "something unrelated"} out = _drop_basic_auth_noise(dict(event), {}) assert out == event def test_passes_through_unrelated_tag_value() -> None: event = {"tags": {"event_type": "something_else"}} out = _drop_basic_auth_noise(dict(event), {}) assert out == event def test_basic_auth_event_types_are_exactly_the_two_emitters_use() -> None: """Явная фиксация словаря — emit_event → basic_auth_failed, emit_digest → basic_auth_storm (см. forwarder.py).""" assert _BASIC_AUTH_EVENT_TYPES == frozenset({"basic_auth_failed", "basic_auth_storm"})