Skip to main content
Glama
test_tools.py16.9 kB
"""Tests for the GitTools class.""" import json import pytest from unittest.mock import Mock, patch from dataclasses import asdict from collections import defaultdict, Counter from mcp_mr_summarizer.tools import GitTools, GitAnalysisError, AnalysisResult from mcp_mr_summarizer.models import CommitInfo, MergeRequestSummary class TestGitTools: """Test cases for GitTools.""" def setup_method(self): """Set up test fixtures.""" self.tools = GitTools("/test/repo") # Ensure analyzer is created and accessible _ = self.tools.analyzer def test_init(self): """Test GitTools initialization.""" tools = GitTools("/path/to/repo") assert tools.repo_path == "/path/to/repo" assert tools.analyzer is not None def test_generate_merge_request_summary_markdown(self): """Test merge request summary generation in markdown format.""" # Mock commits and summary mock_commits = [ CommitInfo( hash="abc123", author="Test Author", date="2023-01-01", message="Add new feature", files_changed=["src/feature.py"], insertions=50, deletions=10, ) ] mock_summary = MergeRequestSummary( title="Feature Enhancement", description="Added new feature with comprehensive tests and documentation.", total_commits=1, total_files_changed=1, total_insertions=50, total_deletions=10, key_changes=["Added new feature"], breaking_changes=[], new_features=["New feature implementation"], bug_fixes=[], refactoring=[], files_affected=["src/feature.py"], estimated_review_time="15 minutes", ) # Mock the entire get_git_log method to avoid any git operations with patch.object( self.tools.analyzer, "get_git_log", return_value=mock_commits ): self.tools.analyzer.generate_summary = Mock(return_value=mock_summary) result = self.tools.generate_merge_request_summary( "main", "feature", "/test/repo", "markdown" ) expected = "# Feature Enhancement\n\nAdded new feature with comprehensive tests and documentation." assert result == expected def test_generate_merge_request_summary_json(self): """Test merge request summary generation in JSON format.""" # Mock commits and summary mock_commits = [ CommitInfo( hash="abc123", author="Test Author", date="2023-01-01", message="Add new feature", files_changed=["src/feature.py"], insertions=50, deletions=10, ) ] mock_summary = MergeRequestSummary( title="Feature Enhancement", description="Added new feature with comprehensive tests and documentation.", total_commits=1, total_files_changed=1, total_insertions=50, total_deletions=10, key_changes=["Added new feature"], breaking_changes=[], new_features=["New feature implementation"], bug_fixes=[], refactoring=[], files_affected=["src/feature.py"], estimated_review_time="15 minutes", ) # Mock the entire get_git_log method to avoid any git operations with patch.object( self.tools.analyzer, "get_git_log", return_value=mock_commits ): self.tools.analyzer.generate_summary = Mock(return_value=mock_summary) result = self.tools.generate_merge_request_summary( "main", "feature", "/test/repo", "json" ) # Parse the JSON result to verify structure parsed_result = json.loads(result) expected_dict = asdict(mock_summary) assert parsed_result == expected_dict def test_generate_merge_request_summary_with_custom_repo_path(self): """Test merge request summary generation with custom repository path.""" custom_path = "/custom/repo" mock_commits = [] mock_summary = MergeRequestSummary( title="No Changes", description="No commits found.", total_commits=0, total_files_changed=0, total_insertions=0, total_deletions=0, key_changes=[], breaking_changes=[], new_features=[], bug_fixes=[], refactoring=[], files_affected=[], estimated_review_time="0 minutes", ) # Mock the analyzer creation and methods with patch("mcp_mr_summarizer.tools.GitLogAnalyzer") as mock_analyzer_class: mock_analyzer_instance = Mock() mock_analyzer_instance.get_git_log = Mock(return_value=mock_commits) mock_analyzer_instance.generate_summary = Mock(return_value=mock_summary) mock_analyzer_class.return_value = mock_analyzer_instance # Mock the _with_repo_path_update method to avoid actual git operations with patch.object(self.tools, "_with_repo_path_update") as mock_update: mock_update.return_value = "# No Changes\n\nNo commits found." result = self.tools.generate_merge_request_summary( "main", "feature", custom_path, "markdown" ) # Verify that the update method was called mock_update.assert_called_once() assert result == "# No Changes\n\nNo commits found." def test_generate_merge_request_summary_exception_handling(self): """Test exception handling in generate_merge_request_summary.""" # Mock the _generate_summary_internal method to directly test error handling with patch.object(self.tools, "_generate_summary_internal") as mock_internal: mock_internal.side_effect = Exception("Git error") with pytest.raises( GitAnalysisError, match="Error during generate_merge_request_summary: Git error", ): self.tools.generate_merge_request_summary( "main", "feature", "/test/repo" ) def test_analyze_git_commits_success(self): """Test successful git commits analysis.""" # Mock commits mock_commits = [ CommitInfo( hash="abc123def456", author="Test Author", date="2023-01-01", message="Fix bug in authentication", files_changed=["src/auth.py", "tests/test_auth.py"], insertions=20, deletions=5, ), CommitInfo( hash="def456ghi789", author="Test Author 2", date="2023-01-02", message="Add new user management feature", files_changed=["src/users.py", "src/models.py"], insertions=150, deletions=10, ), ] # Mock the entire get_git_log method to avoid any git operations with patch.object( self.tools.analyzer, "get_git_log", return_value=mock_commits ): self.tools.analyzer.categorize_commit = Mock( side_effect=[["bug_fix"], ["new_feature"]] ) self.tools.analyzer._categorize_files = Mock( return_value={ "Source": ["src/auth.py", "src/users.py", "src/models.py"], "Tests": ["tests/test_auth.py"], } ) result = self.tools.analyze_git_commits("main", "feature", "/test/repo") # Verify the report structure assert "# Git Commit Analysis" in result assert "## Summary" in result assert "Total Commits:** 2" in result assert "Total Insertions:** 170" in result assert "Total Deletions:** 15" in result assert "Files Affected:** 4" in result assert "## Commit Categories" in result assert "### Bug Fix (1)" in result assert "### New Feature (1)" in result assert "## Significant Changes" in result assert "def456gh" in result # Hash of significant change assert "## Files Affected" in result assert "### Source" in result assert "### Tests" in result def test_analyze_git_commits_no_commits(self): """Test git commits analysis when no commits are found.""" # Mock the entire get_git_log method to avoid any git operations with patch.object(self.tools.analyzer, "get_git_log", return_value=[]): result = self.tools.analyze_git_commits("main", "feature", "/test/repo") assert result == "No commits found between the specified branches." def test_analyze_git_commits_with_custom_repo_path(self): """Test git commits analysis with custom repository path.""" custom_path = "/custom/repo" mock_commits = [] # Mock the analyzer creation with patch("mcp_mr_summarizer.tools.GitLogAnalyzer") as mock_analyzer_class: mock_analyzer_instance = Mock() mock_analyzer_instance.get_git_log = Mock(return_value=mock_commits) mock_analyzer_class.return_value = mock_analyzer_instance # Mock the _with_repo_path_update method to avoid actual git operations with patch.object(self.tools, "_with_repo_path_update") as mock_update: mock_update.return_value = ( "No commits found between the specified branches." ) result = self.tools.analyze_git_commits(custom_path, "main", "feature") # Verify that the update method was called mock_update.assert_called_once() assert result == "No commits found between the specified branches." def test_analyze_git_commits_exception_handling(self): """Test exception handling in analyze_git_commits.""" # Mock the _analyze_commits_internal method to directly test error handling with patch.object(self.tools, "_analyze_commits_internal") as mock_internal: mock_internal.side_effect = Exception("Git error") with pytest.raises( GitAnalysisError, match="Error during analyze_git_commits: Git error" ): self.tools.analyze_git_commits("/test/repo", "main", "feature") def test_analyze_git_commits_commit_processing_exception(self): """Test that individual commit processing exceptions don't stop analysis.""" # Mock commits where one will cause an exception mock_commits = [ CommitInfo( hash="abc123", author="Test Author", date="2023-01-01", message="Good commit", files_changed=["src/file1.py"], insertions=10, deletions=0, ), CommitInfo( hash="def456", author="Test Author 2", date="2023-01-02", message="Bad commit", files_changed=["src/file2.py"], insertions=5, deletions=0, ), ] # Mock the entire get_git_log method to avoid any git operations with patch.object( self.tools.analyzer, "get_git_log", return_value=mock_commits ): # Mock categorize_commit to raise an exception for the second commit self.tools.analyzer.categorize_commit = Mock( side_effect=[["feature"], Exception("Categorization error")] ) result = self.tools.analyze_git_commits("main", "feature", "/test/repo") # Should still generate a report even with the error assert "# Git Commit Analysis" in result assert "Total Commits:** 2" in result def test_generate_analysis_report_empty_analysis(self): """Test report generation with empty analysis data.""" analysis = AnalysisResult( total_commits=0, total_insertions=0, total_deletions=0, categories=defaultdict(list), significant_changes=[], files_affected=set(), stats=Counter(), ) result = self.tools._generate_analysis_report_sync(analysis) assert "# Git Commit Analysis" in result assert "Total Commits:** 0" in result assert "Total Insertions:** 0" in result assert "Total Deletions:** 0" in result assert "Files Affected:** 0" in result # Should not have categories, significant changes, or files sections assert "## Commit Categories" not in result assert "## Significant Changes" not in result assert "## Files Affected" not in result def test_generate_analysis_report_file_categorization_error(self): """Test report generation when file categorization fails.""" analysis = AnalysisResult( total_commits=1, total_insertions=10, total_deletions=5, categories=defaultdict(list), significant_changes=[], files_affected={"src/file1.py", "src/file2.py"}, stats=Counter(), ) self.tools.analyzer._categorize_files = Mock( side_effect=Exception("Categorization error") ) result = self.tools._generate_analysis_report_sync(analysis) assert "# Git Commit Analysis" in result assert "Error categorizing files:" in result assert "### All Files" in result assert "src/file1.py" in result assert "src/file2.py" in result def test_generate_analysis_report_many_files_truncation(self): """Test report generation with many files (truncation).""" # Create analysis with many files many_files = {f"file{i}.py" for i in range(25)} analysis = AnalysisResult( total_commits=1, total_insertions=10, total_deletions=5, categories=defaultdict(list), significant_changes=[], files_affected=many_files, stats=Counter(), ) self.tools.analyzer._categorize_files = Mock( return_value={ "Source": list(many_files), } ) result = self.tools._generate_analysis_report_sync(analysis) assert "# Git Commit Analysis" in result assert "### Source" in result # Should show first 10 files and indicate there are more assert "... and 15 more" in result def test_repo_path_parameter(self): """Test that repo_path parameter is properly used.""" custom_path = "/custom/repo/path" tools = GitTools(custom_path) assert tools.repo_path == custom_path assert tools.analyzer.repo_path == custom_path def test_repo_path_update_same_path(self): """Test that analyzer is not recreated when repo_path is the same.""" original_analyzer = self.tools.analyzer # Mock the entire get_git_log method to avoid any git operations with patch.object(self.tools.analyzer, "get_git_log", return_value=[]): self.tools.analyzer.generate_summary = Mock( return_value=MergeRequestSummary( title="No Changes", description="No commits found.", total_commits=0, total_files_changed=0, total_insertions=0, total_deletions=0, key_changes=[], breaking_changes=[], new_features=[], bug_fixes=[], refactoring=[], files_affected=[], estimated_review_time="0 minutes", ) ) # Call with same repo path self.tools.generate_merge_request_summary( "main", "feature", "/test/repo", "markdown" ) # Analyzer should be the same instance assert self.tools.analyzer is original_analyzer

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sonicjoy/mcp_merge_request_summarizer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server