diff --git a/src/aipass/drone/apps/handlers/git/commit_handler.py b/src/aipass/drone/apps/handlers/git/commit_handler.py index df5d00dc..19d579c2 100644 --- a/src/aipass/drone/apps/handlers/git/commit_handler.py +++ b/src/aipass/drone/apps/handlers/git/commit_handler.py @@ -99,6 +99,18 @@ def commit_changes( text=True, cwd=str(repo_root), ) + lint_check = subprocess.run( + [ruff_bin, "check", "src/", "tests/"], + capture_output=True, + text=True, + cwd=str(repo_root), + ) + if lint_check.returncode != 0: + return { + "stdout": "", + "stderr": f"Lint errors — fix before committing:\n{lint_check.stdout.strip()}", + "exit_code": 1, + } add_result = subprocess.run( ["git", "add", "-A"], capture_output=True, diff --git a/src/aipass/drone/tests/test_git_access.py b/src/aipass/drone/tests/test_git_access.py index a2281691..cf2ec407 100644 --- a/src/aipass/drone/tests/test_git_access.py +++ b/src/aipass/drone/tests/test_git_access.py @@ -94,7 +94,7 @@ class TestGitAccessTiers: assert "checkout" in cmds assert "sync" in cmds assert "unlock" in cmds - assert "system-pr" in cmds + assert "pr" in cmds assert "merge" in cmds assert "smart-sync" in cmds assert "fix" in cmds @@ -103,9 +103,9 @@ class TestGitAccessTiers: allowed = GIT_ACCESS_TIERS["owner"]["allowed_callers"] assert allowed == ["devpulse"] - def test_pr_not_in_any_tier(self) -> None: - all_cmds = GIT_ACCESS_TIERS["global"]["commands"] + GIT_ACCESS_TIERS["owner"]["commands"] - assert "pr" not in all_cmds + def test_pr_in_owner_tier(self) -> None: + cmds = GIT_ACCESS_TIERS["owner"]["commands"] + assert "pr" in cmds # =========================================================================== @@ -151,20 +151,20 @@ class TestVerifyGitAccessOwner: with pytest.raises(PermissionError, match="not authorized"): verify_git_access("unlock") - def test_system_pr_denied_for_seedgo(self, seedgo_dir: Path) -> None: + def test_pr_denied_for_seedgo(self, seedgo_dir: Path) -> None: with pytest.raises(PermissionError, match="not authorized"): - verify_git_access("system-pr") - - -class TestVerifyGitAccessPrDeprecated: - """PR command should be denied with deprecation message.""" - - def test_pr_deprecated_for_devpulse(self, devpulse_dir: Path) -> None: - with pytest.raises(PermissionError, match="deprecated"): verify_git_access("pr") - def test_pr_deprecated_for_any_branch(self, seedgo_dir: Path) -> None: - with pytest.raises(PermissionError, match="deprecated"): + +class TestVerifyGitAccessPrOwnerOnly: + """PR command should be allowed for devpulse, denied for others.""" + + def test_pr_allowed_for_devpulse(self, devpulse_dir: Path) -> None: + result = verify_git_access("pr") + assert result == "devpulse" + + def test_pr_denied_for_seedgo(self, seedgo_dir: Path) -> None: + with pytest.raises(PermissionError, match="not authorized"): verify_git_access("pr") @@ -343,8 +343,9 @@ class TestCommitChanges: assert "nothing to commit" in result["stderr"].lower() def test_commit_all_stages_first(self, repo_dir: Path) -> None: - mock_ruff_check = MagicMock(returncode=0, stdout="", stderr="") + mock_ruff_fix = MagicMock(returncode=0, stdout="", stderr="") mock_ruff_format = MagicMock(returncode=0, stdout="", stderr="") + mock_ruff_gate = MagicMock(returncode=0, stdout="", stderr="") mock_add = MagicMock(returncode=0, stderr="") mock_diff = MagicMock(returncode=1, stdout="", stderr="") mock_commit = MagicMock(returncode=0, stdout="[main def456] all commit", stderr="") @@ -355,7 +356,7 @@ class TestCommitChanges: patch("shutil.which", return_value="/usr/bin/ruff"), patch( "aipass.drone.apps.handlers.git.commit_handler.subprocess.run", - side_effect=[mock_ruff_check, mock_ruff_format, mock_add, mock_diff, mock_commit], + side_effect=[mock_ruff_fix, mock_ruff_format, mock_ruff_gate, mock_add, mock_diff, mock_commit], ), ): result = commit_changes("all commit", branch_dir=branch_dir, all_files=True) @@ -449,18 +450,19 @@ class TestCheckoutHandler: # =========================================================================== -class TestPrDeprecation: - """PR command returns deprecation message via centralized auth.""" +class TestPrCommand: + """PR command routes to create_branch_pr for authorized callers.""" - def test_pr_returns_deprecation(self, devpulse_dir: Path) -> None: - result = handle_command("pr", ["some description"]) - assert result["exit_code"] == 1 - assert "deprecated" in result["stderr"].lower() - - def test_pr_no_args_also_deprecated(self, devpulse_dir: Path) -> None: + def test_pr_no_args_shows_usage(self, devpulse_dir: Path) -> None: result = handle_command("pr") assert result["exit_code"] == 1 - assert "deprecated" in result["stderr"].lower() + assert "usage" in result["stderr"].lower() + + @patch("aipass.drone.apps.handlers.git.dev_pr_handler.create_branch_pr") + def test_pr_with_description_calls_handler(self, mock_pr: MagicMock, devpulse_dir: Path) -> None: + mock_pr.return_value = {"success": True, "message": "PR created", "pr_url": "https://example.com"} + handle_command("pr", ["test description"]) + mock_pr.assert_called_once() # =========================================================================== @@ -560,11 +562,11 @@ class TestUpdatedHelp: assert "global" in text.lower() assert "owner" in text.lower() - def test_help_marks_pr_legacy(self) -> None: + def test_help_includes_pr_command(self) -> None: from aipass.drone.apps.modules.git_module import get_help text = get_help() - assert "deprecated" in text.lower() + assert "pr" in text.lower() def test_introspection_includes_new_handlers(self) -> None: from aipass.drone.apps.modules.git_module import get_introspective diff --git a/src/aipass/drone/tests/test_git_module.py b/src/aipass/drone/tests/test_git_module.py index 30a6cc48..16e2e830 100644 --- a/src/aipass/drone/tests/test_git_module.py +++ b/src/aipass/drone/tests/test_git_module.py @@ -680,16 +680,14 @@ class TestGitModuleRouting: assert "cannot detect" in result["stderr"].lower() def test_pr_no_args(self) -> None: - """pr command is deprecated.""" + """pr command without args fails (auth or usage).""" result = handle_command("pr") assert result["exit_code"] == 1 - assert "deprecated" in result["stderr"].lower() def test_pr_no_branch_dir(self) -> None: - """pr command is deprecated regardless of context.""" + """pr command without passport returns auth error.""" result = handle_command("pr", ["some description"]) assert result["exit_code"] == 1 - assert "deprecated" in result["stderr"].lower() class TestDetectBranchDir: @@ -764,9 +762,8 @@ class TestGitModuleHelp: def test_specific_command_help(self) -> None: """Command-specific help returns relevant text.""" - text = get_help("pr") - assert "pr" in text.lower() - assert "deprecated" in text.lower() + text = get_help("commit") + assert "commit" in text.lower() def test_introspective(self) -> None: """Introspection lists connected handlers.""" diff --git a/src/aipass/drone/tests/test_system_pr.py b/src/aipass/drone/tests/test_system_pr.py index 2cd0e4d7..cd4db13d 100644 --- a/src/aipass/drone/tests/test_system_pr.py +++ b/src/aipass/drone/tests/test_system_pr.py @@ -298,32 +298,31 @@ class TestSystemPrHeadStaysOnMain: # =========================================================================== -# 6. git_module routing for system-pr +# 6. git_module routing for pr command (replaced system-pr in S151) # =========================================================================== -class TestGitModuleSystemPrRouting: - """Test that git_module routes system-pr correctly.""" +class TestGitModulePrRouting: + """Test that git_module routes pr correctly.""" - def test_system_pr_in_commands(self) -> None: - """The _COMMANDS registry includes the 'system-pr' verb.""" + def test_pr_in_commands(self) -> None: + """The _COMMANDS registry includes the 'pr' verb.""" from aipass.drone.apps.modules.git_module import _COMMANDS - assert "system-pr" in _COMMANDS + assert "pr" in _COMMANDS - def test_get_help_includes_system_pr(self) -> None: - """Generic get_help() output mentions 'system-pr'.""" + def test_system_pr_removed_from_commands(self) -> None: + """system-pr was removed in S151.""" + from aipass.drone.apps.modules.git_module import _COMMANDS + + assert "system-pr" not in _COMMANDS + + def test_get_help_includes_pr(self) -> None: + """Generic get_help() output mentions 'pr'.""" from aipass.drone.apps.modules.git_module import get_help help_text = get_help() - assert "system-pr" in help_text - - def test_get_help_system_pr_specific(self) -> None: - """get_help('system-pr') output mentions deprecation.""" - from aipass.drone.apps.modules.git_module import get_help - - help_text = get_help("system-pr") - assert "deprecated" in help_text.lower() + assert "pr" in help_text def test_get_introspective_includes_plugin(self) -> None: """get_introspective() output mentions the devpulse_ops plugin.""" @@ -333,13 +332,13 @@ class TestGitModuleSystemPrRouting: assert "devpulse_ops" in intro @patch("aipass.drone.apps.plugins.devpulse_ops.auth.verify_git_access", return_value="devpulse") - def test_handle_system_pr_no_args(self, mock_verify: MagicMock) -> None: - """handle_command('system-pr', []) exits with code 1 and deprecation message.""" + def test_handle_system_pr_returns_unknown(self, mock_verify: MagicMock) -> None: + """handle_command('system-pr', []) returns unknown command error.""" from aipass.drone.apps.modules.git_module import handle_command result = handle_command("system-pr", []) assert result["exit_code"] == 1 - assert "deprecated" in result["stderr"].lower() + assert "unknown" in result["stderr"].lower() @patch( "aipass.drone.apps.plugins.devpulse_ops.auth.verify_git_access",