#!/usr/bin/env python3 """Tests for the GitLab CLI MCP server implementation.""" import json from unittest.mock import MagicMock, patch from mcp_server_gitlab_glab.server import GitLabServer class TestGitLabServer: """Tests for the GitLabServer class.""" def test_init(self) -> None: """Test initialization of GitLabServer.""" server = GitLabServer() assert hasattr(server, "auth_message") assert "glab auth login" in server.auth_message @patch("subprocess.run") def test_execute_glab_command_success(self, mock_run: MagicMock) -> None: """Test successful execution of a glab command.""" # Mock successful command execution mock_process = MagicMock() mock_process.returncode = 0 mock_process.stdout = "command output" mock_process.stderr = "" mock_run.return_value = mock_process server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["--version"], working_dir) assert success is True assert result == "command output" mock_run.assert_called_once_with( ["glab", "--version"], capture_output=True, text=True, check=False, cwd=working_dir, ) @patch("subprocess.run") def test_execute_glab_command_failure(self, mock_run: MagicMock) -> None: """Test failed execution of a glab command.""" # Mock failed command execution mock_process = MagicMock() mock_process.returncode = 1 mock_process.stdout = "" mock_process.stderr = "command failed" mock_run.return_value = mock_process server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["--version"], working_dir) assert success is False assert result == {"error": "command failed"} mock_run.assert_called_once_with( ["glab", "--version"], capture_output=True, text=True, check=False, cwd=working_dir, ) @patch("subprocess.run") def test_execute_glab_command_auth_error(self, mock_run: MagicMock) -> None: """Test authentication error during glab command execution.""" # Mock authentication error mock_process = MagicMock() mock_process.returncode = 1 mock_process.stdout = "" mock_process.stderr = "authentication required" mock_run.return_value = mock_process server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["api", "/projects"], working_dir) assert success is False assert "error" in result assert "auth login" in result["error"] mock_run.assert_called_once_with( ["glab", "api", "/projects"], capture_output=True, text=True, check=False, cwd=working_dir, ) @patch("subprocess.run") def test_execute_glab_command_not_found(self, mock_run: MagicMock) -> None: """Test glab command not found error.""" # Mock FileNotFoundError mock_run.side_effect = FileNotFoundError("No such file or directory: 'glab'") server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["--version"], working_dir) assert success is False assert "error" in result assert "glab command not found" in result["error"] @patch("subprocess.run") def test_execute_glab_api_command_success(self, mock_run: MagicMock) -> None: """Test successful execution of a glab api command with JSON response.""" # Mock successful API command execution with JSON response mock_process = MagicMock() mock_process.returncode = 0 mock_process.stdout = json.dumps([{"id": 1, "name": "test-project"}]) mock_process.stderr = "" mock_run.return_value = mock_process server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["api", "/projects"], working_dir) assert success is True assert isinstance(result, list) assert len(result) == 1 assert result[0]["id"] == 1 assert result[0]["name"] == "test-project" mock_run.assert_called_once_with( ["glab", "api", "/projects"], capture_output=True, text=True, check=False, cwd=working_dir, ) @patch("subprocess.run") def test_execute_glab_api_command_invalid_json(self, mock_run: MagicMock) -> None: """Test glab api command with invalid JSON response.""" # Mock API command execution with invalid JSON response mock_process = MagicMock() mock_process.returncode = 0 mock_process.stdout = "invalid json" mock_process.stderr = "" mock_run.return_value = mock_process server = GitLabServer() working_dir = "/test/directory" success, result = server.execute_glab_command(["api", "/projects"], working_dir) assert success is False assert "error" in result assert "Failed to parse JSON response" in result["error"] @patch.object(GitLabServer, "execute_glab_command") def test_check_availability_success(self, mock_execute: MagicMock) -> None: """Test successful check_availability.""" # Mock successful command execution mock_execute.return_value = (True, "glab version 1.0.0") server = GitLabServer() working_dir = "/test/directory" result = server.check_availability(working_dir) assert result["available"] is True assert result["version"] == "glab version 1.0.0" mock_execute.assert_called_once_with(["--version"], working_dir) @patch.object(GitLabServer, "execute_glab_command") def test_check_availability_failure(self, mock_execute: MagicMock) -> None: """Test failed check_availability.""" # Mock failed command execution mock_execute.return_value = (False, {"error": "glab command not found"}) server = GitLabServer() working_dir = "/test/directory" result = server.check_availability(working_dir) assert result["available"] is False assert result["error"] == "glab command not found" mock_execute.assert_called_once_with(["--version"], working_dir) @patch.object(GitLabServer, "execute_glab_command") def test_find_project_success(self, mock_execute: MagicMock) -> None: """Test successful find_project with a single project.""" # Mock successful API response with a project mock_execute.return_value = ( True, [ { "id": 1, "name": "test-project", "path_with_namespace": "group/test-project", "web_url": "https://gitlab.com/group/test-project", "description": "A test project", } ], ) server = GitLabServer() working_dir = "/test/directory" result = server.find_project("test-project", working_dir) assert isinstance(result, list) assert len(result) == 1 assert result[0]["id"] == 1 assert result[0]["name"] == "test-project" assert result[0]["path_with_namespace"] == "group/test-project" assert result[0]["web_url"] == "https://gitlab.com/group/test-project" assert result[0]["description"] == "A test project" mock_execute.assert_called_once_with( ["api", "/projects?search_namespaces=true&search=test-project"], working_dir ) @patch.object(GitLabServer, "execute_glab_command") def test_find_project_multiple_results(self, mock_execute: MagicMock) -> None: """Test successful find_project with multiple projects.""" # Mock successful API response with multiple projects mock_execute.return_value = ( True, [ { "id": 1, "name": "test-project", "path_with_namespace": "group/test-project", "web_url": "https://gitlab.com/group/test-project", "description": "A test project", }, { "id": 2, "name": "test-project-2", "path_with_namespace": "group/test-project-2", "web_url": "https://gitlab.com/group/test-project-2", "description": "Another test project", }, { "id": 3, "name": "test-project-3", "path_with_namespace": "group/test-project-3", "web_url": "https://gitlab.com/group/test-project-3", "description": "Yet another test project", }, ], ) server = GitLabServer() working_dir = "/test/directory" result = server.find_project("test-project", working_dir) assert isinstance(result, list) assert len(result) == 3 # Check first project assert result[0]["id"] == 1 assert result[0]["name"] == "test-project" assert result[0]["path_with_namespace"] == "group/test-project" # Check second project assert result[1]["id"] == 2 assert result[1]["name"] == "test-project-2" assert result[1]["path_with_namespace"] == "group/test-project-2" # Check third project assert result[2]["id"] == 3 assert result[2]["name"] == "test-project-3" assert result[2]["path_with_namespace"] == "group/test-project-3" mock_execute.assert_called_once_with( ["api", "/projects?search_namespaces=true&search=test-project"], working_dir ) @patch.object(GitLabServer, "execute_glab_command") def test_find_project_not_found(self, mock_execute: MagicMock) -> None: """Test find_project with no results.""" # Mock API response with no projects mock_execute.return_value = (True, []) server = GitLabServer() working_dir = "/test/directory" result = server.find_project("nonexistent-project", working_dir) assert "error" in result assert "not found" in result["error"] mock_execute.assert_called_once_with( [ "api", "/projects?search_namespaces=true&search=nonexistent-project", ], working_dir ) @patch.object(GitLabServer, "execute_glab_command") def test_find_project_api_error(self, mock_execute: MagicMock) -> None: """Test find_project with API error.""" # Mock API error mock_execute.return_value = (False, {"error": "API error"}) server = GitLabServer() working_dir = "/test/directory" result = server.find_project("test-project", working_dir) assert "error" in result assert result["error"] == "API error" mock_execute.assert_called_once_with( ["api", "/projects?search_namespaces=true&search=test-project"], working_dir ) @patch.object(GitLabServer, "execute_glab_command") def test_search_issues_success(self, mock_execute: MagicMock) -> None: """Test successful issue search with default parameters.""" # Mock successful command execution with JSON output mock_execute.return_value = ( True, [ { "id": 1, "iid": 101, "title": "Test Issue 1", "web_url": "https://gitlab.com/group/project/issues/101", "state": "opened", "created_at": "2025-01-01T00:00:00Z", "updated_at": "2025-01-02T00:00:00Z", }, { "id": 2, "iid": 102, "title": "Test Issue 2", "web_url": "https://gitlab.com/group/project/issues/102", "state": "closed", "created_at": "2025-01-03T00:00:00Z", "updated_at": "2025-01-04T00:00:00Z", }, ], ) server = GitLabServer() working_dir = "/test/directory" result = server.search_issues(working_directory=working_dir) assert "issues" in result assert len(result["issues"]) == 2 assert result["issues"][0]["id"] == 1 assert result["issues"][0]["title"] == "Test Issue 1" assert result["issues"][1]["id"] == 2 assert result["issues"][1]["title"] == "Test Issue 2" # Verify command was called with correct arguments mock_execute.assert_called_once_with( ["issue", "list", "-O", "json"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_search_issues_with_filters(self, mock_execute: MagicMock) -> None: """Test issue search with various filters.""" # Mock successful command execution with JSON output mock_execute.return_value = ( True, [ { "id": 1, "iid": 101, "title": "Test Issue 1", "web_url": "https://gitlab.com/group/project/issues/101", "state": "opened", "created_at": "2025-01-01T00:00:00Z", "updated_at": "2025-01-02T00:00:00Z", }, ], ) server = GitLabServer() working_dir = "/test/directory" result = server.search_issues( working_directory=working_dir, author="user1", assignee="user2", closed=True, confidential=True, group="test-group", issue_type="incident", iteration=123, label=["bug", "critical"], milestone="v1.0", not_assignee="user3", not_author="user4", not_label=["wontfix"], page=2, per_page=10, project="group/project", ) assert "issues" in result assert len(result["issues"]) == 1 assert result["issues"][0]["id"] == 1 assert result["issues"][0]["title"] == "Test Issue 1" # Verify command was called with correct arguments mock_execute.assert_called_once_with( [ "issue", "list", "-O", "json", "--author", "user1", "-a", "user2", "-c", "-C", "-g", "test-group", "-t", "incident", "-i", "123", "-l", "bug", "-l", "critical", "-m", "v1.0", "--not-assignee", "user3", "--not-author", "user4", "--not-label", "wontfix", "-p", "2", "-P", "10", "-R", "group/project", ], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_search_issues_failure(self, mock_execute: MagicMock) -> None: """Test failed issue search.""" # Mock failed command execution mock_execute.return_value = (False, {"error": "Failed to list issues"}) server = GitLabServer() working_dir = "/test/directory" result = server.search_issues(working_directory=working_dir) assert "error" in result assert result["error"] == "Failed to list issues" mock_execute.assert_called_once_with( ["issue", "list", "-O", "json"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_search_issues_invalid_json(self, mock_execute: MagicMock) -> None: """Test issue search with invalid JSON response.""" # Mock successful command execution but with invalid JSON mock_execute.return_value = (True, "invalid json") server = GitLabServer() working_dir = "/test/directory" result = server.search_issues(working_directory=working_dir) assert "error" in result assert result["error"] == "Failed to parse issues list" mock_execute.assert_called_once_with( ["issue", "list", "-O", "json"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_create_issue_success(self, mock_execute: MagicMock) -> None: """Test successful issue creation with required parameters.""" # Mock successful command execution with actual glab output format mock_execute.return_value = (True, """- Creating issue in group/project #1 Test Issue (less than a minute ago) https://gitlab.com/group/project/issues/1""") server = GitLabServer() working_dir = "/test/directory" result = server.create_issue( title="Test Issue", description="Test Description", working_directory=working_dir, ) assert "url" in result assert result["url"] == "https://gitlab.com/group/project/issues/1" mock_execute.assert_called_once_with( ["issue", "create", "-y", "-t", "Test Issue", "-d", "Test Description"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_create_issue_with_all_params(self, mock_execute: MagicMock) -> None: """Test issue creation with all optional parameters.""" # Mock successful command execution with actual glab output format mock_execute.return_value = (True, """- Creating issue in group/project #2 Test Issue (less than a minute ago) https://gitlab.com/group/project/issues/2""") server = GitLabServer() working_dir = "/test/directory" result = server.create_issue( title="Test Issue", description="Test Description", working_directory=working_dir, labels=["bug", "critical"], assignee=["user1", "user2"], milestone="v1.0", epic_id=123, project="group/project", ) assert "url" in result assert result["url"] == "https://gitlab.com/group/project/issues/2" mock_execute.assert_called_once_with( [ "issue", "create", "-y", "-t", "Test Issue", "-d", "Test Description", "-l", "bug,critical", "-a", "user1", "-a", "user2", "-m", "v1.0", "--epic", "123", "-R", "group/project", ], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_create_issue_failure(self, mock_execute: MagicMock) -> None: """Test failed issue creation.""" # Mock failed command execution mock_execute.return_value = (False, {"error": "Failed to create issue"}) server = GitLabServer() working_dir = "/test/directory" result = server.create_issue( title="Test Issue", description="Test Description", working_directory=working_dir, ) assert "error" in result assert result["error"] == "Failed to create issue" mock_execute.assert_called_once_with( ["issue", "create", "-y", "-t", "Test Issue", "-d", "Test Description"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_create_issue_invalid_output(self, mock_execute: MagicMock) -> None: """Test issue creation with invalid output format.""" # Mock successful command execution but without a URL in the output mock_execute.return_value = (True, """- Creating issue in group/project #3 Test Issue (less than a minute ago) Invalid URL format""") server = GitLabServer() working_dir = "/test/directory" result = server.create_issue( title="Test Issue", description="Test Description", working_directory=working_dir, ) assert "error" in result assert result["error"] == "Failed to extract issue URL from command output" mock_execute.assert_called_once_with( ["issue", "create", "-y", "-t", "Test Issue", "-d", "Test Description"], working_dir, ) @patch("subprocess.run") def test_working_directory_is_used(self, mock_run: MagicMock) -> None: """Test that the working directory is correctly passed to subprocess.run.""" # Mock successful command execution mock_process = MagicMock() mock_process.returncode = 0 mock_process.stdout = "command output" mock_process.stderr = "" mock_run.return_value = mock_process server = GitLabServer() # Test with different working directories working_dirs = [ "/home/user/project", "/tmp/gitlab", "/var/www/html", ] for working_dir in working_dirs: server.execute_glab_command(["status"], working_dir) mock_run.assert_called_with( ["glab", "status"], capture_output=True, text=True, check=False, cwd=working_dir, ) # Verify the number of calls assert mock_run.call_count == len(working_dirs) @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_success_small(self, mock_execute: MagicMock) -> None: """Test successful MR diff retrieval with small diff.""" # Mock successful command execution with small diff diff_content = """diff --git a/file.txt b/file.txt index 1234567..abcdefg 100644 --- a/file.txt +++ b/file.txt @@ -1,3 +1,4 @@ line 1 line 2 +new line line 3""" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, mr_id="123", color="never", raw=False, repo="group/project", ) assert "diff" in result assert result["diff"] == diff_content assert result["size_kb"] < 1 assert result["temp_file_path"] is None # Verify command was called with correct arguments mock_execute.assert_called_once_with( ["mr", "diff", "123", "--color", "never", "-R", "group/project"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_success_current_branch(self, mock_execute: MagicMock) -> None: """Test successful MR diff retrieval for current branch.""" diff_content = "diff content" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff(working_directory=working_dir) assert "diff" in result assert result["diff"] == diff_content assert result["temp_file_path"] is None # Verify command was called without MR ID mock_execute.assert_called_once_with( ["mr", "diff", "--color", "never"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_with_raw_option(self, mock_execute: MagicMock) -> None: """Test MR diff retrieval with raw option.""" diff_content = "raw diff content" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, mr_id="branch-name", color="auto", raw=True, ) assert "diff" in result assert result["diff"] == diff_content # Verify command was called with raw option mock_execute.assert_called_once_with( ["mr", "diff", "branch-name", "--color", "auto", "--raw"], working_dir, ) @patch("tempfile.NamedTemporaryFile") @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_large_diff_temp_file( self, mock_execute: MagicMock, mock_temp_file: MagicMock ) -> None: """Test MR diff retrieval with large diff that gets saved to temp file.""" # Create a large diff content (over 100KB) large_diff = "x" * (101 * 1024) # 101 KB mock_execute.return_value = (True, large_diff) # Mock temporary file mock_file = MagicMock() mock_file.name = "/tmp/mr_diff_12345.diff" mock_temp_file.return_value.__enter__.return_value = mock_file server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, mr_id="123", max_size_kb=100, ) assert result["diff_too_large"] is True assert result["size_kb"] > 100 assert result["max_size_kb"] == 100 assert result["temp_file_path"] == "/tmp/mr_diff_12345.diff" assert "message" in result # Verify temp file was created with correct parameters mock_temp_file.assert_called_once_with( mode='w', suffix='.diff', prefix='mr_diff_', delete=False, encoding='utf-8' ) mock_file.write.assert_called_once_with(large_diff) @patch("tempfile.NamedTemporaryFile") @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_large_diff_temp_file_error( self, mock_execute: MagicMock, mock_temp_file: MagicMock ) -> None: """Test MR diff retrieval with large diff and temp file creation error.""" # Create a large diff content large_diff = "x" * (101 * 1024) # 101 KB mock_execute.return_value = (True, large_diff) # Mock temporary file creation error mock_temp_file.side_effect = Exception("Permission denied") server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, mr_id="123", max_size_kb=100, ) assert "error" in result assert "too large" in result["error"] assert "Permission denied" in result["error"] @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_command_failure(self, mock_execute: MagicMock) -> None: """Test MR diff retrieval with command failure.""" mock_execute.return_value = (False, {"error": "MR not found"}) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff(working_directory=working_dir, mr_id="999") assert "error" in result assert result["error"] == "MR not found" @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_invalid_color_option(self, mock_execute: MagicMock) -> None: """Test MR diff with invalid color option skips color parameter.""" diff_content = "diff content" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, mr_id="123", color="invalid", # Invalid color option ) assert "diff" in result # Invalid color options should be filtered out, only valid ones are added mock_execute.assert_called_once_with( ["mr", "diff", "123"], working_dir, ) # Tests for run_ci_pipeline method @patch.object(GitLabServer, "execute_glab_command") @patch("subprocess.run") def test_run_ci_pipeline_success_with_branch( self, mock_subprocess: MagicMock, mock_execute: MagicMock ) -> None: """Test successful pipeline run with specified branch.""" # Mock successful glab command execution mock_execute.return_value = (True, "Pipeline created: https://gitlab.example.com/project/-/pipelines/123") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="main", ) assert result["success"] is True assert result["branch"] == "main" assert result["web_mode"] is False assert "pipeline_url" in result assert result["pipeline_url"] == "https://gitlab.example.com/project/-/pipelines/123" mock_execute.assert_called_once_with( ["ci", "run", "-b", "main"], working_dir, ) # subprocess.run should not be called when branch is specified mock_subprocess.assert_not_called() @patch.object(GitLabServer, "execute_glab_command") @patch("subprocess.run") def test_run_ci_pipeline_success_current_branch( self, mock_subprocess: MagicMock, mock_execute: MagicMock ) -> None: """Test successful pipeline run using current branch.""" # Mock git branch --show-current command mock_git_process = MagicMock() mock_git_process.returncode = 0 mock_git_process.stdout = "feature-branch" mock_subprocess.return_value = mock_git_process # Mock successful glab command execution mock_execute.return_value = (True, "Pipeline created: https://gitlab.example.com/project/-/pipelines/456") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, ) assert result["success"] is True assert result["branch"] == "feature-branch" assert result["web_mode"] is False assert "pipeline_url" in result assert result["pipeline_url"] == "https://gitlab.example.com/project/-/pipelines/456" mock_subprocess.assert_called_once_with( ["git", "branch", "--show-current"], capture_output=True, text=True, check=False, cwd=working_dir, ) mock_execute.assert_called_once_with( ["ci", "run", "-b", "feature-branch"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") @patch("subprocess.run") def test_run_ci_pipeline_git_branch_failure( self, mock_subprocess: MagicMock, mock_execute: MagicMock ) -> None: """Test pipeline run when git branch --show-current fails.""" # Mock git branch --show-current command failure mock_git_process = MagicMock() mock_git_process.returncode = 1 mock_git_process.stdout = "" mock_subprocess.return_value = mock_git_process # Mock successful glab command execution (without branch) mock_execute.return_value = (True, "Pipeline created: https://gitlab.example.com/project/-/pipelines/789") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, ) assert result["success"] is True assert result["branch"] is None assert result["web_mode"] is False mock_subprocess.assert_called_once_with( ["git", "branch", "--show-current"], capture_output=True, text=True, check=False, cwd=working_dir, ) # Should run without -b flag when branch detection fails mock_execute.assert_called_once_with( ["ci", "run"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_run_ci_pipeline_with_web_mode(self, mock_execute: MagicMock) -> None: """Test pipeline run with web mode enabled.""" # Mock successful glab command execution mock_execute.return_value = (True, "Pipeline created in web mode") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="main", web_mode=True, ) assert result["success"] is True assert result["branch"] == "main" assert result["web_mode"] is True mock_execute.assert_called_once_with( ["ci", "run", "-b", "main", "--variables-env", "CI_PIPELINE_SOURCE:web"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_run_ci_pipeline_with_variables(self, mock_execute: MagicMock) -> None: """Test pipeline run with various variable types.""" # Mock successful glab command execution mock_execute.return_value = (True, "Pipeline created with variables") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="develop", variables=["VAR1:value1", "VAR2:value2"], variables_env=["ENV1:envvalue1"], variables_file=["FILE1:file1.txt"], variables_from="/path/to/vars.json", repo="group/project", ) assert result["success"] is True assert result["branch"] == "develop" expected_args = [ "ci", "run", "-b", "develop", "--variables", "VAR1:value1", "--variables", "VAR2:value2", "--variables-env", "ENV1:envvalue1", "--variables-file", "FILE1:file1.txt", "-f", "/path/to/vars.json", "-R", "group/project" ] mock_execute.assert_called_once_with(expected_args, working_dir) @patch.object(GitLabServer, "execute_glab_command") def test_run_ci_pipeline_with_web_mode_and_variables( self, mock_execute: MagicMock ) -> None: """Test pipeline run with web mode and additional variables.""" # Mock successful glab command execution mock_execute.return_value = (True, "Pipeline created") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="main", variables_env=["CUSTOM_VAR:value"], web_mode=True, ) assert result["success"] is True assert result["web_mode"] is True expected_args = [ "ci", "run", "-b", "main", "--variables-env", "CUSTOM_VAR:value", "--variables-env", "CI_PIPELINE_SOURCE:web" ] mock_execute.assert_called_once_with(expected_args, working_dir) @patch.object(GitLabServer, "execute_glab_command") def test_run_ci_pipeline_glab_failure(self, mock_execute: MagicMock) -> None: """Test pipeline run when glab command fails.""" # Mock glab command failure mock_execute.return_value = (False, {"error": "Authentication required"}) server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="main", ) assert result == {"error": "Authentication required"} mock_execute.assert_called_once_with( ["ci", "run", "-b", "main"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_run_ci_pipeline_no_url_in_output(self, mock_execute: MagicMock) -> None: """Test pipeline run when output doesn't contain pipeline URL.""" # Mock successful glab command execution without URL mock_execute.return_value = ( True, "Pipeline created successfully\nCheck status later" ) server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, branch="main", ) assert result["success"] is True assert result["branch"] == "main" assert "pipeline_url" not in result assert "Pipeline created successfully" in result["output"] @patch.object(GitLabServer, "execute_glab_command") @patch("subprocess.run") def test_run_ci_pipeline_git_exception( self, mock_subprocess: MagicMock, mock_execute: MagicMock ) -> None: """Test pipeline run when git command raises an exception.""" # Mock git branch --show-current command exception mock_subprocess.side_effect = Exception("Git command failed") # Mock successful glab command execution (without branch) mock_execute.return_value = (True, "Pipeline created") server = GitLabServer() working_dir = "/test/directory" result = server.run_ci_pipeline( working_directory=working_dir, ) assert result["success"] is True assert result["branch"] is None # Should run without -b flag when git command fails mock_execute.assert_called_once_with( ["ci", "run"], working_dir, ) @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_with_default_filtering(self, mock_execute: MagicMock) -> None: """Test MR diff with default filtering (.lock and .log files).""" # Mock diff content with .lock and .log files diff_content = """diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,4 @@ { "name": "test", + "version": "1.0.0", "lockfileVersion": 1 } diff --git a/debug.log b/debug.log index 1234567..abcdefg 100644 --- a/debug.log +++ b/debug.log @@ -1,2 +1,3 @@ [INFO] Starting application +[INFO] Loading configuration [INFO] Application ready diff --git a/src/main.py b/src/main.py index 1234567..abcdefg 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,4 @@ def main(): print("Hello") + print("World") return 0""" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff(working_directory=working_dir) # Check that .lock and .log files are filtered out filtered_diff = result["diff"] assert "package-lock.json" not in filtered_diff assert "debug.log" not in filtered_diff assert "src/main.py" in filtered_diff assert 'print("World")' in filtered_diff @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_with_custom_filtering(self, mock_execute: MagicMock) -> None: """Test MR diff with custom filtering extensions.""" # Mock diff content with various file types diff_content = """diff --git a/test.txt b/test.txt index 1234567..abcdefg 100644 --- a/test.txt +++ b/test.txt @@ -1,2 +1,3 @@ line 1 +new line line 2 diff --git a/config.json b/config.json index 1234567..abcdefg 100644 --- a/config.json +++ b/config.json @@ -1,3 +1,4 @@ { "setting": "value" + "new_setting": "new_value" } diff --git a/temp.tmp b/temp.tmp index 1234567..abcdefg 100644 --- a/temp.tmp +++ b/temp.tmp @@ -1,2 +1,3 @@ temp data +more temp data end""" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, filter_extensions=[".tmp", ".json"] ) # Check that .tmp and .json files are filtered out filtered_diff = result["diff"] assert "temp.tmp" not in filtered_diff assert "config.json" not in filtered_diff assert "test.txt" in filtered_diff assert "new line" in filtered_diff @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_with_no_filtering(self, mock_execute: MagicMock) -> None: """Test MR diff with filtering disabled (empty list).""" # Mock diff content with .lock file diff_content = """diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,4 @@ { "name": "test", + "version": "1.0.0", "lockfileVersion": 1 }""" mock_execute.return_value = (True, diff_content) server = GitLabServer() working_dir = "/test/directory" result = server.get_mr_diff( working_directory=working_dir, filter_extensions=[] ) # Check that no filtering occurred filtered_diff = result["diff"] assert "package-lock.json" in filtered_diff assert '"version": "1.0.0"' in filtered_diff def test_filter_diff_content_basic(self) -> None: """Test basic diff content filtering.""" server = GitLabServer() diff_content = """diff --git a/src/main.py b/src/main.py index 1234567..abcdefg 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,4 @@ def main(): print("Hello") + print("World") return 0 diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,4 @@ { "name": "test", + "version": "1.0.0", "lockfileVersion": 1 }""" filtered = server.filter_diff_content(diff_content, [".lock"]) assert "src/main.py" in filtered assert 'print("World")' in filtered assert "package-lock.json" not in filtered assert '"version": "1.0.0"' not in filtered def test_filter_diff_content_multiple_extensions(self) -> None: """Test filtering with multiple extensions.""" server = GitLabServer() diff_content = """diff --git a/app.py b/app.py index 1234567..abcdefg 100644 --- a/app.py +++ b/app.py @@ -1,2 +1,3 @@ import logging +logging.basicConfig() app = Flask(__name__) diff --git a/debug.log b/debug.log index 1234567..abcdefg 100644 --- a/debug.log +++ b/debug.log @@ -1,2 +1,3 @@ [INFO] Starting +[DEBUG] Debug message [INFO] Ready diff --git a/yarn.lock b/yarn.lock index 1234567..abcdefg 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,3 +1,4 @@ # THIS IS AN AUTOGENERATED FILE +# Dependencies package@1.0.0: version "1.0.0""" filtered = server.filter_diff_content(diff_content, [".log", ".lock"]) assert "app.py" in filtered assert "logging.basicConfig()" in filtered assert "debug.log" not in filtered assert "yarn.lock" not in filtered assert "[DEBUG] Debug message" not in filtered assert "# Dependencies" not in filtered def test_filter_diff_content_empty_extensions(self) -> None: """Test filtering with empty extensions list.""" server = GitLabServer() diff_content = """diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2 +1,3 @@ { + "name": "test", "lockfileVersion": 1 }""" filtered = server.filter_diff_content(diff_content, []) # Should return original content when no extensions to filter assert filtered == diff_content def test_filter_diff_content_malformed_diff_header(self) -> None: """Test filtering with malformed diff headers.""" server = GitLabServer() diff_content = """diff --git a/file.py index 1234567..abcdefg 100644 --- a/file.py +++ b/file.py @@ -1,2 +1,3 @@ line 1 +new line line 2 diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2 +1,3 @@ { + "version": "1.0.0", "lockfileVersion": 1 }""" filtered = server.filter_diff_content(diff_content, [".lock"]) # Malformed header should not be filtered assert "diff --git a/file.py" in filtered assert "new line" in filtered # Properly formed header with .lock extension should be filtered assert "package-lock.json" not in filtered assert '"version": "1.0.0"' not in filtered @patch.object(GitLabServer, "execute_glab_command") def test_get_mr_diff_filtering_with_large_file(self, mock_execute: MagicMock) -> None: """Test that filtering works correctly even when creating temp files.""" # Create a large diff that will still be large after filtering large_diff_base = """diff --git a/src/main.py b/src/main.py index 1234567..abcdefg 100644 --- a/src/main.py +++ b/src/main.py @@ -1,3 +1,4 @@ def main(): print("Hello") + print("World") return 0 """ # Add a lot of content to main.py to make it large large_diff = large_diff_base + "+ # " + "Large comment line\n" * 3000 # Add package-lock.json content that should be filtered out large_diff += """diff --git a/package-lock.json b/package-lock.json index 1234567..abcdefg 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,500 +1,501 @@ { "name": "test", + "version": "1.0.0", """ + " \"dependency\": \"^1.0.0\",\n" * 1000 + "}\n" mock_execute.return_value = (True, large_diff) server = GitLabServer() working_dir = "/test/directory" with patch("tempfile.NamedTemporaryFile") as mock_temp: mock_file = MagicMock() mock_file.name = "/tmp/test_diff.diff" mock_temp.return_value.__enter__.return_value = mock_file result = server.get_mr_diff( working_directory=working_dir, max_size_kb=1 # Force temp file creation ) # Should still filter before saving to temp file assert result["diff_too_large"] is True # The filtered content should be written to temp file written_content = mock_file.write.call_args[0][0] assert "src/main.py" in written_content assert "package-lock.json" not in written_content