Spaces:
Sleeping
Sleeping
| from datetime import datetime | |
| import json | |
| import os | |
| import sys | |
| from unittest.mock import AsyncMock, Mock, patch | |
| import pytest | |
| # Add the parent directory to the path to import the service | |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from github_service import GitHubService, GitHubServiceType | |
| class TestGitHubService: | |
| """Test suite for GitHubService""" | |
| def setup_method(self): | |
| """Set up test fixtures before each test method""" | |
| self.service = GitHubService() | |
| self.service.set_access_token("test_access_token") | |
| def test_initialization(self): | |
| """Test GitHubService initialization""" | |
| service = GitHubService() | |
| assert service.api_base_url == "https://api.github.com" | |
| assert service.timeout == 30 | |
| assert service.max_retries == 3 | |
| assert service.client_id is None or isinstance(service.client_id, str) | |
| assert service.client_secret is None or isinstance(service.client_secret, str) | |
| def test_set_access_token(self): | |
| """Test setting access token""" | |
| service = GitHubService() | |
| service.set_access_token("test_token") | |
| assert service.access_token == "test_token" | |
| def test_make_request_success(self, mock_request): | |
| """Test successful API request""" | |
| # Mock response | |
| mock_response = Mock() | |
| mock_response.status_code = 200 | |
| mock_response.json.return_value = {"id": 1, "name": "test"} | |
| mock_response.headers = { | |
| "X-RateLimit-Remaining": "4999", | |
| "X-RateLimit-Reset": "1700000000" | |
| } | |
| mock_request.return_value = mock_response | |
| result = self.service._make_request("GET", "/user") | |
| assert result == {"id": 1, "name": "test"} | |
| assert self.service.rate_limit_remaining == 4999 | |
| assert self.service.rate_limit_reset == 1700000000 | |
| mock_request.assert_called_once() | |
| def test_make_request_unauthorized(self, mock_request): | |
| """Test API request with unauthorized error""" | |
| mock_response = Mock() | |
| mock_response.status_code = 401 | |
| mock_response.text = "Unauthorized" | |
| mock_request.return_value = mock_response | |
| result = self.service._make_request("GET", "/user") | |
| assert result is None | |
| def test_make_request_rate_limit_retry(self, mock_request): | |
| """Test API request with rate limiting and retry""" | |
| # First call: rate limited | |
| mock_response1 = Mock() | |
| mock_response1.status_code = 429 | |
| mock_response1.headers = { | |
| "X-RateLimit-Remaining": "0", | |
| "X-RateLimit-Reset": str(int(datetime.now().timestamp()) + 10) | |
| } | |
| # Second call: success | |
| mock_response2 = Mock() | |
| mock_response2.status_code = 200 | |
| mock_response2.json.return_value = {"id": 1, "name": "test"} | |
| mock_response2.headers = { | |
| "X-RateLimit-Remaining": "4999", | |
| "X-RateLimit-Reset": "1700000000" | |
| } | |
| mock_request.side_effect = [mock_response1, mock_response2] | |
| result = self.service._make_request("GET", "/user") | |
| assert result == {"id": 1, "name": "test"} | |
| assert mock_request.call_count == 2 | |
| def test_make_request_network_error_retry(self, mock_request): | |
| """Test API request with network error and retry""" | |
| # First call: network error | |
| mock_request.side_effect = [ | |
| Exception("Network error"), | |
| Mock(status_code=200, json=lambda: {"id": 1}, headers={}) | |
| ] | |
| result = self.service._make_request("GET", "/user") | |
| assert result == {"id": 1} | |
| assert mock_request.call_count == 2 | |
| def test_get_headers_with_token(self): | |
| """Test headers generation with access token""" | |
| self.service.set_access_token("test_token") | |
| headers = self.service._get_headers() | |
| expected_headers = { | |
| "Accept": "application/vnd.github.v3+json", | |
| "Content-Type": "application/json", | |
| "Authorization": "token test_token" | |
| } | |
| assert headers == expected_headers | |
| def test_get_headers_without_token(self): | |
| """Test headers generation without access token""" | |
| service = GitHubService() # No token set | |
| headers = service._get_headers() | |
| expected_headers = { | |
| "Accept": "application/vnd.github.v3+json", | |
| "Content-Type": "application/json" | |
| } | |
| assert headers == expected_headers | |
| def test_get_user_profile(self, mock_make_request): | |
| """Test getting user profile""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "login": "testuser", | |
| "name": "Test User", | |
| "email": "test@example.com" | |
| } | |
| result = self.service.get_user_profile() | |
| assert result == { | |
| "id": 1, | |
| "login": "testuser", | |
| "name": "Test User", | |
| "email": "test@example.com" | |
| } | |
| mock_make_request.assert_called_once_with("GET", "/user") | |
| def test_get_organizations(self, mock_make_request): | |
| """Test getting organizations""" | |
| mock_make_request.return_value = [ | |
| {"id": 1, "login": "org1"}, | |
| {"id": 2, "login": "org2"} | |
| ] | |
| result = self.service.get_organizations() | |
| assert result == [ | |
| {"id": 1, "login": "org1"}, | |
| {"id": 2, "login": "org2"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/user/orgs") | |
| def test_get_repositories_user(self, mock_make_request): | |
| """Test getting user repositories""" | |
| mock_make_request.return_value = [ | |
| {"id": 1, "name": "repo1", "private": False}, | |
| {"id": 2, "name": "repo2", "private": True} | |
| ] | |
| result = self.service.get_repositories() | |
| assert result == [ | |
| {"id": 1, "name": "repo1", "private": False}, | |
| {"id": 2, "name": "repo2", "private": True} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/user/repos") | |
| def test_get_repositories_org(self, mock_make_request): | |
| """Test getting organization repositories""" | |
| mock_make_request.return_value = [ | |
| {"id": 1, "name": "org-repo1", "private": False} | |
| ] | |
| result = self.service.get_repositories(org="testorg") | |
| assert result == [{"id": 1, "name": "org-repo1", "private": False}] | |
| mock_make_request.assert_called_once_with("GET", "/orgs/testorg/repos") | |
| def test_get_repository(self, mock_make_request): | |
| """Test getting specific repository""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "name": "test-repo", | |
| "full_name": "owner/test-repo", | |
| "private": False, | |
| "html_url": "https://github.com/owner/test-repo" | |
| } | |
| result = self.service.get_repository("owner", "test-repo") | |
| assert result == { | |
| "id": 1, | |
| "name": "test-repo", | |
| "full_name": "owner/test-repo", | |
| "private": False, | |
| "html_url": "https://github.com/owner/test-repo" | |
| } | |
| mock_make_request.assert_called_once_with("GET", "/repos/owner/test-repo") | |
| def test_create_repository(self, mock_make_request): | |
| """Test creating repository""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "name": "new-repo", | |
| "full_name": "user/new-repo", | |
| "private": True | |
| } | |
| result = self.service.create_repository( | |
| name="new-repo", | |
| description="Test repository", | |
| private=True, | |
| auto_init=True | |
| ) | |
| expected_data = { | |
| "name": "new-repo", | |
| "description": "Test repository", | |
| "private": True, | |
| "auto_init": True | |
| } | |
| assert result == { | |
| "id": 1, | |
| "name": "new-repo", | |
| "full_name": "user/new-repo", | |
| "private": True | |
| } | |
| mock_make_request.assert_called_once_with("POST", "/user/repos", expected_data) | |
| def test_get_issues(self, mock_make_request): | |
| """Test getting issues""" | |
| mock_make_request.return_value = [ | |
| {"id": 1, "title": "Issue 1", "state": "open"}, | |
| {"id": 2, "title": "Issue 2", "state": "closed"} | |
| ] | |
| result = self.service.get_issues("owner", "repo", state="open") | |
| assert result == [ | |
| {"id": 1, "title": "Issue 1", "state": "open"}, | |
| {"id": 2, "title": "Issue 2", "state": "closed"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/repos/owner/repo/issues") | |
| def test_create_issue(self, mock_make_request): | |
| """Test creating issue""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "title": "New Issue", | |
| "body": "Issue description", | |
| "state": "open" | |
| } | |
| result = self.service.create_issue( | |
| owner="owner", | |
| repo="repo", | |
| title="New Issue", | |
| body="Issue description", | |
| labels=["bug", "enhancement"], | |
| assignees=["user1"] | |
| ) | |
| expected_data = { | |
| "title": "New Issue", | |
| "body": "Issue description", | |
| "labels": ["bug", "enhancement"], | |
| "assignees": ["user1"] | |
| } | |
| assert result == { | |
| "id": 1, | |
| "title": "New Issue", | |
| "body": "Issue description", | |
| "state": "open" | |
| } | |
| mock_make_request.assert_called_once_with( | |
| "POST", "/repos/owner/repo/issues", expected_data | |
| ) | |
| def test_get_pull_requests(self, mock_make_request): | |
| """Test getting pull requests""" | |
| mock_make_request.return_value = [ | |
| {"id": 1, "title": "PR 1", "state": "open"}, | |
| {"id": 2, "title": "PR 2", "state": "closed"} | |
| ] | |
| result = self.service.get_pull_requests("owner", "repo", state="open") | |
| assert result == [ | |
| {"id": 1, "title": "PR 1", "state": "open"}, | |
| {"id": 2, "title": "PR 2", "state": "closed"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/repos/owner/repo/pulls") | |
| def test_create_pull_request(self, mock_make_request): | |
| """Test creating pull request""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "title": "New PR", | |
| "head": "feature-branch", | |
| "base": "main", | |
| "state": "open" | |
| } | |
| result = self.service.create_pull_request( | |
| owner="owner", | |
| repo="repo", | |
| title="New PR", | |
| head="feature-branch", | |
| base="main", | |
| body="PR description" | |
| ) | |
| expected_data = { | |
| "title": "New PR", | |
| "head": "feature-branch", | |
| "base": "main", | |
| "body": "PR description" | |
| } | |
| assert result == { | |
| "id": 1, | |
| "title": "New PR", | |
| "head": "feature-branch", | |
| "base": "main", | |
| "state": "open" | |
| } | |
| mock_make_request.assert_called_once_with( | |
| "POST", "/repos/owner/repo/pulls", expected_data | |
| ) | |
| def test_get_workflow_runs(self, mock_make_request): | |
| """Test getting workflow runs""" | |
| mock_make_request.return_value = { | |
| "workflow_runs": [ | |
| {"id": 1, "name": "CI", "status": "completed"}, | |
| {"id": 2, "name": "Deploy", "status": "in_progress"} | |
| ] | |
| } | |
| result = self.service.get_workflow_runs("owner", "repo", branch="main") | |
| assert result == [ | |
| {"id": 1, "name": "CI", "status": "completed"}, | |
| {"id": 2, "name": "Deploy", "status": "in_progress"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/repos/owner/repo/actions/runs") | |
| def test_search_code(self, mock_make_request): | |
| """Test searching code""" | |
| mock_make_request.return_value = { | |
| "items": [ | |
| {"id": 1, "name": "file1.py", "path": "src/file1.py"}, | |
| {"id": 2, "name": "file2.py", "path": "src/file2.py"} | |
| ] | |
| } | |
| result = self.service.search_code("test query", org="testorg") | |
| assert result == [ | |
| {"id": 1, "name": "file1.py", "path": "src/file1.py"}, | |
| {"id": 2, "name": "file2.py", "path": "src/file2.py"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/search/code?q=org:testorg test query") | |
| def test_search_issues(self, mock_make_request): | |
| """Test searching issues""" | |
| mock_make_request.return_value = { | |
| "items": [ | |
| {"id": 1, "title": "Issue 1", "state": "open"}, | |
| {"id": 2, "title": "Issue 2", "state": "closed"} | |
| ] | |
| } | |
| result = self.service.search_issues("bug", org="testorg") | |
| assert result == [ | |
| {"id": 1, "title": "Issue 1", "state": "open"}, | |
| {"id": 2, "title": "Issue 2", "state": "closed"} | |
| ] | |
| mock_make_request.assert_called_once_with("GET", "/search/issues?q=org:testorg bug") | |
| def test_get_rate_limit(self, mock_make_request): | |
| """Test getting rate limit""" | |
| mock_make_request.return_value = { | |
| "resources": { | |
| "core": {"limit": 5000, "remaining": 4999, "reset": 1700000000}, | |
| "search": {"limit": 30, "remaining": 29, "reset": 1700000000} | |
| } | |
| } | |
| result = self.service.get_rate_limit() | |
| assert result == { | |
| "resources": { | |
| "core": {"limit": 5000, "remaining": 4999, "reset": 1700000000}, | |
| "search": {"limit": 30, "remaining": 29, "reset": 1700000000} | |
| } | |
| } | |
| mock_make_request.assert_called_once_with("GET", "/rate_limit") | |
| def test_health_check_healthy(self, mock_get_rate_limit, mock_get_user_profile): | |
| """Test health check when service is healthy""" | |
| mock_get_user_profile.return_value = {"login": "testuser"} | |
| mock_get_rate_limit.return_value = { | |
| "resources": {"core": {"remaining": 4999, "reset": 1700000000}} | |
| } | |
| result = self.service.health_check() | |
| assert result["status"] == "healthy" | |
| assert result["service"] == "github" | |
| assert result["user"] == "testuser" | |
| assert "timestamp" in result | |
| def test_health_check_unhealthy(self, mock_get_user_profile): | |
| """Test health check when service is unhealthy""" | |
| mock_get_user_profile.return_value = None | |
| result = self.service.health_check() | |
| assert result["status"] == "unhealthy" | |
| assert result["service"] == "github" | |
| assert "error" in result | |
| assert "timestamp" in result | |
| def test_create_webhook(self, mock_make_request): | |
| """Test creating webhook""" | |
| mock_make_request.return_value = { | |
| "id": 1, | |
| "name": "web", | |
| "active": True, | |
| "events": ["push", "pull_request"] | |
| } | |
| result = self.service.create_webhook( | |
| owner="owner", | |
| repo="repo", | |
| url="https://example.com/webhook", | |
| events=["push", "pull_request"] | |
| ) | |
| expected_data = { | |
| "name": "web", | |
| "active": True, | |
| "events": ["push", "pull_request"], | |
| "config": { | |
| "url": "https://example.com/webhook", | |
| "content_type": "json" | |
| } | |
| } | |
| assert result == { | |
| "id": 1, | |
| "name": "web", | |
| "active": True, | |
| "events": ["push", "pull_request"] | |
| } | |
| mock_make_request.assert_called_once_with( | |