cd423c65c7
* Fix GitLab Merged MRs Not Displaying Fixes https://github.com/AndyMik90/Auto-Claude/issues/1521 - Added UseGitLabMRsOptions interface with stateFilter parameter - Updated hook signature to accept optional options parameter - Removed hardcoded useState for stateFilter - Defaults to 'opened' for backward compatibility - Pass stateFilter state to useGitLabMRs hook via options parameter - Enables proper filtering of MRs by state (opened/merged/closed/all) - Completes frontend implementation for GitLab MR state filtering Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> * GitLab Support for Create PR Button (#2) Title: feat: add GitLab support for Create PR button (#2) Fixes: https://github.com/AndyMik90/Auto-Claude/issues/1519 Body: Add automatic git remote detection to route GitLab repositories to the `glab` CLI for creating merge requests, while preserving existing GitHub functionality. ## Changes - Add `git_provider.py` for detecting GitHub vs GitLab from remote URLs - Supports SSH and HTTPS formats - Supports self-hosted GitLab instances (detects "gitlab" in hostname) - Add `glab_executable.py` for finding GitLab CLI with platform-specific fallbacks - Update `WorktreeManager.push_and_create_pr()` to detect provider and route to either `create_pull_request()` (GitHub) or `create_merge_request()` (GitLab) - Add `create_merge_request()` method for GitLab MR creation via glab CLI - Update error messages to include provider-specific installation instructions ## Testing - Unit tests for `git_provider.py` detection logic - Integration tests for WorktreeManager PR/MR creation - Manual E2E tests for GitLab remote repositories - Regression tests to ensure GitHub PR creation still works Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add GitLab CLI (glab) Path Configuration to Settings (#3) feat(frontend): add GitLab CLI (glab) path configuration to Settings Add support for configuring the GitLab CLI (glab) path in the Settings page, consistent with existing GitHub CLI (gh) path configuration. Changes: - Add 'glab' to CLITool type union and gitlabCLIPath to ToolConfig - Implement detectGitLabCLI() and validateGitLabCLI() with multi-level detection (user config, Homebrew, system PATH, Windows Program Files) - Implement async variants for non-blocking detection - Add gitlabCLIPath to AppSettings interface and DEFAULT_APP_SETTINGS - Add glab to getCliToolsInfo IPC handler return type - Add gitlabCLIPath to pathFields array and configureTools calls - Add English and French translation keys for GitLab CLI path - Add GitLab CLI path input field to GeneralSettings component - Fix glab version regex to match actual output format ("glab X.Y.Z" instead of "glab version X.Y.Z") - Add augmented env to all sync CLI validators (Python, Git, gh, glab) for consistency with validateClaude and async validators Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * Fix GitLab bugs from CodeRabbitAI review comments Address actionable comments reported by CodeRabbitAI: - Fix SSH URL parsing in git_provider.py to support ssh:// URLs and arbitrary usernames (not just git@) - Fix Windows glab paths to use correct installation directory (glab\glab.exe instead of GitLab CLI\glab.exe) - Fix regex for GitLab MR URLs to correctly match both /merge_requests/ and /-/merge_requests/ patterns - Move inline json import to top-level in worktree.py - Fix incorrect mock paths in test_worktree_gitlab.py - Remove unused imports and f-strings without placeholders - Add WINDOWS_GLAB_PATHS constant to frontend for centralized path management - Replace fragile monkeypatch with unittest.mock.patch in manual tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Consolidate GitLab test files and move tests to tests/ directory - Remove duplicate test_gitlab_pr_manual.py, consolidate into test_gitlab_e2e.py - Expand provider detection to test 8 URL patterns (GitHub/GitLab variants) - Add WorktreeManager method signature verification test - Improve error message test to use unittest.mock.patch - Move all GitLab test files from apps/backend/core/ to tests/ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Convert GitLab E2E tests to pytest-style assertions Rename test functions to _check_* helpers and create proper test_* pytest functions with assertions. This fixes PytestReturnNotNoneWarning warnings and ensures tests actually fail when checks return False. Fixes: https://github.com/AndyMik90/Auto-Claude/pull/1544#discussion_r2729260291 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix GitHub Enterprise detection in git_provider Broaden the hostname check in _classify_hostname to also detect GitHub Enterprise hostnames (e.g., github.company.com) by checking for "github" substring, matching the pattern already used for GitLab. Addresses CodeRabbitAI review comment on PR #1544. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix CI failures: Ruff formatting and test isolation issues - Split long regex line in worktree.py for Ruff compliance - Fix test isolation issue caused by worktree.py importlib shim - Convert patch() calls to patch.object() pattern in test files - Add fixtures to test_github_pr_regression.py for consistency The importlib shim in apps/backend/worktree.py causes module-level patches to fail when tests run after test_agent_flow.py. Using patch.object() on the imported module directly resolves this. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Skip glab detection test when glab CLI is not installed Address CodeRabbit recommendation: add pytest import and guard test_glab_detection with get_glab_executable() check, using pytest.skip() when glab is not available on the system. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Disable GPG signing in test git repos to prevent CI hangs Tests may hang if the runner has global GPG signing enabled. Explicitly disable commit.gpgsign in create_test_git_repo. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix glab CLI path not passed to backend subprocess The frontend detected glab but never set GITLAB_CLI_PATH env var. Added 'glab' to CliTool type and CLI_TOOL_ENV_MAP, and call detectAndSetCliPath('glab') in setupProcessEnvironment. This ensures GitLab MR creation works when the app is launched from Finder/Dock and glab is in a non-standard PATH location. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix glab CLI flags and JSON field name in _get_existing_mr_url glab uses --output json (not --json fieldName like gh CLI) and returns snake_case field names (web_url instead of webUrl). Verified with actual glab mr view command on lcoffice repo. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Extract shared test fixtures to conftest.py Move temp_project_dir and worktree_manager fixtures from test_gitlab_worktree.py and test_github_pr_regression.py to a shared conftest.py file. Also adds GPG signing disable to the shared fixture for CI. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Use throwaway variable for unused WorktreeManager instance Replace `manager` with `_` to indicate the variable is intentionally unused - the test only verifies the constructor doesn't raise. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove unused imports in test_gitlab_worktree.py Remove pytest and WorktreeManager imports that are not used in the file. Fixtures are provided by conftest.py which handles the imports. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Address PR review findings: cleanup and improve hostname matching - Remove unused MergeRequestResult TypedDict (dead code) - Rename GH_CLI_TIMEOUT/GH_QUERY_TIMEOUT to provider-neutral CLI_TIMEOUT/CLI_QUERY_TIMEOUT - Improve hostname classification to use precise domain segment matching (rejects edge cases like attacker-github.com while still matching github-enterprise.local) - Add test coverage for GitHub/GitLab hostname detection edge cases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "Extract shared test fixtures to conftest.py" This reverts commit 22ab6662ee2710b86651dd630ee613e2d73ce395. * Extract shared test fixtures to conftest.py - Move temp_project_dir and worktree_manager fixtures to conftest.py (shared across GitLab and GitHub test suites) - Remove duplicate fixtures from test_github_pr_regression.py and test_gitlab_worktree.py - Remove unused subprocess import from test_gitlab_worktree.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Use consistent shim imports in GitLab/GitHub tests Switch to shim imports (worktree instead of core.worktree) to match other test files and avoid module aliasing issues caused by Python's module caching when tests use different import paths for the same module. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Isolate git environment in temp_project_dir fixture Pass sanitized environment to subprocess.run calls to prevent git operations from leaking into parent repos when tests run inside git worktrees (e.g., during pre-commit hooks). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix CodeQL findings in test files - Remove URL substring check that triggered py/incomplete-url-substring-sanitization (redundant - error message check is sufficient) - Remove unused pytest import in test_gitlab_worktree.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use pushed remote for provider detection in multi-remote repos detect_git_provider() now accepts an optional remote_name parameter so push_and_create_pr() can pass the actual pushed remote instead of always checking 'origin'. This fixes incorrect PR/MR creation when repos have multiple remotes pointing to different providers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: StillKnotKnown <192589389+StillKnotKnown@users.noreply.github.com>
602 lines
21 KiB
Python
602 lines
21 KiB
Python
"""
|
|
Integration Tests for WorktreeManager GitLab/GitHub PR/MR Creation
|
|
==================================================================
|
|
|
|
Tests the WorktreeManager class methods for creating pull requests (GitHub)
|
|
and merge requests (GitLab), including provider detection and CLI routing.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# Add apps/backend directory to path for imports
|
|
_backend_dir = Path(__file__).parent.parent / "apps" / "backend"
|
|
if str(_backend_dir) not in sys.path:
|
|
sys.path.insert(0, str(_backend_dir))
|
|
|
|
from worktree import (
|
|
PullRequestResult,
|
|
WorktreeInfo,
|
|
)
|
|
|
|
|
|
class TestCreateMergeRequest:
|
|
"""Test create_merge_request method for GitLab MR creation."""
|
|
|
|
def test_successful_mr_creation(self, worktree_manager, temp_project_dir):
|
|
"""Test successful MR creation with glab CLI."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
# Mock get_worktree_info to return a valid WorktreeInfo
|
|
mock_worktree_info = WorktreeInfo(
|
|
path=temp_project_dir / ".auto-claude" / "worktrees" / "tasks" / spec_name,
|
|
branch=f"auto-claude/{spec_name}",
|
|
spec_name=spec_name,
|
|
base_branch="main",
|
|
is_active=True,
|
|
)
|
|
|
|
# Mock subprocess for glab CLI
|
|
mock_subprocess_result = MagicMock(
|
|
returncode=0,
|
|
stdout="https://gitlab.com/user/repo/-/merge_requests/42\n",
|
|
stderr="",
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "get_worktree_info", return_value=mock_worktree_info
|
|
),
|
|
patch.object(
|
|
worktree_module,
|
|
"get_glab_executable",
|
|
return_value="/usr/local/bin/glab",
|
|
),
|
|
patch.object(
|
|
worktree_module.subprocess, "run", return_value=mock_subprocess_result
|
|
),
|
|
patch.object(
|
|
worktree_manager, "_extract_spec_summary", return_value="Test MR body"
|
|
),
|
|
):
|
|
result = worktree_manager.create_merge_request(
|
|
spec_name=spec_name,
|
|
target_branch="main",
|
|
title="Test MR",
|
|
draft=False,
|
|
)
|
|
|
|
# Verify result
|
|
assert result["success"] is True
|
|
assert result["pr_url"] == "https://gitlab.com/user/repo/-/merge_requests/42"
|
|
assert result.get("already_exists") is False
|
|
assert "error" not in result or result["error"] is None
|
|
|
|
def test_mr_already_exists(self, worktree_manager, temp_project_dir):
|
|
"""Test MR already exists scenario."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_worktree_info = WorktreeInfo(
|
|
path=temp_project_dir / ".auto-claude" / "worktrees" / "tasks" / spec_name,
|
|
branch=f"auto-claude/{spec_name}",
|
|
spec_name=spec_name,
|
|
base_branch="main",
|
|
is_active=True,
|
|
)
|
|
|
|
# Mock glab CLI returning "already exists" error
|
|
mock_subprocess_result = MagicMock(
|
|
returncode=1,
|
|
stdout="",
|
|
stderr="Error: merge request already exists\n",
|
|
)
|
|
|
|
# Mock _get_existing_mr_url to return existing URL
|
|
existing_url = "https://gitlab.com/user/repo/-/merge_requests/42"
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "get_worktree_info", return_value=mock_worktree_info
|
|
),
|
|
patch.object(
|
|
worktree_module,
|
|
"get_glab_executable",
|
|
return_value="/usr/local/bin/glab",
|
|
),
|
|
patch.object(
|
|
worktree_module.subprocess, "run", return_value=mock_subprocess_result
|
|
),
|
|
patch.object(
|
|
worktree_manager, "_extract_spec_summary", return_value="Test MR body"
|
|
),
|
|
patch.object(
|
|
worktree_manager, "_get_existing_mr_url", return_value=existing_url
|
|
),
|
|
):
|
|
result = worktree_manager.create_merge_request(
|
|
spec_name=spec_name,
|
|
target_branch="main",
|
|
)
|
|
|
|
# Verify result
|
|
assert result["success"] is True
|
|
assert result["pr_url"] == existing_url
|
|
assert result["already_exists"] is True
|
|
assert "error" not in result or result["error"] is None
|
|
|
|
def test_missing_glab_cli(self, worktree_manager, temp_project_dir):
|
|
"""Test error when glab CLI is not installed."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_worktree_info = WorktreeInfo(
|
|
path=temp_project_dir / ".auto-claude" / "worktrees" / "tasks" / spec_name,
|
|
branch=f"auto-claude/{spec_name}",
|
|
spec_name=spec_name,
|
|
base_branch="main",
|
|
is_active=True,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "get_worktree_info", return_value=mock_worktree_info
|
|
),
|
|
patch.object(worktree_module, "get_glab_executable", return_value=None),
|
|
):
|
|
result = worktree_manager.create_merge_request(spec_name=spec_name)
|
|
|
|
# Verify error
|
|
assert result["success"] is False
|
|
assert "GitLab CLI (glab) not found" in result["error"]
|
|
assert "https://gitlab.com/gitlab-org/cli" in result["error"]
|
|
|
|
def test_no_worktree_found(self, worktree_manager):
|
|
"""Test error when worktree doesn't exist."""
|
|
spec_name = "nonexistent-spec"
|
|
|
|
with patch.object(worktree_manager, "get_worktree_info", return_value=None):
|
|
result = worktree_manager.create_merge_request(spec_name=spec_name)
|
|
|
|
# Verify error
|
|
assert result["success"] is False
|
|
assert f"No worktree found for spec: {spec_name}" in result["error"]
|
|
|
|
def test_mr_with_draft_flag(self, worktree_manager, temp_project_dir):
|
|
"""Test MR creation with draft flag."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_worktree_info = WorktreeInfo(
|
|
path=temp_project_dir / ".auto-claude" / "worktrees" / "tasks" / spec_name,
|
|
branch=f"auto-claude/{spec_name}",
|
|
spec_name=spec_name,
|
|
base_branch="main",
|
|
is_active=True,
|
|
)
|
|
|
|
mock_subprocess_result = MagicMock(
|
|
returncode=0,
|
|
stdout="https://gitlab.com/user/repo/-/merge_requests/43\n",
|
|
stderr="",
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "get_worktree_info", return_value=mock_worktree_info
|
|
),
|
|
patch.object(
|
|
worktree_module,
|
|
"get_glab_executable",
|
|
return_value="/usr/local/bin/glab",
|
|
),
|
|
patch.object(
|
|
worktree_module.subprocess, "run", return_value=mock_subprocess_result
|
|
) as mock_run,
|
|
patch.object(
|
|
worktree_manager, "_extract_spec_summary", return_value="Test MR body"
|
|
),
|
|
):
|
|
result = worktree_manager.create_merge_request(
|
|
spec_name=spec_name,
|
|
draft=True,
|
|
)
|
|
|
|
# Verify draft flag was passed to glab
|
|
call_args = mock_run.call_args[0][0]
|
|
assert "--draft" in call_args
|
|
assert result["success"] is True
|
|
|
|
def test_network_error_retry(self, worktree_manager, temp_project_dir):
|
|
"""Test retry logic for network errors."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_worktree_info = WorktreeInfo(
|
|
path=temp_project_dir / ".auto-claude" / "worktrees" / "tasks" / spec_name,
|
|
branch=f"auto-claude/{spec_name}",
|
|
spec_name=spec_name,
|
|
base_branch="main",
|
|
is_active=True,
|
|
)
|
|
|
|
# First call fails with network error, second succeeds
|
|
mock_failure = MagicMock(
|
|
returncode=1,
|
|
stdout="",
|
|
stderr="Error: connection timeout\n",
|
|
)
|
|
mock_success = MagicMock(
|
|
returncode=0,
|
|
stdout="https://gitlab.com/user/repo/-/merge_requests/44\n",
|
|
stderr="",
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "get_worktree_info", return_value=mock_worktree_info
|
|
),
|
|
patch.object(
|
|
worktree_module,
|
|
"get_glab_executable",
|
|
return_value="/usr/local/bin/glab",
|
|
),
|
|
patch.object(
|
|
worktree_module.subprocess,
|
|
"run",
|
|
side_effect=[mock_failure, mock_success],
|
|
),
|
|
patch.object(
|
|
worktree_manager, "_extract_spec_summary", return_value="Test MR body"
|
|
),
|
|
patch.object(worktree_module.time, "sleep"), # Skip sleep in tests
|
|
):
|
|
result = worktree_manager.create_merge_request(spec_name=spec_name)
|
|
|
|
# Verify retry succeeded
|
|
assert result["success"] is True
|
|
assert result["pr_url"] == "https://gitlab.com/user/repo/-/merge_requests/44"
|
|
|
|
|
|
class TestPushAndCreatePR:
|
|
"""Test push_and_create_pr method with provider detection."""
|
|
|
|
def test_gitlab_routing(self, worktree_manager, temp_project_dir):
|
|
"""Test routing to create_merge_request for GitLab repos."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
# Mock push_branch to succeed
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
# Mock MR creation result
|
|
mock_mr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://gitlab.com/user/repo/-/merge_requests/42",
|
|
already_exists=False,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(worktree_module, "detect_git_provider", return_value="gitlab"),
|
|
patch.object(
|
|
worktree_manager, "create_merge_request", return_value=mock_mr_result
|
|
) as mock_create_mr,
|
|
):
|
|
result = worktree_manager.push_and_create_pr(
|
|
spec_name=spec_name,
|
|
target_branch="main",
|
|
title="Test MR",
|
|
)
|
|
|
|
# Verify routing to GitLab
|
|
mock_create_mr.assert_called_once_with(
|
|
spec_name=spec_name,
|
|
target_branch="main",
|
|
title="Test MR",
|
|
draft=False,
|
|
)
|
|
|
|
# Verify result
|
|
assert result["success"] is True
|
|
assert result["pushed"] is True
|
|
assert result["provider"] == "gitlab"
|
|
assert result["pr_url"] == "https://gitlab.com/user/repo/-/merge_requests/42"
|
|
|
|
def test_unknown_provider_error(self, worktree_manager, temp_project_dir):
|
|
"""Test error handling for unknown git providers."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
# Mock push_branch to succeed
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(
|
|
worktree_module, "detect_git_provider", return_value="unknown"
|
|
),
|
|
):
|
|
result = worktree_manager.push_and_create_pr(spec_name=spec_name)
|
|
|
|
# Verify error
|
|
assert result["success"] is False
|
|
assert result["pushed"] is True
|
|
assert result["provider"] == "unknown"
|
|
assert "Unable to determine git hosting provider" in result["error"]
|
|
assert "Supported: GitHub, GitLab" in result["error"]
|
|
|
|
def test_push_failure(self, worktree_manager, temp_project_dir):
|
|
"""Test handling of push failures."""
|
|
spec_name = "test-feature"
|
|
|
|
# Mock push_branch to fail
|
|
mock_push_result = {
|
|
"success": False,
|
|
"error": "Failed to push: remote rejected",
|
|
}
|
|
|
|
with patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
):
|
|
result = worktree_manager.push_and_create_pr(spec_name=spec_name)
|
|
|
|
# Verify error
|
|
assert result["success"] is False
|
|
assert result["pushed"] is False
|
|
assert "Failed to push: remote rejected" in result["error"]
|
|
|
|
def test_draft_pr_flag(self, worktree_manager, temp_project_dir):
|
|
"""Test draft flag is passed through correctly."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
mock_pr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://github.com/user/repo/pull/124",
|
|
already_exists=False,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(worktree_module, "detect_git_provider", return_value="github"),
|
|
patch.object(
|
|
worktree_manager, "create_pull_request", return_value=mock_pr_result
|
|
) as mock_create_pr,
|
|
):
|
|
result = worktree_manager.push_and_create_pr(
|
|
spec_name=spec_name,
|
|
draft=True,
|
|
)
|
|
|
|
# Verify draft flag was passed
|
|
assert mock_create_pr.call_args[1]["draft"] is True
|
|
assert result["success"] is True
|
|
|
|
def test_force_push_flag(self, worktree_manager, temp_project_dir):
|
|
"""Test force push flag is passed to push_branch."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
mock_pr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://github.com/user/repo/pull/125",
|
|
already_exists=False,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
) as mock_push,
|
|
patch.object(worktree_module, "detect_git_provider", return_value="github"),
|
|
patch.object(
|
|
worktree_manager, "create_pull_request", return_value=mock_pr_result
|
|
),
|
|
):
|
|
result = worktree_manager.push_and_create_pr(
|
|
spec_name=spec_name,
|
|
force_push=True,
|
|
)
|
|
|
|
# Verify force flag was passed to push_branch
|
|
assert mock_push.call_args[1]["force"] is True
|
|
assert result["success"] is True
|
|
|
|
def test_custom_target_branch(self, worktree_manager, temp_project_dir):
|
|
"""Test custom target branch is passed through."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
custom_target = "develop"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
mock_pr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://github.com/user/repo/pull/126",
|
|
already_exists=False,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(worktree_module, "detect_git_provider", return_value="github"),
|
|
patch.object(
|
|
worktree_manager, "create_pull_request", return_value=mock_pr_result
|
|
) as mock_create_pr,
|
|
):
|
|
result = worktree_manager.push_and_create_pr(
|
|
spec_name=spec_name,
|
|
target_branch=custom_target,
|
|
)
|
|
|
|
# Verify target branch was passed
|
|
assert mock_create_pr.call_args[1]["target_branch"] == custom_target
|
|
assert result["success"] is True
|
|
|
|
|
|
class TestProviderIntegration:
|
|
"""Test integration between provider detection and CLI routing."""
|
|
|
|
def test_self_hosted_gitlab_routing(self, worktree_manager, temp_project_dir):
|
|
"""Test that self-hosted GitLab instances route to glab CLI."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
mock_mr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://gitlab.company.com/team/repo/-/merge_requests/1",
|
|
already_exists=False,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(
|
|
worktree_module, "detect_git_provider", return_value="gitlab"
|
|
), # Self-hosted detected as gitlab
|
|
patch.object(
|
|
worktree_manager, "create_merge_request", return_value=mock_mr_result
|
|
) as mock_create_mr,
|
|
):
|
|
result = worktree_manager.push_and_create_pr(spec_name=spec_name)
|
|
|
|
# Verify routing to GitLab (not GitHub)
|
|
mock_create_mr.assert_called_once()
|
|
assert result["provider"] == "gitlab"
|
|
assert result["success"] is True
|
|
|
|
def test_pr_already_exists_propagation(self, worktree_manager, temp_project_dir):
|
|
"""Test that already_exists flag propagates correctly."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
# Mock PR that already exists
|
|
mock_pr_result = PullRequestResult(
|
|
success=True,
|
|
pr_url="https://github.com/user/repo/pull/127",
|
|
already_exists=True,
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(worktree_module, "detect_git_provider", return_value="github"),
|
|
patch.object(
|
|
worktree_manager, "create_pull_request", return_value=mock_pr_result
|
|
),
|
|
):
|
|
result = worktree_manager.push_and_create_pr(spec_name=spec_name)
|
|
|
|
# Verify already_exists flag
|
|
assert result["success"] is True
|
|
assert result["already_exists"] is True
|
|
assert result["pr_url"] == "https://github.com/user/repo/pull/127"
|
|
|
|
def test_error_propagation_from_pr_creation(
|
|
self, worktree_manager, temp_project_dir
|
|
):
|
|
"""Test that errors from PR/MR creation propagate correctly."""
|
|
# Import the actual module to patch it directly (handles importlib shim)
|
|
import core.worktree as worktree_module
|
|
|
|
spec_name = "test-feature"
|
|
|
|
mock_push_result = {
|
|
"success": True,
|
|
"remote": "origin",
|
|
"branch": f"auto-claude/{spec_name}",
|
|
}
|
|
|
|
# Mock PR creation failure
|
|
mock_pr_result = PullRequestResult(
|
|
success=False,
|
|
error="Authentication failed",
|
|
)
|
|
|
|
with (
|
|
patch.object(
|
|
worktree_manager, "push_branch", return_value=mock_push_result
|
|
),
|
|
patch.object(worktree_module, "detect_git_provider", return_value="github"),
|
|
patch.object(
|
|
worktree_manager, "create_pull_request", return_value=mock_pr_result
|
|
),
|
|
):
|
|
result = worktree_manager.push_and_create_pr(spec_name=spec_name)
|
|
|
|
# Verify error propagation
|
|
assert result["success"] is False
|
|
assert result["pushed"] is True
|
|
assert "Authentication failed" in result["error"]
|