Option C Tools
Loading...
Searching...
No Matches
test_git_status.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_git/test_git_status.py
4
5"""
6Purpose
7-------
8Integration tests for ``oct git status`` (Phase 4B / G-B2).
9Validates terminal output, ``--json`` schema, ``--no-check`` mode,
10and edge cases (detached HEAD, no remote, clean repo).
11
12Responsibilities
13----------------
14- Verify ``[OK]`` / ``[!!]`` / ``---`` badge rendering for staged,
15 modified, and untracked files.
16- Verify ``--json`` output is valid JSON with the expected schema.
17- Verify ``--no-check`` suppresses compliance annotations.
18- Verify clean repo produces empty sections.
19- Verify detached HEAD and no-remote edge cases.
20
21Diagnostics
22-----------
23Domain: GIT-TESTS
24Levels:
25 L2 — test lifecycle
26 L3 — assertion details
27 L4 — deep tracing
28
29Contracts
30---------
31- Every test uses the ``temp_git_project`` fixture (real git repo).
32- Tests never modify the system git config.
33"""
34
35import json
36import subprocess
37from pathlib import Path
38
39import pytest
40
41from tests.tests_git.conftest import _git, commit_file
42
43
44# =====================================================================
45# Helpers
46# =====================================================================
47
48
49def _run_status(root: Path, *extra_args: str) -> subprocess.CompletedProcess:
50 """Run ``oct git status`` in a subprocess and return the result."""
51 import sys
52 return subprocess.run(
53 [sys.executable, "-m", "oct.cli", "git", "status", *extra_args],
54 cwd=root,
55 capture_output=True,
56 text=True,
57 )
58
59
60# =====================================================================
61# Tests: Clean repo
62# =====================================================================
63
64
66 def test_clean_repo_shows_branch(self, temp_git_project):
67 result = _run_status(temp_git_project)
68 assert result.returncode == 0
69 assert "On branch main" in result.stdout
70
71 def test_clean_repo_no_files(self, temp_git_project):
72 result = _run_status(temp_git_project)
73 assert result.returncode == 0
74 # No staged/modified/untracked sections should have files.
75 assert "Staged" not in result.stdout
76 assert "Modified" not in result.stdout
77
78 def test_clean_repo_json(self, temp_git_project):
79 result = _run_status(temp_git_project, "--json")
80 assert result.returncode == 0
81 data = json.loads(result.stdout)
82 assert data["branch"] == "main"
83 assert data["staged"] == []
84 assert data["modified"] == []
85 assert data["untracked"] == []
86
87
88# =====================================================================
89# Tests: Badges
90# =====================================================================
91
92
95 self, temp_git_project, make_compliant_py
96 ):
97 path = make_compliant_py("src/good.py")
98 _git(temp_git_project, "add", "src/good.py")
99
100 result = _run_status(temp_git_project)
101 assert result.returncode == 0
102 assert "[OK]" in result.stdout
103 assert "src/good.py" in result.stdout
104
106 self, temp_git_project, make_noncompliant_py
107 ):
108 make_noncompliant_py("src/bad.py", "missing_header")
109 _git(temp_git_project, "add", "src/bad.py")
110
111 result = _run_status(temp_git_project)
112 assert result.returncode == 0
113 assert "[!!]" in result.stdout
114 assert "src/bad.py" in result.stdout
115
116 def test_staged_non_python_shows_dash(self, temp_git_project):
117 readme = temp_git_project / "src" / "notes.txt"
118 readme.write_text("hello", encoding="utf-8")
119 _git(temp_git_project, "add", "src/notes.txt")
120
121 result = _run_status(temp_git_project)
122 assert result.returncode == 0
123 assert "---" in result.stdout
124 assert "notes.txt" in result.stdout
125
127 self, temp_git_project, make_compliant_py
128 ):
129 # Commit a file first, then modify it (unstaged).
130 path = make_compliant_py("src/modme.py")
131 commit_file(
132 temp_git_project, "src/modme.py",
133 path.read_text(encoding="utf-8"), "add modme",
134 )
135 path.write_text(
136 path.read_text(encoding="utf-8") + "\n# modified\n",
137 encoding="utf-8",
138 )
139
140 result = _run_status(temp_git_project)
141 assert result.returncode == 0
142 assert "Modified" in result.stdout
143 assert "modme.py" in result.stdout
144
145
146# =====================================================================
147# Tests: --no-check
148# =====================================================================
149
150
153 self, temp_git_project, make_noncompliant_py
154 ):
155 make_noncompliant_py("src/bad.py", "missing_header")
156 _git(temp_git_project, "add", "src/bad.py")
157
158 result = _run_status(temp_git_project, "--no-check")
159 assert result.returncode == 0
160 assert "[!!]" not in result.stdout
161 assert "[OK]" not in result.stdout
162 assert "bad.py" in result.stdout
163
164
165# =====================================================================
166# Tests: --json
167# =====================================================================
168
169
171 def test_json_schema(self, temp_git_project, make_compliant_py):
172 make_compliant_py("src/a.py")
173 _git(temp_git_project, "add", "src/a.py")
174
175 result = _run_status(temp_git_project, "--json")
176 assert result.returncode == 0
177 data = json.loads(result.stdout)
178
179 # Required top-level keys.
180 for key in ("branch", "detached", "ahead", "behind",
181 "staged", "modified", "untracked"):
182 assert key in data, f"missing key: {key}"
183
184 # Staged entry schema.
185 assert len(data["staged"]) == 1
186 entry = data["staged"][0]
187 assert "path" in entry
188 assert "status" in entry
189 assert "issues" in entry
190 assert entry["status"] in ("ok", "violations", "non_python", "unchecked")
191
193 self, temp_git_project, make_noncompliant_py
194 ):
195 make_noncompliant_py("src/bad.py", "missing_header")
196 _git(temp_git_project, "add", "src/bad.py")
197
198 result = _run_status(temp_git_project, "--json")
199 data = json.loads(result.stdout)
200 entry = data["staged"][0]
201 assert entry["status"] == "violations"
202 assert len(entry["issues"]) > 0
203
204 def test_json_no_check(self, temp_git_project, make_compliant_py):
205 make_compliant_py("src/a.py")
206 _git(temp_git_project, "add", "src/a.py")
207
208 result = _run_status(temp_git_project, "--json", "--no-check")
209 data = json.loads(result.stdout)
210 entry = data["staged"][0]
211 assert entry["status"] == "unchecked"
212
213
214# =====================================================================
215# Tests: Edge cases
216# =====================================================================
217
218
220 def test_detached_head(self, temp_git_project):
221 sha = _git(temp_git_project, "rev-parse", "HEAD").stdout.strip()
222 _git(temp_git_project, "checkout", "--detach", sha)
223
224 result = _run_status(temp_git_project)
225 assert result.returncode == 0
226 assert "HEAD detached" in result.stdout
227
228 def test_detached_head_json(self, temp_git_project):
229 sha = _git(temp_git_project, "rev-parse", "HEAD").stdout.strip()
230 _git(temp_git_project, "checkout", "--detach", sha)
231
232 result = _run_status(temp_git_project, "--json")
233 data = json.loads(result.stdout)
234 assert data["detached"] is True
235 assert data["branch"] is None
236
237 def test_no_remote_ahead_behind_zero(self, temp_git_project):
238 # No remote configured — ahead/behind should be 0/0.
239 result = _run_status(temp_git_project, "--json")
240 data = json.loads(result.stdout)
241 assert data["ahead"] == 0
242 assert data["behind"] == 0
243
244 def test_untracked_file_appears(self, temp_git_project):
245 (temp_git_project / "stray.txt").write_text("x", encoding="utf-8")
246
247 result = _run_status(temp_git_project, "--json")
248 data = json.loads(result.stdout)
249 paths = [e["path"] for e in data["untracked"]]
250 assert "stray.txt" in paths
test_staged_compliant_py_shows_ok(self, temp_git_project, make_compliant_py)
test_staged_non_python_shows_dash(self, temp_git_project)
test_modified_file_shows_badge(self, temp_git_project, make_compliant_py)
test_staged_noncompliant_py_shows_violation(self, temp_git_project, make_noncompliant_py)
test_clean_repo_json(self, temp_git_project)
test_clean_repo_shows_branch(self, temp_git_project)
test_clean_repo_no_files(self, temp_git_project)
test_no_remote_ahead_behind_zero(self, temp_git_project)
test_untracked_file_appears(self, temp_git_project)
test_detached_head_json(self, temp_git_project)
test_json_schema(self, temp_git_project, make_compliant_py)
test_json_violation_includes_issues(self, temp_git_project, make_noncompliant_py)
test_json_no_check(self, temp_git_project, make_compliant_py)
test_no_check_suppresses_badges(self, temp_git_project, make_noncompliant_py)
subprocess.CompletedProcess _run_status(Path root, *str extra_args)