""" Tests for the MCP server and tools. """ from unittest.mock import AsyncMock, MagicMock, patch import pytest from exceptions import ErrorCode class TestInputValidation: """Tests for input validation functions.""" def test_validate_id_valid(self): """Test valid IDs pass validation.""" from server import _validate_id assert _validate_id("test-123", "project_id") is None assert _validate_id("my_project", "project_id") is None assert _validate_id("Agent-001", "agent_id") is None def test_validate_id_empty(self): """Test empty ID fails validation.""" from server import _validate_id error = _validate_id("", "project_id") assert error is not None assert "required" in error.lower() def test_validate_id_too_long(self): """Test too-long ID fails validation.""" from server import _validate_id error = _validate_id("a" * 200, "project_id") assert error is not None assert "1-128" in error def test_validate_id_invalid_chars(self): """Test invalid characters fail validation.""" from server import _validate_id assert _validate_id("test@invalid", "project_id") is not None assert _validate_id("test!project", "project_id") is not None assert _validate_id("test project", "project_id") is not None def test_validate_branch_valid(self): """Test valid branch names.""" from server import _validate_branch assert _validate_branch("main") is None assert _validate_branch("feature/new-thing") is None assert _validate_branch("release-1.0.0") is None assert _validate_branch("hotfix.urgent") is None def test_validate_branch_invalid(self): """Test invalid branch names.""" from server import _validate_branch assert _validate_branch("") is not None assert _validate_branch("a" * 300) is not None def test_validate_url_valid(self): """Test valid repository URLs.""" from server import _validate_url assert _validate_url("https://github.com/owner/repo.git") is None assert _validate_url("https://gitea.example.com/owner/repo") is None assert _validate_url("git@github.com:owner/repo.git") is None def test_validate_url_invalid(self): """Test invalid repository URLs.""" from server import _validate_url assert _validate_url("") is not None assert _validate_url("not-a-url") is not None assert _validate_url("ftp://invalid.com/repo") is not None class TestHealthCheck: """Tests for health check endpoint.""" @pytest.mark.asyncio async def test_health_check_structure(self): """Test health check returns proper structure.""" from server import health_check with patch("server._gitea_provider", None), \ patch("server._workspace_manager", None): result = await health_check() assert "status" in result assert "service" in result assert "version" in result assert "timestamp" in result assert "dependencies" in result @pytest.mark.asyncio async def test_health_check_no_providers(self): """Test health check without providers configured.""" from server import health_check with patch("server._gitea_provider", None), \ patch("server._workspace_manager", None): result = await health_check() assert result["dependencies"]["gitea"] == "not configured" class TestToolRegistry: """Tests for tool registration.""" def test_tool_registry_populated(self): """Test that tools are registered.""" from server import _tool_registry assert len(_tool_registry) > 0 assert "clone_repository" in _tool_registry assert "git_status" in _tool_registry assert "create_branch" in _tool_registry assert "commit" in _tool_registry def test_tool_schema_structure(self): """Test tool schemas have proper structure.""" from server import _tool_registry for name, info in _tool_registry.items(): assert "func" in info assert "description" in info assert "schema" in info assert info["schema"]["type"] == "object" assert "properties" in info["schema"] class TestCloneRepository: """Tests for clone_repository tool.""" @pytest.mark.asyncio async def test_clone_invalid_project_id(self): """Test clone with invalid project ID.""" from server import clone_repository # Access the underlying function via .fn result = await clone_repository.fn( project_id="invalid@id", agent_id="agent-1", repo_url="https://github.com/owner/repo.git", ) assert result["success"] is False assert "project_id" in result["error"].lower() @pytest.mark.asyncio async def test_clone_invalid_repo_url(self): """Test clone with invalid repo URL.""" from server import clone_repository 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 "url" in result["error"].lower() class TestGitStatus: """Tests for git_status tool.""" @pytest.mark.asyncio async def test_status_workspace_not_found(self): """Test status when workspace doesn't exist.""" from server import git_status mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await git_status.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False assert result["code"] == ErrorCode.WORKSPACE_NOT_FOUND.value class TestBranchOperations: """Tests for branch operation tools.""" @pytest.mark.asyncio async def test_create_branch_invalid_name(self): """Test creating branch with invalid name.""" from server import create_branch result = await create_branch.fn( project_id="test-project", agent_id="agent-1", branch_name="", # Invalid ) assert result["success"] is False @pytest.mark.asyncio async def test_list_branches_workspace_not_found(self): """Test listing branches when workspace doesn't exist.""" from server import list_branches mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await list_branches.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False @pytest.mark.asyncio async def test_checkout_invalid_project(self): """Test checkout with invalid project ID.""" from server import checkout result = await checkout.fn( project_id="inv@lid", agent_id="agent-1", ref="main", ) assert result["success"] is False class TestCommitOperations: """Tests for commit operation tools.""" @pytest.mark.asyncio async def test_commit_invalid_project(self): """Test commit with invalid project ID.""" from server import commit result = await commit.fn( project_id="inv@lid", agent_id="agent-1", message="Test commit", ) assert result["success"] is False class TestPushPullOperations: """Tests for push/pull operation tools.""" @pytest.mark.asyncio async def test_push_workspace_not_found(self): """Test push when workspace doesn't exist.""" from server import push mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await push.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False @pytest.mark.asyncio async def test_pull_workspace_not_found(self): """Test pull when workspace doesn't exist.""" from server import pull mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await pull.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False class TestDiffLogOperations: """Tests for diff and log operation tools.""" @pytest.mark.asyncio async def test_diff_workspace_not_found(self): """Test diff when workspace doesn't exist.""" from server import diff mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await diff.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False @pytest.mark.asyncio async def test_log_workspace_not_found(self): """Test log when workspace doesn't exist.""" from server import log mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await log.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False class TestPROperations: """Tests for pull request operation tools.""" @pytest.mark.asyncio async def test_create_pr_no_repo_url(self): """Test create PR when workspace has no repo URL.""" from models import WorkspaceInfo, WorkspaceState from server import create_pull_request mock_workspace = WorkspaceInfo( project_id="test-project", path="/tmp/test", state=WorkspaceState.READY, repo_url=None, # No repo URL ) mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=mock_workspace) with patch("server._workspace_manager", mock_manager): result = await create_pull_request.fn( project_id="test-project", agent_id="agent-1", title="Test PR", source_branch="feature", target_branch="main", ) assert result["success"] is False assert "repository URL" in result["error"] @pytest.mark.asyncio async def test_list_prs_invalid_state(self): """Test list PRs with invalid state filter.""" from models import WorkspaceInfo, WorkspaceState from server import list_pull_requests mock_workspace = WorkspaceInfo( project_id="test-project", path="/tmp/test", state=WorkspaceState.READY, repo_url="https://gitea.test.com/owner/repo.git", ) mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=mock_workspace) mock_provider = AsyncMock() mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo")) with patch("server._workspace_manager", mock_manager), \ 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="invalid-state", ) assert result["success"] is False assert "Invalid state" in result["error"] @pytest.mark.asyncio async def test_merge_pr_invalid_strategy(self): """Test merge PR with invalid strategy.""" from models import WorkspaceInfo, WorkspaceState from server import merge_pull_request mock_workspace = WorkspaceInfo( project_id="test-project", path="/tmp/test", state=WorkspaceState.READY, repo_url="https://gitea.test.com/owner/repo.git", ) mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=mock_workspace) mock_provider = AsyncMock() mock_provider.parse_repo_url = MagicMock(return_value=("owner", "repo")) with patch("server._workspace_manager", mock_manager), \ 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="invalid-strategy", ) assert result["success"] is False assert "Invalid strategy" in result["error"] class TestWorkspaceOperations: """Tests for workspace operation tools.""" @pytest.mark.asyncio async def test_get_workspace_not_found(self): """Test get workspace when it doesn't exist.""" from server import get_workspace mock_manager = AsyncMock() mock_manager.get_workspace = AsyncMock(return_value=None) with patch("server._workspace_manager", mock_manager): result = await get_workspace.fn( project_id="nonexistent", agent_id="agent-1", ) assert result["success"] is False @pytest.mark.asyncio async def test_lock_workspace_success(self): """Test successful workspace locking.""" from datetime import UTC, datetime, timedelta from models import WorkspaceInfo, WorkspaceState from server import lock_workspace mock_workspace = WorkspaceInfo( project_id="test-project", path="/tmp/test", state=WorkspaceState.LOCKED, lock_holder="agent-1", lock_expires=datetime.now(UTC) + timedelta(seconds=300), ) mock_manager = AsyncMock() mock_manager.lock_workspace = AsyncMock(return_value=True) mock_manager.get_workspace = AsyncMock(return_value=mock_workspace) with patch("server._workspace_manager", mock_manager): 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 successful workspace unlocking.""" from server import unlock_workspace mock_manager = AsyncMock() mock_manager.unlock_workspace = AsyncMock(return_value=True) with patch("server._workspace_manager", mock_manager): result = await unlock_workspace.fn( project_id="test-project", agent_id="agent-1", ) assert result["success"] is True class TestJSONRPCEndpoint: """Tests for the JSON-RPC endpoint.""" def test_python_type_to_json_schema_str(self): """Test string type conversion.""" from server import _python_type_to_json_schema result = _python_type_to_json_schema(str) assert result["type"] == "string" def test_python_type_to_json_schema_int(self): """Test int type conversion.""" from server import _python_type_to_json_schema result = _python_type_to_json_schema(int) assert result["type"] == "integer" def test_python_type_to_json_schema_bool(self): """Test bool type conversion.""" from server import _python_type_to_json_schema result = _python_type_to_json_schema(bool) assert result["type"] == "boolean" def test_python_type_to_json_schema_list(self): """Test list type conversion.""" from server import _python_type_to_json_schema result = _python_type_to_json_schema(list[str]) assert result["type"] == "array" assert result["items"]["type"] == "string"