test: comprehensive test suite
Unit tests: - test_security_boundaries.py: vault block, vault lock sentinel - test_security_injection.py: all 4 injection categories, case-insensitive - test_vault_rw.py: roundtrip, file permissions (chmod 400), no key in config - test_config.py: schema roundtrip, no api_key field, chmod 600 on config.yaml - test_memory_reader.py: list, read, sandboxing, context loading - test_memory_writer.py: write, append, index update, traversal blocked, chmod 600 - test_providers.py: required fields, unique IDs, litellm prefix format - test_renderer.py: key redaction for sk-ant-, sk-, AIza patterns Security tests: - test_vault_ai_isolation.py: 7 traversal patterns blocked via memory read/write - test_path_traversal.py: 20+ traversal patterns — all rejected for read and write - test_prompt_injection.py: 21-item corpus + 5 clean texts (no false positives) Integration tests: - test_lmstudio.py: live call to localhost:1234, streaming, full stack session, injection scan on real output (skips if LM Studio not running) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
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
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
# Bootstrap the directory structure
|
||||
from pyra.config.dirs import bootstrap
|
||||
(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()
|
||||
|
||||
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")
|
||||
Reference in New Issue
Block a user