""" Tests for Stage 84 — request correlation IDs + tracing log fields. Coverage: 1. sanitize_upstream_id — accept valid, reject empty/whitespace/ too-long/special-chars 2. generate_request_id — 16 hex chars, two calls produce different values 3. set/clear_request_context + contextvar reads 4. JsonLogFormatter auto-injects request_id + tenant_id when set in context; omits when None 5. Integration: X-Request-ID present in response (generated) 6. Integration: upstream X-Request-ID preserved (valid) 7. Integration: malicious upstream rejected → fresh generated 8. Integration: different requests get different IDs 9. Integration: authenticated request sets tenant_id in context visible to handler logs 10. Context cleared after request returns (no leakage) """ import json import logging import pytest pytest.importorskip("fastapi") pytest.importorskip("httpx") from fastapi.testclient import TestClient from infra import OrgStateService from infra.api import create_app from infra.api.request_context import ( clear_request_context, current_request_id, current_tenant_id, generate_request_id, sanitize_upstream_id, set_request_context, set_tenant_context, ) from infra.deployment.observability import JsonLogFormatter # ========================================================= # sanitize_upstream_id — unit # ========================================================= def test_sanitize_accepts_uuid_hex(): """32-char hex (uuid.hex format) — should pass.""" assert sanitize_upstream_id( "aabbccdd11223344aabbccdd11223344" ) == "aabbccdd11223344aabbccdd11223344" def test_sanitize_accepts_uuid_with_dashes(): assert sanitize_upstream_id( "aabbccdd-1122-3344-aabb-ccdd11223344" ) == "aabbccdd-1122-3344-aabb-ccdd11223344" def test_sanitize_accepts_cloudflare_ray(): """CF-RAY format is letters+digits+dash; should pass.""" assert sanitize_upstream_id("8a3f12cd6a17abcd-DUB") == \ "8a3f12cd6a17abcd-DUB" def test_sanitize_rejects_none_and_empty(): assert sanitize_upstream_id(None) is None assert sanitize_upstream_id("") is None assert sanitize_upstream_id(" ") is None def test_sanitize_rejects_too_long(): """Defense against log injection — upstream sending 10KB request ID would balloon every log line.""" assert sanitize_upstream_id("a" * 129) is None # exactly at the limit is OK assert sanitize_upstream_id("a" * 128) == "a" * 128 def test_sanitize_rejects_special_chars(): """Whitespace + newline + control chars would break log grep + Sentry tag rules.""" for bad in ("has space", "has\nnewline", "has;semi", "has