from unittest.mock import MagicMock, patch import pytest from ghapi.all import GhApi from model_bakery import baker from libraries.github import LibraryUpdater from core.githubhelper import GithubAPIClient from libraries.models import Category, Issue, Library, LibraryVersion, PullRequest @pytest.fixture def github_api_client(): return GithubAPIClient() @pytest.fixture(scope="function") def mock_api() -> GhApi: """Fixture that mocks the GitHub API.""" with patch("libraries.github.GhApi") as mock_api_class: yield mock_api_class.return_value @pytest.fixture def github_api_client_mock(): """ """ mock = MagicMock() return mock @pytest.fixture def mock_gh_api_client(): client = GithubAPIClient() client.get_libraries_json = MagicMock(return_value=None) client.get_repo = MagicMock(return_value=None) client.get_gitmodules = MagicMock(return_value=b"sample content") return client @pytest.fixture def library_updater(mock_gh_api_client): return LibraryUpdater() def test_get_library_list(library_updater): """Test the get_library_list method of LibraryUpdater.""" gitmodules = [{"module": "test"}] library_updater.client.get_libraries_json = MagicMock( return_value=[ { "key": "test", "name": "Test Library", "description": "Test description", "cxxstd": "11", "category": ["Test"], "authors": ["John Doe"], "maintainers": ["Jane Doe"], } ] ) library_updater.client.get_repo = MagicMock( return_value={"html_url": "example.com"} ) expected = [ { "key": "test", "name": "Test Library", "github_url": "example.com", "description": "Test description", "cxxstd": "11", "category": ["Test"], "authors": ["John Doe"], "maintainers": ["Jane Doe"], "cpp20_module_support": False, } ] result = library_updater.get_library_list(gitmodules=gitmodules) assert result == expected def test_get_library_list_skip(library_updater): """Test that get_library_list method of LibraryUpdater skips the right modules""" gitmodules = [{"module": "litre"}] result = library_updater.get_library_list(gitmodules=gitmodules) assert result == [] def test_update_authors(library_updater, user, library_version): library = library_version.library assert library.authors.exists() is False user.claimed = True user.email = "t_testerson@example.com" user.save() library_updater.update_authors( library, authors=[ "Tester Testerston ", "Tester2 Testerson2", ], ) library.refresh_from_db() assert library.authors.exists() assert library.authors.filter(email="t_testerson@example.com").exists() assert library.authors.filter(email="tester2_testerson2@example.com").exists() def test_update_maintainers(library_updater, user, library_version): assert library_version.maintainers.exists() is False user.claimed = True user.email = "t_testerson@example.com" user.save() library_updater.update_maintainers( library_version, maintainers=[ "Tester Testerston ", "Tester2 Testerson2", ], ) library_version.refresh_from_db() assert library_version.maintainers.exists() assert library_version.maintainers.filter(email="t_testerson@example.com").exists() assert library_version.maintainers.filter( email="tester2_testerson2@example.com" ).exists() @pytest.mark.skip("Add this test when we have figured out GH API mocking") def test_update_libraries(library_updater, version): """Test the update_libraries method of LibraryUpdater.""" assert Library.objects.filter(key="test").exists() is False library_updater.parser.parse_gitmodules = MagicMock(return_value=[]) library_updater.get_library_list = MagicMock( return_value=[ { "key": "test", "name": "Test Library", "github_url": "https://github.com/test/test", "description": "Test description", "cxxstd": "11", "category": ["Test"], "authors": ["John Doe"], "maintainers": ["Jane Doe"], } ] ) library_updater.update_libraries() assert Library.objects.filter(key="test").exists() def test_update_library(library_updater, version): """Test the update_library method of LibraryUpdater.""" assert Library.objects.filter(key="test").exists() is False library_data = { "key": "test", "name": "Test Library", "github_url": "https://github.com/test/test", "description": "Test description", "cxxstd": "11", "category": ["Test"], "authors": ["John Doe"], "maintainers": ["Jane Doe"], } library_updater.update_library(library_data) assert Library.objects.filter(key="test").exists() Library.objects.get(key="test") def test_update_categories(library, library_updater): """Test the update_categories method of LibraryUpdater.""" assert Category.objects.filter(name="Test").exists() is False assert library.categories.filter(name="Test").exists() is False library_updater.update_categories(library, ["Test"]) library.refresh_from_db() assert Category.objects.filter(name="Test").exists() assert library.categories.filter(name="Test").exists() # Ensure category overrides respected assert Category.objects.filter(name="Containers").exists() is False assert Category.objects.filter(name="Container").exists() is False library_updater.update_categories(library, ["Container"]) # overridden library.refresh_from_db() assert Category.objects.filter(name="Containers").exists() is True assert Category.objects.filter(name="Container").exists() is False def test_update_issues_new( tp, library, github_api_repo_issues_response, library_updater ): """Test the update_issues method of LibraryUpdater with new issues.""" new_issues_count = len(github_api_repo_issues_response) expected_count = Issue.objects.count() + new_issues_count library_updater.client.get_repo_issues = MagicMock( return_value=github_api_repo_issues_response ) library_updater.update_issues(library) ids = [issue.id for issue in github_api_repo_issues_response] issues = Issue.objects.filter(library=library, github_id__in=ids) assert Issue.objects.filter(library=library, github_id__in=ids).exists() assert ( Issue.objects.filter(library=library, github_id__in=ids).count() == expected_count ) # Test the values of a sample Issue gh_issue = github_api_repo_issues_response[0] issue = issues.get(github_id=gh_issue.id) assert issue.title == gh_issue.title assert issue.number == gh_issue.number if gh_issue.state == "open": assert issue.is_open else: assert not issue.is_open assert issue.data == gh_issue def test_update_issues_existing( tp, library, github_api_repo_issues_response, library_updater ): """Test the update_issues method of LibraryUpdater with existing issues.""" existing_issue_data = github_api_repo_issues_response[0] old_title = "Old title" issue = baker.make( Issue, library=library, github_id=existing_issue_data.id, title=old_title ) # Make sure we are expected one fewer new issue, since we created one in advance new_issues_count = len(github_api_repo_issues_response) expected_count = Issue.objects.count() + new_issues_count - 1 library_updater.client.get_repo_issues = MagicMock( return_value=github_api_repo_issues_response ) library_updater.update_issues(library) assert Issue.objects.count() == expected_count ids = [issue.id for issue in github_api_repo_issues_response] issues = Issue.objects.filter(library=library, github_id__in=ids) assert issues.exists() assert issues.count() == expected_count # Test that the existing issue updated issue.refresh_from_db() assert issue.title == existing_issue_data.title def test_update_issues_long_title( tp, library, github_api_repo_issues_response, library_updater ): """Test the update_issues method of LibraryUpdater handles long title gracefully""" new_issues_count = len(github_api_repo_issues_response) expected_count = Issue.objects.count() + new_issues_count title = "sample" * 100 assert len(title) > 255 expected_title = title[:255] assert len(expected_title) <= 255 github_id = github_api_repo_issues_response[0]["id"] github_api_repo_issues_response[0]["title"] = "sample" * 100 library_updater.client.get_repo_issues = MagicMock( return_value=github_api_repo_issues_response ) library_updater.update_issues(library) assert Issue.objects.count() == expected_count assert Issue.objects.filter(library=library, github_id=github_id).exists() issue = Issue.objects.get(library=library, github_id=github_id) assert issue.title == expected_title def test_update_prs_new(tp, library, github_api_repo_prs_response, library_updater): """Test that LibraryUpdater.update_prs() imports new PRs appropriately""" new_prs_count = len(github_api_repo_prs_response) expected_count = PullRequest.objects.count() + new_prs_count github_api_repo_prs_response[0]["title"] = "sample" * 100 library_updater.client.get_repo_prs = MagicMock( return_value=github_api_repo_prs_response ) library_updater.update_prs(library) assert PullRequest.objects.count() == expected_count ids = [pr.id for pr in github_api_repo_prs_response] pulls = PullRequest.objects.filter(library=library, github_id__in=ids) assert pulls.exists() assert pulls.count() == expected_count # Test the values of a sample PR gh_pull = github_api_repo_prs_response[0] pr = pulls.get(github_id=gh_pull.id) assert pr.title == gh_pull.title[:255] assert pr.number == gh_pull.number if gh_pull.state == "open": assert pr.is_open else: assert not pr.is_open assert pr.data == gh_pull def test_update_prs_existing( tp, library, github_api_repo_prs_response, library_updater ): """Test that LibraryUpdater.update_prs() updates existing PRs when appropriate""" existing_pr_data = github_api_repo_prs_response[0] old_title = "Old title" pull = baker.make( PullRequest, library=library, github_id=existing_pr_data.id, title=old_title ) # Make sure we are expected one fewer new PRs, since we created one in advance new_prs_count = len(github_api_repo_prs_response) expected_count = PullRequest.objects.count() + new_prs_count - 1 library_updater.client.get_repo_prs = MagicMock( return_value=github_api_repo_prs_response ) library_updater.update_prs(library) assert PullRequest.objects.count() == expected_count ids = [pr.id for pr in github_api_repo_prs_response] pulls = PullRequest.objects.filter(library=library, github_id__in=ids) assert pulls.exists() assert pulls.count() == expected_count # Test that the existing PR updated pull.refresh_from_db() assert pull.title == existing_pr_data.title def test_parse_boostdep_artifact( github_action_boostdep_output_artifact, library_updater ): """Test that the GH artifact format can be parsed and dependencies are created.""" baker.make( "libraries.LibraryVersion", library__key="algorithm", version__name="boost-1.35.0", ) baker.make( "libraries.LibraryVersion", library__key="callable_traits", version__name="boost-1.85.0", ) baker.make( "libraries.LibraryVersion", library__key="algorithm", version__name="boost-1.85.0", ) baker.make( "libraries.LibraryVersion", library__key="numeric/conversion", version__name="boost-1.85.0", ) deps = [ "concept_check", "config", "detail", "array", "assert", "bind", "core", "logic/tribool", "numeric/conversion", ] for key in deps: baker.make("libraries.Library", key=key) library_updater.fetch_most_recent_boost_dep_artifact_content = MagicMock( return_value=github_action_boostdep_output_artifact ) library_updater.update_library_version_dependencies() lv = LibraryVersion.objects.get( library__key="algorithm", version__name="boost-1.35.0" ) assert lv.dependencies.count() == 5 lv = LibraryVersion.objects.get( library__key="algorithm", version__name="boost-1.85.0" ) assert lv.dependencies.count() == 6 # callable traits is in the file but has no dependencies lv = LibraryVersion.objects.get( library__key="callable_traits", version__name="boost-1.85.0" ) assert lv.dependencies.count() == 0 lv = LibraryVersion.objects.get( library__key="numeric/conversion", version__name="boost-1.85.0" ) assert lv.dependencies.count() == 1