""" Unit tests for the github module. """ import unittest from unittest.mock import patch, MagicMock import requests from functions import github # pylint: disable=protected-access class TestExtractGitHubUsername(unittest.TestCase): """Test cases for the _extract_github_username function.""" def test_valid_github_urls(self): """Test extraction from valid GitHub URLs.""" test_cases = [ ("https://github.com/octocat", "octocat"), ("https://github.com/octocat/", "octocat"), ("http://github.com/test-user", "test-user"), ("github.com/user_name", "user_name"), ("https://github.com/user123", "user123"), ("https://github.com/octocat/Hello-World", "octocat"), ] for url, expected in test_cases: with self.subTest(url=url): result = github._extract_github_username(url) self.assertEqual(result, expected) def test_invalid_github_urls(self): """Test handling of invalid GitHub URLs.""" invalid_urls = [ "", "https://gitlab.com/user", "https://github.com/", "not@a-valid-url.com", "https://github.com/user-with-very-long-name-that-exceeds-github-limit", None ] for url in invalid_urls: with self.subTest(url=url): result = github._extract_github_username(url) self.assertIsNone(result) def test_username_validation(self): """Test username format validation.""" # Valid usernames valid_usernames = ["user", "user-name", "user_name", "user123", "a" * 39] for username in valid_usernames: with self.subTest(username=username): result = github._extract_github_username(f"github.com/{username}") self.assertEqual(result, username) # Invalid usernames invalid_usernames = ["", "a" * 40, "user@name", "user.name"] for username in invalid_usernames: with self.subTest(username=username): result = github._extract_github_username(f"github.com/{username}") self.assertIsNone(result) class TestGetGitHubUserInfo(unittest.TestCase): """Test cases for the _get_github_user_info function.""" @patch('requests.get') def test_successful_user_info(self, mock_get): """Test successful user info retrieval.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "login": "octocat", "public_repos": 10, "name": "The Octocat" } mock_get.return_value = mock_response result = github._get_github_user_info("octocat") self.assertEqual(result["status"], "success") self.assertIn("data", result) self.assertEqual(result["data"]["login"], "octocat") @patch('requests.get') def test_user_not_found(self, mock_get): """Test handling of non-existent user.""" mock_response = MagicMock() mock_response.status_code = 404 mock_get.return_value = mock_response result = github._get_github_user_info("nonexistentuser") self.assertEqual(result["status"], "error") self.assertIn("not found", result["message"]) @patch('requests.get') def test_rate_limit_exceeded(self, mock_get): """Test handling of rate limit exceeded.""" mock_response = MagicMock() mock_response.status_code = 403 mock_get.return_value = mock_response result = github._get_github_user_info("octocat") self.assertEqual(result["status"], "error") self.assertIn("rate limit", result["message"]) @patch('requests.get') def test_network_error(self, mock_get): """Test handling of network errors.""" mock_get.side_effect = requests.RequestException("Connection error") result = github._get_github_user_info("octocat") self.assertEqual(result["status"], "error") self.assertIn("Network error", result["message"]) class TestGetUserRepositories(unittest.TestCase): """Test cases for the _get_user_repositories function.""" @patch('requests.get') def test_successful_repository_retrieval(self, mock_get): """Test successful repository retrieval.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = [ { "name": "Hello-World", "description": "My first repository", "language": "Python", "stargazers_count": 5, "forks_count": 2 } ] mock_get.return_value = mock_response result = github._get_user_repositories("octocat") self.assertEqual(result["status"], "success") self.assertIn("data", result) self.assertEqual(len(result["data"]), 1) self.assertEqual(result["data"][0]["name"], "Hello-World") @patch('requests.get') def test_empty_repository_list(self, mock_get): """Test handling of empty repository list.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = [] mock_get.return_value = mock_response result = github._get_user_repositories("octocat") self.assertEqual(result["status"], "success") self.assertEqual(len(result["data"]), 0) @patch('requests.get') 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_user_repositories("octocat") self.assertEqual(result["status"], "error") self.assertIn("GitHub API error", result["message"]) class TestProcessRepositoryData(unittest.TestCase): """Test cases for the _process_repository_data function.""" def test_basic_processing(self): """Test basic repository data processing.""" 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 } ] 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.assertFalse(processed_repo["is_fork"]) def test_fork_filtering(self): """Test filtering of unmodified forks.""" raw_repos = [ { "name": "original-repo", "fork": False, "stargazers_count": 5 }, { "name": "unmodified-fork", "fork": True, "stargazers_count": 0 }, { "name": "modified-fork", "fork": True, "stargazers_count": 3 } ] 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) def test_missing_fields(self): """Test handling of missing fields in repository data.""" 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) class TestGetGitHubRepositories(unittest.TestCase): """Test cases for the get_github_repositories function.""" def test_empty_url(self): """Test handling of empty or None URL.""" test_cases = [None, "", " "] for url in test_cases: with self.subTest(url=url): result = github.get_github_repositories(url) self.assertEqual(result["status"], "error") self.assertIn("No GitHub URL provided", result["message"]) @patch('functions.github._get_user_repositories') @patch('functions.github._get_github_user_info') @patch('functions.github._extract_github_username') def test_successful_retrieval(self, mock_extract, mock_user_info, mock_repos): """Test successful repository retrieval.""" mock_extract.return_value = "octocat" mock_user_info.return_value = { "status": "success", "data": {"public_repos": 10} } mock_repos.return_value = { "status": "success", "data": [{"name": "Hello-World", "fork": False, "stargazers_count": 5}] } result = github.get_github_repositories("https://github.com/octocat") self.assertEqual(result["status"], "success") self.assertIn("repositories", result) self.assertIn("metadata", result) self.assertEqual(result["metadata"]["username"], "octocat") def test_invalid_url_format(self): """Test handling of invalid URL format.""" result = github.get_github_repositories("https://gitlab.com/user") self.assertEqual(result["status"], "error") self.assertIn("Invalid GitHub URL format", result["message"]) class TestFormatRepositoriesForLLM(unittest.TestCase): """Test cases for the format_repositories_for_llm function.""" def test_successful_formatting(self): """Test successful formatting of repository data.""" github_result = { "status": "success", "repositories": [ { "name": "test-repo", "description": "A test repository", "language": "Python", "stars": 10, "forks": 5, "updated_at": "2024-01-01T00:00:00Z", "html_url": "https://github.com/user/test-repo", "topics": ["python", "test"] } ], "metadata": { "username": "testuser", "profile_url": "https://github.com/testuser" } } result = github.format_repositories_for_llm(github_result) self.assertIn("=== GITHUB REPOSITORIES ===", result) self.assertIn("testuser", result) self.assertIn("test-repo", result) self.assertIn("A test repository", result) self.assertIn("Python", result) self.assertIn("=== END GITHUB REPOSITORIES ===", result) def test_error_status(self): """Test formatting when there's an error status.""" github_result = { "status": "error", "message": "User not found" } result = github.format_repositories_for_llm(github_result) self.assertIn("could not be retrieved", result) self.assertIn("User not found", result) def test_no_repositories(self): """Test formatting when no repositories are found.""" github_result = { "status": "success", "repositories": [], "metadata": {"username": "emptyuser"} } result = github.format_repositories_for_llm(github_result) self.assertIn("No public repositories found", result) self.assertIn("emptyuser", result) def test_many_repositories_limit(self): """Test that formatting limits to 20 repositories.""" repositories = [] for i in range(25): repositories.append({ "name": f"repo-{i}", "description": f"Repository {i}", "language": "Python", "stars": i, "forks": 0, "updated_at": "2024-01-01T00:00:00Z", "html_url": f"https://github.com/user/repo-{i}", "topics": [] }) github_result = { "status": "success", "repositories": repositories, "metadata": {"username": "manyrepos"} } result = github.format_repositories_for_llm(github_result) # Should mention "and 5 more repositories" self.assertIn("and 5 more repositories", result) # Should contain the first 20 repos self.assertIn("repo-0", result) self.assertIn("repo-19", result) # Should not contain repos beyond 20 self.assertNotIn("repo-20", result) class TestGetRepositoryDetails(unittest.TestCase): """Test cases for the get_repository_details function.""" def test_invalid_repository_url(self): """Test handling of invalid repository URLs.""" invalid_urls = [ "", None, "https://gitlab.com/user/repo", "https://github.com/", "https://github.com/user", "not-a-url", ] for url in invalid_urls: with self.subTest(url=url): result = github.get_repository_details(url) self.assertEqual(result["status"], "error") self.assertIn("message", result) @patch('functions.github._get_repository_info') @patch('functions.github._extract_repo_info') def test_repository_not_found(self, mock_extract, mock_get_info): """Test handling of non-existent repository.""" mock_extract.return_value = ("user", "nonexistent-repo") mock_get_info.return_value = { "status": "error", "message": "Repository 'user/nonexistent-repo' not found" } result = github.get_repository_details("https://github.com/user/nonexistent-repo") self.assertEqual(result["status"], "error") self.assertIn("not found", result["message"]) @patch('functions.github._get_repository_contributors') @patch('functions.github._get_repository_releases') @patch('functions.github._get_repository_contents') @patch('functions.github._get_repository_readme') @patch('functions.github._get_repository_languages') @patch('functions.github._get_repository_info') @patch('functions.github._extract_repo_info') def test_successful_repository_details(self, mock_extract, mock_get_info, mock_languages, mock_readme, mock_contents, mock_releases, mock_contributors): """Test successful repository details retrieval.""" # Mock URL extraction mock_extract.return_value = ("octocat", "Hello-World") # Mock basic repository info mock_get_info.return_value = { "status": "success", "data": { "name": "Hello-World", "full_name": "octocat/Hello-World", "description": "This your first repo!", "language": "C", "stargazers_count": 80, "forks_count": 9, "watchers_count": 80, "size": 108, "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:14:43Z", "pushed_at": "2011-01-26T19:06:43Z", "html_url": "https://github.com/octocat/Hello-World", "clone_url": "https://github.com/octocat/Hello-World.git", "ssh_url": "git@github.com:octocat/Hello-World.git", "topics": ["example", "tutorial"], "license": {"name": "MIT License", "spdx_id": "MIT"}, "fork": False, "archived": False, "private": False, "default_branch": "master", "open_issues_count": 0, "has_issues": True, "has_wiki": True, "has_pages": False, "has_projects": True, "visibility": "public" } } # Mock additional data mock_languages.return_value = { "status": "success", "data": {"C": 78.1, "Makefile": 21.9} } mock_readme.return_value = { "status": "success", "data": "# Hello World\n\nThis is a test repository." } mock_contents.return_value = { "status": "success", "data": ["README.md", "hello.c", "Makefile"] } mock_releases.return_value = { "status": "success", "data": [ { "tag_name": "v1.0.0", "name": "First Release", "published_at": "2011-01-26T19:14:43Z", "prerelease": False, "draft": False } ] } mock_contributors.return_value = { "status": "success", "data": [ { "login": "octocat", "contributions": 32, "html_url": "https://github.com/octocat", "type": "User" } ] } result = github.get_repository_details("https://github.com/octocat/Hello-World") # Verify success self.assertEqual(result["status"], "success") self.assertIn("repository", result) repo = result["repository"] # Verify basic info self.assertEqual(repo["name"], "Hello-World") self.assertEqual(repo["full_name"], "octocat/Hello-World") self.assertEqual(repo["description"], "This your first repo!") self.assertEqual(repo["language"], "C") self.assertEqual(repo["stars"], 80) self.assertEqual(repo["forks"], 9) # Verify additional data self.assertEqual(repo["languages"], {"C": 78.1, "Makefile": 21.9}) self.assertIn("Hello World", repo["readme"]) self.assertEqual(repo["file_structure"], ["README.md", "hello.c", "Makefile"]) self.assertEqual(len(repo["releases"]), 1) self.assertEqual(repo["releases"][0]["tag_name"], "v1.0.0") self.assertEqual(len(repo["contributors"]), 1) self.assertEqual(repo["contributors"][0]["login"], "octocat") # Verify boolean flags self.assertFalse(repo["is_fork"]) self.assertFalse(repo["is_archived"]) self.assertFalse(repo["is_private"]) self.assertTrue(repo["has_issues"]) def test_extract_repo_info_valid_urls(self): """Test _extract_repo_info with valid repository URLs.""" test_cases = [ ("https://github.com/octocat/Hello-World", ("octocat", "Hello-World")), ("https://github.com/user/repo.git", ("user", "repo")), ("https://github.com/org/project/", ("org", "project")), ("github.com/test/example", ("test", "example")), ("https://github.com/user/repo/issues", ("user", "repo")), ] for url, expected in test_cases: with self.subTest(url=url): result = github._extract_repo_info(url) self.assertEqual(result, expected) def test_extract_repo_info_invalid_urls(self): """Test _extract_repo_info with invalid repository URLs.""" invalid_urls = [ "", "https://gitlab.com/user/repo", "https://github.com/user", "https://github.com/", "not-a-url", ] for url in invalid_urls: with self.subTest(url=url): result = github._extract_repo_info(url) self.assertEqual(result, (None, None)) @patch('requests.get') def test_get_repository_info_success(self, mock_get): """Test _get_repository_info with successful response.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "name": "test-repo", "full_name": "user/test-repo" } mock_get.return_value = mock_response result = github._get_repository_info("user", "test-repo") self.assertEqual(result["status"], "success") self.assertIn("data", result) self.assertEqual(result["data"]["name"], "test-repo") @patch('requests.get') def test_get_repository_info_not_found(self, mock_get): """Test _get_repository_info with 404 response.""" mock_response = MagicMock() mock_response.status_code = 404 mock_get.return_value = mock_response result = github._get_repository_info("user", "nonexistent") self.assertEqual(result["status"], "error") self.assertIn("not found", result["message"]) @patch('requests.get') def test_get_repository_languages_success(self, mock_get): """Test _get_repository_languages with successful response.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "Python": 50000, "JavaScript": 25000, "CSS": 25000 } mock_get.return_value = mock_response result = github._get_repository_languages("user", "repo") self.assertEqual(result["status"], "success") expected_percentages = {"Python": 50.0, "JavaScript": 25.0, "CSS": 25.0} self.assertEqual(result["data"], expected_percentages) @patch('requests.get') def test_get_repository_readme_success(self, mock_get): """Test _get_repository_readme with successful response.""" # Mock the README metadata response readme_response = MagicMock() readme_response.status_code = 200 readme_response.json.return_value = { "download_url": "https://raw.githubusercontent.com/user/repo/main/README.md" } # Mock the README content response content_response = MagicMock() content_response.status_code = 200 content_response.text = "# Test Repository\n\nThis is a test." mock_get.side_effect = [readme_response, content_response] result = github._get_repository_readme("user", "repo") self.assertEqual(result["status"], "success") self.assertIn("Test Repository", result["data"]) @patch('requests.get') def test_get_repository_contents_success(self, mock_get): """Test _get_repository_contents with successful response.""" mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = [ {"name": "src", "type": "dir"}, {"name": "README.md", "type": "file"}, {"name": "setup.py", "type": "file"}, {"name": "tests", "type": "dir"} ] mock_get.return_value = mock_response result = github._get_repository_contents("user", "repo") self.assertEqual(result["status"], "success") expected_structure = ["src/", "tests/", "README.md", "setup.py"] self.assertEqual(result["data"], expected_structure) if __name__ == '__main__': unittest.main()