Spaces:
Sleeping
Sleeping
| """ | |
| GitHub API simulator. | |
| Provides realistic mock responses for GitHub API actions. | |
| """ | |
| from typing import Optional | |
| from .base import BaseSimulator | |
| from datetime import datetime | |
| import httpx | |
| class GitHubSimulator(BaseSimulator): | |
| """Simulator for GitHub API.""" | |
| def __init__(self): | |
| super().__init__('github') | |
| self.base_url = "https://api.github.com" | |
| def load_mock_responses(self): | |
| """Load GitHub mock response templates.""" | |
| self.mock_responses = { | |
| 'get_pull_request': self._get_pr_template, | |
| 'create_pull_request': self._create_pr_template, | |
| 'add_comment': self._add_comment_template, | |
| 'create_issue': self._create_issue_template, | |
| 'list_pull_requests': self._list_prs_template, | |
| } | |
| def get_required_permissions(self, action: str) -> set[str]: | |
| """Get required GitHub scopes for an action.""" | |
| permissions_map = { | |
| 'get_pull_request': {'repo'}, | |
| 'create_pull_request': {'repo'}, | |
| 'add_comment': {'repo'}, | |
| 'create_issue': {'repo'}, | |
| 'list_pull_requests': {'repo'}, | |
| } | |
| return permissions_map.get(action, set()) | |
| def validate_params(self, action: str, params: dict) -> tuple[bool, Optional[str]]: | |
| """Validate parameters for GitHub actions.""" | |
| if action == 'get_pull_request': | |
| required = {'owner', 'repo', 'number'} | |
| missing = required - set(params.keys()) | |
| if missing: | |
| return False, f"Missing required parameters: {missing}" | |
| if not isinstance(params['number'], int): | |
| return False, "Parameter 'number' must be an integer" | |
| elif action == 'create_pull_request': | |
| required = {'owner', 'repo', 'title', 'head', 'base'} | |
| missing = required - set(params.keys()) | |
| if missing: | |
| return False, f"Missing required parameters: {missing}" | |
| elif action == 'add_comment': | |
| required = {'owner', 'repo', 'issue_number', 'body'} | |
| missing = required - set(params.keys()) | |
| if missing: | |
| return False, f"Missing required parameters: {missing}" | |
| elif action == 'create_issue': | |
| required = {'owner', 'repo', 'title'} | |
| missing = required - set(params.keys()) | |
| if missing: | |
| return False, f"Missing required parameters: {missing}" | |
| elif action == 'list_pull_requests': | |
| required = {'owner', 'repo'} | |
| missing = required - set(params.keys()) | |
| if missing: | |
| return False, f"Missing required parameters: {missing}" | |
| else: | |
| return False, f"Unknown action: {action}" | |
| return True, None | |
| def generate_mock_response(self, action: str, params: dict) -> dict: | |
| """Generate realistic GitHub API response.""" | |
| if action not in self.mock_responses: | |
| raise ValueError(f"Unknown action: {action}") | |
| template_func = self.mock_responses[action] | |
| return template_func(params) | |
| def _get_pr_template(self, params: dict) -> dict: | |
| """Mock response for getting a pull request.""" | |
| return { | |
| "url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/pulls/{params['number']}", | |
| "id": 1234567890, | |
| "node_id": "PR_kwDOAB1CkM5ZGxyz", | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/pull/{params['number']}", | |
| "number": params['number'], | |
| "state": "open", | |
| "locked": False, | |
| "title": "Add new feature for improved performance", | |
| "user": { | |
| "login": "developer123", | |
| "id": 12345, | |
| "avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4", | |
| "type": "User" | |
| }, | |
| "body": "This PR implements the new caching mechanism to improve API response times by 40%.\n\n## Changes\n- Added Redis caching layer\n- Updated documentation\n- Added tests\n\n## Testing\nAll tests pass locally.", | |
| "created_at": "2025-11-28T10:30:00Z", | |
| "updated_at": "2025-11-30T14:22:00Z", | |
| "closed_at": None, | |
| "merged_at": None, | |
| "merge_commit_sha": None, | |
| "assignee": None, | |
| "assignees": [], | |
| "requested_reviewers": [ | |
| { | |
| "login": "reviewer1", | |
| "id": 67890, | |
| "type": "User" | |
| } | |
| ], | |
| "labels": [ | |
| { | |
| "id": 111, | |
| "name": "enhancement", | |
| "color": "84b6eb" | |
| }, | |
| { | |
| "id": 222, | |
| "name": "performance", | |
| "color": "0e8a16" | |
| } | |
| ], | |
| "milestone": None, | |
| "draft": False, | |
| "commits": 5, | |
| "additions": 250, | |
| "deletions": 42, | |
| "changed_files": 8, | |
| "head": { | |
| "label": f"{params['owner']}:feature-branch", | |
| "ref": "feature-branch", | |
| "sha": "abc123def456" | |
| }, | |
| "base": { | |
| "label": f"{params['owner']}:main", | |
| "ref": "main", | |
| "sha": "def456abc123" | |
| }, | |
| "mergeable": True, | |
| "mergeable_state": "clean", | |
| "comments": 3, | |
| "review_comments": 5, | |
| "commits_url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/pulls/{params['number']}/commits", | |
| "statuses_url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/statuses/abc123def456" | |
| } | |
| def _create_pr_template(self, params: dict) -> dict: | |
| """Mock response for creating a pull request.""" | |
| return { | |
| "url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/pulls/1", | |
| "id": 1234567891, | |
| "number": 1, | |
| "state": "open", | |
| "title": params['title'], | |
| "user": { | |
| "login": "api-user", | |
| "id": 99999, | |
| "type": "User" | |
| }, | |
| "body": params.get('body', ''), | |
| "created_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "updated_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "head": { | |
| "ref": params['head'], | |
| "sha": "new123abc456" | |
| }, | |
| "base": { | |
| "ref": params['base'], | |
| "sha": "base456def789" | |
| }, | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/pull/1", | |
| "mergeable": None, | |
| "draft": params.get('draft', False) | |
| } | |
| def _add_comment_template(self, params: dict) -> dict: | |
| """Mock response for adding a comment.""" | |
| return { | |
| "id": 987654321, | |
| "node_id": "IC_kwDOAB1CkM5ZGxyz", | |
| "url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/issues/comments/987654321", | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/issues/{params['issue_number']}#issuecomment-987654321", | |
| "body": params['body'], | |
| "user": { | |
| "login": "api-user", | |
| "id": 99999, | |
| "type": "User" | |
| }, | |
| "created_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "updated_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "issue_url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/issues/{params['issue_number']}" | |
| } | |
| def _create_issue_template(self, params: dict) -> dict: | |
| """Mock response for creating an issue.""" | |
| return { | |
| "id": 1111111111, | |
| "number": 42, | |
| "state": "open", | |
| "title": params['title'], | |
| "body": params.get('body', ''), | |
| "user": { | |
| "login": "api-user", | |
| "id": 99999, | |
| "type": "User" | |
| }, | |
| "labels": [], | |
| "assignees": [], | |
| "comments": 0, | |
| "created_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "updated_at": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/issues/42", | |
| "url": f"https://api.github.com/repos/{params['owner']}/{params['repo']}/issues/42" | |
| } | |
| def _list_prs_template(self, params: dict) -> dict: | |
| """Mock response for listing pull requests.""" | |
| return [ | |
| { | |
| "id": 1234567890, | |
| "number": 1, | |
| "state": params.get('state', 'open'), | |
| "title": "Feature: Add caching layer", | |
| "user": {"login": "dev1", "id": 111}, | |
| "created_at": "2025-11-28T10:00:00Z", | |
| "updated_at": "2025-11-30T12:00:00Z", | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/pull/1" | |
| }, | |
| { | |
| "id": 1234567891, | |
| "number": 2, | |
| "state": params.get('state', 'open'), | |
| "title": "Fix: Resolve memory leak", | |
| "user": {"login": "dev2", "id": 222}, | |
| "created_at": "2025-11-29T14:00:00Z", | |
| "updated_at": "2025-11-30T11:00:00Z", | |
| "html_url": f"https://github.com/{params['owner']}/{params['repo']}/pull/2" | |
| } | |
| ] | |
| def _execute_real(self, action: str, params: dict, credentials: dict) -> dict: | |
| """Execute real GitHub API call.""" | |
| token = credentials.get('token') | |
| if not token: | |
| return {'success': False, 'error': 'MISSING_TOKEN', 'message': 'GitHub token required'} | |
| headers = { | |
| 'Authorization': f'Bearer {token}', | |
| 'Accept': 'application/vnd.github+json', | |
| 'X-GitHub-Api-Version': '2022-11-28' | |
| } | |
| try: | |
| if action == 'get_pull_request': | |
| url = f"{self.base_url}/repos/{params['owner']}/{params['repo']}/pulls/{params['number']}" | |
| response = httpx.get(url, headers=headers, timeout=30.0) | |
| response.raise_for_status() | |
| return {'success': True, 'mode': 'real', 'service': self.service_name, 'action': action, 'data': response.json(), 'note': '๐ Real API call'} | |
| elif action == 'list_pull_requests': | |
| url = f"{self.base_url}/repos/{params['owner']}/{params['repo']}/pulls" | |
| response = httpx.get(url, headers=headers, params={'state': params.get('state', 'open')}, timeout=30.0) | |
| response.raise_for_status() | |
| return {'success': True, 'mode': 'real', 'service': self.service_name, 'action': action, 'data': response.json(), 'note': '๐ Real API call'} | |
| else: | |
| return {'success': False, 'error': 'NOT_IMPLEMENTED', 'message': f'Real API not implemented for: {action}'} | |
| except httpx.HTTPStatusError as e: | |
| return {'success': False, 'error': 'API_ERROR', 'message': f'GitHub API error: {e.response.status_code}', 'detail': e.response.text} | |
| except Exception as e: | |
| return {'success': False, 'error': 'UNKNOWN_ERROR', 'message': str(e)} | |