""" Shared fixtures for Cortex test suite. Key design choices: - All file I/O goes to a tmp_path, never touching home/ or real sessions. - LLM calls are mocked by default — tests are fast and deterministic. - The 'client' fixture patches settings before importing main, so all modules see the temp directory. Home layout mirrors the two-level structure: tmp/ scott/ persona/ inara/ ← the default test persona holly/ persona/ tina/ """ import json import pytest import pytest_asyncio from pathlib import Path from unittest.mock import AsyncMock, patch import httpx from httpx import ASGITransport # --------------------------------------------------------------------------- # Temp home directory # --------------------------------------------------------------------------- @pytest.fixture(scope="session") def home_root(tmp_path_factory) -> Path: """A temp home/ dir with minimal user/persona stubs for testing.""" root = tmp_path_factory.mktemp("home") _make_persona(root, "scott", "inara", "Inara", "Scott") _make_persona(root, "holly", "tina", "Tina", "Holly") return root def _make_persona(root: Path, username: str, persona: str, agent: str, user: str) -> Path: p = root / username / "persona" / persona p.mkdir(parents=True, exist_ok=True) (p / "IDENTITY.md").write_text(f"# {agent}\nTest identity for {agent}.") (p / "SOUL.md").write_text(f"# Soul\nTest soul for {agent}.") (p / "PROTOCOLS.md").write_text("# Protocols\nBe helpful.") (p / "USER.md").write_text(f"# {user}\nTest user profile.") (p / "HELP.md").write_text("# Help\nTest help content.") (p / "MEMORY_LONG.md").write_text("Not yet populated.") (p / "MEMORY_MID.md").write_text("Not yet populated.") (p / "MEMORY_SHORT.md").write_text("Not yet populated.") (p / "TASKS.json").write_text("[]") (p / "CRONS.json").write_text("[]") (p / "SCRATCH.md").write_text("") (p / "REMINDERS.md").write_text("") (p / "sessions").mkdir() return p # --------------------------------------------------------------------------- # App fixture — patches settings before the ASGI app is started # --------------------------------------------------------------------------- @pytest_asyncio.fixture async def client(home_root, tmp_path): """ HTTPX async test client with a valid session cookie for 'scott'. The auth middleware is active but a JWT cookie is pre-set so API tests don't need to go through the login flow. """ import config import persona as persona_mod sessions_dir = tmp_path / "sessions" sessions_dir.mkdir() with ( patch.object(config.settings, "home_dir", home_root), patch.object(config.settings, "sessions_dir", sessions_dir), patch.object(config.settings, "jwt_secret", "test-secret-key-xxxxxxxxxxxxxxxx"), patch("scheduler.start"), # don't run APScheduler in tests patch("scheduler.stop"), ): persona_mod.set_context("scott", "inara") from main import app from auth_utils import create_token token = create_token("scott") async with httpx.AsyncClient( transport=ASGITransport(app=app), base_url="http://test", cookies={"cortex_session": token}, ) as c: yield c # --------------------------------------------------------------------------- # LLM mock # --------------------------------------------------------------------------- @pytest.fixture def mock_llm(): """ Patch complete() at every import site so no real LLM calls are made. Each router does `from llm_client import complete`, creating a local reference. Patching llm_client.complete alone won't intercept those — patch each site. """ ret = ("Hello, I am a test response.", "claude") with ( patch("routers.chat.complete", new_callable=AsyncMock, return_value=ret), patch("routers.nextcloud_talk.complete", new_callable=AsyncMock, return_value=ret), patch("routers.google_chat.complete", new_callable=AsyncMock, return_value=ret), patch("llm_client.complete", new_callable=AsyncMock, return_value=ret), ): yield