| from datetime import datetime |
| import json |
| import os |
| import sys |
| from unittest.mock import AsyncMock, Mock, patch |
| import pytest |
|
|
| |
| 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" |
|
|
| @patch('github_service.requests.request') |
| def test_make_request_success(self, mock_request): |
| """Test successful API request""" |
| |
| 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() |
|
|
| @patch('github_service.requests.request') |
| 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 |
|
|
| @patch('github_service.requests.request') |
| def test_make_request_rate_limit_retry(self, mock_request): |
| """Test API request with rate limiting and retry""" |
| |
| mock_response1 = Mock() |
| mock_response1.status_code = 429 |
| mock_response1.headers = { |
| "X-RateLimit-Remaining": "0", |
| "X-RateLimit-Reset": str(int(datetime.now().timestamp()) + 10) |
| } |
|
|
| |
| 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 |
|
|
| @patch('github_service.requests.request') |
| def test_make_request_network_error_retry(self, mock_request): |
| """Test API request with network error and retry""" |
| |
| 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() |
| headers = service._get_headers() |
|
|
| expected_headers = { |
| "Accept": "application/vnd.github.v3+json", |
| "Content-Type": "application/json" |
| } |
| assert headers == expected_headers |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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) |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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 |
| ) |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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 |
| ) |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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") |
|
|
| @patch.object(GitHubService, 'get_user_profile') |
| @patch.object(GitHubService, '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 |
|
|
| @patch.object(GitHubService, 'get_user_profile') |
| 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 |
|
|
| @patch.object(GitHubService, '_make_request') |
| 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( |
|
|