[tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" testpaths = ["tests", "management/tests"] # management/tests/ has __init__.py but management/ itself doesn't (implicit # namespace package) — pytest's default rootdir walk-up stops at management/ # instead of reaching the repo root, breaking `from management.api import ...` # under bare `pytest` (phase-808). pythonpath sidesteps that without touching # application packaging. pythonpath = ["."] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] # --tb=short: concise tracebacks # --timeout=60: per-test hard limit (requires pytest-timeout) # -q: quiet mode by default; run-local-tests.sh adds -v when writing to log addopts = "--tb=short --timeout=60" markers = [ "live_services: requires live Go/Python proxy and Redis; excluded from make test (use make test-live)", "integration: requires live external IdP (Okta, Entra ID, Keycloak); set OKTA_METADATA_URL or ENTRA_OIDC_DISCOVERY_URL", "unit: marks a unit test", "chaos: chaos/resilience test", "adversarial: adversarial/fuzz test", ] # Warning policy: # - Third-party library internals: ignore (too noisy, nothing we can fix) # - asyncio cleanup noise from mocks: ignore specific patterns # - Everything else: default behaviour (shown but not fatal) # - Our own code: any unexpected RuntimeWarning will surface naturally filterwarnings = [ # pytest-asyncio internal deprecation notices "ignore::DeprecationWarning:pytest_asyncio", "ignore::DeprecationWarning:_pytest", # Third-party library internals — nothing actionable for us "ignore::DeprecationWarning:pkg_resources", "ignore::DeprecationWarning:google", "ignore::DeprecationWarning:aiohttp", "ignore::DeprecationWarning:redis", # urllib3 / requests noise in test environments "ignore::urllib3.exceptions.InsecureRequestWarning", "ignore::urllib3.exceptions.SecurityWarning", # asyncio teardown with mocked tasks — not a real resource leak "ignore:.*Enable tracemalloc.*:pytest.PytestUnraisableExceptionWarning", "ignore:coroutine .* was never awaited:RuntimeWarning", # aiohttp connector cleanup in unit tests (no real connections, just mocks) "ignore:.*Unclosed client session.*:ResourceWarning", "ignore:.*Unclosed connector.*:ResourceWarning", # ssl.OP_NO_TLS* deprecated in Python 3.10+ (security/validation.py — third-party pattern) "ignore:ssl.OP_NO_SSL.*:DeprecationWarning", "ignore:ssl.OP_NO_TLS.*:DeprecationWarning", # pycares async DNS callbacks arrive after asyncio loop closes during test teardown. # This is a known interaction between pycares, Python 3.10 asyncio, and pytest-asyncio. # Nothing actionable in our code — pycares teardown is outside our control. # Approved Exception #001: see docs/security/EXCEPTIONS.md "ignore::pytest.PytestUnraisableExceptionWarning", # Google API Python 3.10 end-of-support notice — not actionable until we upgrade "ignore::FutureWarning:google.api_core", # Strict warning enforcement on first-party management and src modules "error::DeprecationWarning:management.*", "error::DeprecationWarning:src.*", # Upstream python3-saml (onelogin.saml2) datetime & port deprecations (Python 3.14 upstream debt) "ignore:datetime.datetime.utcnow\\(\\) is deprecated:DeprecationWarning:onelogin.saml2.*", "ignore:datetime.datetime.utcfromtimestamp\\(\\) is deprecated:DeprecationWarning:onelogin.saml2.*", "ignore:The server_port key in request data is deprecated:DeprecationWarning:onelogin.saml2.*", # Upstream Authlib / Jose deprecation warnings (scheduled for Authlib 2.0.0) "ignore::authlib.deprecate.AuthlibDeprecationWarning", # Upstream Starlette / FastAPI testclient httpx warning "ignore::starlette.exceptions.StarletteDeprecationWarning", # HTTPX per-request cookie deprecation in integration test clients "ignore:Setting per-request cookies=.* is being deprecated:DeprecationWarning:httpx.*", # Intentional short HMAC key in test_protected_route_wrong_secret negative test "ignore:The HMAC key is 12 bytes long:UserWarning:jwt.api_jwt", ] # ── mypy configuration (consolidated from mypy.ini) ────────────────────────── [tool.mypy] exclude = ["^archive/", "^vendor/", "^node_modules/"] # Phase 16f baseline — --ignore-missing-imports --no-strict-optional # Goal: zero errors in new code; suppress known legacy patterns. # Phase 16g will tighten to --strict once Redis typing is resolved. python_version = "3.15" ignore_missing_imports = true no_strict_optional = true warn_return_any = false warn_unused_ignores = false # Suppress "Library stubs not installed" (import-untyped) raised by mypy ≥1.19 # when third-party stub packages (e.g. types-PyYAML) are absent in CI Docker. disable_error_code = "import-untyped" [[tool.mypy.overrides]] module = [ "proxy", "src.security.validation", "src.analytics.detection", "src.analytics.stream_consumer", "src.security.rdap_enrichment", "src.security.tcp_analyzer", "src.security.abuseipdb", "src.security.beaconing_detector", "src.security.sni_analyzer", "src.security.tls_enforcer", "src.security.dns_enrichment", "src.security.pipeline", "src.security.asn_classifier", "src.security.gdpr_storage", "src.cache.bloom", "src.analytics.monitoring", "src.analytics.shadow_scoring", "src.analytics.baseline_monitor", "src.pubsub", "src.analytics.security_hardening", "src.analytics.ml_detector", ] ignore_errors = true # ── flake8 configuration (consolidated from .flake8) ───────────────────────── # Note: flake8 does not natively read pyproject.toml; requires Flake8-pyproject # or use ruff instead. Kept here for reference alongside ruff config. [tool.flake8] max-line-length = 88 extend-ignore = ["E203", "W503", "E501", "E402", "F841"] [tool.ruff] exclude = ["archive", "vendor", "node_modules", "deploy/secrets"] # Phase 16f baseline — check src/ and proxy.py; tests are advisory only. target-version = "py313" line-length = 120 [tool.ruff.lint] # Enable: pyflakes (F), pycodestyle errors (E), isort (I) # Skip: W (warnings — cosmetic), C (complexity), ANN (annotations — covered by mypy) select = ["E", "F", "I"] ignore = [ "E501", # line-too-long — handled by black "E402", # module-level import not at top — some files have conditional imports "F401", # unused import — too noisy in tests; addressed separately in src/ ] [tool.ruff.lint.per-file-ignores] # Tests: allow unused imports (often imported for re-export or side effects) "tests/**" = ["F401", "F811", "F841", "E711", "E712", "E721", "E741"] # Admin CLI: allow broad exception handling "scripts/ja4proxy_admin.py" = ["E722"] # ── pylint configuration ───────────────────────────────────────────────────── # `make lint-pylint` runs errors-only over src/ and management/. Two classes of # false positive were drowning out real findings; both are silenced here rather # than at each call site so the reason is recorded once. [tool.pylint.main] # `management/` is a PEP 420 namespace package (no __init__.py) while # `management/api/` has one, so pylint sometimes anchors on `api` as the top # level and then reports `from ..tasks.dial_revert` as an import beyond it — # intermittently, depending on walk order. At runtime the module is always # imported as `management.api.main` and the import is fine. Pinning the source # root makes pylint resolve packages the same way the application does. source-roots = ["."] [tool.pylint.typecheck] # weasyprint is an OPTIONAL dependency — it is deliberately absent from # requirements.txt because it needs system libraries (pango, cairo, fontconfig) # that the lint and CI images do not carry. Every import of it in this repo sits # inside a try/except that degrades to HTML output, so `import-error` here # reports a supported configuration as a failure. Removing this entry is only # correct if weasyprint becomes a hard dependency. ignored-modules = ["weasyprint"]