gendesign/backend/tests/test_sentry_init.py
lekss361 b61f025673
Some checks failed
Deploy / build-backend (push) Has been cancelled
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 4s
Deploy / build-worker (push) Has been cancelled
feat(backend): sentry-sdk init для FastAPI + Celery (#204 backend) (#207)
2026-05-16 15:09:16 +00:00

158 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Unit-тесты логики инициализации GlitchTip SDK.
Проверяем что init-блок в main.py / celery_app.py вызывает sentry_sdk.init()
только при непустом GLITCHTIP_DSN, что release-fallback работает корректно,
и что scrub_sensitive_query redact-ит api keys из URL spans.
"""
import os
from unittest.mock import patch
import sentry_sdk
def test_sdk_imports_without_error() -> None:
"""Все интеграции импортируются без ModuleNotFoundError."""
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.httpx import HttpxIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
assert StarletteIntegration()
assert FastApiIntegration()
assert CeleryIntegration()
assert SqlalchemyIntegration()
assert HttpxIntegration()
assert LoggingIntegration()
def test_no_sdk_init_when_dsn_empty() -> None:
"""Если GLITCHTIP_DSN пустой, sentry_sdk.init() не должен вызываться."""
with patch("sentry_sdk.init") as mock_init:
glitchtip_dsn = None
if glitchtip_dsn:
sentry_sdk.init(dsn=glitchtip_dsn)
mock_init.assert_not_called()
def test_sdk_init_called_when_dsn_set() -> None:
"""Если GLITCHTIP_DSN задан, sentry_sdk.init() вызывается с правильными параметрами."""
dsn = "https://key@errors.gendsgn.ru/1"
with patch("sentry_sdk.init") as mock_init:
glitchtip_dsn = dsn
if glitchtip_dsn:
sentry_sdk.init(
dsn=glitchtip_dsn,
environment="test",
release="unknown",
traces_sample_rate=0.05,
profiles_sample_rate=0.0,
send_default_pii=False,
integrations=[],
)
mock_init.assert_called_once()
call_kwargs = mock_init.call_args.kwargs
assert call_kwargs["dsn"] == dsn
assert call_kwargs["send_default_pii"] is False
assert call_kwargs["profiles_sample_rate"] == 0.0
# ── release fallback ──────────────────────────────────────────────────────────
def test_release_uses_git_sha_when_set() -> None:
"""GIT_SHA имеет приоритет над SENTRY_RELEASE."""
env = {"GIT_SHA": "abc1234", "SENTRY_RELEASE": "v1.0.0"}
with patch.dict(os.environ, env, clear=False):
release = os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown"
assert release == "abc1234"
def test_release_falls_back_to_sentry_release() -> None:
"""Если GIT_SHA не задан, используется SENTRY_RELEASE."""
env = {"SENTRY_RELEASE": "v1.2.3"}
with patch.dict(os.environ, env, clear=False):
# Убираем GIT_SHA если он есть
os.environ.pop("GIT_SHA", None)
release = os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown"
assert release == "v1.2.3"
def test_release_falls_back_to_unknown() -> None:
"""Если ни одна переменная не задана, release='unknown'."""
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("GIT_SHA", None)
os.environ.pop("SENTRY_RELEASE", None)
release = os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown"
assert release == "unknown"
# ── sentry_scrub ──────────────────────────────────────────────────────────────
def test_scrub_redacts_apikey_in_span_url() -> None:
"""scrub_sensitive_query заменяет apiKey= в span data['url']."""
from app.observability.sentry_scrub import scrub_sensitive_query
event: dict = {
"spans": [
{
"data": {
"url": "https://api.objctv.ru/v2/Report?apiKey=supersecret&group=EKB",
"http.url": "https://api.objctv.ru/v2/Report?api_key=topsecret",
}
}
]
}
result = scrub_sensitive_query(event, {})
span_data = result["spans"][0]["data"]
assert "[REDACTED]" in span_data["url"]
assert "supersecret" not in span_data["url"]
assert "[REDACTED]" in span_data["http.url"]
assert "topsecret" not in span_data["http.url"]
def test_scrub_redacts_token_in_description() -> None:
"""scrub_sensitive_query заменяет token= в span description."""
from app.observability.sentry_scrub import scrub_sensitive_query
event: dict = {
"spans": [{"description": "GET https://example.com?token=mysecrettoken&foo=bar"}]
}
result = scrub_sensitive_query(event, {})
assert "[REDACTED]" in result["spans"][0]["description"]
assert "mysecrettoken" not in result["spans"][0]["description"]
def test_scrub_redacts_request_url() -> None:
"""scrub_sensitive_query заменяет token в event['request']['url']."""
from app.observability.sentry_scrub import scrub_sensitive_query
event: dict = {"request": {"url": "https://example.com?access_token=abc123&other=val"}}
result = scrub_sensitive_query(event, {})
assert "[REDACTED]" in result["request"]["url"]
assert "abc123" not in result["request"]["url"]
def test_scrub_passes_through_clean_event() -> None:
"""scrub_sensitive_query не трогает URL без чувствительных параметров."""
from app.observability.sentry_scrub import scrub_sensitive_query
event: dict = {
"spans": [{"data": {"url": "https://example.com?foo=bar&page=1"}}],
"request": {"url": "https://example.com/api/health"},
}
result = scrub_sensitive_query(event, {})
assert result["spans"][0]["data"]["url"] == "https://example.com?foo=bar&page=1"
assert result["request"]["url"] == "https://example.com/api/health"
def test_scrub_handles_missing_spans() -> None:
"""scrub_sensitive_query не падает если 'spans' отсутствует."""
from app.observability.sentry_scrub import scrub_sensitive_query
event: dict = {"request": {"url": "https://example.com"}}
result = scrub_sensitive_query(event, {})
assert result["request"]["url"] == "https://example.com"