Option C Tools
Loading...
Searching...
No Matches
test_auto_branch.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_git/test_auto_branch.py
4
5"""
6Purpose
7-------
8Integration tests for the auto-branching behaviour in ``oct git commit``
9(Phase 4F — G-F2): when committing on a protected branch, the command
10automatically creates a feature branch instead of blocking.
11
12Responsibilities
13----------------
14- Verify auto-branch creates a correctly named feature branch from the
15 commit message.
16- Verify the commit lands on the new branch, not on the protected branch.
17- Verify stderr reports the auto-branch action.
18- Verify ``git.auto_branch_on_protected: false`` disables auto-branching.
19- Verify branch name deduplication when the generated name already exists.
20
21Diagnostics
22-----------
23Domain: GIT-TESTS
24Levels:
25 L2 -- test lifecycle
26 L3 -- assertion details
27 L4 -- deep tracing
28
29Contracts
30---------
31- All tests use subprocess invocation (same pattern as test_git_commit.py).
32- All tests use tmp_path fixtures so they are automatically cleaned up.
33"""
34
35import json
36import shutil
37import subprocess
38import sys
39from pathlib import Path
40
41import pytest
42
43from tests.tests_git.conftest import _git, commit_file
44
45
46# =====================================================================
47# Helpers
48# =====================================================================
49
50
51def _git_available() -> bool:
52 return shutil.which("git") is not None
53
54
55def _run_commit(root: Path, *extra_args: str) -> subprocess.CompletedProcess:
56 """Run ``oct git commit`` in a subprocess."""
57 return subprocess.run(
58 [sys.executable, "-m", "oct.cli", "git", "commit", *extra_args],
59 cwd=root,
60 capture_output=True,
61 text=True,
62 )
63
64
65def _set_octrc(root: Path, octrc: dict) -> None:
66 """Overwrite ``.octrc.json`` in *root*."""
67 (root / ".octrc.json").write_text(
68 json.dumps(octrc, indent=2), encoding="utf-8",
69 )
70
71
72# =====================================================================
73# Auto-branch on protected branch
74# =====================================================================
75
76
78
79 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
81 self, temp_git_project: Path, make_compliant_py,
82 ) -> None:
83 """Commit on main auto-creates feature/... and succeeds."""
84 path = make_compliant_py("src/good.py")
85 _git(temp_git_project, "add", "src/good.py")
86 result = _run_commit(
87 temp_git_project, "-m", "feat(auth): add JWT tokens",
88 )
89 assert result.returncode == 0, f"stderr: {result.stderr}"
90 # Verify we moved to the auto-created branch.
91 proc = _git(temp_git_project, "rev-parse", "--abbrev-ref", "HEAD")
92 branch = proc.stdout.strip()
93 assert branch.startswith("feature/"), f"expected feature/..., got {branch!r}"
94 assert "auth" in branch
95 # Verify main is untouched (still at initial commit).
96 assert "auto-created branch" in result.stderr.lower()
97
98 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
100 self, temp_git_project: Path, make_compliant_py,
101 ) -> None:
102 """The commit message appears on the new branch, not main."""
103 path = make_compliant_py("src/module.py")
104 _git(temp_git_project, "add", "src/module.py")
105 _run_commit(temp_git_project, "-m", "fix: resolve null pointer")
106 # Check commit log on current branch.
107 proc = _git(temp_git_project, "log", "--oneline", "-1")
108 assert "resolve null pointer" in proc.stdout
109 # Verify main does NOT have the commit.
110 proc_main = _git(temp_git_project, "log", "main", "--oneline", "-1")
111 assert "resolve null pointer" not in proc_main.stdout
112
113 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
115 self, temp_git_project: Path, make_compliant_py,
116 ) -> None:
117 """fix: message creates a bugfix/ branch."""
118 path = make_compliant_py("src/fix_it.py")
119 _git(temp_git_project, "add", "src/fix_it.py")
120 _run_commit(temp_git_project, "-m", "fix: patch memory leak")
121 proc = _git(temp_git_project, "rev-parse", "--abbrev-ref", "HEAD")
122 assert proc.stdout.strip().startswith("bugfix/")
123
124 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
126 self, temp_git_project: Path, make_compliant_py,
127 ) -> None:
128 """sec: message creates a hotfix/ branch."""
129 path = make_compliant_py("src/sec_fix.py")
130 _git(temp_git_project, "add", "src/sec_fix.py")
131 _run_commit(temp_git_project, "-m", "sec: patch CVE-2024-9999")
132 proc = _git(temp_git_project, "rev-parse", "--abbrev-ref", "HEAD")
133 assert proc.stdout.strip().startswith("hotfix/")
134
135 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
137 self, temp_git_project: Path, make_compliant_py,
138 ) -> None:
139 """auto_branch_on_protected=false preserves the blocking behaviour."""
140 _set_octrc(temp_git_project, {
141 "linter": {"profile": "compact"},
142 "git": {
143 "protected_branches": ["main", "master"],
144 "auto_branch_on_protected": False,
145 },
146 })
147 path = make_compliant_py("src/blocked.py")
148 _git(temp_git_project, "add", "src/blocked.py")
149 result = _run_commit(
150 temp_git_project, "-m", "feat: should be blocked",
151 )
152 assert result.returncode == 1
153 assert "protected branch" in result.stderr.lower()
154
155 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
157 self, temp_git_project: Path, make_compliant_py,
158 ) -> None:
159 """When the generated branch name already exists, append a counter."""
160 # Pre-create the branch that would be generated.
161 _git(temp_git_project, "branch", "feature/dup-test-feature")
162 path = make_compliant_py("src/dedup.py")
163 _git(temp_git_project, "add", "src/dedup.py")
164 result = _run_commit(
165 temp_git_project, "-m", "feat: dup test feature",
166 )
167 assert result.returncode == 0, f"stderr: {result.stderr}"
168 proc = _git(temp_git_project, "rev-parse", "--abbrev-ref", "HEAD")
169 branch = proc.stdout.strip()
170 # Should have a -2 suffix.
171 assert branch == "feature/dup-test-feature-2"
None test_auto_branch_fix_type(self, Path temp_git_project, make_compliant_py)
None test_auto_branch_disabled_blocks(self, Path temp_git_project, make_compliant_py)
None test_auto_branch_commit_lands_on_new_branch(self, Path temp_git_project, make_compliant_py)
None test_auto_branch_creates_feature_branch(self, Path temp_git_project, make_compliant_py)
None test_auto_branch_sec_type(self, Path temp_git_project, make_compliant_py)
None test_auto_branch_deduplication(self, Path temp_git_project, make_compliant_py)
subprocess.CompletedProcess _run_commit(Path root, *str extra_args)
None _set_octrc(Path root, dict octrc)