"""Tests for YAML policy engine.""" from pathlib import Path import pytest from aegis_gitea_mcp.config import get_settings, reset_settings from aegis_gitea_mcp.policy import PolicyEngine, PolicyError def _set_base_env(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("GITEA_URL", "https://gitea.example.com") monkeypatch.setenv("GITEA_TOKEN", "token-12345") monkeypatch.setenv("MCP_API_KEYS", "a" * 64) def _yaml_with_trailing_newline(content: str) -> str: return content.strip() + "\n" def test_default_policy_allows_read_and_denies_write( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """Default policy should allow reads and deny writes when write mode is disabled.""" _set_base_env(monkeypatch) reset_settings() _ = get_settings() engine = PolicyEngine.from_yaml_file(tmp_path / "does-not-exist.yaml") read_decision = engine.authorize("list_repositories", is_write=False) write_decision = engine.authorize("create_issue", is_write=True, repository="owner/repo") assert read_decision.allowed assert not write_decision.allowed def test_policy_global_deny(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Global deny should reject matching tool names.""" _set_base_env(monkeypatch) policy_path = tmp_path / "policy.yaml" policy_path.write_text( _yaml_with_trailing_newline(""" defaults: read: allow write: deny tools: deny: - list_repositories """), encoding="utf-8", ) reset_settings() _ = get_settings() engine = PolicyEngine.from_yaml_file(policy_path) decision = engine.authorize("list_repositories", is_write=False) assert not decision.allowed assert "denied" in decision.reason def test_repository_path_restriction(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Repository path allow-list should block unknown paths.""" _set_base_env(monkeypatch) policy_path = tmp_path / "policy.yaml" policy_path.write_text( _yaml_with_trailing_newline(""" repositories: acme/app: tools: allow: - get_file_contents paths: allow: - src/* """), encoding="utf-8", ) reset_settings() _ = get_settings() engine = PolicyEngine.from_yaml_file(policy_path) allowed = engine.authorize( "get_file_contents", is_write=False, repository="acme/app", target_path="src/main.py", ) denied = engine.authorize( "get_file_contents", is_write=False, repository="acme/app", target_path="docs/readme.md", ) assert allowed.allowed assert not denied.allowed def test_invalid_policy_structure(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Invalid policy YAML should raise PolicyError.""" _set_base_env(monkeypatch) policy_path = tmp_path / "policy.yaml" policy_path.write_text("repositories: []\n", encoding="utf-8") reset_settings() _ = get_settings() with pytest.raises(PolicyError): PolicyEngine.from_yaml_file(policy_path) def test_write_mode_repository_whitelist(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Write mode should require repo whitelist and honor configured repository entries.""" _set_base_env(monkeypatch) monkeypatch.setenv("WRITE_MODE", "true") monkeypatch.setenv("WRITE_REPOSITORY_WHITELIST", "acme/app") policy_path = tmp_path / "policy.yaml" policy_path.write_text("defaults:\n write: allow\n", encoding="utf-8") reset_settings() _ = get_settings() engine = PolicyEngine.from_yaml_file(policy_path) allowed = engine.authorize("create_issue", is_write=True, repository="acme/app") denied = engine.authorize("create_issue", is_write=True, repository="acme/other") assert allowed.allowed assert denied.allowed is False def test_write_mode_allow_all_token_repos(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Write mode can be configured to allow all repos accessible to token.""" _set_base_env(monkeypatch) monkeypatch.setenv("WRITE_MODE", "true") monkeypatch.setenv("WRITE_ALLOW_ALL_TOKEN_REPOS", "true") policy_path = tmp_path / "policy.yaml" policy_path.write_text("defaults:\n write: allow\n", encoding="utf-8") reset_settings() _ = get_settings() engine = PolicyEngine.from_yaml_file(policy_path) decision = engine.authorize("create_issue", is_write=True, repository="acme/any-repo") assert decision.allowed