"""Tests for issue operations tools.""" import pytest from mcp.types import Tool, TextContent from gitea_mcp.tools.issues import ( get_issue_tools, handle_issue_tool, _list_issues, _get_issue, _create_issue, _update_issue, ) from gitea_mcp.client import GiteaClientError class TestIssueToolDefinitions: """Test issue tool schema definitions.""" def test_get_issue_tools_returns_list(self): """Test that get_issue_tools returns a list of Tool objects.""" tools = get_issue_tools() assert isinstance(tools, list) assert len(tools) == 4 assert all(isinstance(tool, Tool) for tool in tools) def test_list_issues_tool_schema(self): """Test gitea_list_issues tool has correct schema.""" tools = get_issue_tools() list_tool = next(t for t in tools if t.name == "gitea_list_issues") assert list_tool.name == "gitea_list_issues" assert "list issues" in list_tool.description.lower() schema = list_tool.inputSchema assert schema["type"] == "object" assert set(schema["required"]) == {"owner", "repo"} assert "owner" in schema["properties"] assert "repo" in schema["properties"] assert "state" in schema["properties"] assert "labels" in schema["properties"] assert "milestone" in schema["properties"] def test_get_issue_tool_schema(self): """Test gitea_get_issue tool has correct schema.""" tools = get_issue_tools() get_tool = next(t for t in tools if t.name == "gitea_get_issue") assert get_tool.name == "gitea_get_issue" assert "get details" in get_tool.description.lower() schema = get_tool.inputSchema assert set(schema["required"]) == {"owner", "repo", "index"} assert "index" in schema["properties"] def test_create_issue_tool_schema(self): """Test gitea_create_issue tool has correct schema.""" tools = get_issue_tools() create_tool = next(t for t in tools if t.name == "gitea_create_issue") assert create_tool.name == "gitea_create_issue" assert "create" in create_tool.description.lower() schema = create_tool.inputSchema assert set(schema["required"]) == {"owner", "repo", "title"} assert "body" in schema["properties"] assert "labels" in schema["properties"] assert "assignees" in schema["properties"] def test_update_issue_tool_schema(self): """Test gitea_update_issue tool has correct schema.""" tools = get_issue_tools() update_tool = next(t for t in tools if t.name == "gitea_update_issue") assert update_tool.name == "gitea_update_issue" assert "update" in update_tool.description.lower() schema = update_tool.inputSchema assert set(schema["required"]) == {"owner", "repo", "index"} assert "title" in schema["properties"] assert "state" in schema["properties"] class TestListIssues: """Test _list_issues function.""" @pytest.mark.asyncio async def test_list_issues_success(self, mock_client, sample_issue): """Test successful issue listing.""" mock_client.get.return_value = [sample_issue] arguments = { "owner": "testowner", "repo": "testrepo", "state": "open", } result = await _list_issues(arguments, mock_client) assert isinstance(result, list) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Test Issue" in result[0].text assert "#42" in result[0].text assert "bug" in result[0].text mock_client.get.assert_called_once() call_args = mock_client.get.call_args assert "/repos/testowner/testrepo/issues" in call_args[0][0] @pytest.mark.asyncio async def test_list_issues_empty(self, mock_client): """Test listing when no issues found.""" mock_client.get.return_value = [] arguments = { "owner": "testowner", "repo": "testrepo", "state": "closed", } result = await _list_issues(arguments, mock_client) assert len(result) == 1 assert "No closed issues found" in result[0].text @pytest.mark.asyncio async def test_list_issues_with_filters(self, mock_client, sample_issue): """Test listing with label and milestone filters.""" mock_client.get.return_value = [sample_issue] arguments = { "owner": "testowner", "repo": "testrepo", "state": "all", "labels": "bug,priority-high", "milestone": "v1.0", "page": 2, "limit": 50, } result = await _list_issues(arguments, mock_client) mock_client.get.assert_called_once() call_args = mock_client.get.call_args params = call_args[1]["params"] assert params["labels"] == "bug,priority-high" assert params["milestone"] == "v1.0" assert params["page"] == 2 assert params["limit"] == 50 class TestGetIssue: """Test _get_issue function.""" @pytest.mark.asyncio async def test_get_issue_success(self, mock_client, sample_issue): """Test successful issue retrieval.""" mock_client.get.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "index": 42, } result = await _get_issue(arguments, mock_client) assert len(result) == 1 assert "Issue #42" in result[0].text assert "Test Issue" in result[0].text assert "This is a test issue" in result[0].text assert "bug" in result[0].text assert "v1.0" in result[0].text assert "testuser" in result[0].text mock_client.get.assert_called_once() assert "/repos/testowner/testrepo/issues/42" in mock_client.get.call_args[0][0] class TestCreateIssue: """Test _create_issue function.""" @pytest.mark.asyncio async def test_create_issue_minimal(self, mock_client, sample_issue): """Test creating issue with minimal required fields.""" mock_client.post.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "title": "New Issue", } result = await _create_issue(arguments, mock_client) assert len(result) == 1 assert "Created issue #42" in result[0].text assert "Test Issue" in result[0].text mock_client.post.assert_called_once() call_args = mock_client.post.call_args assert "/repos/testowner/testrepo/issues" in call_args[0][0] assert call_args[1]["json"]["title"] == "New Issue" @pytest.mark.asyncio async def test_create_issue_full(self, mock_client, sample_issue): """Test creating issue with all optional fields.""" mock_client.post.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "title": "New Issue", "body": "Detailed description", "labels": [1, 2], "milestone": 10, "assignees": ["user1", "user2"], } result = await _create_issue(arguments, mock_client) assert len(result) == 1 call_args = mock_client.post.call_args json_data = call_args[1]["json"] assert json_data["title"] == "New Issue" assert json_data["body"] == "Detailed description" assert json_data["labels"] == [1, 2] assert json_data["milestone"] == 10 assert json_data["assignees"] == ["user1", "user2"] class TestUpdateIssue: """Test _update_issue function.""" @pytest.mark.asyncio async def test_update_issue_title(self, mock_client, sample_issue): """Test updating issue title.""" updated_issue = sample_issue.copy() updated_issue["title"] = "Updated Title" mock_client.patch.return_value = updated_issue arguments = { "owner": "testowner", "repo": "testrepo", "index": 42, "title": "Updated Title", } result = await _update_issue(arguments, mock_client) assert len(result) == 1 assert "Updated issue #42" in result[0].text mock_client.patch.assert_called_once() call_args = mock_client.patch.call_args assert "/repos/testowner/testrepo/issues/42" in call_args[0][0] assert call_args[1]["json"]["title"] == "Updated Title" @pytest.mark.asyncio async def test_update_issue_state(self, mock_client, sample_issue): """Test closing an issue.""" closed_issue = sample_issue.copy() closed_issue["state"] = "closed" mock_client.patch.return_value = closed_issue arguments = { "owner": "testowner", "repo": "testrepo", "index": 42, "state": "closed", } result = await _update_issue(arguments, mock_client) assert "State: closed" in result[0].text call_args = mock_client.patch.call_args assert call_args[1]["json"]["state"] == "closed" class TestIssueToolHandler: """Test handle_issue_tool function.""" @pytest.mark.asyncio async def test_handle_list_issues(self, mock_client, sample_issue): """Test handler routes to _list_issues.""" mock_client.get.return_value = [sample_issue] arguments = { "owner": "testowner", "repo": "testrepo", } result = await handle_issue_tool("gitea_list_issues", arguments, mock_client) assert len(result) == 1 assert isinstance(result[0], TextContent) mock_client.get.assert_called_once() @pytest.mark.asyncio async def test_handle_get_issue(self, mock_client, sample_issue): """Test handler routes to _get_issue.""" mock_client.get.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "index": 42, } result = await handle_issue_tool("gitea_get_issue", arguments, mock_client) assert len(result) == 1 assert "Issue #42" in result[0].text @pytest.mark.asyncio async def test_handle_create_issue(self, mock_client, sample_issue): """Test handler routes to _create_issue.""" mock_client.post.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "title": "New Issue", } result = await handle_issue_tool("gitea_create_issue", arguments, mock_client) assert len(result) == 1 assert "Created issue" in result[0].text @pytest.mark.asyncio async def test_handle_update_issue(self, mock_client, sample_issue): """Test handler routes to _update_issue.""" mock_client.patch.return_value = sample_issue arguments = { "owner": "testowner", "repo": "testrepo", "index": 42, "title": "Updated", } result = await handle_issue_tool("gitea_update_issue", arguments, mock_client) assert len(result) == 1 assert "Updated issue" in result[0].text @pytest.mark.asyncio async def test_handle_unknown_tool(self, mock_client): """Test handler with unknown tool name.""" result = await handle_issue_tool("gitea_unknown_tool", {}, mock_client) assert len(result) == 1 assert "Unknown issue tool" in result[0].text @pytest.mark.asyncio async def test_handle_client_error(self, mock_client): """Test handler gracefully handles GiteaClientError.""" mock_client.get.side_effect = GiteaClientError("API Error") arguments = { "owner": "testowner", "repo": "testrepo", } result = await handle_issue_tool("gitea_list_issues", arguments, mock_client) assert len(result) == 1 assert "Error:" in result[0].text assert "API Error" in result[0].text