Files
AegisGitea-MCP/tests/test_policy.py

128 lines
3.7 KiB
Python

"""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 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(
"""
defaults:
read: allow
write: deny
tools:
deny:
- list_repositories
""".strip() + "\n",
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(
"""
repositories:
acme/app:
tools:
allow:
- get_file_contents
paths:
allow:
- src/*
""".strip() + "\n",
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