Files
pr-agent/tests/unittest/test_pr_update_changelog.py
César Pérez 3904743e85 Fixed comment 4
2025-06-07 21:13:16 +02:00

247 lines
11 KiB
Python

import pytest
from unittest.mock import MagicMock, patch, AsyncMock
from pr_agent.tools.pr_update_changelog import PRUpdateChangelog
class TestPRUpdateChangelog:
"""Test suite for the PR Update Changelog functionality."""
@pytest.fixture
def mock_git_provider(self):
"""Create a mock git provider."""
provider = MagicMock()
provider.get_pr_branch.return_value = "feature-branch"
provider.get_pr_file_content.return_value = ""
provider.pr.title = "Test PR"
provider.get_pr_description.return_value = "Test description"
provider.get_commit_messages.return_value = "fix: test commit"
provider.get_languages.return_value = {"Python": 80, "JavaScript": 20}
provider.get_files.return_value = ["test.py", "test.js"]
return provider
@pytest.fixture
def mock_ai_handler(self):
"""Create a mock AI handler."""
handler = MagicMock()
handler.chat_completion = AsyncMock(return_value=("Test changelog entry", "stop"))
return handler
@pytest.fixture
def changelog_tool(self, mock_git_provider, mock_ai_handler):
"""Create a PRUpdateChangelog instance with mocked dependencies."""
with patch('pr_agent.tools.pr_update_changelog.get_git_provider', return_value=lambda url: mock_git_provider), \
patch('pr_agent.tools.pr_update_changelog.get_main_pr_language', return_value="Python"), \
patch('pr_agent.tools.pr_update_changelog.get_settings') as mock_settings:
# Configure mock settings
mock_settings.return_value.pr_update_changelog.push_changelog_changes = False
mock_settings.return_value.pr_update_changelog.extra_instructions = ""
mock_settings.return_value.pr_update_changelog_prompt.system = "System prompt"
mock_settings.return_value.pr_update_changelog_prompt.user = "User prompt"
mock_settings.return_value.config.temperature = 0.2
tool = PRUpdateChangelog("https://gitlab.com/test/repo/-/merge_requests/1", ai_handler=lambda: mock_ai_handler)
return tool
def test_get_changelog_file_with_existing_content(self, changelog_tool, mock_git_provider):
"""Test retrieving existing changelog content."""
# Arrange
existing_content = "# Changelog\n\n## v1.0.0\n- Initial release\n- Bug fixes"
mock_git_provider.get_pr_file_content.return_value = existing_content
# Act
changelog_tool._get_changelog_file()
# Assert
assert changelog_tool.changelog_file == existing_content
assert "# Changelog" in changelog_tool.changelog_file_str
def test_get_changelog_file_with_no_existing_content(self, changelog_tool, mock_git_provider):
"""Test handling when no changelog file exists."""
# Arrange
mock_git_provider.get_pr_file_content.return_value = ""
# Act
changelog_tool._get_changelog_file()
# Assert
assert changelog_tool.changelog_file == ""
assert "Example:" in changelog_tool.changelog_file_str # Default template
def test_get_changelog_file_with_bytes_content(self, changelog_tool, mock_git_provider):
"""Test handling when git provider returns bytes instead of string."""
# Arrange
content_bytes = b"# Changelog\n\n## v1.0.0\n- Initial release"
mock_git_provider.get_pr_file_content.return_value = content_bytes
# Act
changelog_tool._get_changelog_file()
# Assert
assert isinstance(changelog_tool.changelog_file, str)
assert changelog_tool.changelog_file == "# Changelog\n\n## v1.0.0\n- Initial release"
def test_get_changelog_file_with_exception(self, changelog_tool, mock_git_provider):
"""Test handling exceptions during file retrieval."""
# Arrange
mock_git_provider.get_pr_file_content.side_effect = Exception("Network error")
# Act
changelog_tool._get_changelog_file()
# Assert
assert changelog_tool.changelog_file == ""
assert changelog_tool.changelog_file_str == "" # Exception should result in empty string, no default template
def test_prepare_changelog_update_with_existing_content(self, changelog_tool):
"""Test preparing changelog update when existing content exists."""
# Arrange
changelog_tool.prediction = "## v1.1.0\n- New feature\n- Bug fix"
changelog_tool.changelog_file = "# Changelog\n\n## v1.0.0\n- Initial release"
changelog_tool.commit_changelog = True
# Act
new_content, answer = changelog_tool._prepare_changelog_update()
# Assert
assert new_content.startswith("## v1.1.0\n- New feature\n- Bug fix\n\n")
assert "# Changelog\n\n## v1.0.0\n- Initial release" in new_content
assert answer == "## v1.1.0\n- New feature\n- Bug fix"
def test_prepare_changelog_update_without_existing_content(self, changelog_tool):
"""Test preparing changelog update when no existing content."""
# Arrange
changelog_tool.prediction = "## v1.0.0\n- Initial release"
changelog_tool.changelog_file = ""
changelog_tool.commit_changelog = True
# Act
new_content, answer = changelog_tool._prepare_changelog_update()
# Assert
assert new_content == "## v1.0.0\n- Initial release"
assert answer == "## v1.0.0\n- Initial release"
def test_prepare_changelog_update_no_commit(self, changelog_tool):
"""Test preparing changelog update when not committing."""
# Arrange
changelog_tool.prediction = "## v1.1.0\n- New feature"
changelog_tool.changelog_file = ""
changelog_tool.commit_changelog = False
# Act
new_content, answer = changelog_tool._prepare_changelog_update()
# Assert
assert new_content == "## v1.1.0\n- New feature"
assert "to commit the new content" in answer
@pytest.mark.asyncio
async def test_run_without_push_support(self, changelog_tool, mock_git_provider):
"""Test running changelog update when git provider doesn't support pushing."""
# Arrange
delattr(mock_git_provider, 'create_or_update_pr_file') # Remove the method
changelog_tool.commit_changelog = True
with patch('pr_agent.tools.pr_update_changelog.get_settings') as mock_settings:
mock_settings.return_value.pr_update_changelog.push_changelog_changes = True
mock_settings.return_value.config.publish_output = True
# Act
await changelog_tool.run()
# Assert
mock_git_provider.publish_comment.assert_called_once()
assert "not currently supported" in str(mock_git_provider.publish_comment.call_args)
@pytest.mark.asyncio
async def test_run_with_push_support(self, changelog_tool, mock_git_provider):
"""Test running changelog update when git provider supports pushing."""
# Arrange
mock_git_provider.create_or_update_pr_file = MagicMock()
changelog_tool.commit_changelog = True
changelog_tool.prediction = "## v1.1.0\n- New feature"
with patch('pr_agent.tools.pr_update_changelog.get_settings') as mock_settings, \
patch('pr_agent.tools.pr_update_changelog.retry_with_fallback_models') as mock_retry, \
patch('pr_agent.tools.pr_update_changelog.sleep'):
mock_settings.return_value.pr_update_changelog.push_changelog_changes = True
mock_settings.return_value.pr_update_changelog.get.return_value = True
mock_settings.return_value.config.publish_output = True
mock_settings.return_value.config.git_provider = "gitlab"
mock_retry.return_value = None
# Act
await changelog_tool.run()
# Assert
mock_git_provider.create_or_update_pr_file.assert_called_once()
call_args = mock_git_provider.create_or_update_pr_file.call_args
assert call_args[1]['file_path'] == 'CHANGELOG.md'
assert call_args[1]['branch'] == 'feature-branch'
def test_push_changelog_update(self, changelog_tool, mock_git_provider):
"""Test the push changelog update functionality."""
# Arrange
mock_git_provider.create_or_update_pr_file = MagicMock()
mock_git_provider.get_pr_branch.return_value = "feature-branch"
new_content = "# Updated changelog content"
answer = "Changes made"
with patch('pr_agent.tools.pr_update_changelog.get_settings') as mock_settings, \
patch('pr_agent.tools.pr_update_changelog.sleep'):
mock_settings.return_value.pr_update_changelog.get.return_value = True
# Act
changelog_tool._push_changelog_update(new_content, answer)
# Assert
mock_git_provider.create_or_update_pr_file.assert_called_once_with(
file_path="CHANGELOG.md",
branch="feature-branch",
contents=new_content,
message="[skip ci] Update CHANGELOG.md"
)
def test_gitlab_provider_method_detection(self, changelog_tool, mock_git_provider):
"""Test that the tool correctly detects GitLab provider method availability."""
# Arrange
mock_git_provider.create_or_update_pr_file = MagicMock()
# Act & Assert
assert hasattr(mock_git_provider, "create_or_update_pr_file")
@pytest.mark.parametrize("existing_content,new_entry,expected_order", [
(
"# Changelog\n\n## v1.0.0\n- Old feature",
"## v1.1.0\n- New feature",
["v1.1.0", "v1.0.0"]
),
(
"",
"## v1.0.0\n- Initial release",
["v1.0.0"]
),
(
"Some existing content",
"## v1.0.0\n- New entry",
["v1.0.0", "Some existing content"]
),
])
def test_changelog_order_preservation(self, changelog_tool, existing_content, new_entry, expected_order):
"""Test that changelog entries are properly ordered (newest first)."""
# Arrange
changelog_tool.prediction = new_entry
changelog_tool.changelog_file = existing_content
changelog_tool.commit_changelog = True
# Act
new_content, _ = changelog_tool._prepare_changelog_update()
# Assert
for i, expected in enumerate(expected_order[:-1]):
current_pos = new_content.find(expected)
next_pos = new_content.find(expected_order[i + 1])
assert current_pos < next_pos, f"Expected {expected} to come before {expected_order[i + 1]}"