Files
local-commit-message-generator/tests/test_generator.py
7000pctAUTO f7246d6d2d
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
fix: Add Gitea Actions CI workflow and fix linting issues
2026-02-04 16:59:33 +00:00

241 lines
8.5 KiB
Python

"""Tests for commit message generator."""
from unittest.mock import patch
import pytest
from src.analyzer import ChangeSet, ChangeType, StagedChange
from src.generator import (
GenerationError,
detect_commit_type,
detect_scope,
generate_commit_message,
generate_description,
)
class TestDetectCommitType:
"""Tests for commit type detection."""
def test_detects_feat_for_src_files(self):
"""Test detecting 'feat' for source files."""
change_set = ChangeSet([
StagedChange("src/main.py", ChangeType.ADDED),
StagedChange("src/cli.py", ChangeType.MODIFIED),
])
result = detect_commit_type(change_set)
assert result == "feat"
def test_detects_fix_for_bug_fixes(self):
"""Test detecting 'fix' for bug-related changes."""
change_set = ChangeSet([
StagedChange("bug_fix.py", ChangeType.MODIFIED),
StagedChange("fix_login.py", ChangeType.MODIFIED),
])
result = detect_commit_type(change_set)
assert result == "fix"
def test_detects_docs_for_markdown(self):
"""Test detecting 'docs' for markdown files."""
change_set = ChangeSet([
StagedChange("README.md", ChangeType.MODIFIED),
StagedChange("docs/guide.md", ChangeType.ADDED),
])
result = detect_commit_type(change_set)
assert result == "docs"
def test_detects_test_for_test_files(self):
"""Test detecting 'test' for test files."""
change_set = ChangeSet([
StagedChange("tests/test_main.py", ChangeType.ADDED),
StagedChange("test_main.spec.js", ChangeType.MODIFIED),
])
result = detect_commit_type(change_set)
assert result == "test"
def test_detects_chore_for_config(self):
"""Test detecting 'chore' for configuration files."""
change_set = ChangeSet([
StagedChange("package.json", ChangeType.MODIFIED),
StagedChange(".gitignore", ChangeType.ADDED),
])
result = detect_commit_type(change_set)
assert result == "chore"
def test_falls_back_to_chore(self):
"""Test falling back to 'chore' when no pattern matches."""
change_set = ChangeSet([
StagedChange("unknown/file.xyz", ChangeType.ADDED),
])
result = detect_commit_type(change_set)
assert result == "chore"
def test_custom_type_rules(self):
"""Test using custom type rules."""
change_set = ChangeSet([
StagedChange("custom/feature.py", ChangeType.ADDED),
])
custom_rules = {"custom_type": ["custom/"]}
result = detect_commit_type(change_set, custom_rules)
assert result == "custom_type"
class TestDetectScope:
"""Tests for scope detection."""
def test_detects_single_scope(self):
"""Test detecting single scope from directory."""
change_set = ChangeSet([
StagedChange("src/cli/main.py", ChangeType.ADDED),
StagedChange("src/cli/commands.py", ChangeType.MODIFIED),
])
result = detect_scope(change_set)
assert result == "src"
def test_detects_multiple_scopes(self):
"""Test detecting multiple scopes."""
change_set = ChangeSet([
StagedChange("src/a.py", ChangeType.ADDED),
StagedChange("lib/b.py", ChangeType.MODIFIED),
])
result = detect_scope(change_set)
assert "src" in result and "lib" in result
def test_empty_for_no_changes(self):
"""Test returning empty string for no changes."""
change_set = ChangeSet([])
result = detect_scope(change_set)
assert result == ""
def test_root_file_no_scope(self):
"""Test no scope for root-level files."""
change_set = ChangeSet([
StagedChange("main.py", ChangeType.ADDED),
])
result = detect_scope(change_set)
assert result == ""
class TestGenerateDescription:
"""Tests for description generation."""
def test_describes_single_added(self):
"""Test generating description for single added file."""
change_set = ChangeSet([
StagedChange("new_file.py", ChangeType.ADDED),
])
result = generate_description(change_set)
assert result == "add new_file.py"
def test_describes_single_deleted(self):
"""Test generating description for deleted file."""
change_set = ChangeSet([
StagedChange("old_file.py", ChangeType.DELETED),
])
result = generate_description(change_set)
assert result == "remove old_file.py"
def test_describes_single_modified(self):
"""Test generating description for modified file."""
change_set = ChangeSet([
StagedChange("existing.py", ChangeType.MODIFIED),
])
result = generate_description(change_set)
assert result == "update existing.py"
def test_describes_multiple_added(self):
"""Test generating description for multiple added files."""
change_set = ChangeSet([
StagedChange("a.py", ChangeType.ADDED),
StagedChange("b.py", ChangeType.ADDED),
])
result = generate_description(change_set)
assert result == "add 2 files"
def test_describes_multiple_modified(self):
"""Test generating description for multiple modified files."""
change_set = ChangeSet([
StagedChange("a.py", ChangeType.MODIFIED),
StagedChange("b.py", ChangeType.MODIFIED),
])
result = generate_description(change_set)
assert result == "update 2 files"
def test_default_for_empty(self):
"""Test default description for empty change set."""
change_set = ChangeSet([])
result = generate_description(change_set)
assert result == "update"
class TestGenerateCommitMessage:
"""Tests for full message generation."""
@patch("src.generator.ChangeAnalyzer")
def test_generates_valid_message(self, mock_analyzer):
"""Test generating a complete commit message."""
change_set = ChangeSet([
StagedChange("src/main.py", ChangeType.ADDED),
])
mock_analyzer.return_value.get_staged_changes.return_value = change_set
result = generate_commit_message()
assert "feat" in result
assert "src" in result
def test_raises_error_on_no_changes(self):
"""Test raising error when no staged changes."""
with patch("src.generator.ChangeAnalyzer") as mock_analyzer:
mock_analyzer.return_value.get_staged_changes.return_value = ChangeSet([])
with pytest.raises(GenerationError, match="No staged changes"):
generate_commit_message()
def test_raises_error_on_invalid_repo(self):
"""Test raising error for invalid repository."""
with patch("src.generator.ChangeAnalyzer") as mock_analyzer:
mock_analyzer.return_value.get_staged_changes.side_effect = ValueError(
"Not a git repository"
)
with pytest.raises(GenerationError, match="Not a git repository"):
generate_commit_message()
@patch("src.generator.ChangeAnalyzer")
def test_respects_config_template(self, mock_analyzer):
"""Test using custom template from config."""
change_set = ChangeSet([
StagedChange("src/test.py", ChangeType.ADDED),
])
custom_config = {"template": "CUSTOM: {description}"}
mock_analyzer.return_value.get_staged_changes.return_value = change_set
with patch("src.generator.load_config") as mock_load_config:
mock_load_config.return_value = custom_config
result = generate_commit_message()
assert result.startswith("CUSTOM:")
@patch("src.generator.ChangeAnalyzer")
def test_max_files_limit(self, mock_analyzer):
"""Test respecting max files configuration."""
changes = [StagedChange(f"file{i}.py", ChangeType.ADDED) for i in range(10)]
change_set = ChangeSet(changes)
custom_config = {
"include_file_list": True,
"max_files": 3,
"template": "{files}"
}
mock_analyzer.return_value.get_staged_changes.return_value = change_set
with patch("src.generator.load_config") as mock_load_config:
mock_load_config.return_value = custom_config
result = generate_commit_message()
file_count = result.count("file")
assert file_count <= 3