dotb_ai-platform/tests/test_observability.py
2026-09-14 09:57:12 +07:00

126 lines
4.3 KiB
Python

import logging
import pytest
from httpx import ASGITransport, AsyncClient
from lead_scoring.config import Settings
from lead_scoring.main import create_app
from lead_scoring.observability import Observability, redact_payload, redact_text
def test_redact_payload_removes_credentials_snapshot_and_pii() -> None:
secret = "sentinel-vkey-123"
payload = {
"authorization": f"Bearer {secret}",
"tenant_v_key": secret,
"service_secret": "sentinel-service-secret",
"snapshot": {"lead_profile": {"email": "person@example.com"}},
"review_note": "private note",
"safe_code": "aihub_timeout",
}
redacted = redact_payload(payload)
assert secret not in repr(redacted)
assert "sentinel-service-secret" not in repr(redacted)
assert redacted["snapshot"] == "[REDACTED]"
assert redacted["review_note"] == "[REDACTED]"
assert redacted["safe_code"] == "aihub_timeout"
technical_metadata = redact_payload({"stage_name": "AIHub", "message_id": "msg-1"})
assert technical_metadata == {"stage_name": "AIHub", "message_id": "msg-1"}
def test_observability_records_redacted_exception_and_alert_threshold(caplog) -> None:
caplog.set_level(logging.DEBUG)
telemetry = Observability(
alert_thresholds={"aihub_failures_total": 1},
)
telemetry.record_exception(
"aihub_failure",
RuntimeError("request contained sentinel-secret"),
run_id="run-1",
secrets=("sentinel-secret",),
)
telemetry.increment(
"aihub_failures_total",
run_id="run-1",
fields={
"snapshot": {
"email": "person@example.com",
"phone": "+84901234567",
}
},
)
telemetry.record(
"trace.test",
fields={"safe_value": "sentinel-service-secret"},
secrets=("sentinel-service-secret",),
)
snapshot = telemetry.snapshot()
assert snapshot["counters"]["aihub_failures_total"] == 1
assert snapshot["alerts"][0]["metric"] == "aihub_failures_total"
assert "sentinel-secret" not in repr(snapshot)
assert "sentinel-service-secret" not in repr(snapshot)
assert "sentinel-service-secret" not in caplog.text
assert "+84901234567" not in repr(snapshot)
assert "person@example.com" not in repr(snapshot)
def test_settings_expose_kill_switch_and_alert_thresholds() -> None:
settings = Settings(kill_switch_enabled=True, stuck_run_alert_threshold=2)
assert settings.kill_switch_enabled is True
assert settings.stuck_run_alert_threshold == 2
def test_observability_exposes_required_metric_families_and_redacted_text() -> None:
telemetry = Observability()
for metric in (
"scoring_admission_total",
"scoring_run_latency_seconds",
"aihub_failures_total",
"scoring_degraded_rate",
"scoring_hitl_rate",
"scoring_override_rate",
"aihub_tokens_total",
"scoring_stuck_runs_total",
"outbox_retries_total",
"outbox_dead_letter_total",
"retention_failures_total",
):
telemetry.increment(metric)
telemetry.observe("scoring_run_latency_seconds", 0.25)
telemetry.observe("scoring_degraded_rate", 1.0)
snapshot = telemetry.snapshot()
assert set(snapshot["counters"]).issuperset(
{
"scoring_admission_total",
"aihub_failures_total",
"outbox_dead_letter_total",
"retention_failures_total",
}
)
assert snapshot["histograms"]["scoring_degraded_rate"]["average"] == 1.0
assert redact_text("Authorization: Bearer vkey-secret", secrets=("vkey-secret",)) == (
"Authorization: Bearer [REDACTED]"
)
assert "person@example.com" not in redact_text("person@example.com")
assert "+84901234567" not in redact_text("+84901234567")
@pytest.mark.asyncio
async def test_request_correlation_does_not_echo_pii() -> None:
app = create_app(Settings())
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.get(
"/health",
headers={"X-Request-ID": "person@example.com"},
)
assert response.status_code == 503
assert response.headers["X-Request-ID"] != "person@example.com"
assert "person@example.com" not in repr(app.state.telemetry.snapshot())