Tests cover: - Smoke: /health, /auth/status, /distill/status (test_health.py) - Persona validation: path traversal, bad names, list_personas (test_persona.py) - Chat API: persona routing, session persistence, error handling (test_api_chat.py) - Files API: ALLOWED set enforcement, read/write, missing files (test_api_files.py) - Webhooks: NC Talk HMAC accept/reject, Google Chat JWT (test_webhooks.py) - Tools: scratch read/write/append/clear, tasks CRUD, cron parser + tools (test_tools.py) - Security: path traversal, replay attack, known gaps documented (test_security.py) All LLM calls mocked — suite runs in ~1.4s. Run: cd cortex && .venv/bin/pytest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""
|
|
Unit tests for persona.py — validation, routing, path traversal.
|
|
No HTTP involved.
|
|
"""
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
|
|
def _make_temp_personas(tmp_path: Path) -> Path:
|
|
root = tmp_path / "personas"
|
|
for name in ("alice", "bob"):
|
|
p = root / name
|
|
p.mkdir(parents=True)
|
|
(p / "IDENTITY.md").write_text(f"# {name}")
|
|
# A directory WITHOUT IDENTITY.md — should not appear in list_personas()
|
|
(root / "incomplete").mkdir()
|
|
return root
|
|
|
|
|
|
def test_validate_good(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
assert persona.validate("alice") == "alice"
|
|
assert persona.validate("bob") == "bob"
|
|
|
|
|
|
def test_validate_unknown(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
with pytest.raises(ValueError, match="Unknown persona"):
|
|
persona.validate("charlie")
|
|
|
|
|
|
def test_validate_path_traversal(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
with pytest.raises(ValueError, match="Invalid persona name"):
|
|
persona.validate("../../etc/passwd")
|
|
with pytest.raises(ValueError, match="Invalid persona name"):
|
|
persona.validate("../alice")
|
|
with pytest.raises(ValueError, match="Invalid persona name"):
|
|
persona.validate("alice/../../etc")
|
|
|
|
|
|
def test_validate_special_chars(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
for bad in ("alice bob", "alice;bob", "alice\x00bob", "A" * 33, ""):
|
|
with pytest.raises(ValueError):
|
|
persona.validate(bad)
|
|
|
|
|
|
def test_validate_allows_hyphen_underscore(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
# Create a persona with hyphen and underscore in name
|
|
p = root / "my_ai-agent"
|
|
p.mkdir(parents=True)
|
|
(p / "IDENTITY.md").write_text("# My Agent")
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
assert persona.validate("my_ai-agent") == "my_ai-agent"
|
|
|
|
|
|
def test_list_personas(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
names = persona.list_personas()
|
|
assert "alice" in names
|
|
assert "bob" in names
|
|
assert "incomplete" not in names # no IDENTITY.md
|
|
|
|
|
|
def test_persona_path_uses_contextvar(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
persona.set_persona("alice")
|
|
assert persona.persona_path() == root / "alice"
|
|
persona.set_persona("bob")
|
|
assert persona.persona_path() == root / "bob"
|
|
|
|
|
|
def test_persona_path_explicit_name(tmp_path):
|
|
root = _make_temp_personas(tmp_path)
|
|
import config, persona
|
|
with patch.object(config.settings, "personas_dir", root):
|
|
persona.set_persona("alice")
|
|
assert persona.persona_path("bob") == root / "bob"
|