Merged-PR source-branch cleanup is reconciler work (task_capability_map maps cleanup_merged_pr_branch -> reconciler / gitea.branch.delete), but the reconciler profile schema, execution-profile docs, and tests never covered the permission, so no configured profile could run the guarded gitea_cleanup_merged_pr_branch path. - reconciler_profile.py: add gitea.branch.delete to RECONCILER_RECOMMENDED_OPERATIONS (not required; not forbidden) - docs/gitea-execution-profiles.md: document merged-branch cleanup ownership, least-privilege constraints, and the no-alias caveat - tests/test_reconciler_profile.py: reconciler profile with branch.delete stays valid and classified reconciler; missing grant is reported as missing-recommended - tests/test_branch_cleanup_guard.py: author- and merger-shaped profiles without gitea.branch.delete fail closed on gitea_cleanup_merged_pr_branch Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
"""Tests for reconciler profile model (#304)."""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import gitea_mcp_server as mcp_server
|
|
import migrate_profiles
|
|
import reconciler_profile
|
|
|
|
|
|
PRGS_RECONCILER_ALLOWED = [
|
|
"gitea.read",
|
|
"gitea.pr.comment",
|
|
"gitea.issue.comment",
|
|
"gitea.issue.close",
|
|
"gitea.pr.close",
|
|
]
|
|
PRGS_RECONCILER_FORBIDDEN = [
|
|
"gitea.pr.approve",
|
|
"gitea.pr.merge",
|
|
"gitea.pr.create",
|
|
"gitea.branch.push",
|
|
]
|
|
|
|
|
|
class TestReconcilerProfileModel(unittest.TestCase):
|
|
def test_prgs_reconciler_shape_valid(self):
|
|
result = reconciler_profile.assess_reconciler_profile(
|
|
PRGS_RECONCILER_ALLOWED,
|
|
PRGS_RECONCILER_FORBIDDEN,
|
|
)
|
|
self.assertTrue(result["is_reconciler_profile"])
|
|
self.assertTrue(result["valid"])
|
|
self.assertEqual(result["reasons"], [])
|
|
|
|
def test_author_profile_not_reconciler(self):
|
|
allowed = [
|
|
"gitea.read",
|
|
"gitea.pr.create",
|
|
"gitea.branch.push",
|
|
"gitea.issue.comment",
|
|
]
|
|
forbidden = ["gitea.pr.approve", "gitea.pr.merge"]
|
|
self.assertFalse(
|
|
reconciler_profile.is_reconciler_profile(allowed, forbidden)
|
|
)
|
|
|
|
def test_reviewer_profile_not_reconciler(self):
|
|
allowed = [
|
|
"gitea.read",
|
|
"gitea.pr.review",
|
|
"gitea.pr.approve",
|
|
"gitea.pr.merge",
|
|
]
|
|
forbidden = ["gitea.pr.create", "gitea.branch.push"]
|
|
self.assertFalse(
|
|
reconciler_profile.is_reconciler_profile(allowed, forbidden)
|
|
)
|
|
|
|
def test_broad_close_on_author_invalid(self):
|
|
allowed = PRGS_RECONCILER_ALLOWED + ["gitea.pr.create"]
|
|
result = reconciler_profile.assess_reconciler_profile(
|
|
allowed,
|
|
PRGS_RECONCILER_FORBIDDEN,
|
|
)
|
|
self.assertFalse(result["valid"])
|
|
self.assertFalse(result["is_reconciler_profile"])
|
|
|
|
def test_role_kind_detects_reconciler(self):
|
|
role = mcp_server._role_kind(
|
|
PRGS_RECONCILER_ALLOWED,
|
|
PRGS_RECONCILER_FORBIDDEN,
|
|
)
|
|
self.assertEqual(role, "reconciler")
|
|
|
|
def test_migrate_infer_role_reconciler(self):
|
|
self.assertEqual(
|
|
migrate_profiles.infer_role("prgs-reconciler", "prgs-reconciler"),
|
|
"reconciler",
|
|
)
|
|
|
|
def test_branch_delete_is_recommended_for_reconciler(self):
|
|
self.assertIn(
|
|
"gitea.branch.delete",
|
|
reconciler_profile.RECONCILER_RECOMMENDED_OPERATIONS,
|
|
)
|
|
self.assertNotIn(
|
|
"gitea.branch.delete",
|
|
reconciler_profile.RECONCILER_REQUIRED_OPERATIONS,
|
|
)
|
|
|
|
def test_reconciler_with_branch_delete_stays_valid(self):
|
|
allowed = PRGS_RECONCILER_ALLOWED + ["gitea.branch.delete"]
|
|
result = reconciler_profile.assess_reconciler_profile(
|
|
allowed,
|
|
PRGS_RECONCILER_FORBIDDEN,
|
|
)
|
|
self.assertTrue(result["is_reconciler_profile"])
|
|
self.assertTrue(result["valid"])
|
|
self.assertNotIn(
|
|
"gitea.branch.delete", result["missing_recommended_operations"]
|
|
)
|
|
self.assertEqual(
|
|
mcp_server._role_kind(allowed, PRGS_RECONCILER_FORBIDDEN),
|
|
"reconciler",
|
|
)
|
|
|
|
def test_reconciler_without_branch_delete_reports_missing_recommended(self):
|
|
result = reconciler_profile.assess_reconciler_profile(
|
|
PRGS_RECONCILER_ALLOWED,
|
|
PRGS_RECONCILER_FORBIDDEN,
|
|
)
|
|
self.assertTrue(result["valid"])
|
|
self.assertIn(
|
|
"gitea.branch.delete", result["missing_recommended_operations"]
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |