pr-agent/tests/unittest/test_gitlab_provider.py
alex107ivanov 411f933a34
[GitLab] Submodule expansion functionality added (#2014)
* [GitLab] Submodule expansion functionality added

Functionality to include the submodule changes to the Model input added

* Enhance GitLab submodule handling

* docs: document GitLab submodule diff expansion

* docs: document GitLab submodule diff option
2025-09-02 12:26:42 +03:00

194 lines
7.9 KiB
Python

from unittest.mock import MagicMock, patch
import pytest
from gitlab import Gitlab
from gitlab.exceptions import GitlabGetError
from gitlab.v4.objects import Project, ProjectFile
from pr_agent.git_providers.gitlab_provider import GitLabProvider
class TestGitLabProvider:
"""Test suite for GitLab provider functionality."""
@pytest.fixture
def mock_gitlab_client(self):
client = MagicMock()
return client
@pytest.fixture
def mock_project(self):
project = MagicMock()
return project
@pytest.fixture
def gitlab_provider(self, mock_gitlab_client, mock_project):
with patch('pr_agent.git_providers.gitlab_provider.gitlab.Gitlab', return_value=mock_gitlab_client), \
patch('pr_agent.git_providers.gitlab_provider.get_settings') as mock_settings:
mock_settings.return_value.get.side_effect = lambda key, default=None: {
"GITLAB.URL": "https://gitlab.com",
"GITLAB.PERSONAL_ACCESS_TOKEN": "fake_token"
}.get(key, default)
mock_gitlab_client.projects.get.return_value = mock_project
provider = GitLabProvider("https://gitlab.com/test/repo/-/merge_requests/1")
provider.gl = mock_gitlab_client
provider.id_project = "test/repo"
return provider
def test_get_pr_file_content_success(self, gitlab_provider, mock_project):
mock_file = MagicMock(ProjectFile)
mock_file.decode.return_value = "# Changelog\n\n## v1.0.0\n- Initial release"
mock_project.files.get.return_value = mock_file
content = gitlab_provider.get_pr_file_content("CHANGELOG.md", "main")
assert content == "# Changelog\n\n## v1.0.0\n- Initial release"
mock_project.files.get.assert_called_once_with("CHANGELOG.md", "main")
mock_file.decode.assert_called_once()
def test_get_pr_file_content_with_bytes(self, gitlab_provider, mock_project):
mock_file = MagicMock(ProjectFile)
mock_file.decode.return_value = b"# Changelog\n\n## v1.0.0\n- Initial release"
mock_project.files.get.return_value = mock_file
content = gitlab_provider.get_pr_file_content("CHANGELOG.md", "main")
assert content == "# Changelog\n\n## v1.0.0\n- Initial release"
mock_project.files.get.assert_called_once_with("CHANGELOG.md", "main")
def test_get_pr_file_content_file_not_found(self, gitlab_provider, mock_project):
mock_project.files.get.side_effect = GitlabGetError("404 Not Found")
content = gitlab_provider.get_pr_file_content("CHANGELOG.md", "main")
assert content == ""
mock_project.files.get.assert_called_once_with("CHANGELOG.md", "main")
def test_get_pr_file_content_other_exception(self, gitlab_provider, mock_project):
mock_project.files.get.side_effect = Exception("Network error")
content = gitlab_provider.get_pr_file_content("CHANGELOG.md", "main")
assert content == ""
def test_create_or_update_pr_file_create_new(self, gitlab_provider, mock_project):
mock_project.files.get.side_effect = GitlabGetError("404 Not Found")
mock_file = MagicMock()
mock_project.files.create.return_value = mock_file
new_content = "# Changelog\n\n## v1.1.0\n- New feature"
commit_message = "Add CHANGELOG.md"
gitlab_provider.create_or_update_pr_file(
"CHANGELOG.md", "feature-branch", new_content, commit_message
)
mock_project.files.get.assert_called_once_with("CHANGELOG.md", "feature-branch")
mock_project.files.create.assert_called_once_with({
'file_path': 'CHANGELOG.md',
'branch': 'feature-branch',
'content': new_content,
'commit_message': commit_message,
})
def test_create_or_update_pr_file_update_existing(self, gitlab_provider, mock_project):
mock_file = MagicMock(ProjectFile)
mock_file.decode.return_value = "# Old changelog content"
mock_project.files.get.return_value = mock_file
new_content = "# New changelog content"
commit_message = "Update CHANGELOG.md"
gitlab_provider.create_or_update_pr_file(
"CHANGELOG.md", "feature-branch", new_content, commit_message
)
mock_project.files.get.assert_called_once_with("CHANGELOG.md", "feature-branch")
mock_file.content = new_content
mock_file.save.assert_called_once_with(branch="feature-branch", commit_message=commit_message)
def test_create_or_update_pr_file_update_exception(self, gitlab_provider, mock_project):
mock_project.files.get.side_effect = Exception("Network error")
with pytest.raises(Exception):
gitlab_provider.create_or_update_pr_file(
"CHANGELOG.md", "feature-branch", "content", "message"
)
def test_has_create_or_update_pr_file_method(self, gitlab_provider):
assert hasattr(gitlab_provider, "create_or_update_pr_file")
assert callable(getattr(gitlab_provider, "create_or_update_pr_file"))
def test_method_signature_compatibility(self, gitlab_provider):
import inspect
sig = inspect.signature(gitlab_provider.create_or_update_pr_file)
params = list(sig.parameters.keys())
expected_params = ['file_path', 'branch', 'contents', 'message']
assert params == expected_params
@pytest.mark.parametrize("content,expected", [
("simple text", "simple text"),
(b"bytes content", "bytes content"),
("", ""),
(b"", ""),
("unicode: café", "unicode: café"),
(b"unicode: caf\xc3\xa9", "unicode: café"),
])
def test_content_encoding_handling(self, gitlab_provider, mock_project, content, expected):
mock_file = MagicMock(ProjectFile)
mock_file.decode.return_value = content
mock_project.files.get.return_value = mock_file
result = gitlab_provider.get_pr_file_content("test.md", "main")
assert result == expected
def test_get_gitmodules_map_parsing(self, gitlab_provider, mock_project):
gitlab_provider.id_project = "1"
gitlab_provider.mr = MagicMock()
gitlab_provider.mr.target_branch = "main"
file_obj = MagicMock(ProjectFile)
file_obj.decode.return_value = (
"[submodule \"libs/a\"]\n"
" path = \"libs/a\"\n"
" url = \"https://gitlab.com/a.git\"\n"
"[submodule \"libs/b\"]\n"
" path = libs/b\n"
" url = git@gitlab.com:b.git\n"
)
mock_project.files.get.return_value = file_obj
gitlab_provider.gl.projects.get.return_value = mock_project
result = gitlab_provider._get_gitmodules_map()
assert result == {
"libs/a": "https://gitlab.com/a.git",
"libs/b": "git@gitlab.com:b.git",
}
def test_project_by_path_requires_exact_match(self, gitlab_provider):
gitlab_provider.gl.projects.get.reset_mock()
gitlab_provider.gl.projects.get.side_effect = Exception("not found")
fake = MagicMock()
fake.path_with_namespace = "other/group/repo"
gitlab_provider.gl.projects.list.return_value = [fake]
result = gitlab_provider._project_by_path("group/repo")
assert result is None
assert gitlab_provider.gl.projects.get.call_count == 2
def test_compare_submodule_cached(self, gitlab_provider):
proj = MagicMock()
proj.repository_compare.return_value = {"diffs": [{"diff": "d"}]}
with patch.object(gitlab_provider, "_project_by_path", return_value=proj) as m_pbp:
first = gitlab_provider._compare_submodule("grp/repo", "old", "new")
second = gitlab_provider._compare_submodule("grp/repo", "old", "new")
assert first == second == [{"diff": "d"}]
m_pbp.assert_called_once_with("grp/repo")
proj.repository_compare.assert_called_once_with("old", "new")