8Integration tests for ``oct git branch`` (Phase 4F — G-F1).
12- Verify ``--create`` creates a branch with naming validation.
13- Verify ``--switch`` switches to an existing branch.
14- Verify mutual exclusivity of ``--create`` and ``--switch``.
17- Verify error cases (duplicate, non-existent, uncommitted changes).
24 L3 -- assertion details
29- All tests use subprocess invocation (same pattern as test_git_commit.py).
30- All tests use tmp_path fixtures so they are automatically cleaned up.
37from pathlib
import Path
41from tests.tests_git.conftest
import _git, commit_file
50 return shutil.which(
"git")
is not None
53def _run_branch(root: Path, *extra_args: str) -> subprocess.CompletedProcess:
54 """Run ``oct git branch`` in a subprocess."""
55 return subprocess.run(
56 [sys.executable,
"-m",
"oct.cli",
"git",
"branch", *extra_args],
64 """Run ``oct git branch --json`` and parse JSON output."""
66 return json.loads(result.stdout)
76 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
78 result =
_run_branch(temp_git_project,
"--create",
"feature/new-thing")
79 assert result.returncode == 0
80 assert "Created branch" in result.stdout
82 proc = _git(temp_git_project,
"branch",
"--list",
"feature/new-thing")
83 assert "feature/new-thing" in proc.stdout
85 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
87 _run_branch(temp_git_project,
"--create",
"feature/stay-here")
88 proc = _git(temp_git_project,
"rev-parse",
"--abbrev-ref",
"HEAD")
89 assert proc.stdout.strip() ==
"main"
91 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
93 result =
_run_branch(temp_git_project,
"--create",
"bad-name")
94 assert result.returncode == 1
95 assert "does not match" in result.stderr.lower()
97 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
99 _run_branch(temp_git_project,
"--create",
"feature/dup")
100 result =
_run_branch(temp_git_project,
"--create",
"feature/dup")
101 assert result.returncode == 1
102 assert "already exists" in result.stderr.lower()
104 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
107 assert data[
"operation"] ==
"create"
108 assert data[
"branch"] ==
"feature/json-test"
109 assert data[
"exit_code"] == 0
110 assert data[
"errors"] == []
120 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
122 _git(temp_git_project,
"branch",
"feature/target")
123 result =
_run_branch(temp_git_project,
"--switch",
"feature/target")
124 assert result.returncode == 0
125 assert "Switched to branch" in result.stdout
126 proc = _git(temp_git_project,
"rev-parse",
"--abbrev-ref",
"HEAD")
127 assert proc.stdout.strip() ==
"feature/target"
129 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
131 result =
_run_branch(temp_git_project,
"--switch",
"feature/nope")
132 assert result.returncode == 1
133 assert "does not exist" in result.stderr.lower()
135 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
137 """Switching with dirty working tree is refused."""
138 _git(temp_git_project,
"branch",
"feature/clean-target")
140 (temp_git_project /
"src" /
"dirty.py").write_text(
"x = 1\n", encoding=
"utf-8")
141 _git(temp_git_project,
"add",
"src/dirty.py")
142 result =
_run_branch(temp_git_project,
"--switch",
"feature/clean-target")
143 assert result.returncode == 1
144 assert "uncommitted" in result.stderr.lower()
146 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
148 _git(temp_git_project,
"branch",
"feature/json-switch")
149 data =
_run_branch_json(temp_git_project,
"--switch",
"feature/json-switch")
150 assert data[
"operation"] ==
"switch"
151 assert data[
"branch"] ==
"feature/json-switch"
152 assert data[
"exit_code"] == 0
162 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
166 "--create",
"feature/a",
167 "--switch",
"feature/b",
169 assert result.returncode == 1
170 assert "mutually exclusive" in result.stderr.lower()
172 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
175 assert result.returncode == 1
176 assert "--create" in result.stderr
or "--switch" in result.stderr
None test_create_does_not_switch(self, Path temp_git_project)
None test_create_json_output(self, Path temp_git_project)
None test_create_duplicate_rejected(self, Path temp_git_project)
None test_create_invalid_name_rejected(self, Path temp_git_project)
None test_create_valid_branch(self, Path temp_git_project)
None test_switch_json_output(self, Path temp_git_project)
None test_switch_nonexistent_rejected(self, Path temp_git_project)
None test_switch_to_existing(self, Path temp_git_project)
None test_switch_with_uncommitted_changes(self, Path temp_git_project)
None test_both_flags_rejected(self, Path temp_git_project)
None test_no_flags_shows_usage(self, Path temp_git_project)
subprocess.CompletedProcess _run_branch(Path root, *str extra_args)
dict _run_branch_json(Path root, *str extra_args)