| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5c39e5f commit 6a38436
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,6 +55,9 @@ class CheckOutcome: | |||
| 55 | 55 | ||
| 56 | 56 | check: str | |
| 57 | 57 | status: str # "pass" or "fail" | |
| 58 | + # The concrete value that was checked (subject, branch, author, ...), | ||
| 59 | + # populated on both pass and fail so consumers can report what was | ||
| 60 | + # validated even when the check succeeded. | ||
| 58 | 61 | value: str = "" | |
| 59 | 62 | error: str = "" | |
| 60 | 63 | suggest: str = "" | |
@@ -87,6 +90,11 @@ def __init__(self, rule: ValidationRule): | |||
| 87 | 90 | self._compact: bool = False | |
| 88 | 91 | # Populated by _print_failure() on every failure, regardless of mode. | |
| 89 | 92 | self._last_failure: dict[str, str] | None = None | |
| 93 | + # Populated by subclasses on every validation (pass or fail) with the | ||
| 94 | + # concrete value that was checked (subject, branch, author, ...), so | ||
| 95 | + # structured consumers (--format json, validate_all_detailed) can | ||
| 96 | + # report what was checked even when the check passed. | ||
| 97 | + self._checked_value: str = "" | ||
| 90 | 98 | ||
| 91 | 99 | @abstractmethod | |
| 92 | 100 | def validate(self, context: ValidationContext) -> ValidationResult: | |
@@ -244,6 +252,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 244 | 252 | if not message: | |
| 245 | 253 | return ValidationResult.PASS | |
| 246 | 254 | ||
| 255 | + self._checked_value = message | ||
| 256 | + | ||
| 247 | 257 | import re | |
| 248 | 258 | ||
| 249 | 259 | if self.rule.regex and re.match(self.rule.regex, message): | |
@@ -264,6 +274,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 264 | 274 | if not subject: | |
| 265 | 275 | return ValidationResult.PASS | |
| 266 | 276 | ||
| 277 | + self._checked_value = subject | ||
| 278 | + | ||
| 267 | 279 | return self._validate_subject(subject) | |
| 268 | 280 | ||
| 269 | 281 | def _get_subject(self, context: ValidationContext) -> str: | |
@@ -369,6 +381,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 369 | 381 | if not author_value: | |
| 370 | 382 | return ValidationResult.PASS | |
| 371 | 383 | ||
| 384 | + self._checked_value = author_value | ||
| 385 | + | ||
| 372 | 386 | return self._validate_author(author_value) | |
| 373 | 387 | ||
| 374 | 388 | def _get_author_value(self, context: ValidationContext) -> str: | |
@@ -429,6 +443,7 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 429 | 443 | branch_name = ( | |
| 430 | 444 | context.stdin_text.strip() if context.stdin_text else get_branch_name() | |
| 431 | 445 | ) | |
| 446 | + self._checked_value = branch_name | ||
| 432 | 447 | ||
| 433 | 448 | if not self.rule.regex: | |
| 434 | 449 | return ValidationResult.PASS | |
@@ -451,6 +466,7 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 451 | 466 | ||
| 452 | 467 | current_branch = get_branch_name() | |
| 453 | 468 | target_pattern = self.rule.regex | |
| 469 | + self._checked_value = current_branch | ||
| 454 | 470 | ||
| 455 | 471 | if not target_pattern: | |
| 456 | 472 | return ValidationResult.PASS | |
@@ -525,6 +541,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 525 | 541 | if not message: | |
| 526 | 542 | return ValidationResult.PASS | |
| 527 | 543 | ||
| 544 | + self._checked_value = message | ||
| 545 | + | ||
| 528 | 546 | import re | |
| 529 | 547 | ||
| 530 | 548 | if self.rule.regex and re.search(self.rule.regex, message): | |
@@ -545,6 +563,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 545 | 563 | if not message: | |
| 546 | 564 | return ValidationResult.PASS | |
| 547 | 565 | ||
| 566 | + self._checked_value = message | ||
| 567 | + | ||
| 548 | 568 | # Split message into lines and check if there's content after the subject | |
| 549 | 569 | lines = message.strip().split("\n") | |
| 550 | 570 | ||
@@ -598,6 +618,8 @@ def _check_current_branch_against_upstream(self) -> ValidationResult: | |||
| 598 | 618 | if not upstream_ref: | |
| 599 | 619 | return ValidationResult.PASS | |
| 600 | 620 | ||
| 621 | + self._checked_value = f"{get_branch_name()} -> {upstream_ref}" | ||
| 622 | + | ||
| 601 | 623 | target_ref = get_upstream_remote_sha(upstream_ref) or upstream_ref | |
| 602 | 624 | returncode = git_merge_base(target_ref, "HEAD") | |
| 603 | 625 | if ( | |
@@ -627,6 +649,7 @@ def _check_push_line(self, line: str) -> ValidationResult: | |||
| 627 | 649 | parts[2], | |
| 628 | 650 | parts[3], | |
| 629 | 651 | ) | |
| 652 | + self._checked_value = f"{local_ref} -> {remote_ref}" | ||
| 630 | 653 | ||
| 631 | 654 | # Zero SHA for remote means a new branch push (not a force push) | |
| 632 | 655 | if remote_sha == self.ZERO_SHA: | |
@@ -677,6 +700,8 @@ def validate(self, context: ValidationContext) -> ValidationResult: | |||
| 677 | 700 | if not message: | |
| 678 | 701 | return ValidationResult.PASS | |
| 679 | 702 | ||
| 703 | + self._checked_value = message | ||
| 704 | + | ||
| 680 | 705 | # Check if this commit type is allowed based on rule configuration | |
| 681 | 706 | is_allowed = self._is_commit_type_allowed(message) | |
| 682 | 707 | ||
@@ -886,6 +911,7 @@ def validate_all_detailed(self, context: ValidationContext) -> list[CheckOutcome | |||
| 886 | 911 | CheckOutcome( | |
| 887 | 912 | check=rule.check, | |
| 888 | 913 | status="pass", | |
| 914 | + value=validator._checked_value or "", | ||
| 889 | 915 | rule_id=rule.rule_id or "", | |
| 890 | 916 | docs_url=rule.docs_url or "", | |
| 891 | 917 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1158,6 +1158,46 @@ def test_validation_engine_validate_all_pass(self): | |||
| 1158 | 1158 | result = engine.validate_all(context) | |
| 1159 | 1159 | assert result == ValidationResult.PASS | |
| 1160 | 1160 | ||
| 1161 | + @pytest.mark.benchmark | ||
| 1162 | + def test_validate_all_detailed_reports_value_on_pass(self): | ||
| 1163 | + """Passed checks still report the concrete value that was checked.""" | ||
| 1164 | + rules = [ | ||
| 1165 | + ValidationRule(check="message", regex=r"^feat:"), | ||
| 1166 | + ValidationRule(check="subject_imperative", regex=r""), | ||
| 1167 | + ] | ||
| 1168 | + engine = ValidationEngine(rules) | ||
| 1169 | + context = ValidationContext(stdin_text="feat: add feature") | ||
| 1170 | + | ||
| 1171 | + outcomes = engine.validate_all_detailed(context) | ||
| 1172 | + assert len(outcomes) == 2 | ||
| 1173 | + assert all(o.status == "pass" for o in outcomes) | ||
| 1174 | + by_check = {o.check: o for o in outcomes} | ||
| 1175 | + assert by_check["message"].value == "feat: add feature" | ||
| 1176 | + assert by_check["subject_imperative"].value == "feat: add feature" | ||
| 1177 | + | ||
| 1178 | + @pytest.mark.benchmark | ||
| 1179 | + def test_validate_all_detailed_author_reports_author_name(self): | ||
| 1180 | + """Author check reports the checked identity even when it passes.""" | ||
| 1181 | + rules = [ValidationRule(check="author_name", regex=r"^Jane")] | ||
| 1182 | + engine = ValidationEngine(rules) | ||
| 1183 | + | ||
| 1184 | + with patch(GIT_CONFIG_VALUE, return_value="Jane Doe"): | ||
| 1185 | + outcomes = engine.validate_all_detailed(ValidationContext()) | ||
| 1186 | + | ||
| 1187 | + assert outcomes[0].status == "pass" | ||
| 1188 | + assert outcomes[0].value == "Jane Doe" | ||
| 1189 | + | ||
| 1190 | + @pytest.mark.benchmark | ||
| 1191 | + def test_validate_all_detailed_branch_reports_branch_name(self): | ||
| 1192 | + """Branch check reports the branch name even when it passes.""" | ||
| 1193 | + rules = [ValidationRule(check="branch", regex=r"^feature/")] | ||
| 1194 | + engine = ValidationEngine(rules) | ||
| 1195 | + context = ValidationContext(stdin_text="feature/add-login") | ||
| 1196 | + | ||
| 1197 | + outcomes = engine.validate_all_detailed(context) | ||
| 1198 | + assert outcomes[0].status == "pass" | ||
| 1199 | + assert outcomes[0].value == "feature/add-login" | ||
| 1200 | + | ||
| 1161 | 1201 | @pytest.mark.benchmark | |
| 1162 | 1202 | def test_validation_engine_validate_all_fail(self): | |
| 1163 | 1203 | """Test ValidationEngine with some validations failing.""" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -555,6 +555,23 @@ def test_json_format_valid_message_returns_pass(self, mocker, capsys, monkeypatc | |||
| 555 | 555 | assert isinstance(data["checks"], list) | |
| 556 | 556 | assert all("check" in c and "status" in c for c in data["checks"]) | |
| 557 | 557 | ||
| 558 | + @pytest.mark.benchmark | ||
| 559 | + def test_json_format_pass_reports_checked_value(self, mocker, capsys, monkeypatch): | ||
| 560 | + """JSON output reports the checked value even when the check passed.""" | ||
| 561 | + mocker.patch("sys.stdin.isatty", return_value=False) | ||
| 562 | + mocker.patch("sys.stdin.read", return_value="feat: add new feature\n") | ||
| 563 | + | ||
| 564 | + monkeypatch.setattr("sys.argv", [CMD, "-m", "--format", "json"]) | ||
| 565 | + main() | ||
| 566 | + | ||
| 567 | + out, _ = capsys.readouterr() | ||
| 568 | + data = json.loads(out) | ||
| 569 | + passed_with_value = [ | ||
| 570 | + c for c in data["checks"] if c["status"] == "pass" and c["value"] | ||
| 571 | + ] | ||
| 572 | + assert passed_with_value | ||
| 573 | + assert all(c["value"] == "feat: add new feature" for c in passed_with_value) | ||
| 574 | + | ||
| 558 | 575 | @pytest.mark.benchmark | |
| 559 | 576 | def test_json_format_invalid_message_returns_fail( | |
| 560 | 577 | self, mocker, capsys, monkeypatch | |
| Back | FazBrowse Home | New Git URL |
0 commit comments