Spaces:
Configuration error
Configuration error
""" | |
Unit tests for the github module. | |
""" | |
import unittest | |
from unittest.mock import patch, MagicMock | |
import requests | |
import base64 | |
from functions import github | |
# pylint: disable=protected-access | |
class TestGetGitHubRepositories(unittest.TestCase): | |
"""Test cases for the get_github_repositories function.""" | |
def test_successful_repository_retrieval(self, mock_process, mock_get_repos): | |
"""Test successful repository retrieval.""" | |
# Mock raw repository data | |
mock_raw_repos = [ | |
{ | |
"name": "test-repo", | |
"description": "Test repository", | |
"language": "Python", | |
"stargazers_count": 10, | |
"forks_count": 5, | |
"updated_at": "2024-01-01T00:00:00Z", | |
"html_url": "https://github.com/user/test-repo", | |
"topics": ["python", "test"], | |
"fork": False | |
} | |
] | |
# Mock processed repository data | |
mock_processed_repos = [ | |
{ | |
"name": "test-repo", | |
"description": "Test repository", | |
"language": "Python", | |
"stars": 10, | |
"forks": 5, | |
"updated_at": "2024-01-01T00:00:00Z", | |
"created_at": "2024-01-01T00:00:00Z", | |
"html_url": "https://github.com/user/test-repo", | |
"topics": ["python", "test"], | |
"size": 100, | |
"readme": "# Test Repository\n\nThis is a test README." | |
} | |
] | |
mock_get_repos.return_value = mock_raw_repos | |
mock_process.return_value = mock_processed_repos | |
with patch('pathlib.Path.mkdir'), patch('builtins.open'), patch('json.dump'): | |
result = github.get_github_repositories("testuser") | |
self.assertEqual(result, mock_processed_repos) | |
mock_get_repos.assert_called_once_with("testuser") | |
mock_process.assert_called_once_with(mock_raw_repos) | |
def test_no_repositories_found(self, mock_get_repos): | |
"""Test when no repositories are found.""" | |
mock_get_repos.return_value = None | |
result = github.get_github_repositories("emptyuser") | |
self.assertIsNone(result) | |
mock_get_repos.assert_called_once_with("emptyuser") | |
def test_exception_during_processing(self, mock_get_repos): | |
"""Test exception handling during repository processing.""" | |
mock_get_repos.side_effect = Exception("API error") | |
result = github.get_github_repositories("erroruser") | |
self.assertIsNone(result) | |
mock_get_repos.assert_called_once_with("erroruser") | |
def test_file_saving_error(self, mock_process, mock_get_repos): | |
"""Test that file saving errors don't break the function.""" | |
mock_get_repos.return_value = [{"name": "test"}] | |
mock_process.return_value = [{"name": "test", "stars": 0}] | |
# Mock file operations to raise an exception | |
with patch('pathlib.Path.mkdir'), \ | |
patch('builtins.open', side_effect=Exception("File error")), \ | |
patch('logging.getLogger') as mock_get_logger: | |
mock_logger = mock_get_logger.return_value | |
result = github.get_github_repositories("testuser") | |
# Should still return the repositories despite file error | |
self.assertEqual(result, [{"name": "test", "stars": 0}]) | |
# Should log a warning about the file save error | |
mock_logger.warning.assert_called() | |
class TestGetUserRepositories(unittest.TestCase): | |
"""Test cases for the _get_user_repositories function.""" | |
def test_successful_single_page(self, mock_get): | |
"""Test successful repository retrieval with single page.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = [ | |
{ | |
"name": "repo1", | |
"description": "First repo", | |
"language": "Python" | |
}, | |
{ | |
"name": "repo2", | |
"description": "Second repo", | |
"language": "JavaScript" | |
} | |
] | |
mock_get.return_value = mock_response | |
result = github._get_user_repositories("testuser") | |
self.assertEqual(len(result), 2) | |
self.assertEqual(result[0]["name"], "repo1") | |
self.assertEqual(result[1]["name"], "repo2") | |
# Verify API call parameters | |
mock_get.assert_called_once() | |
call_args = mock_get.call_args | |
self.assertIn("https://api.github.com/users/testuser/repos", call_args[0][0]) | |
self.assertEqual(call_args[1]["params"]["type"], "public") | |
self.assertEqual(call_args[1]["params"]["sort"], "updated") | |
self.assertEqual(call_args[1]["headers"]["User-Agent"], "Resumate-App/1.0") | |
def test_successful_multiple_pages(self, mock_get): | |
"""Test successful repository retrieval with multiple pages.""" | |
# First page response | |
first_response = MagicMock() | |
first_response.status_code = 200 | |
first_response.json.return_value = [{"name": f"repo{i}"} for i in range(100)] | |
# Second page response (less than per_page, so pagination stops) | |
second_response = MagicMock() | |
second_response.status_code = 200 | |
second_response.json.return_value = [{"name": f"repo{i}"} for i in range(100, 150)] | |
mock_get.side_effect = [first_response, second_response] | |
result = github._get_user_repositories("testuser") | |
self.assertEqual(len(result), 150) | |
self.assertEqual(mock_get.call_count, 2) | |
def test_api_error_404(self, mock_get): | |
"""Test handling of 404 user not found error.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 404 | |
mock_get.return_value = mock_response | |
result = github._get_user_repositories("nonexistentuser") | |
self.assertIsNone(result) | |
def test_api_error_403(self, mock_get): | |
"""Test handling of 403 rate limit error.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 403 | |
mock_get.return_value = mock_response | |
result = github._get_user_repositories("testuser") | |
self.assertIsNone(result) | |
def test_network_error_no_repos(self, mock_get): | |
"""Test handling of network errors with no existing repos.""" | |
mock_get.side_effect = requests.RequestException("Connection error") | |
result = github._get_user_repositories("testuser") | |
self.assertIsNone(result) | |
def test_network_error_with_partial_repos(self, mock_get): | |
"""Test handling of network errors after getting some repos.""" | |
# First call succeeds | |
first_response = MagicMock() | |
first_response.status_code = 200 | |
first_response.json.return_value = [{"name": "repo1"}] | |
# Second call fails | |
mock_get.side_effect = [first_response, requests.RequestException("Connection error")] | |
with patch('logging.getLogger'): | |
result = github._get_user_repositories("testuser") | |
# Should return the partial data from the first successful call | |
self.assertEqual(result, [{"name": "repo1"}]) | |
def test_safety_limit_prevents_infinite_loop(self, mock_get): | |
"""Test that safety limit prevents infinite pagination.""" | |
# Mock response that always returns full pages | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = [{"name": f"repo{i}"} for i in range(100)] | |
mock_get.return_value = mock_response | |
result = github._get_user_repositories("testuser") | |
# Should stop at page 10 (safety limit) | |
self.assertEqual(mock_get.call_count, 10) | |
self.assertEqual(len(result), 1000) # 10 pages * 100 repos each | |
class TestProcessRepositoryData(unittest.TestCase): | |
"""Test cases for the _process_repository_data function.""" | |
def test_basic_processing(self, mock_get_readme): | |
"""Test basic repository data processing.""" | |
mock_get_readme.return_value = "# Test Repository\n\nThis is a test README." | |
raw_repos = [ | |
{ | |
"name": "test-repo", | |
"description": "Test repository", | |
"language": "Python", | |
"stargazers_count": 10, | |
"forks_count": 5, | |
"updated_at": "2024-01-01T00:00:00Z", | |
"created_at": "2024-01-01T00:00:00Z", | |
"html_url": "https://github.com/user/test-repo", | |
"topics": ["python", "test"], | |
"size": 100, | |
"fork": False | |
} | |
] | |
result = github._process_repository_data(raw_repos) | |
self.assertEqual(len(result), 1) | |
processed_repo = result[0] | |
self.assertEqual(processed_repo["name"], "test-repo") | |
self.assertEqual(processed_repo["description"], "Test repository") | |
self.assertEqual(processed_repo["language"], "Python") | |
self.assertEqual(processed_repo["stars"], 10) | |
self.assertEqual(processed_repo["forks"], 5) | |
self.assertEqual(processed_repo["updated_at"], "2024-01-01T00:00:00Z") | |
self.assertEqual(processed_repo["created_at"], "2024-01-01T00:00:00Z") | |
self.assertEqual(processed_repo["html_url"], "https://github.com/user/test-repo") | |
self.assertEqual(processed_repo["topics"], ["python", "test"]) | |
self.assertEqual(processed_repo["size"], 100) | |
self.assertEqual(processed_repo["readme"], "# Test Repository\n\nThis is a test README.") | |
# Verify README was fetched | |
mock_get_readme.assert_called_once_with("https://github.com/user/test-repo") | |
def test_fork_filtering(self, mock_get_readme): | |
"""Test filtering of unmodified forks.""" | |
mock_get_readme.return_value = "# Repository README" | |
raw_repos = [ | |
{ | |
"name": "original-repo", | |
"fork": False, | |
"stargazers_count": 5, | |
"html_url": "https://github.com/user/original-repo" | |
}, | |
{ | |
"name": "unmodified-fork", | |
"fork": True, | |
"stargazers_count": 0, | |
"html_url": "https://github.com/user/unmodified-fork" | |
}, | |
{ | |
"name": "modified-fork", | |
"fork": True, | |
"stargazers_count": 3, | |
"html_url": "https://github.com/user/modified-fork" | |
} | |
] | |
result = github._process_repository_data(raw_repos) | |
# Should include original repo and modified fork, exclude unmodified fork | |
self.assertEqual(len(result), 2) | |
repo_names = [repo["name"] for repo in result] | |
self.assertIn("original-repo", repo_names) | |
self.assertIn("modified-fork", repo_names) | |
self.assertNotIn("unmodified-fork", repo_names) | |
# Verify README was fetched for included repos only | |
self.assertEqual(mock_get_readme.call_count, 2) | |
def test_missing_fields(self, mock_get_readme): | |
"""Test handling of missing fields in repository data.""" | |
mock_get_readme.return_value = "" | |
raw_repos = [ | |
{ | |
"name": "minimal-repo" | |
# Missing most optional fields | |
} | |
] | |
result = github._process_repository_data(raw_repos) | |
self.assertEqual(len(result), 1) | |
processed_repo = result[0] | |
self.assertEqual(processed_repo["name"], "minimal-repo") | |
self.assertEqual(processed_repo["description"], "") | |
self.assertEqual(processed_repo["language"], "") | |
self.assertEqual(processed_repo["stars"], 0) | |
self.assertEqual(processed_repo["forks"], 0) | |
self.assertEqual(processed_repo["updated_at"], "") | |
self.assertEqual(processed_repo["created_at"], "") | |
self.assertEqual(processed_repo["html_url"], "") | |
self.assertEqual(processed_repo["topics"], []) | |
self.assertEqual(processed_repo["size"], 0) | |
self.assertEqual(processed_repo["readme"], "") | |
# Verify README function was NOT called since there's no URL | |
mock_get_readme.assert_not_called() | |
def test_processing_error_handling(self, mock_get_readme): | |
"""Test handling of processing errors for individual repos.""" | |
mock_get_readme.return_value = "README content" | |
# Create a repo dict that will cause an error during processing | |
raw_repos = [ | |
{ | |
"name": "good-repo", | |
"stargazers_count": 5, | |
"html_url": "https://github.com/user/good-repo" | |
}, | |
# This will cause an AttributeError when trying to call .get() on None | |
None, | |
{ | |
"name": "another-good-repo", | |
"stargazers_count": 3, | |
"html_url": "https://github.com/user/another-good-repo" | |
} | |
] | |
with patch('logging.getLogger') as mock_get_logger: | |
_ = mock_get_logger.return_value | |
# The function currently has a bug where it doesn't handle None repos | |
# This will raise an AttributeError | |
with self.assertRaises(AttributeError): | |
github._process_repository_data(raw_repos) | |
def test_empty_repository_list(self, mock_get_readme): | |
"""Test processing of empty repository list.""" | |
result = github._process_repository_data([]) | |
self.assertEqual(result, []) | |
# Verify no README calls were made | |
mock_get_readme.assert_not_called() | |
def test_readme_retrieval_error_handling(self, mock_get_readme): | |
"""Test handling when README retrieval fails.""" | |
# Simulate README function returning empty string (error case) | |
mock_get_readme.return_value = "" | |
raw_repos = [ | |
{ | |
"name": "test-repo", | |
"html_url": "https://github.com/user/test-repo", | |
"stargazers_count": 5 | |
} | |
] | |
result = github._process_repository_data(raw_repos) | |
self.assertEqual(len(result), 1) | |
self.assertEqual(result[0]["readme"], "") | |
mock_get_readme.assert_called_once_with("https://github.com/user/test-repo") | |
def test_all_forks_filtered(self): | |
"""Test when all repositories are unmodified forks.""" | |
raw_repos = [ | |
{ | |
"name": "fork1", | |
"fork": True, | |
"stargazers_count": 0 | |
}, | |
{ | |
"name": "fork2", | |
"fork": True, | |
"stargazers_count": 0 | |
} | |
] | |
result = github._process_repository_data(raw_repos) | |
self.assertEqual(result, []) | |
class TestGetRepositoryReadme(unittest.TestCase): | |
"""Test cases for the get_repository_readme function.""" | |
def test_successful_readme_retrieval(self, mock_get): | |
"""Test successful README file retrieval.""" | |
readme_content = "# Test Repository\n\nThis is a test README file." | |
encoded_content = base64.b64encode(readme_content.encode('utf-8')).decode('ascii') | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"content": encoded_content, | |
"encoding": "base64" | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, readme_content) | |
mock_get.assert_called_once() | |
call_args = mock_get.call_args | |
self.assertIn("https://api.github.com/repos/owner/repo/readme", call_args[0][0]) | |
self.assertEqual(call_args[1]["headers"]["User-Agent"], "Resumate-App/1.0") | |
def test_readme_not_found(self, mock_get): | |
"""Test handling when README file doesn't exist.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 404 | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, "") | |
def test_api_error(self, mock_get): | |
"""Test handling of API errors.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 500 | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, "") | |
def test_network_error(self, mock_get): | |
"""Test handling of network errors.""" | |
mock_get.side_effect = requests.RequestException("Connection error") | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, "") | |
def test_invalid_url_format(self): | |
"""Test handling of invalid URL formats.""" | |
invalid_urls = [ | |
"https://gitlab.com/owner/repo", | |
"https://github.com/owner", | |
"https://github.com/owner/repo/extra/path", | |
"not-a-url", | |
"", | |
"https://github.com/" | |
] | |
for url in invalid_urls: | |
with self.subTest(url=url): | |
result = github.get_repository_readme(url) | |
self.assertEqual(result, "") | |
def test_missing_content_field(self, mock_get): | |
"""Test handling when API response is missing content field.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"encoding": "base64" | |
# Missing "content" field | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, "") | |
def test_invalid_base64_content(self, mock_get): | |
"""Test handling of invalid base64 content.""" | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"content": "invalid-base64-content!@#$", | |
"encoding": "base64" | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, "") | |
def test_unicode_readme_content(self, mock_get): | |
"""Test handling of README with Unicode characters.""" | |
readme_content = "# Test 🚀\n\nEmoji and unicode: 中文 русский" | |
encoded_content = base64.b64encode(readme_content.encode('utf-8')).decode('ascii') | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"content": encoded_content, | |
"encoding": "base64" | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, readme_content) | |
def test_large_readme_content(self, mock_get): | |
"""Test handling of large README files.""" | |
# Create a large README content | |
readme_content = "# Large README\n\n" + "This is a line of content.\n" * 1000 | |
encoded_content = base64.b64encode(readme_content.encode('utf-8')).decode('ascii') | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"content": encoded_content, | |
"encoding": "base64" | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo") | |
self.assertEqual(result, readme_content) | |
self.assertGreater(len(result), 10000) # Verify it's actually large | |
def test_url_with_trailing_slash(self, mock_get): | |
"""Test handling of URLs with trailing slash.""" | |
readme_content = "# Test README" | |
encoded_content = base64.b64encode(readme_content.encode('utf-8')).decode('ascii') | |
mock_response = MagicMock() | |
mock_response.status_code = 200 | |
mock_response.json.return_value = { | |
"content": encoded_content, | |
"encoding": "base64" | |
} | |
mock_get.return_value = mock_response | |
result = github.get_repository_readme("https://github.com/owner/repo/") | |
self.assertEqual(result, readme_content) | |
# Verify the API call used the correct URL without trailing slash | |
call_args = mock_get.call_args | |
self.assertIn("https://api.github.com/repos/owner/repo/readme", call_args[0][0]) | |
if __name__ == '__main__': | |
unittest.main() | |