PyGithub MCP Server

"""Integration tests for GitHub issue tools. These tests verify that the issue tools work correctly with the real GitHub API. They follow the principles in ADR-002 which emphasizes testing with real API calls instead of mocks. """ import os import json import uuid import time import logging from datetime import datetime, timedelta import pytest from mcp.server.fastmcp import FastMCP from pygithub_mcp_server.server import create_server from pygithub_mcp_server.tools.issues.tools import ( create_issue, get_issue, update_issue, list_issues, add_issue_comment, list_issue_comments, update_issue_comment, delete_issue_comment, add_issue_labels, remove_issue_label ) # Configure logging logger = logging.getLogger(__name__) @pytest.mark.integration class TestIssueTools: """Integration tests for GitHub issue tools.""" def test_issue_lifecycle(self, test_repo_info, unique_id, issue_cleanup): """Test the complete lifecycle of an issue.""" # Step 1: Create an issue create_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "title": f"Test Issue {unique_id}", "body": f"This is a test issue created by integration tests at {datetime.now().isoformat()}", "labels": ["test", "integration"], } create_result = create_issue(create_params) # Verify successful creation assert not create_result.get("is_error") assert len(create_result["content"]) == 1 assert create_result["content"][0]["type"] == "text" issue_data = json.loads(create_result["content"][0]["text"]) assert issue_data["title"] == create_params["title"] assert issue_data["body"] == create_params["body"] assert "test" in [label["name"] for label in issue_data["labels"]] issue_number = issue_data["issue_number"] issue_cleanup.append(issue_number) logger.debug(f"Created test issue #{issue_number}") # Step 2: Get the issue get_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number } get_result = get_issue(get_params) # Verify successful retrieval assert not get_result.get("is_error") fetched_issue = json.loads(get_result["content"][0]["text"]) assert fetched_issue["issue_number"] == issue_number assert fetched_issue["title"] == create_params["title"] # Step 3: Update the issue update_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "title": f"Updated: {create_params['title']}", "body": f"Updated body: {create_params['body']}" } update_result = update_issue(update_params) # Verify successful update assert not update_result.get("is_error") updated_issue = json.loads(update_result["content"][0]["text"]) assert updated_issue["title"] == update_params["title"] assert updated_issue["body"] == update_params["body"] # Step 4: Add a comment comment_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "body": f"Test comment {unique_id}" } comment_result = add_issue_comment(comment_params) # Verify successful comment creation assert not comment_result.get("is_error") comment_data = json.loads(comment_result["content"][0]["text"]) assert comment_data["body"] == comment_params["body"] comment_id = comment_data["id"] # Step 5: List comments list_comments_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number } list_comments_result = list_issue_comments(list_comments_params) # Verify comments are listed assert not list_comments_result.get("is_error") comments = json.loads(list_comments_result["content"][0]["text"]) assert len(comments) >= 1 assert any(comment["id"] == comment_id for comment in comments) # Step 6: Update comment update_comment_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "comment_id": comment_id, "body": f"Updated comment {unique_id}" } update_comment_result = update_issue_comment(update_comment_params) # Verify successful comment update assert not update_comment_result.get("is_error") updated_comment = json.loads(update_comment_result["content"][0]["text"]) assert updated_comment["body"] == update_comment_params["body"] # Step 7: Add labels add_labels_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "labels": ["documentation", "enhancement"] } add_labels_result = add_issue_labels(add_labels_params) # Verify labels were added assert not add_labels_result.get("is_error") labels = json.loads(add_labels_result["content"][0]["text"]) label_names = [label["name"] for label in labels] assert "documentation" in label_names assert "enhancement" in label_names # Step 8: Remove a label remove_label_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "label": "documentation" } remove_label_result = remove_issue_label(remove_label_params) # Verify label was removed assert not remove_label_result.get("is_error") # Verify with get_issue updated_get_result = get_issue(get_params) updated_issue_data = json.loads(updated_get_result["content"][0]["text"]) updated_label_names = [label["name"] for label in updated_issue_data["labels"]] assert "documentation" not in updated_label_names assert "enhancement" in updated_label_names # Step 9: Delete comment delete_comment_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "comment_id": comment_id } delete_comment_result = delete_issue_comment(delete_comment_params) # Verify successful deletion assert not delete_comment_result.get("is_error") assert "Comment deleted successfully" in delete_comment_result["content"][0]["text"] # Step 10: Close the issue close_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "issue_number": issue_number, "state": "closed" } close_result = update_issue(close_params) # Verify successful closure assert not close_result.get("is_error") closed_issue = json.loads(close_result["content"][0]["text"]) assert closed_issue["state"] == "closed" def test_list_issues(self, test_repo_info, unique_id, issue_cleanup): """Test listing issues with various filters.""" # Create a test issue first create_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "title": f"List Test Issue {unique_id}", "body": "This is a test issue for list_issues", "labels": ["test", "list-test"] } create_result = create_issue(create_params) issue_data = json.loads(create_result["content"][0]["text"]) issue_number = issue_data["issue_number"] issue_cleanup.append(issue_number) # Wait a moment to ensure the issue is indexed time.sleep(1) # Test basic listing list_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "state": "open" } list_result = list_issues(list_params) # Verify issues are listed assert not list_result.get("is_error") issues = json.loads(list_result["content"][0]["text"]) assert len(issues) > 0 assert any(issue["issue_number"] == issue_number for issue in issues) # Test filtering by label label_filter_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "labels": ["list-test"] } label_result = list_issues(label_filter_params) # Verify filtered issues assert not label_result.get("is_error") filtered_issues = json.loads(label_result["content"][0]["text"]) assert len(filtered_issues) > 0 assert all("list-test" in [label["name"] for label in issue["labels"]] for issue in filtered_issues) # Test since parameter since_date = datetime.now() - timedelta(days=1) since_params = { "owner": test_repo_info["owner"], "repo": test_repo_info["repo"], "since": since_date.isoformat() + "Z" # Add Z for UTC timezone } since_result = list_issues(since_params) # Verify issues since date assert not since_result.get("is_error")