import json import os from pathlib import Path import pytest @pytest.fixture def tmp_pyra_home(tmp_path, monkeypatch): """Redirect pyra_home() to a temporary directory for isolation.""" fake_home = tmp_path / ".pyra" # Patch Path.home() so pyra_home() returns our tmp dir monkeypatch.setattr( "pyra.utils.paths.Path", type("FakePath", (Path,), {"home": staticmethod(lambda: tmp_path)}), ) # Also patch the module-level constants already computed import pyra.security.boundaries as b import pyra.memory.index as mi import pyra.memory.reader as mr import pyra.memory.writer as mw import pyra.vault.reader as vr import pyra.vault.writer as vw import pyra.security.injection as si import pyra.config.manager as cm import pyra.plugins.loader as pl import pyra.plugins.executor as pe import pyra.memory.database as mdb b.VAULT_PATH = fake_home / "vault" b.BLOCKED_PREFIXES = [b.VAULT_PATH] mi._MEMORY_ROOT = fake_home / "memory" mi._INDEX_FILE = fake_home / "memory" / "MEMORY_INDEX.md" mr._MEMORY_ROOT = fake_home / "memory" mw._MEMORY_ROOT = fake_home / "memory" mdb._DB_PATH = fake_home / "memory" / "memory.db" mdb._MEMORY_ROOT = fake_home / "memory" vr._KEYS_FILE = fake_home / "vault" / "secrets" / "api_keys.json" vw._KEYS_FILE = fake_home / "vault" / "secrets" / "api_keys.json" si._LOG_FILE = fake_home / "security.log" cm._CONFIG_PATH = fake_home / "config.yaml" pl._LOG_FILE = fake_home / "logs" / "plugin_errors.log" pe._LOG_FILE = fake_home / "logs" / "tool_executions.log" # Bootstrap the directory structure (fake_home / "vault").mkdir(parents=True) (fake_home / "vault" / "secrets").mkdir() (fake_home / "vault" / ".vault_lock").touch(mode=0o400) (fake_home / "memory" / "user").mkdir(parents=True) (fake_home / "memory" / "context").mkdir() (fake_home / "memory" / "knowledge").mkdir() (fake_home / "plugins").mkdir() (fake_home / "logs").mkdir() mdb.init_db() # Reset plugin registry singleton so tests don't share state from pyra.plugins.registry import PluginRegistry PluginRegistry.reset() return fake_home @pytest.fixture def lmstudio_available(): """Skip test if LM Studio is not reachable.""" import httpx try: r = httpx.get("http://localhost:1234/v1/models", timeout=2.0) r.raise_for_status() return True except Exception: pytest.skip("LM Studio not reachable at localhost:1234")