diff --git a/git_commit_ai/tests/test_conventional.py b/git_commit_ai/tests/test_conventional.py new file mode 100644 index 0000000..92c00eb --- /dev/null +++ b/git_commit_ai/tests/test_conventional.py @@ -0,0 +1,131 @@ +"""Tests for conventional commit validation.""" + +import pytest + +from git_commit_ai.core.conventional import ( + ConventionalCommitParser, + ConventionalCommitFixer, + validate_commit_message, + format_conventional, + extract_conventional_parts, +) + + +class TestConventionalCommitParser: + """Tests for ConventionalCommitParser.""" + + def test_parse_valid_message(self): + message = "feat(auth): add user authentication" + parsed = ConventionalCommitParser.parse(message) + assert parsed is not None + assert parsed.type == "feat" + assert parsed.scope == "auth" + assert parsed.description == "add user authentication" + + def test_parse_without_scope(self): + message = "fix: resolve memory leak" + parsed = ConventionalCommitParser.parse(message) + assert parsed is not None + assert parsed.type == "fix" + assert parsed.scope is None + + def test_parse_invalid_message(self): + message = "just a random message" + parsed = ConventionalCommitParser.parse(message) + assert parsed is None + + def test_is_valid(self): + assert ConventionalCommitParser.is_valid("feat: new feature") is True + assert ConventionalCommitParser.is_valid("invalid message") is False + + def test_validate_valid(self): + errors = ConventionalCommitParser.validate("feat(auth): add login") + assert len(errors) == 0 + + def test_validate_invalid_type(self): + errors = ConventionalCommitParser.validate("invalid(scope): desc") + assert len(errors) > 0 + assert any("Invalid type" in e for e in errors) + + def test_validate_empty_message(self): + errors = ConventionalCommitParser.validate("") + assert len(errors) > 0 + + +class TestConventionalCommitFixer: + """Tests for ConventionalCommitFixer.""" + + def test_fix_simple_message(self): + diff = """+++ b/src/auth.py +@@ -1,3 +1,4 @@ ++def login(): ++ pass +""" + fixed = ConventionalCommitFixer.fix("add login feature", diff) + assert fixed.startswith("feat:") + + def test_fix_with_type_detection(self): + diff = """--- a/src/bug.py ++++ b/src/bug.py +@@ -1,3 +1,4 @@ +-def calculate(): ++def calculate(): + return 1 / 0 ++ return 1 / 1 +""" + fixed = ConventionalCommitFixer.fix("fix bug", diff) + assert fixed.startswith("fix:") + + def test_fix_preserves_description(self): + diff = """+++ b/src/auth.py +@@ -1,3 +1,4 @@ ++def login(): +""" + fixed = ConventionalCommitFixer.fix("add login functionality", diff) + assert "login" in fixed.lower() + + +class TestValidateCommitMessage: + """Tests for validate_commit_message function.""" + + def test_validate_valid(self): + is_valid, errors = validate_commit_message("feat(auth): add login") + assert is_valid is True + assert len(errors) == 0 + + def test_validate_invalid(self): + is_valid, errors = validate_commit_message("invalid") + assert is_valid is False + assert len(errors) > 0 + + +class TestFormatConventional: + """Tests for format_conventional function.""" + + def test_format_with_type_and_scope(self): + result = format_conventional("add login", "feat", "auth") + assert result == "feat(auth): add login" + + def test_format_with_type_only(self): + result = format_conventional("fix bug", "fix") + assert result == "fix: fix bug" + + def test_format_already_formatted(self): + result = format_conventional("feat(auth): add login", "feat", "auth") + assert result == "feat(auth): add login" + + +class TestExtractConventionalParts: + """Tests for extract_conventional_parts function.""" + + def test_extract_all_parts(self): + result = extract_conventional_parts("feat(auth): add login") + assert result["type"] == "feat" + assert result["scope"] == "auth" + assert result["description"] == "add login" + + def test_extract_invalid_message(self): + result = extract_conventional_parts("invalid message") + assert result["type"] is None + assert result["scope"] is None + assert result["description"] == "invalid message"