import pytest from mcp_server_gitlab_python.server import create_server, GitLabPythonServer from unittest.mock import MagicMock, patch def test_create_server(): server = create_server() assert server is not None def make_mock_diff(old_path, new_path, diff): m = MagicMock() m.old_path = old_path m.new_path = new_path m.diff = diff return m @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_get_mr_diff_small_diff(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() mr = MagicMock() mock_gitlab.return_value.projects.get.return_value = proj proj.mergerequests.get.return_value = mr mr.diffs.list.return_value = [ make_mock_diff("file1.txt", "file1.txt", "diff content 1"), make_mock_diff("file2.py", "file2.py", "diff content 2"), ] result = server.get_mr_diff("project/path", 1, max_size_kb=100) assert "diff" in result assert "file1.txt" in result["diff"] assert result["temp_file_path"] is None assert result["size_kb"] < 100 @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") @patch("tempfile.NamedTemporaryFile") def test_get_mr_diff_large_diff_temp_file(mock_tempfile, mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() mr = MagicMock() mock_gitlab.return_value.projects.get.return_value = proj proj.mergerequests.get.return_value = mr # Create a large diff large_diff = "x" * (101 * 1024) mock_diff = make_mock_diff("bigfile.txt", "bigfile.txt", large_diff) mr.diffs.list.return_value = [mock_diff] mock_file = MagicMock() mock_file.name = "/tmp/mr_diff_12345.diff" mock_tempfile.return_value.__enter__.return_value = mock_file result = server.get_mr_diff("project/path", 1, max_size_kb=100) assert result["diff_too_large"] is True assert result["temp_file_path"] == "/tmp/mr_diff_12345.diff" assert result["size_kb"] > 100 assert "message" in result mock_file.write.assert_called_once() @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_get_mr_diff_filter_extensions(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() mr = MagicMock() mock_gitlab.return_value.projects.get.return_value = proj proj.mergerequests.get.return_value = mr # One file should be filtered out mr.diffs.list.return_value = [ make_mock_diff("file.lock", "file.lock", "should be filtered"), make_mock_diff("file.txt", "file.txt", "should be present"), ] result = server.get_mr_diff("project/path", 1, max_size_kb=100, filter_extensions=[".lock"]) assert "file.lock" not in result["diff"] assert "file.txt" in result["diff"] assert "should be present" in result["diff"] assert "should be filtered" not in result["diff"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_get_mr_diff_error_handling(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") mock_gitlab.return_value.projects.get.side_effect = Exception("Not found") result = server.get_mr_diff("project/path", 1) assert "error" in result assert "Not found" in result["error"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_run_ci_pipeline_success_with_branch(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() pipeline = MagicMock() pipeline.id = 123 pipeline.web_url = "https://gitlab.com/project/-/pipelines/123" proj.pipelines.create.return_value = pipeline mock_gitlab.return_value.projects.get.return_value = proj result = server.run_ci_pipeline( project="project/path", branch="main", variables={"FOO": "bar"}, web_mode=False, working_directory="/tmp" ) assert result["success"] is True assert result["pipeline_id"] == 123 assert result["pipeline_url"] == "https://gitlab.com/project/-/pipelines/123" assert result["branch"] == "main" assert result["web_mode"] is False proj.pipelines.create.assert_called_once() args = proj.pipelines.create.call_args[0][0] assert args["ref"] == "main" assert {"key": "FOO", "value": "bar"} in args["variables"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") @patch("git.Repo") def test_run_ci_pipeline_success_current_branch(mock_repo, mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() pipeline = MagicMock() pipeline.id = 456 pipeline.web_url = "https://gitlab.com/project/-/pipelines/456" proj.pipelines.create.return_value = pipeline proj.default_branch = "main" mock_gitlab.return_value.projects.get.return_value = proj # Simulate git branch detection using GitPython mock_branch = MagicMock() mock_branch.name = "feature-branch" mock_repo.return_value.active_branch = mock_branch result = server.run_ci_pipeline( project="project/path", branch=None, variables=None, web_mode=False, working_directory="/tmp" ) assert result["success"] is True assert result["pipeline_id"] == 456 assert result["pipeline_url"] == "https://gitlab.com/project/-/pipelines/456" assert result["branch"] == "feature-branch" proj.pipelines.create.assert_called_once() @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") @patch("git.Repo") def test_run_ci_pipeline_fallback_to_default_branch(mock_repo, mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() pipeline = MagicMock() pipeline.id = 789 pipeline.web_url = "https://gitlab.com/project/-/pipelines/789" proj.pipelines.create.return_value = pipeline proj.default_branch = "main" mock_gitlab.return_value.projects.get.return_value = proj # Simulate git branch detection failure by raising an exception mock_repo.return_value.active_branch = property(lambda self: (_ for _ in ()).throw(Exception("No branch"))) result = server.run_ci_pipeline( project="project/path", branch=None, variables=None, web_mode=False, working_directory="/tmp" ) assert result["success"] is True assert result["pipeline_id"] == 789 assert result["pipeline_url"] == "https://gitlab.com/project/-/pipelines/789" assert result["branch"] == "main" proj.pipelines.create.assert_called_once() @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_run_ci_pipeline_web_mode(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") proj = MagicMock() pipeline = MagicMock() pipeline.id = 321 pipeline.web_url = "https://gitlab.com/project/-/pipelines/321" proj.pipelines.create.return_value = pipeline mock_gitlab.return_value.projects.get.return_value = proj result = server.run_ci_pipeline( project="project/path", branch="main", variables={"FOO": "bar"}, web_mode=True, working_directory="/tmp" ) assert result["success"] is True assert result["web_mode"] is True args = proj.pipelines.create.call_args[0][0] # Should include CI_PIPELINE_SOURCE=web assert {"key": "CI_PIPELINE_SOURCE", "value": "web"} in args["variables"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_run_ci_pipeline_error_handling(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") mock_gitlab.return_value.projects.get.side_effect = Exception("Not found") result = server.run_ci_pipeline( project="project/path", branch="main", variables=None, web_mode=False, working_directory="/tmp" ) assert "error" in result assert "Not found" in result["error"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_find_group(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") group1 = MagicMock() group1.id = 1 group1.name = "Test Group" group1.full_path = "test-group" group1.web_url = "https://gitlab.com/groups/test-group" group1.description = "A test group" mock_gitlab.return_value.groups.list.return_value = [group1] result = server.find_group("test-group") assert isinstance(result, list) assert result[0]["name"] == "Test Group" assert result[0]["full_path"] == "test-group" @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_search_epics_success(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") group = MagicMock() epic = MagicMock() epic.id = 1 epic.iid = 101 epic.title = "Epic Title" epic.web_url = "https://gitlab.com/groups/test-group/-/epics/101" epic.state = "opened" epic.created_at = "2024-01-01T00:00:00Z" epic.updated_at = "2024-01-02T00:00:00Z" epic.author = {"id": 2, "name": "Author"} epic.labels = ["label1"] epic.description = "Epic description" group.epics.list.return_value = [epic] mock_gitlab.return_value.groups.get.return_value = group result = server.search_epics("test-group", state="opened") assert "epics" in result assert result["epics"][0]["title"] == "Epic Title" assert result["epics"][0]["state"] == "opened" assert result["epics"][0]["author"]["name"] == "Author" @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_search_epics_error(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") mock_gitlab.return_value.groups.get.side_effect = Exception("Group not found") result = server.search_epics("bad-group") assert "error" in result assert "Group not found" in result["error"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_create_epic_success(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") group = MagicMock() epic = MagicMock() epic.web_url = "https://gitlab.com/groups/test-group/-/epics/101" group.epics.create.return_value = epic mock_gitlab.return_value.groups.get.return_value = group result = server.create_epic("test-group", "Epic Title", "Epic description", labels=["label1"]) assert "url" in result assert result["url"] == "https://gitlab.com/groups/test-group/-/epics/101" group.epics.create.assert_called_once() args = group.epics.create.call_args[0][0] assert args["title"] == "Epic Title" assert args["description"] == "Epic description" assert args["labels"] == ["label1"] @patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token")) @patch("gitlab.Gitlab") def test_create_epic_error(mock_gitlab, mock_settings): server = GitLabPythonServer(working_directory="/tmp") mock_gitlab.return_value.groups.get.side_effect = Exception("Group not found") result = server.create_epic("bad-group", "Epic Title", "Epic description") assert "error" in result assert "Group not found" in result["error"]