diff --git a/app/local_commit_message_generator/tests/test_analyzer.py b/app/local_commit_message_generator/tests/test_analyzer.py new file mode 100644 index 0000000..634162b --- /dev/null +++ b/app/local_commit_message_generator/tests/test_analyzer.py @@ -0,0 +1,208 @@ +"""Tests for change analyzer module.""" + +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from git import Repo + +from src.analyzer import ( + ChangeType, + StagedChange, + ChangeSet, + ChangeAnalyzer, +) + + +class TestStagedChange: + """Tests for StagedChange class.""" + + def test_filename_property(self): + """Test extracting filename from path.""" + change = StagedChange( + path="src/cli.py", + change_type=ChangeType.MODIFIED + ) + assert change.filename == "cli.py" + + def test_is_new_true_for_added(self): + """Test is_new returns True for added files.""" + change = StagedChange( + path="new_file.py", + change_type=ChangeType.ADDED + ) + assert change.is_new is True + + def test_is_new_false_for_modified(self): + """Test is_new returns False for modified files.""" + change = StagedChange( + path="existing.py", + change_type=ChangeType.MODIFIED + ) + assert change.is_new is False + + def test_is_deleted_true_for_deleted(self): + """Test is_deleted returns True for deleted files.""" + change = StagedChange( + path="deleted.py", + change_type=ChangeType.DELETED + ) + assert change.is_deleted is True + + def test_is_deleted_false_for_modified(self): + """Test is_deleted returns False for modified files.""" + change = StagedChange( + path="existing.py", + change_type=ChangeType.MODIFIED + ) + assert change.is_deleted is False + + def test_deleted_file_path(self): + """Test path handling for deleted files.""" + change = StagedChange( + path="deleted.py", + change_type=ChangeType.DELETED + ) + assert change.path == "deleted.py" + + +class TestChangeSet: + """Tests for ChangeSet class.""" + + def test_added_property(self): + """Test getting added files.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.ADDED), + StagedChange("b.py", ChangeType.MODIFIED), + StagedChange("c.py", ChangeType.ADDED), + ]) + assert len(change_set.added) == 2 + assert all(c.is_new for c in change_set.added) + + def test_deleted_property(self): + """Test getting deleted files.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.DELETED), + StagedChange("b.py", ChangeType.MODIFIED), + ]) + assert len(change_set.deleted) == 1 + assert change_set.deleted[0].path == "a.py" + + def test_modified_property(self): + """Test getting modified files.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.MODIFIED), + StagedChange("b.py", ChangeType.MODIFIED), + ]) + assert len(change_set.modified) == 2 + + def test_renamed_property(self): + """Test getting renamed files.""" + change_set = ChangeSet([ + StagedChange("old.py", ChangeType.RENAMED, old_path="old.py", new_path="new.py"), + StagedChange("a.py", ChangeType.ADDED), + ]) + assert len(change_set.renamed) == 1 + + def test_total_count(self): + """Test counting total changes.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.ADDED), + StagedChange("b.py", ChangeType.MODIFIED), + StagedChange("c.py", ChangeType.DELETED), + ]) + assert change_set.total_count == 3 + + def test_file_paths(self): + """Test getting all file paths.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.ADDED), + StagedChange("b.py", ChangeType.MODIFIED), + ]) + assert change_set.file_paths == ["a.py", "b.py"] + + def test_has_changes_true(self): + """Test has_changes returns True when there are changes.""" + change_set = ChangeSet([ + StagedChange("a.py", ChangeType.ADDED), + ]) + assert change_set.has_changes is True + + def test_has_changes_false(self): + """Test has_changes returns False for empty change set.""" + change_set = ChangeSet([]) + assert change_set.has_changes is False + + +class TestChangeAnalyzer: + """Tests for ChangeAnalyzer class.""" + + def test_init_with_repo_path(self): + """Test initialization with repository path.""" + with tempfile.TemporaryDirectory() as tmpdir: + analyzer = ChangeAnalyzer(tmpdir) + assert analyzer.repo_path == tmpdir + + def test_get_staged_changes_no_repo(self): + """Test error when not in git repository.""" + with tempfile.TemporaryDirectory() as tmpdir: + analyzer = ChangeAnalyzer(tmpdir) + with pytest.raises(ValueError, match="Not a git repository"): + analyzer.get_staged_changes() + + def test_get_staged_changes_with_repo(self): + """Test getting staged changes from real repository.""" + with tempfile.TemporaryDirectory() as tmpdir: + Repo.init(tmpdir) + analyzer = ChangeAnalyzer(tmpdir) + + result = analyzer.get_staged_changes() + assert isinstance(result, ChangeSet) + + def test_get_changed_extensions(self): + """Test getting unique file extensions.""" + change_set = ChangeSet([ + StagedChange("file.py", ChangeType.ADDED), + StagedChange("test.py", ChangeType.MODIFIED), + StagedChange("noext", ChangeType.ADDED), + ]) + with patch.object(ChangeAnalyzer, 'get_staged_changes', return_value=change_set): + analyzer = ChangeAnalyzer() + extensions = analyzer.get_changed_extensions() + assert ".py" in extensions + + def test_get_changed_directories(self): + """Test getting unique directories.""" + change_set = ChangeSet([ + StagedChange("src/main.py", ChangeType.ADDED), + StagedChange("src/cli.py", ChangeType.MODIFIED), + StagedChange("tests/test.py", ChangeType.ADDED), + ]) + with patch.object(ChangeAnalyzer, 'get_staged_changes', return_value=change_set): + analyzer = ChangeAnalyzer() + directories = analyzer.get_changed_directories() + assert "src" in directories + assert "tests" in directories + + +class TestChangeType: + """Tests for ChangeType enum.""" + + def test_enum_values(self): + """Test enum values.""" + assert ChangeType.ADDED.value == "added" + assert ChangeType.DELETED.value == "deleted" + assert ChangeType.MODIFIED.value == "modified" + assert ChangeType.RENAMED.value == "renamed" + + def test_enum_members(self): + """Test enum has expected members.""" + members = list(ChangeType) + assert ChangeType.ADDED in members + assert ChangeType.DELETED in members + assert ChangeType.MODIFIED in members + assert ChangeType.RENAMED in members + assert ChangeType.TYPE_CHANGE in members + assert ChangeType.UNMERGED in members + assert ChangeType.UNKNOWN in members