Files
syndarix/mcp-servers/git-ops/tests/test_server_tools.py
Felipe Cardoso 0a624a94af **test(git-ops): add comprehensive tests for server and API tools**
- Introduced extensive test coverage for FastAPI endpoints, including health check, MCP tools, and JSON-RPC operations.
- Added tests for Git operations MCP tools, including cloning, status, branching, committing, and provider detection.
- Mocked dependencies and ensured reliable test isolation with unittest.mock and pytest fixtures.
- Validated error handling, workspace management, tool execution, and type conversion functions.
2026-01-07 09:17:32 +01:00

1171 lines
38 KiB
Python

"""
Comprehensive tests for server MCP tools.
Tests the actual tool execution with mocked dependencies.
"""
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from exceptions import ErrorCode
from models import CreatePRResult, GetPRResult, ListPRsResult, MergePRResult
from server import (
_get_auth_token_for_url,
_get_provider_for_url,
_validate_branch,
_validate_id,
_validate_url,
checkout,
clone_repository,
commit,
create_branch,
create_pull_request,
diff,
get_pull_request,
get_workspace,
git_status,
list_branches,
list_pull_requests,
lock_workspace,
log,
merge_pull_request,
pull,
push,
unlock_workspace,
)
from workspace import WorkspaceInfo, WorkspaceState
def make_clone_result(workspace_path="/tmp/test", branch="main", commit_sha="abc123"):
"""Create a mock clone result."""
result = MagicMock()
result.workspace_path = workspace_path
result.branch = branch
result.commit_sha = commit_sha
return result
def make_status_result(
branch="main",
commit_sha="abc123",
is_clean=True,
staged=None,
unstaged=None,
untracked=None,
ahead=0,
behind=0,
):
"""Create a mock status result."""
result = MagicMock()
result.branch = branch
result.commit_sha = commit_sha
result.is_clean = is_clean
result.staged = staged or []
result.unstaged = unstaged or []
result.untracked = untracked or []
result.ahead = ahead
result.behind = behind
return result
def make_branch_result(branch="feature", commit_sha="abc123", is_current=True):
"""Create a mock branch result."""
result = MagicMock()
result.branch = branch
result.commit_sha = commit_sha
result.is_current = is_current
return result
def make_list_branches_result(current="main", local=None, remote=None):
"""Create a mock list branches result."""
result = MagicMock()
result.current_branch = current
result.local_branches = local or ["main"]
result.remote_branches = remote or []
return result
def make_checkout_result(ref="main", commit_sha="abc123"):
"""Create a mock checkout result."""
result = MagicMock()
result.ref = ref
result.commit_sha = commit_sha
return result
def make_commit_result(
commit_sha="abc123",
short_sha="abc123",
message="Test",
files_changed=1,
insertions=10,
deletions=5,
):
"""Create a mock commit result."""
result = MagicMock()
result.commit_sha = commit_sha
result.short_sha = short_sha
result.message = message
result.files_changed = files_changed
result.insertions = insertions
result.deletions = deletions
return result
def make_push_result(branch="main", remote="origin", commits_pushed=1):
"""Create a mock push result."""
result = MagicMock()
result.branch = branch
result.remote = remote
result.commits_pushed = commits_pushed
return result
def make_pull_result(
branch="main", commits_received=1, fast_forward=True, conflicts=None
):
"""Create a mock pull result."""
result = MagicMock()
result.branch = branch
result.commits_received = commits_received
result.fast_forward = fast_forward
result.conflicts = conflicts or []
return result
def make_diff_result(
base="HEAD~1", head="HEAD", files=None, additions=10, deletions=5, files_changed=2
):
"""Create a mock diff result."""
result = MagicMock()
result.base = base
result.head = head
result.files = files or []
result.total_additions = additions
result.total_deletions = deletions
result.files_changed = files_changed
return result
def make_log_result(commits=None, total=2):
"""Create a mock log result."""
result = MagicMock()
result.commits = commits or [{"sha": "abc123"}, {"sha": "def456"}]
result.total_commits = total
return result
@pytest.fixture
def mock_workspace_info():
"""Create a mock workspace info."""
return WorkspaceInfo(
project_id="test-project",
path="/tmp/test-workspace",
repo_url="https://gitea.test.com/owner/repo.git",
current_branch="main",
state=WorkspaceState.READY,
last_accessed=datetime.now(UTC),
)
@pytest.fixture
def mock_github_workspace_info():
"""Create a mock workspace info for GitHub."""
return WorkspaceInfo(
project_id="github-project",
path="/tmp/github-workspace",
repo_url="https://github.com/owner/repo.git",
current_branch="main",
state=WorkspaceState.READY,
last_accessed=datetime.now(UTC),
)
class TestProviderDetection:
"""Tests for provider URL detection."""
def test_get_provider_for_github_url(self, test_settings):
"""Test GitHub URL detection."""
with (
patch("server._settings", test_settings),
patch("server._github_provider", MagicMock(name="github")),
patch("server._gitea_provider", MagicMock(name="gitea")),
):
provider = _get_provider_for_url("https://github.com/owner/repo.git")
assert provider is not None
def test_get_provider_for_gitea_url(self, test_settings):
"""Test Gitea URL detection."""
with (
patch("server._settings", test_settings),
patch("server._github_provider", MagicMock(name="github")),
patch("server._gitea_provider", MagicMock(name="gitea")),
):
provider = _get_provider_for_url("https://gitea.test.com/owner/repo.git")
assert provider is not None
def test_get_provider_no_settings(self):
"""Test provider detection without settings."""
with patch("server._settings", None):
provider = _get_provider_for_url("https://github.com/owner/repo.git")
assert provider is None
def test_get_auth_token_for_github(self, test_settings):
"""Test GitHub token selection."""
with patch("server._settings", test_settings):
token = _get_auth_token_for_url("https://github.com/owner/repo.git")
assert token == test_settings.github_token
def test_get_auth_token_for_gitea(self, test_settings):
"""Test Gitea token selection."""
with patch("server._settings", test_settings):
token = _get_auth_token_for_url("https://gitea.test.com/owner/repo.git")
assert token == test_settings.gitea_token
def test_get_auth_token_no_settings(self):
"""Test token selection without settings."""
with patch("server._settings", None):
token = _get_auth_token_for_url("https://github.com/owner/repo.git")
assert token is None
class TestCloneRepository:
"""Tests for clone_repository tool."""
@pytest.mark.asyncio
async def test_clone_success(self, test_settings, mock_workspace_info):
"""Test successful clone."""
mock_manager = AsyncMock()
mock_manager.create_workspace = AsyncMock(return_value=mock_workspace_info)
mock_manager.update_workspace_branch = AsyncMock()
mock_git = MagicMock()
mock_git.clone = AsyncMock(
return_value=make_clone_result(
workspace_path=mock_workspace_info.path,
branch="main",
commit_sha="abc123",
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await clone_repository.fn(
project_id="test-project",
agent_id="agent-1",
repo_url="https://gitea.test.com/owner/repo.git",
)
assert result["success"] is True
assert result["branch"] == "main"
assert result["commit_sha"] == "abc123"
@pytest.mark.asyncio
async def test_clone_with_branch_and_depth(
self, test_settings, mock_workspace_info
):
"""Test clone with branch and depth options."""
mock_manager = AsyncMock()
mock_manager.create_workspace = AsyncMock(return_value=mock_workspace_info)
mock_manager.update_workspace_branch = AsyncMock()
mock_git = MagicMock()
mock_git.clone = AsyncMock(
return_value=make_clone_result(
workspace_path=mock_workspace_info.path,
branch="develop",
commit_sha="def456",
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await clone_repository.fn(
project_id="test-project",
agent_id="agent-1",
repo_url="https://gitea.test.com/owner/repo.git",
branch="develop",
depth=1,
)
assert result["success"] is True
assert result["branch"] == "develop"
class TestGitStatus:
"""Tests for git_status tool."""
@pytest.mark.asyncio
async def test_status_success(self, test_settings, mock_workspace_info):
"""Test successful status."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.status = AsyncMock(
return_value=make_status_result(
branch="main",
commit_sha="abc123",
is_clean=True,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await git_status.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
assert result["is_clean"] is True
assert result["branch"] == "main"
@pytest.mark.asyncio
async def test_status_with_changes(self, test_settings, mock_workspace_info):
"""Test status with uncommitted changes."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.status = AsyncMock(
return_value=make_status_result(
branch="feature",
commit_sha="abc123",
is_clean=False,
staged=["file1.py"],
unstaged=["file2.py"],
untracked=["file3.py"],
ahead=2,
behind=1,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await git_status.fn(
project_id="test-project",
agent_id="agent-1",
include_untracked=True,
)
assert result["success"] is True
assert result["is_clean"] is False
assert len(result["staged"]) == 1
assert len(result["unstaged"]) == 1
assert len(result["untracked"]) == 1
class TestBranchOperations:
"""Tests for branch operation tools."""
@pytest.mark.asyncio
async def test_create_branch_success(self, test_settings, mock_workspace_info):
"""Test successful branch creation."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_manager.update_workspace_branch = AsyncMock()
mock_git = MagicMock()
mock_git.create_branch = AsyncMock(
return_value=make_branch_result(
branch="feature-new",
commit_sha="abc123",
is_current=True,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await create_branch.fn(
project_id="test-project",
agent_id="agent-1",
branch_name="feature-new",
)
assert result["success"] is True
assert result["branch"] == "feature-new"
@pytest.mark.asyncio
async def test_create_branch_from_ref(self, test_settings, mock_workspace_info):
"""Test branch creation from specific ref."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_manager.update_workspace_branch = AsyncMock()
mock_git = MagicMock()
mock_git.create_branch = AsyncMock(
return_value=make_branch_result(
branch="hotfix",
commit_sha="def456",
is_current=False,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await create_branch.fn(
project_id="test-project",
agent_id="agent-1",
branch_name="hotfix",
from_ref="v1.0.0",
checkout=False,
)
assert result["success"] is True
assert result["is_current"] is False
@pytest.mark.asyncio
async def test_list_branches_success(self, test_settings, mock_workspace_info):
"""Test listing branches."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.list_branches = AsyncMock(
return_value=make_list_branches_result(
current="main",
local=["main", "develop", "feature-1"],
remote=["origin/main", "origin/develop"],
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await list_branches.fn(
project_id="test-project",
agent_id="agent-1",
include_remote=True,
)
assert result["success"] is True
assert result["current_branch"] == "main"
assert len(result["local_branches"]) == 3
@pytest.mark.asyncio
async def test_checkout_success(self, test_settings, mock_workspace_info):
"""Test checkout."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_manager.update_workspace_branch = AsyncMock()
mock_git = MagicMock()
mock_git.checkout = AsyncMock(
return_value=make_checkout_result(
ref="develop",
commit_sha="abc123",
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await checkout.fn(
project_id="test-project",
agent_id="agent-1",
ref="develop",
)
assert result["success"] is True
assert result["ref"] == "develop"
class TestCommitOperations:
"""Tests for commit operation tools."""
@pytest.mark.asyncio
async def test_commit_success(self, test_settings, mock_workspace_info):
"""Test successful commit."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.commit = AsyncMock(
return_value=make_commit_result(
commit_sha="abc123def456",
short_sha="abc123d",
message="Test commit",
files_changed=2,
insertions=10,
deletions=5,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await commit.fn(
project_id="test-project",
agent_id="agent-1",
message="Test commit",
)
assert result["success"] is True
assert result["files_changed"] == 2
assert result["insertions"] == 10
@pytest.mark.asyncio
async def test_commit_with_author(self, test_settings, mock_workspace_info):
"""Test commit with custom author."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.commit = AsyncMock(
return_value=make_commit_result(
commit_sha="abc123",
short_sha="abc123",
message="Custom author commit",
files_changed=1,
insertions=5,
deletions=0,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await commit.fn(
project_id="test-project",
agent_id="agent-1",
message="Custom author commit",
author_name="Custom Author",
author_email="custom@test.com",
)
assert result["success"] is True
class TestPushPullOperations:
"""Tests for push/pull operation tools."""
@pytest.mark.asyncio
async def test_push_success(self, test_settings, mock_workspace_info):
"""Test successful push."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.push = AsyncMock(
return_value=make_push_result(
branch="main",
remote="origin",
commits_pushed=2,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await push.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
assert result["commits_pushed"] == 2
@pytest.mark.asyncio
async def test_pull_success(self, test_settings, mock_workspace_info):
"""Test successful pull."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.pull = AsyncMock(
return_value=make_pull_result(
branch="main",
commits_received=3,
fast_forward=True,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await pull.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
assert result["commits_received"] == 3
assert result["fast_forward"] is True
class TestDiffLogOperations:
"""Tests for diff/log operation tools."""
@pytest.mark.asyncio
async def test_diff_success(self, test_settings, mock_workspace_info):
"""Test successful diff."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.diff = AsyncMock(
return_value=make_diff_result(
base="HEAD~1",
head="HEAD",
additions=10,
deletions=5,
files_changed=2,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await diff.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
assert result["total_additions"] == 10
@pytest.mark.asyncio
async def test_log_success(self, test_settings, mock_workspace_info):
"""Test successful log."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.log = AsyncMock(
return_value=make_log_result(
commits=[
{"sha": "abc123", "message": "Commit 1"},
{"sha": "def456", "message": "Commit 2"},
],
total=2,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await log.fn(
project_id="test-project",
agent_id="agent-1",
limit=10,
)
assert result["success"] is True
assert result["total_commits"] == 2
class TestPROperations:
"""Tests for PR operation tools."""
@pytest.mark.asyncio
async def test_create_pr_success(self, test_settings, mock_workspace_info):
"""Test successful PR creation."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_provider = AsyncMock()
mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))
mock_provider.create_pr = AsyncMock(
return_value=CreatePRResult(
success=True,
pr_number=42,
pr_url="https://gitea.test.com/owner/repo/pull/42",
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=mock_provider),
):
result = await create_pull_request.fn(
project_id="test-project",
agent_id="agent-1",
title="Test PR",
source_branch="feature",
body="Test body",
)
assert result["success"] is True
assert result["pr_number"] == 42
@pytest.mark.asyncio
async def test_get_pr_success(self, test_settings, mock_workspace_info):
"""Test getting PR."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_provider = AsyncMock()
mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))
mock_provider.get_pr = AsyncMock(
return_value=GetPRResult(
success=True,
pr={"number": 42, "title": "Test PR", "state": "open"},
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=mock_provider),
):
result = await get_pull_request.fn(
project_id="test-project",
agent_id="agent-1",
pr_number=42,
)
assert result["success"] is True
assert result["pr"]["number"] == 42
@pytest.mark.asyncio
async def test_list_prs_success(self, test_settings, mock_workspace_info):
"""Test listing PRs."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_provider = AsyncMock()
mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))
mock_provider.list_prs = AsyncMock(
return_value=ListPRsResult(
success=True,
pull_requests=[{"number": 1}, {"number": 2}],
total_count=2,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=mock_provider),
):
result = await list_pull_requests.fn(
project_id="test-project",
agent_id="agent-1",
state=None,
author=None,
limit=20,
)
assert result["success"] is True
assert result["total_count"] == 2
@pytest.mark.asyncio
async def test_list_prs_with_state(self, test_settings, mock_workspace_info):
"""Test listing PRs with state filter."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_provider = AsyncMock()
mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))
mock_provider.list_prs = AsyncMock(
return_value=ListPRsResult(
success=True,
pull_requests=[{"number": 1, "state": "open"}],
total_count=1,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=mock_provider),
):
result = await list_pull_requests.fn(
project_id="test-project",
agent_id="agent-1",
state="open",
author=None,
limit=20,
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_merge_pr_success(self, test_settings, mock_workspace_info):
"""Test merging PR."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
mock_provider = AsyncMock()
mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo"))
mock_provider.merge_pr = AsyncMock(
return_value=MergePRResult(
success=True,
merge_commit_sha="abc123",
branch_deleted=True,
)
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=mock_provider),
):
result = await merge_pull_request.fn(
project_id="test-project",
agent_id="agent-1",
pr_number=42,
merge_strategy="squash",
)
assert result["success"] is True
assert result["merge_commit_sha"] == "abc123"
@pytest.mark.asyncio
async def test_pr_no_provider(self, test_settings, mock_workspace_info):
"""Test PR operation without provider."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server._get_provider_for_url", return_value=None),
):
result = await create_pull_request.fn(
project_id="test-project",
agent_id="agent-1",
title="Test PR",
source_branch="feature",
)
assert result["success"] is False
assert "provider" in result["error"].lower()
class TestWorkspaceOperations:
"""Tests for workspace operation tools."""
@pytest.mark.asyncio
async def test_get_workspace_success(self, test_settings, mock_workspace_info):
"""Test getting workspace."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await get_workspace.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
assert "workspace" in result
@pytest.mark.asyncio
async def test_lock_workspace_success(self, test_settings, mock_workspace_info):
"""Test locking workspace."""
mock_workspace_info.lock_holder = "agent-1"
mock_workspace_info.lock_expires = datetime.now(UTC)
mock_manager = AsyncMock()
mock_manager.lock_workspace = AsyncMock(return_value=True)
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await lock_workspace.fn(
project_id="test-project",
agent_id="agent-1",
timeout=300,
)
assert result["success"] is True
assert result["lock_holder"] == "agent-1"
@pytest.mark.asyncio
async def test_unlock_workspace_success(self, test_settings):
"""Test unlocking workspace."""
mock_manager = AsyncMock()
mock_manager.unlock_workspace = AsyncMock(return_value=True)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await unlock_workspace.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is True
class TestErrorHandling:
"""Tests for error handling in tools."""
@pytest.mark.asyncio
async def test_git_ops_error_handling(self, test_settings, mock_workspace_info):
"""Test GitOpsError is handled properly."""
from exceptions import CloneError
mock_manager = AsyncMock()
mock_manager.create_workspace = AsyncMock(return_value=mock_workspace_info)
mock_git = MagicMock()
mock_git.clone = AsyncMock(
side_effect=CloneError("https://test.com/repo.git", "Clone failed")
)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
patch("server.GitWrapper", return_value=mock_git),
):
result = await clone_repository.fn(
project_id="test-project",
agent_id="agent-1",
repo_url="https://gitea.test.com/owner/repo.git",
)
assert result["success"] is False
assert "Clone failed" in result["error"]
@pytest.mark.asyncio
async def test_unexpected_error_handling(self, test_settings, mock_workspace_info):
"""Test unexpected errors are handled."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(side_effect=RuntimeError("Unexpected"))
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await git_status.fn(
project_id="test-project",
agent_id="agent-1",
)
assert result["success"] is False
assert result["code"] == ErrorCode.INTERNAL_ERROR.value
class TestValidationFunctions:
"""Tests for input validation functions."""
def test_validate_id_non_string(self):
"""Test validation when value is not a string."""
result = _validate_id(123, "project_id")
assert result == "project_id must be a string"
def test_validate_id_empty(self):
"""Test validation when value is empty."""
result = _validate_id("", "project_id")
assert result == "project_id is required"
def test_validate_id_invalid_chars(self):
"""Test validation when value has invalid characters."""
result = _validate_id("project@!#", "project_id")
assert "Invalid project_id" in result
def test_validate_id_valid(self):
"""Test validation with valid ID."""
result = _validate_id("valid-project_123", "project_id")
assert result is None
def test_validate_branch_non_string(self):
"""Test validation when branch is not a string."""
result = _validate_branch(123)
assert result == "Branch name must be a string"
def test_validate_branch_empty(self):
"""Test validation when branch is empty."""
result = _validate_branch("")
assert result == "Branch name is required"
def test_validate_branch_invalid(self):
"""Test validation with invalid branch name."""
result = _validate_branch("branch with spaces")
assert "Invalid branch name" in result
def test_validate_branch_valid(self):
"""Test validation with valid branch name."""
result = _validate_branch("feature/my-branch.v1")
assert result is None
def test_validate_url_non_string(self):
"""Test validation when URL is not a string."""
result = _validate_url(123)
assert result == "Repository URL must be a string"
def test_validate_url_empty(self):
"""Test validation when URL is empty."""
result = _validate_url("")
assert result == "Repository URL is required"
def test_validate_url_invalid(self):
"""Test validation with invalid URL."""
result = _validate_url("not-a-url")
assert "Invalid repository URL" in result
def test_validate_url_valid_https(self):
"""Test validation with valid HTTPS URL."""
result = _validate_url("https://github.com/owner/repo.git")
assert result is None
def test_validate_url_valid_ssh(self):
"""Test validation with valid SSH URL."""
result = _validate_url("git@github.com:owner/repo.git")
assert result is None
class TestProviderDetectionAdvanced:
"""Additional tests for provider detection."""
def test_get_provider_github_enterprise(self, test_settings):
"""Test provider detection for GitHub Enterprise URL."""
test_settings.github_api_url = "https://github.mycompany.com/api/v3"
with (
patch("server._settings", test_settings),
patch("server._github_provider", MagicMock()) as mock_github,
):
result = _get_provider_for_url(
"https://github.mycompany.com/owner/repo.git"
)
assert result == mock_github
def test_get_provider_default_to_gitea(self, test_settings):
"""Test default to Gitea for unknown URLs."""
test_settings.gitea_base_url = "https://gitea.example.com"
test_settings.github_api_url = None
with (
patch("server._settings", test_settings),
patch("server._gitea_provider", MagicMock()) as mock_gitea,
):
# A random URL that doesn't match github patterns
result = _get_provider_for_url("https://git.example.com/owner/repo.git")
assert result == mock_gitea
class TestCloneValidation:
"""Tests for clone tool validation."""
@pytest.mark.asyncio
async def test_clone_invalid_project_id(self, test_settings):
"""Test clone with invalid project_id."""
with patch("server._settings", test_settings):
result = await clone_repository.fn(
project_id="invalid!project",
agent_id="agent-1",
repo_url="https://github.com/owner/repo.git",
)
assert result["success"] is False
assert result["code"] == ErrorCode.INVALID_REQUEST.value
@pytest.mark.asyncio
async def test_clone_invalid_agent_id(self, test_settings):
"""Test clone with invalid agent_id."""
with patch("server._settings", test_settings):
result = await clone_repository.fn(
project_id="valid-project",
agent_id="invalid!agent",
repo_url="https://github.com/owner/repo.git",
)
assert result["success"] is False
assert result["code"] == ErrorCode.INVALID_REQUEST.value
@pytest.mark.asyncio
async def test_clone_invalid_url(self, test_settings):
"""Test clone with invalid URL."""
with patch("server._settings", test_settings):
result = await clone_repository.fn(
project_id="valid-project",
agent_id="agent-1",
repo_url="not-a-valid-url",
)
assert result["success"] is False
assert result["code"] == ErrorCode.INVALID_REQUEST.value
class TestBranchValidation:
"""Tests for branch tool validation."""
@pytest.mark.asyncio
async def test_create_branch_invalid_name(self, test_settings, mock_workspace_info):
"""Test create_branch with invalid branch name."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=mock_workspace_info)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await create_branch.fn(
project_id="test-project",
agent_id="agent-1",
branch_name="invalid branch name",
)
assert result["success"] is False
assert result["code"] == ErrorCode.INVALID_REQUEST.value
class TestStatusWithWorkspace:
"""Additional tests for status operations."""
@pytest.mark.asyncio
async def test_status_workspace_not_found(self, test_settings):
"""Test status when workspace doesn't exist."""
mock_manager = AsyncMock()
mock_manager.get_workspace = AsyncMock(return_value=None)
with (
patch("server._workspace_manager", mock_manager),
patch("server._settings", test_settings),
):
result = await git_status.fn(
project_id="nonexistent-project",
agent_id="agent-1",
)
assert result["success"] is False
assert result["code"] == ErrorCode.WORKSPACE_NOT_FOUND.value