"""Cross-suite test infrastructure.""" from __future__ import annotations import os import ssl import sys from collections.abc import Iterator from pathlib import Path import certifi import pytest from loguru import logger @pytest.fixture(autouse=True) def _isolate_nanobot_log_activation() -> Iterator[None]: """Keep CLI log settings from leaking into later tests in the same process.""" logger.enable("nanobot") try: yield finally: logger.enable("nanobot") @pytest.fixture(autouse=True) def _isolate_sessions_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]: """Redirect session storage away from the real active config data directory. Session storage lives under the active runtime data root (outside the workspace, per ADR-0001), so without redirection tests would write into the real home. """ runtime_root = tmp_path.parent / f"{tmp_path.name}-runtime-root" legacy_root = tmp_path.parent / f"{tmp_path.name}-legacy-sessions-root" def runtime_subdir(name: str) -> Path: path = runtime_root / name path.mkdir(parents=True, exist_ok=True) return path monkeypatch.setattr( "nanobot.session.manager.get_runtime_subdir", runtime_subdir, ) monkeypatch.setattr( "nanobot.session.manager.get_legacy_sessions_dir", lambda: legacy_root, ) yield @pytest.fixture(scope="session", autouse=True) def _use_windows_system_ca_for_default_http_clients() -> Iterator[None]: """Avoid reparsing certifi's CA bundle for every offline HTTP client. Loading certifi takes roughly 0.7 seconds per client on Windows. The test suite constructs hundreds of clients while mocking their I/O. System roots preserve certificate verification for accidental local requests; explicit ``cafile``, ``capath``, and ``cadata`` arguments still use the real loader. """ if sys.platform != "win32": yield return original = ssl.create_default_context certifi_path = os.path.normcase(os.path.abspath(certifi.where())) def create_default_context( purpose: ssl.Purpose = ssl.Purpose.SERVER_AUTH, *, cafile: str | None = None, capath: str | None = None, cadata: str | bytes | None = None, ) -> ssl.SSLContext: requested_path = os.path.normcase(os.path.abspath(cafile)) if cafile else None if requested_path == certifi_path and capath is None and cadata is None: return original(purpose) return original( purpose, cafile=cafile, capath=capath, cadata=cadata, ) ssl.create_default_context = create_default_context try: yield finally: ssl.create_default_context = original