""" 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"