""" Unit tests for persona.py — validation, routing, path traversal. Tests the two-level home/{username}/persona/{name}/ structure. No HTTP involved. """ import pytest from pathlib import Path from unittest.mock import patch def _make_home(tmp_path: Path) -> Path: """Create a minimal home/ tree with two users and some personas.""" root = tmp_path / "home" for username, persona in [("scott", "inara"), ("holly", "tina"), ("scott", "alt")]: p = root / username / "persona" / persona p.mkdir(parents=True) (p / "IDENTITY.md").write_text(f"# {persona}") # A persona dir WITHOUT IDENTITY.md — should be invisible to list_user_personas() incomplete = root / "scott" / "persona" / "broken" incomplete.mkdir(parents=True) return root def test_validate_good(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): assert persona.validate("scott", "inara") == ("scott", "inara") assert persona.validate("holly", "tina") == ("holly", "tina") def test_validate_unknown_user(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): with pytest.raises(ValueError, match="Unknown user"): persona.validate("charlie", "inara") def test_validate_unknown_persona(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): with pytest.raises(ValueError, match="Unknown persona"): persona.validate("scott", "ghost") def test_validate_path_traversal(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): for bad in ("../../etc/passwd", "../scott", "scott/../../etc"): with pytest.raises(ValueError, match="Invalid"): persona.validate(bad, "inara") with pytest.raises(ValueError, match="Invalid"): persona.validate("scott", "../../etc/passwd") def test_validate_special_chars(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): for bad in ("alice bob", "alice;bob", "alice\x00bob", "A" * 33, ""): with pytest.raises(ValueError): persona.validate(bad, "inara") def test_validate_allows_hyphen_underscore(tmp_path): root = _make_home(tmp_path) p = root / "my_user" / "persona" / "my-agent" p.mkdir(parents=True) (p / "IDENTITY.md").write_text("# My Agent") import config, persona with patch.object(config.settings, "home_dir", root): assert persona.validate("my_user", "my-agent") == ("my_user", "my-agent") def test_list_users(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): users = persona.list_users() assert "scott" in users assert "holly" in users def test_list_user_personas(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): names = persona.list_user_personas("scott") assert "inara" in names assert "alt" in names assert "broken" not in names # no IDENTITY.md def test_persona_path_uses_contextvars(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): persona.set_context("scott", "inara") assert persona.persona_path() == root / "scott" / "persona" / "inara" persona.set_context("holly", "tina") assert persona.persona_path() == root / "holly" / "persona" / "tina" def test_persona_path_explicit_args(tmp_path): root = _make_home(tmp_path) import config, persona with patch.object(config.settings, "home_dir", root): persona.set_context("scott", "inara") # Explicit args override the ContextVar assert persona.persona_path("holly", "tina") == root / "holly" / "persona" / "tina" # ContextVar unchanged assert persona.persona_path() == root / "scott" / "persona" / "inara" def test_get_user_and_persona(tmp_path): import persona persona.set_context("scott", "inara") assert persona.get_user() == "scott" assert persona.get_persona() == "inara" persona.set_context("holly", "tina") assert persona.get_user() == "holly" assert persona.get_persona() == "tina"