-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathapi_test.py
More file actions
336 lines (287 loc) · 13 KB
/
api_test.py
File metadata and controls
336 lines (287 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
"""Tests for commit_check.api – the public Python API."""
import pytest
from unittest.mock import patch
from commit_check.api import (
validate_message,
validate_branch,
validate_author,
validate_all,
validate_push,
)
class TestValidateMessage:
"""Tests for validate_message()."""
@pytest.mark.benchmark
def test_valid_conventional_commit_passes(self):
"""A well-formed conventional commit message returns status='pass'."""
result = validate_message("feat: add streaming endpoint")
assert result["status"] == "pass"
assert isinstance(result["checks"], list)
@pytest.mark.benchmark
def test_invalid_commit_returns_fail(self):
"""A non-conventional commit message returns status='fail'."""
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_message("bad commit message without type")
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_failed_check_has_required_keys(self):
"""Each failed check dict exposes check/status/value/error/suggest."""
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_message("wrong format")
failed = [c for c in result["checks"] if c["status"] == "fail"]
assert len(failed) >= 1
for check in failed:
assert "check" in check
assert "status" in check
assert "value" in check
assert "error" in check
assert "suggest" in check
@pytest.mark.benchmark
def test_result_contains_check_names(self):
"""Result checks list always contains the expected check names."""
result = validate_message("docs: update readme")
check_names = {c["check"] for c in result["checks"]}
# The 'message' check must always be present
assert "message" in check_names
@pytest.mark.benchmark
def test_no_terminal_output_produced(self, capsys):
"""validate_message must not print anything to stdout or stderr."""
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
validate_message("bad commit no type")
captured = capsys.readouterr()
assert captured.out == ""
assert "Commit rejected" not in captured.err
@pytest.mark.benchmark
def test_custom_config_restricts_types(self):
"""Custom config limiting allowed types causes unknown types to fail."""
cfg = {"commit": {"allow_commit_types": ["feat", "fix"]}}
# 'docs' type should now be disallowed
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_message("docs: update readme", config=cfg)
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_custom_config_pass(self):
"""Custom config with explicit types still passes valid commits."""
cfg = {"commit": {"allow_commit_types": ["feat", "fix", "docs"]}}
result = validate_message("feat: new feature", config=cfg)
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_fix_commit_passes(self):
"""fix: type always passes with default config."""
result = validate_message("fix: correct null pointer dereference")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_commit_with_scope_passes(self):
"""Commit with optional scope passes."""
result = validate_message("feat(api): add user endpoint")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_breaking_change_notation_passes(self):
"""Commit with breaking-change '!' notation passes."""
result = validate_message("feat!: remove legacy auth")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_wip_commit_fails_by_default(self):
"""WIP commits fail when allow_wip_commits=false (default in cchk.toml)."""
cfg = {"commit": {"allow_wip_commits": False}}
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_message("WIP: half-baked change", config=cfg)
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_empty_message_returns_fail(self):
"""Empty commit messages fail the message check."""
cfg = {"commit": {"allow_empty_commits": False}}
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_message("", config=cfg)
assert result["status"] == "fail"
class TestValidateBranch:
"""Tests for validate_branch()."""
@pytest.mark.benchmark
def test_valid_feature_branch_passes(self):
"""feature/<name> branch passes conventional branch check."""
result = validate_branch("feature/add-json-output")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_valid_fix_branch_passes(self):
"""fix/<name> branch passes."""
result = validate_branch("fix/null-pointer")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_main_branch_passes(self):
"""'main' is always allowed."""
result = validate_branch("main")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_invalid_branch_fails(self):
"""Branch name without a conventional prefix fails."""
result = validate_branch("my_random_branch")
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_result_contains_branch_check(self):
"""Result always contains a 'branch' check entry."""
result = validate_branch("feature/test")
check_names = {c["check"] for c in result["checks"]}
assert "branch" in check_names
@pytest.mark.benchmark
def test_no_terminal_output_produced(self, capsys):
"""validate_branch must not print anything."""
validate_branch("bad_branch_name")
captured = capsys.readouterr()
assert captured.out == ""
@pytest.mark.benchmark
def test_custom_allowed_types(self):
"""Custom branch types are respected."""
cfg = {"branch": {"allow_branch_types": ["topic"]}}
result = validate_branch("topic/my-change", config=cfg)
assert result["status"] == "pass"
class TestValidateAuthor:
"""Tests for validate_author()."""
@pytest.mark.benchmark
def test_valid_name_and_email_pass(self):
"""Valid name and email both pass."""
result = validate_author(name="Ada Lovelace", email="ada@example.com")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_invalid_email_fails(self):
"""Email without '@' fails the author_email check."""
result = validate_author(email="not-an-email")
assert result["status"] == "fail"
failed = [c for c in result["checks"] if c["status"] == "fail"]
assert any(c["check"] == "author_email" for c in failed)
@pytest.mark.benchmark
def test_valid_email_passes(self):
"""Valid email passes."""
result = validate_author(email="dev@example.org")
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_no_terminal_output_produced(self, capsys):
"""validate_author must not print anything."""
validate_author(email="bad-email")
captured = capsys.readouterr()
assert captured.out == ""
@pytest.mark.benchmark
def test_both_name_and_email_validated(self):
"""When both name and email are passed, both checks appear in output."""
result = validate_author(name="Jane Doe", email="jane@example.com")
check_names = {c["check"] for c in result["checks"]}
assert "author_name" in check_names
assert "author_email" in check_names
class TestValidateAll:
"""Tests for validate_all()."""
@pytest.mark.benchmark
def test_all_valid_returns_pass(self):
"""Valid message and branch return combined pass."""
result = validate_all(
message="feat: implement search",
branch="feature/implement-search",
)
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_invalid_message_causes_fail(self):
"""Invalid commit message causes overall fail."""
with patch("commit_check.engine.get_commit_info", return_value="test-user"):
result = validate_all(
message="not a conventional commit",
branch="feature/something",
)
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_invalid_branch_causes_fail(self):
"""Invalid branch name causes overall fail."""
result = validate_all(
message="feat: good commit",
branch="bad_branch",
)
assert result["status"] == "fail"
@pytest.mark.benchmark
def test_combined_checks_appear_in_result(self):
"""Result checks list merges message and branch check entries."""
result = validate_all(
message="fix: patch auth",
branch="fix/patch-auth",
)
check_names = {c["check"] for c in result["checks"]}
assert "message" in check_names
assert "branch" in check_names
@pytest.mark.benchmark
def test_no_args_returns_pass(self):
"""Called with no args, validate_all returns pass with empty checks."""
result = validate_all()
assert result["status"] == "pass"
assert result["checks"] == []
@pytest.mark.benchmark
def test_no_terminal_output(self, capsys):
"""validate_all must not write to stdout or stderr."""
with patch("commit_check.engine.get_commit_info", return_value="test"):
validate_all(message="bad message", branch="bad_branch")
captured = capsys.readouterr()
assert captured.out == ""
assert "Commit rejected" not in captured.err
@pytest.mark.benchmark
def test_author_validation_included(self):
"""Author checks appear in combined result when requested."""
result = validate_all(
message="feat: add feature",
author_name="Valid Name",
author_email="valid@example.com",
)
check_names = {c["check"] for c in result["checks"]}
assert "author_name" in check_names
assert "author_email" in check_names
class TestValidatePush:
"""Tests for validate_push() – the programmatic push safety API."""
ZERO_SHA = "0000000000000000000000000000000000000000"
@pytest.mark.benchmark
def test_new_branch_push_passes(self):
"""Push to a new (zero-SHA) remote ref returns status='pass'."""
push_info = f"refs/heads/feature/x abc1 refs/heads/feature/x {self.ZERO_SHA}"
result = validate_push(push_info)
assert result["status"] == "pass"
checks = result["checks"]
assert any(c["check"] == "no_force_push" for c in checks)
@pytest.mark.benchmark
def test_fast_forward_push_passes(self):
"""A fast-forward push (ancestor check returns 0) passes."""
push_info = "refs/heads/main abc123 refs/heads/main def456"
with patch("commit_check.engine.git_merge_base", return_value=0):
result = validate_push(push_info)
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_force_push_fails(self):
"""A force push (ancestor check returns 1) returns status='fail'."""
push_info = "refs/heads/main abc1 refs/heads/main def2"
with patch("commit_check.engine.git_merge_base", return_value=1):
result = validate_push(push_info)
assert result["status"] == "fail"
failed = [c for c in result["checks"] if c["status"] == "fail"]
assert len(failed) >= 1
assert failed[0]["check"] == "no_force_push"
assert "Force push" in failed[0]["error"]
@pytest.mark.benchmark
def test_none_push_refs_passes(self):
"""Calling with push_refs=None (no stdin) returns pass."""
result = validate_push(None)
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_custom_config_is_merged(self):
"""Custom config overrides are applied."""
push_info = f"refs/heads/main abc1 refs/heads/main {self.ZERO_SHA}"
# Even with allow_force_push=True in user config, validate_push
# always forces it to False so blocking is always active.
result = validate_push(
push_info,
config={"push": {"allow_force_push": True}},
)
assert result["status"] == "pass"
@pytest.mark.benchmark
def test_result_has_expected_structure(self):
"""Result dict has 'status' and 'checks' with correct keys."""
push_info = f"refs/heads/main abc1 refs/heads/main {self.ZERO_SHA}"
result = validate_push(push_info)
assert "status" in result
assert "checks" in result
for c in result["checks"]:
assert "check" in c
assert "status" in c
assert "value" in c
assert "error" in c
assert "suggest" in c