"""Tests for cmd_update — branch fallback when remote branch doesn't exist.""" import subprocess from types import SimpleNamespace from unittest.mock import patch import pytest from hermes_cli.main import cmd_update, PROJECT_ROOT def _make_run_side_effect(branch="main", verify_ok=True, commit_count="0"): """Build a side_effect function for subprocess.run that simulates git commands.""" def side_effect(cmd, **kwargs): joined = " ".join(str(c) for c in cmd) # git rev-parse --abbrev-ref HEAD (get current branch) if "rev-parse" in joined and "--abbrev-ref" in joined: return subprocess.CompletedProcess(cmd, 0, stdout=f"{branch}\n", stderr="") # git rev-parse --verify origin/{branch} (check remote branch exists) if "rev-parse" in joined and "--verify" in joined: rc = 0 if verify_ok else 128 return subprocess.CompletedProcess(cmd, rc, stdout="", stderr="") # git rev-list HEAD..origin/{branch} --count if "rev-list" in joined: return subprocess.CompletedProcess(cmd, 0, stdout=f"{commit_count}\n", stderr="") # Fallback: return a successful CompletedProcess with empty stdout return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") return side_effect @pytest.fixture def mock_args(): return SimpleNamespace() class TestCmdUpdateBranchFallback: """cmd_update falls back to main when current branch has no remote counterpart.""" @patch("shutil.which", return_value=None) @patch("subprocess.run") def test_update_falls_back_to_main_when_branch_not_on_remote( self, mock_run, _mock_which, mock_args, capsys ): mock_run.side_effect = _make_run_side_effect( branch="fix/stoicneko", verify_ok=False, commit_count="3" ) cmd_update(mock_args) commands = [" ".join(str(a) for a in c.args[0]) for c in mock_run.call_args_list] # rev-list should use origin/main, not origin/fix/stoicneko rev_list_cmds = [c for c in commands if "rev-list" in c] assert len(rev_list_cmds) == 1 assert "origin/main" in rev_list_cmds[0] assert "origin/fix/stoicneko" not in rev_list_cmds[0] # pull should use main, not fix/stoicneko pull_cmds = [c for c in commands if "pull" in c] assert len(pull_cmds) == 1 assert "main" in pull_cmds[0] @patch("shutil.which", return_value=None) @patch("subprocess.run") def test_update_uses_current_branch_when_on_remote( self, mock_run, _mock_which, mock_args, capsys ): mock_run.side_effect = _make_run_side_effect( branch="main", verify_ok=True, commit_count="2" ) cmd_update(mock_args) commands = [" ".join(str(a) for a in c.args[0]) for c in mock_run.call_args_list] rev_list_cmds = [c for c in commands if "rev-list" in c] assert len(rev_list_cmds) == 1 assert "origin/main" in rev_list_cmds[0] pull_cmds = [c for c in commands if "pull" in c] assert len(pull_cmds) == 1 assert "main" in pull_cmds[0] @patch("shutil.which", return_value=None) @patch("subprocess.run") def test_update_already_up_to_date( self, mock_run, _mock_which, mock_args, capsys ): mock_run.side_effect = _make_run_side_effect( branch="main", verify_ok=True, commit_count="0" ) cmd_update(mock_args) captured = capsys.readouterr() assert "Already up to date!" in captured.out # Should NOT have called pull commands = [" ".join(str(a) for a in c.args[0]) for c in mock_run.call_args_list] pull_cmds = [c for c in commands if "pull" in c] assert len(pull_cmds) == 0