diff --git a/app/tests/test_git_utils.py b/app/tests/test_git_utils.py new file mode 100644 index 0000000..a1f2923 --- /dev/null +++ b/app/tests/test_git_utils.py @@ -0,0 +1,138 @@ +"""Tests for git_utils.py.""" +from unittest.mock import MagicMock, patch + +import pytest + +from git_commit_generator.git_utils import GitUtils + + +class TestGitUtils: + """Test cases for GitUtils.""" + + @pytest.fixture + def git_utils(self): + """Create a GitUtils instance with mocked repository.""" + with patch("git_commit_generator.git_utils.Repo") as mock_repo: + repo = MagicMock() + mock_repo.return_value = repo + utils = GitUtils("/fake/path") + utils.repo = repo + yield utils + + def test_is_repo_true(self, git_utils): + """Test is_repo returns True for valid repository.""" + git_utils.repo.bare = False + result = git_utils.is_repo() + assert result is True + + def test_is_repo_false(self, git_utils): + """Test is_repo returns False for invalid repository.""" + git_utils.repo.bare = True + result = git_utils.is_repo() + assert result is False + + def test_get_staged_diff(self, git_utils): + """Test getting staged diff.""" + diff_mock = MagicMock() + diff_mock.diff = b"diff --git a/file.py b/file.py" + git_utils.repo.index.diff.return_value = [diff_mock] + + result = git_utils.get_staged_diff() + + assert "diff --git a/file.py b/file.py" in result + + def test_get_unstaged_diff(self, git_utils): + """Test getting unstaged diff.""" + diff_mock = MagicMock() + diff_mock.diff = "diff --git a/file.py b/file.py" + git_utils.repo.index.diff.return_value = [diff_mock] + + result = git_utils.get_unstaged_diff() + + assert "diff --git a/file.py b/file.py" in result + + def test_get_staged_files(self, git_utils): + """Test getting list of staged files.""" + diff_mock = MagicMock() + diff_mock.a_path = "src/main.py" + git_utils.repo.index.diff.return_value = [diff_mock] + + result = git_utils.get_staged_files() + + assert result == ["src/main.py"] + + def test_get_commit_history(self, git_utils): + """Test getting commit history.""" + commit_mock = MagicMock() + commit_mock.hexsha = "abc1234567890" + commit_mock.message = "feat: add new feature" + commit_mock.author.name = "Test User" + commit_mock.author.email = "test@example.com" + commit_mock.committed_datetime.isoformat.return_value = "2024-01-15T10:30:00" + + git_utils.repo.iter_commits.return_value = [commit_mock] + + result = git_utils.get_commit_history(limit=10) + + assert len(result) == 1 + assert result[0]["hash"] == "abc1234" + assert result[0]["full_hash"] == "abc1234567890" + assert result[0]["message"] == "feat: add new feature" + + def test_get_conventional_commits(self, git_utils): + """Test filtering conventional commits.""" + commit_mock = MagicMock() + commit_mock.hexsha = "abc1234567890" + commit_mock.message = "feat(api): new feature" + commit_mock.author.name = "Test User" + commit_mock.author.email = "test@example.com" + commit_mock.committed_datetime.isoformat.return_value = "2024-01-15T10:30:00" + + git_utils.repo.iter_commits.return_value = [commit_mock] + + result = git_utils.get_conventional_commits() + + assert len(result) == 1 + assert result[0]["type"] == "feat" + assert result[0]["scope"] == "api" + + def test_get_file_scopes(self, git_utils): + """Test extracting scopes from file paths.""" + git_utils.get_changed_files = MagicMock(return_value=[ + "api/routes.py", + "auth/login.py", + "tests/test_main.py", + ]) + + result = git_utils.get_file_scopes() + + assert "api" in result + assert "auth" in result + + def test_is_conventional_message_valid(self, git_utils): + """Test conventional message validation.""" + message = "feat(auth): add login functionality" + result = git_utils._is_conventional_message(message) + assert result is True + + def test_is_conventional_message_invalid(self, git_utils): + """Test non-conventional message validation.""" + message = "Added new feature" + result = git_utils._is_conventional_message(message) + assert result is False + + def test_parse_conventional_commit_full(self, git_utils): + """Test parsing full conventional commit.""" + message = "fix(database): resolve connection timeout issue" + result = git_utils._parse_conventional_commit(message) + assert result["type"] == "fix" + assert result["scope"] == "database" + assert result["description"] == "resolve connection timeout issue" + + def test_parse_conventional_commit_no_scope(self, git_utils): + """Test parsing conventional commit without scope.""" + message = "chore: update dependencies" + result = git_utils._parse_conventional_commit(message) + assert result["type"] == "chore" + assert result["scope"] == "" + assert result["description"] == "update dependencies"