""" Sentry API simulator. Provides realistic mock responses for Sentry API actions. """ from typing import Optional from .base import BaseSimulator from datetime import datetime import time import random class SentrySimulator(BaseSimulator): """Simulator for Sentry API.""" def __init__(self): super().__init__('sentry') def load_mock_responses(self): """Load Sentry mock response templates.""" self.mock_responses = { 'get_issues': self._get_issues_template, 'update_issue': self._update_issue_template, 'get_events': self._get_events_template, 'get_issue_details': self._get_issue_details_template, 'resolve_issue': self._resolve_issue_template, 'get_projects': self._get_projects_template, } def get_required_permissions(self, action: str) -> set[str]: """Get required Sentry permissions for an action.""" permissions_map = { 'get_issues': {'project:read'}, 'update_issue': {'project:write'}, 'get_events': {'event:read'}, 'get_issue_details': {'project:read'}, 'resolve_issue': {'project:write'}, 'get_projects': {'project:read'}, } return permissions_map.get(action, set()) def validate_params(self, action: str, params: dict) -> tuple[bool, Optional[str]]: """Validate parameters for Sentry actions.""" if action == 'get_issues': required = {'project_id'} missing = required - set(params.keys()) if missing: return False, f"Missing required parameters: {missing}" elif action == 'update_issue': required = {'issue_id'} missing = required - set(params.keys()) if missing: return False, f"Missing required parameters: {missing}" elif action == 'get_events': required = {'issue_id'} missing = required - set(params.keys()) if missing: return False, f"Missing required parameters: {missing}" elif action == 'get_issue_details': required = {'issue_id'} missing = required - set(params.keys()) if missing: return False, f"Missing required parameters: {missing}" elif action == 'resolve_issue': required = {'issue_id'} missing = required - set(params.keys()) if missing: return False, f"Missing required parameters: {missing}" elif action == 'get_projects': # No required params pass else: return False, f"Unknown action: {action}" return True, None def generate_mock_response(self, action: str, params: dict) -> dict: """Generate realistic Sentry 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_issues_template(self, params: dict) -> list: """Mock response for getting project issues.""" issues = [] # Generate a few mock issues error_types = [ ("TypeError", "Cannot read property 'map' of undefined"), ("ReferenceError", "userId is not defined"), ("NetworkError", "Failed to fetch data from API"), ] for i, (error_type, message) in enumerate(error_types): issue_id = f"issue-{1000+i}" issues.append({ "id": issue_id, "shareId": None, "shortId": f"PROJECT-{i+1}", "title": f"{error_type}: {message}", "culprit": f"app/components/UserList.tsx in ", "permalink": f"https://sentry.io/organizations/myorg/issues/{issue_id}/", "logger": None, "level": "error", "status": "unresolved" if i < 2 else "resolved", "statusDetails": {}, "isPublic": False, "platform": "javascript", "project": { "id": params['project_id'], "name": "my-app", "slug": "my-app", "platform": "javascript" }, "type": "error", "metadata": { "type": error_type, "value": message, "filename": "app/components/UserList.tsx", "function": "" }, "numComments": 0, "assignedTo": None, "isBookmarked": False, "isSubscribed": False, "subscriptionDetails": None, "hasSeen": True, "annotations": [], "isUnhandled": True, "count": str(random.randint(5, 100)), "userCount": random.randint(2, 20), "firstSeen": f"2025-11-{20+i}T10:00:00.000000Z", "lastSeen": datetime.utcnow().isoformat() + "Z", "stats": { "24h": [[int(time.time()) - 86400, random.randint(1, 10)] for _ in range(24)] } }) return issues def _update_issue_template(self, params: dict) -> dict: """Mock response for updating an issue.""" issue_id = params['issue_id'] return { "id": issue_id, "status": params.get('status', 'resolved'), "statusDetails": {}, "assignedTo": { "id": params.get('assignedTo'), "name": "Team Member", "email": "member@company.com" } if params.get('assignedTo') else None, "hasSeen": True, "isBookmarked": params.get('isBookmarked', False), "isSubscribed": params.get('isSubscribed', True) } def _get_events_template(self, params: dict) -> list: """Mock response for getting events for an issue.""" events = [] # Generate a few mock events for i in range(5): event_id = f"event-{int(time.time())}-{i}" events.append({ "id": event_id, "groupID": params['issue_id'], "eventID": event_id, "projectID": "123456", "size": 12345, "platform": "javascript", "message": "TypeError: Cannot read property 'map' of undefined", "datetime": f"2025-11-30T{10+i}:00:00.000000Z", "tags": [ {"key": "browser", "value": "Chrome 119.0.0"}, {"key": "environment", "value": "production"}, {"key": "level", "value": "error"}, {"key": "url", "value": "https://myapp.com/users"} ], "context": { "browser": { "name": "Chrome", "version": "119.0.0" }, "os": { "name": "Mac OS X", "version": "10.15.7" } }, "user": { "id": f"user-{i}", "email": f"user{i}@example.com", "ip_address": f"192.168.1.{i+1}" }, "entries": [ { "type": "exception", "data": { "values": [ { "type": "TypeError", "value": "Cannot read property 'map' of undefined", "stacktrace": { "frames": [ { "filename": "app/components/UserList.tsx", "function": "", "lineno": 42, "colno": 15, "context_line": " const userNames = users.map(u => u.name);", "in_app": True } ] } } ] } } ] }) return events def _get_issue_details_template(self, params: dict) -> dict: """Mock response for getting detailed issue information.""" issue_id = params['issue_id'] return { "id": issue_id, "shareId": None, "shortId": "PROJECT-1", "title": "TypeError: Cannot read property 'map' of undefined", "culprit": "app/components/UserList.tsx in ", "permalink": f"https://sentry.io/organizations/myorg/issues/{issue_id}/", "logger": None, "level": "error", "status": "unresolved", "statusDetails": {}, "isPublic": False, "platform": "javascript", "project": { "id": "123456", "name": "my-app", "slug": "my-app", "platform": "javascript" }, "type": "error", "metadata": { "type": "TypeError", "value": "Cannot read property 'map' of undefined", "filename": "app/components/UserList.tsx", "function": "" }, "numComments": 2, "assignedTo": { "id": "user-1", "name": "Jane Developer", "email": "jane@company.com" }, "isBookmarked": False, "isSubscribed": True, "subscriptionDetails": { "reason": "committed" }, "hasSeen": True, "annotations": [], "isUnhandled": True, "count": "47", "userCount": 12, "firstSeen": "2025-11-20T10:00:00.000000Z", "lastSeen": datetime.utcnow().isoformat() + "Z", "stats": { "24h": [[int(time.time()) - 86400 + (i * 3600), random.randint(1, 5)] for i in range(24)] }, "activity": [ { "type": "note", "user": { "name": "John Developer", "email": "john@company.com" }, "dateCreated": "2025-11-29T14:00:00.000000Z", "data": { "text": "Looking into this issue now. Appears to be related to missing user data." } } ] } def _resolve_issue_template(self, params: dict) -> dict: """Mock response for resolving an issue.""" issue_id = params['issue_id'] return { "id": issue_id, "status": "resolved", "statusDetails": { "inNextRelease": params.get('inNextRelease', False), "inRelease": params.get('inRelease') }, "hasSeen": True } def _get_projects_template(self, params: dict) -> list: """Mock response for listing projects.""" return [ { "id": "123456", "slug": "my-app", "name": "My App", "platform": "javascript", "dateCreated": "2025-01-15T00:00:00.000000Z", "isBookmarked": False, "isMember": True, "teams": [ { "id": "team-1", "slug": "engineering", "name": "Engineering" } ], "stats": { "24h": { "total": 125 } } }, { "id": "123457", "slug": "backend-api", "name": "Backend API", "platform": "python", "dateCreated": "2025-02-01T00:00:00.000000Z", "isBookmarked": True, "isMember": True, "teams": [ { "id": "team-1", "slug": "engineering", "name": "Engineering" } ], "stats": { "24h": { "total": 43 } } } ]