Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cchk.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ ignore_authors = ["dependabot[bot]", "copilot[bot]", "pre-commit-ci[bot]"]
[branch]
# https://conventional-branch.github.io/
conventional_branch = true
allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix"]
allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "copilot"]
require_rebase_target = "main"
ignore_authors = ["dependabot[bot]", "copilot[bot]", "pre-commit-ci[bot]", "shenxianpeng"]
8 changes: 3 additions & 5 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def load(f):

with patch("builtins.__import__", side_effect=mock_import):
# Now import config - should use tomli fallback
import commit_check.config as config
from commit_check.config import toml_load

# Test that it works
config_content = b'test_key = "test_value"'
Expand All @@ -332,7 +332,7 @@ def load(f):

try:
with open(f.name, "rb") as config_file:
result = config.toml_load(config_file)
result = toml_load(config_file)
assert result == {"test_key": "test_value"}
finally:
os.unlink(f.name)
Expand Down Expand Up @@ -375,9 +375,7 @@ def test_tomli_import_fallback_simulation(self):
with patch.dict("sys.modules", {"tomllib": None}):
with patch(
"builtins.__import__",
side_effect=lambda name, *args, **kwargs: self._mock_import_error(
name, *args, **kwargs
),
side_effect=self._mock_import_error,
):
namespace2 = {}
exec(test_code, namespace2)
Expand Down
344 changes: 28 additions & 316 deletions tests/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,34 @@ def test_validate_all_mixed_results(self):
result = engine.validate_all(context)
assert result == ValidationResult.FAIL # Any failure = overall failure

@pytest.mark.benchmark
def test_validation_engine_validator_map(self):
"""Test ValidationEngine VALIDATOR_MAP contains expected mappings."""
engine = ValidationEngine([])

expected_mappings = {
"message": CommitMessageValidator,
"subject_capitalized": SubjectCapitalizationValidator,
"subject_imperative": SubjectImperativeValidator,
"subject_max_length": SubjectLengthValidator,
"subject_min_length": SubjectLengthValidator,
"author_name": AuthorValidator,
"author_email": AuthorValidator,
"branch": BranchValidator,
"merge_base": MergeBaseValidator,
"require_signed_off_by": SignoffValidator,
"require_body": BodyValidator,
"allow_merge_commits": CommitTypeValidator,
"allow_revert_commits": CommitTypeValidator,
"allow_empty_commits": CommitTypeValidator,
"allow_fixup_commits": CommitTypeValidator,
"allow_wip_commits": CommitTypeValidator,
"ignore_authors": CommitTypeValidator,
}

for check, validator_class in expected_mappings.items():
assert engine.VALIDATOR_MAP[check] == validator_class


class TestSubjectValidator:
"""Test SubjectValidator base class."""
Expand Down Expand Up @@ -921,319 +949,3 @@ def test_validate_with_scoped_breaking_change(self):
# "resolve" is a valid imperative word with scope and breaking change notation
result = validator.validate(context)
assert result == ValidationResult.PASS


class TestComprehensiveValidationResult:
"""Comprehensive tests for ValidationResult enum."""

@pytest.mark.benchmark
def test_validation_result_values(self):
"""Test ValidationResult enum values."""
assert ValidationResult.PASS == 0
assert ValidationResult.FAIL == 1


class TestComprehensiveValidationContext:
"""Comprehensive tests for ValidationContext."""

@pytest.mark.benchmark
def test_validation_context_creation(self):
"""Test ValidationContext creation."""
context = ValidationContext()
assert context.stdin_text is None
assert context.commit_file is None

context_with_data = ValidationContext(
stdin_text="test commit", commit_file="commit.txt"
)
assert context_with_data.stdin_text == "test commit"
assert context_with_data.commit_file == "commit.txt"


class TestComprehensiveCommitMessageValidator:
"""Comprehensive tests for CommitMessageValidator."""

@pytest.mark.benchmark
def test_commit_message_validator_creation(self):
"""Test CommitMessageValidator creation."""
rule = ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
validator = CommitMessageValidator(rule)
assert validator.rule == rule

@patch("commit_check.engine.has_commits")
@pytest.mark.benchmark
def test_commit_message_validator_with_stdin(self, mock_has_commits):
"""Test CommitMessageValidator with stdin text."""
mock_has_commits.return_value = True

rule = ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
validator = CommitMessageValidator(rule)
context = ValidationContext(stdin_text="feat: add new feature")

result = validator.validate(context)
assert result == ValidationResult.PASS

@patch("commit_check.engine.get_commit_info")
@patch("commit_check.engine.has_commits")
@pytest.mark.benchmark
def test_commit_message_validator_failure(
self, mock_has_commits, mock_get_commit_info
):
"""Test CommitMessageValidator failure case."""
mock_has_commits.return_value = True

rule = ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
validator = CommitMessageValidator(rule)
context = ValidationContext(stdin_text="bad commit message")

with patch.object(validator, "_print_failure") as mock_print:
result = validator.validate(context)
assert result == ValidationResult.FAIL
mock_print.assert_called_once()

@patch("commit_check.engine.has_commits")
@pytest.mark.benchmark
def test_commit_message_validator_skip_validation(self, mock_has_commits):
"""Test CommitMessageValidator skips when no commits and no stdin."""
mock_has_commits.return_value = False

rule = ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
validator = CommitMessageValidator(rule)
context = ValidationContext() # No stdin_text

result = validator.validate(context)
assert result == ValidationResult.PASS


class TestComprehensiveSubjectCapitalizationValidator:
"""Comprehensive tests for SubjectCapitalizationValidator."""

@pytest.mark.benchmark
def test_subject_capitalization_pass(self):
"""Test SubjectCapitalizationValidator pass case."""
rule = ValidationRule(
check="subject_capitalized",
regex="^[A-Z]",
error="Subject must be capitalized",
suggest="Capitalize first letter",
)
validator = SubjectCapitalizationValidator(rule)
# Use conventional commit format with capitalized description
context = ValidationContext(stdin_text="feat: Add new feature")

result = validator.validate(context)
assert result == ValidationResult.PASS

@pytest.mark.benchmark
def test_subject_capitalization_fail(self):
"""Test SubjectCapitalizationValidator fail case."""
rule = ValidationRule(
check="subject_capitalized",
regex="^[A-Z]",
error="Subject must be capitalized",
suggest="Capitalize first letter",
)
validator = SubjectCapitalizationValidator(rule)
# Use conventional commit format with lowercase description
context = ValidationContext(stdin_text="feat: add new feature")

with patch.object(validator, "_print_failure") as mock_print:
result = validator.validate(context)
assert result == ValidationResult.FAIL
mock_print.assert_called_once()


class TestComprehensiveSubjectImperativeValidator:
"""Comprehensive tests for SubjectImperativeValidator."""

@pytest.mark.benchmark
def test_subject_imperative_pass(self):
"""Test SubjectImperativeValidator pass case."""
rule = ValidationRule(
check="subject_imperative",
regex="",
error="Subject must be imperative",
suggest="Use imperative mood",
)
validator = SubjectImperativeValidator(rule)
# Use conventional commit with imperative verb "add"
context = ValidationContext(stdin_text="feat: add new feature")

result = validator.validate(context)
assert result == ValidationResult.PASS

@pytest.mark.benchmark
def test_subject_imperative_fail(self):
"""Test SubjectImperativeValidator fail case."""
rule = ValidationRule(
check="subject_imperative",
regex="",
error="Subject must be imperative",
suggest="Use imperative mood",
)
validator = SubjectImperativeValidator(rule)
# Use past tense "added" which is not imperative
context = ValidationContext(stdin_text="feat: added new feature")

with patch.object(validator, "_print_failure") as mock_print:
result = validator.validate(context)
assert result == ValidationResult.FAIL
mock_print.assert_called_once()


class TestComprehensiveSubjectLengthValidator:
"""Comprehensive tests for SubjectLengthValidator."""

@pytest.mark.benchmark
def test_subject_length_pass(self):
"""Test SubjectLengthValidator pass case."""
rule = ValidationRule(
check="subject_max_length",
regex="",
error="Subject too long",
suggest="Keep subject short",
value=50,
)
validator = SubjectLengthValidator(rule)
context = ValidationContext(stdin_text="Add feature")

result = validator.validate(context)
assert result == ValidationResult.PASS

@pytest.mark.benchmark
def test_subject_length_fail(self):
"""Test SubjectLengthValidator fail case."""
rule = ValidationRule(
check="subject_max_length",
regex="",
error="Subject too long: max 10 characters",
suggest="Keep subject short",
value=10,
)
validator = SubjectLengthValidator(rule)
context = ValidationContext(stdin_text="This is a very long subject line")

with patch.object(validator, "_print_failure") as mock_print:
result = validator.validate(context)
assert result == ValidationResult.FAIL
mock_print.assert_called_once()


class TestComprehensiveValidationEngine:
"""Comprehensive tests for ValidationEngine."""

@pytest.mark.benchmark
def test_validation_engine_creation(self):
"""Test ValidationEngine creation."""
rules = [
ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
]
engine = ValidationEngine(rules)
assert engine.rules == rules

@pytest.mark.benchmark
def test_validation_engine_validator_map(self):
"""Test ValidationEngine VALIDATOR_MAP contains expected mappings."""
engine = ValidationEngine([])

expected_mappings = {
"message": CommitMessageValidator,
"subject_capitalized": SubjectCapitalizationValidator,
"subject_imperative": SubjectImperativeValidator,
"subject_max_length": SubjectLengthValidator,
"subject_min_length": SubjectLengthValidator,
"author_name": AuthorValidator,
"author_email": AuthorValidator,
"branch": BranchValidator,
"merge_base": MergeBaseValidator,
"require_signed_off_by": SignoffValidator,
"require_body": BodyValidator,
"allow_merge_commits": CommitTypeValidator,
"allow_revert_commits": CommitTypeValidator,
"allow_empty_commits": CommitTypeValidator,
"allow_fixup_commits": CommitTypeValidator,
"allow_wip_commits": CommitTypeValidator,
"ignore_authors": CommitTypeValidator,
}

for check, validator_class in expected_mappings.items():
assert engine.VALIDATOR_MAP[check] == validator_class

@pytest.mark.benchmark
def test_validation_engine_validate_all_pass(self):
"""Test ValidationEngine validate_all with all passing rules."""
rules = [
ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
]
engine = ValidationEngine(rules)
context = ValidationContext(stdin_text="feat: add new feature")

with patch("commit_check.engine.has_commits", return_value=True):
result = engine.validate_all(context)
assert result == ValidationResult.PASS

@pytest.mark.benchmark
def test_validation_engine_validate_all_fail(self):
"""Test ValidationEngine validate_all with failing rule."""
rules = [
ValidationRule(
check="message",
regex="^(feat|fix):",
error="Invalid commit message",
suggest="Use conventional format",
)
]
engine = ValidationEngine(rules)
context = ValidationContext(stdin_text="bad commit message")

with patch("commit_check.engine.has_commits", return_value=True):
result = engine.validate_all(context)
assert result == ValidationResult.FAIL

@pytest.mark.benchmark
def test_validation_engine_unknown_validator(self):
"""Test ValidationEngine with unknown validator type."""
rules = [
ValidationRule(
check="unknown_check",
regex="",
error="Unknown error",
suggest="Unknown suggest",
)
]
engine = ValidationEngine(rules)
context = ValidationContext(stdin_text="test")

# Should skip unknown validators and continue
result = engine.validate_all(context)
assert result == ValidationResult.PASS
Loading