from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest from pydantic import SecretStr from openhands.integrations.forgejo.forgejo_service import ForgejoService from openhands.integrations.service_types import ( ProviderType, Repository, RequestMethod, User, ) from openhands.server.types import AppMode @pytest.fixture def forgejo_service(): return ForgejoService(token=SecretStr('test_token')) @pytest.mark.asyncio async def test_get_user(forgejo_service): # Mock response data mock_user_data = { 'id': 1, 'username': 'test_user', 'avatar_url': 'https://codeberg.org/avatar/test_user', 'full_name': 'Test User', 'email': 'test@example.com', 'organization': 'Test Org', } # Mock the _make_request method forgejo_service._make_request = AsyncMock(return_value=(mock_user_data, {})) # Call the method user = await forgejo_service.get_user() # Verify the result assert isinstance(user, User) assert user.id == '1' assert user.login == 'test_user' assert user.avatar_url == 'https://codeberg.org/avatar/test_user' assert user.name == 'Test User' assert user.email == 'test@example.com' assert user.company == 'Test Org' # Verify the _fetch_data call forgejo_service._make_request.assert_called_once_with( f'{forgejo_service.BASE_URL}/user' ) @pytest.mark.asyncio async def test_search_repositories(forgejo_service): # Mock response data mock_repos_data = { 'data': [ { 'id': 1, 'full_name': 'test_user/repo1', 'stars_count': 10, }, { 'id': 2, 'full_name': 'test_user/repo2', 'stars_count': 20, }, ] } # Mock the _fetch_data method forgejo_service._make_request = AsyncMock(return_value=(mock_repos_data, {})) # Call the method repos = await forgejo_service.search_repositories( 'test', 10, 'updated', 'desc', public=False, app_mode=AppMode.OPENHANDS ) # Verify the result assert len(repos) == 2 assert all(isinstance(repo, Repository) for repo in repos) assert repos[0].id == '1' assert repos[0].full_name == 'test_user/repo1' assert repos[0].stargazers_count == 10 assert repos[0].git_provider == ProviderType.FORGEJO assert repos[1].id == '2' assert repos[1].full_name == 'test_user/repo2' assert repos[1].stargazers_count == 20 assert repos[1].git_provider == ProviderType.FORGEJO # Verify the _fetch_data call forgejo_service._make_request.assert_called_once_with( f'{forgejo_service.BASE_URL}/repos/search', { 'q': 'test', 'limit': 10, 'sort': 'updated', 'order': 'desc', 'mode': 'source', }, ) @pytest.mark.asyncio async def test_get_all_repositories(forgejo_service): # Mock response data for first page mock_repos_data_page1 = [ { 'id': 1, 'full_name': 'test_user/repo1', 'stars_count': 10, }, { 'id': 2, 'full_name': 'test_user/repo2', 'stars_count': 20, }, ] # Mock response data for second page mock_repos_data_page2 = [ { 'id': 3, 'full_name': 'test_user/repo3', 'stars_count': 30, }, ] # Mock the _fetch_data method to return different data for different pages forgejo_service._make_request = AsyncMock() forgejo_service._make_request.side_effect = [ ( mock_repos_data_page1, {'Link': '; rel="next"'}, ), (mock_repos_data_page2, {'Link': ''}), ] # Call the method repos = await forgejo_service.get_all_repositories('updated', AppMode.OPENHANDS) # Verify the result assert len(repos) == 3 assert all(isinstance(repo, Repository) for repo in repos) assert repos[0].id == '1' assert repos[0].full_name == 'test_user/repo1' assert repos[0].stargazers_count == 10 assert repos[0].git_provider == ProviderType.FORGEJO assert repos[1].id == '2' assert repos[1].full_name == 'test_user/repo2' assert repos[1].stargazers_count == 20 assert repos[1].git_provider == ProviderType.FORGEJO assert repos[2].id == '3' assert repos[2].full_name == 'test_user/repo3' assert repos[2].stargazers_count == 30 assert repos[2].git_provider == ProviderType.FORGEJO # Verify the _fetch_data calls assert forgejo_service._make_request.call_count == 2 forgejo_service._make_request.assert_any_call( f'{forgejo_service.BASE_URL}/user/repos', {'page': '1', 'limit': '100', 'sort': 'updated'}, ) forgejo_service._make_request.assert_any_call( f'{forgejo_service.BASE_URL}/user/repos', {'page': '2', 'limit': '100', 'sort': 'updated'}, ) @pytest.mark.asyncio async def test_make_request_success(forgejo_service): # Mock httpx.AsyncClient mock_client = AsyncMock() mock_response = MagicMock() mock_response.status_code = 200 mock_response.raise_for_status = MagicMock() mock_response.json.return_value = {'key': 'value'} mock_response.headers = {'Link': 'next_link', 'Content-Type': 'application/json'} mock_client.__aenter__.return_value.get.return_value = mock_response # Patch httpx.AsyncClient with patch('httpx.AsyncClient', return_value=mock_client): # Call the method result, headers = await forgejo_service._make_request( 'https://test.url', {'param': 'value'} ) # Verify the result assert result == {'key': 'value'} assert headers == {'Link': 'next_link'} mock_response.raise_for_status.assert_called_once() @pytest.mark.asyncio async def test_make_request_auth_error(forgejo_service): # Mock httpx.AsyncClient mock_client = AsyncMock() mock_response = MagicMock() mock_response.status_code = 401 mock_response.raise_for_status.side_effect = httpx.HTTPStatusError( '401 Unauthorized', request=MagicMock(), response=mock_response ) mock_client.__aenter__.return_value.get.return_value = mock_response # Patch httpx.AsyncClient with patch('httpx.AsyncClient', return_value=mock_client): # Call the method and expect an exception with pytest.raises(Exception) as excinfo: await forgejo_service._make_request('https://test.url', {'param': 'value'}) # Verify the exception assert 'Invalid forgejo token' in str(excinfo.value) @pytest.mark.asyncio async def test_make_request_other_error(forgejo_service): # Mock httpx.AsyncClient mock_client = AsyncMock() mock_response = MagicMock() mock_response.status_code = 500 mock_response.raise_for_status.side_effect = httpx.HTTPStatusError( '500 Server Error', request=MagicMock(), response=mock_response ) mock_client.__aenter__.return_value.get.return_value = mock_response # Patch httpx.AsyncClient with patch('httpx.AsyncClient', return_value=mock_client): # Call the method and expect an exception with pytest.raises(Exception) as excinfo: await forgejo_service._make_request('https://test.url', {'param': 'value'}) # Verify the exception assert 'Unknown error' in str(excinfo.value) @pytest.mark.asyncio async def test_create_pull_request(forgejo_service): mock_response = {'index': 42, 'html_url': 'https://example/pr/42'} forgejo_service._make_request = AsyncMock(return_value=(mock_response, {})) data = {'owner': 'org', 'repo': 'project', 'title': 'Add feature'} result = await forgejo_service.create_pull_request(data.copy()) assert result['number'] == 42 forgejo_service._make_request.assert_awaited_once_with( f'{forgejo_service.BASE_URL}/repos/org/project/pulls', {'title': 'Add feature'}, method=RequestMethod.POST, ) @pytest.mark.asyncio async def test_request_reviewers(forgejo_service): forgejo_service._make_request = AsyncMock(return_value=({}, {})) await forgejo_service.request_reviewers('org/project', 5, ['alice']) forgejo_service._make_request.assert_awaited_once_with( f'{forgejo_service.BASE_URL}/repos/org/project/pulls/5/requested_reviewers', {'reviewers': ['alice']}, method=RequestMethod.POST, ) @pytest.mark.asyncio async def test_request_reviewers_empty_list(forgejo_service): forgejo_service._make_request = AsyncMock() await forgejo_service.request_reviewers('org/project', 5, []) forgejo_service._make_request.assert_not_called()