251e509ee0
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>
51 lines
1.5 KiB
Python
51 lines
1.5 KiB
Python
import pytest
|
|
from pyra.config.schema import PyraConfig, ProviderConfig
|
|
|
|
|
|
def test_config_saves_no_api_key(tmp_pyra_home):
|
|
from pyra.config.manager import save_config, load_config
|
|
|
|
cfg = PyraConfig(ai=ProviderConfig(provider_id="anthropic", model="claude-sonnet-4-6"))
|
|
save_config(cfg)
|
|
|
|
config_text = (tmp_pyra_home / "config.yaml").read_text()
|
|
assert "sk-" not in config_text
|
|
assert "api_key" not in config_text.lower()
|
|
|
|
|
|
def test_config_round_trip(tmp_pyra_home):
|
|
from pyra.config.manager import save_config, load_config
|
|
|
|
cfg = PyraConfig(
|
|
ai=ProviderConfig(
|
|
provider_id="lmstudio",
|
|
model="gemma-4-e4b",
|
|
base_url="http://localhost:1234/v1",
|
|
)
|
|
)
|
|
save_config(cfg)
|
|
loaded = load_config()
|
|
|
|
assert loaded.ai.provider_id == "lmstudio"
|
|
assert loaded.ai.model == "gemma-4-e4b"
|
|
assert loaded.ai.base_url == "http://localhost:1234/v1"
|
|
|
|
|
|
def test_config_file_permissions(tmp_pyra_home):
|
|
import os
|
|
from pyra.config.manager import save_config
|
|
|
|
cfg = PyraConfig(ai=ProviderConfig(provider_id="ollama", model="llama3"))
|
|
save_config(cfg)
|
|
|
|
config_file = tmp_pyra_home / "config.yaml"
|
|
if os.name != "nt":
|
|
mode = oct(config_file.stat().st_mode)[-3:]
|
|
assert mode == "600", f"Expected 600, got {mode}"
|
|
|
|
|
|
def test_load_config_missing_raises(tmp_pyra_home):
|
|
from pyra.config.manager import load_config
|
|
with pytest.raises(FileNotFoundError):
|
|
load_config()
|