diff --git a/tests/plugins/test_scm_request_processor.py b/tests/plugins/test_scm_request_processor.py index e8be7f4..5fb2c82 100644 --- a/tests/plugins/test_scm_request_processor.py +++ b/tests/plugins/test_scm_request_processor.py @@ -134,10 +134,7 @@ class TestProcess: @patch("toddlers.utils.pagure.set_pagure") @patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.bugzilla_system.set_bz") - @patch("toddlers.utils.anitya.set_anitya") - def test_process_exception( - self, mock_anitya, mock_bugzilla, mock_fasjson, mock_pagure, toddler - ): + def test_process_exception(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler): """ Assert that message toddler will be initialized correctly, if message passes initial processing. @@ -183,16 +180,12 @@ class TestProcess: ) mock_fasjson.assert_called_with(config) mock_bugzilla.assert_called_with(config) - mock_anitya.assert_called_with(config) mock_pagure_io.add_comment_to_issue.assert_called_once() - @patch("toddlers.utils.anitya.set_anitya") @patch("toddlers.utils.pagure.set_pagure") @patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.bugzilla_system.set_bz") - def test_process( - self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler - ): + def test_process(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler): """ Assert that message toddler will be initialized correctly, if message passes initial processing. @@ -234,15 +227,11 @@ class TestProcess: ) mock_fasjson.assert_called_with(config) mock_bugzilla.assert_called_with(config) - mock_anitya.assert_called_with(config) - @patch("toddlers.utils.anitya.set_anitya") @patch("toddlers.utils.pagure.set_pagure") @patch("toddlers.utils.fedora_account.set_fasjson") @patch("toddlers.utils.bugzilla_system.set_bz") - def test_process_comment( - self, mock_bugzilla, mock_fasjson, mock_pagure, mock_anitya, toddler - ): + def test_process_comment(self, mock_bugzilla, mock_fasjson, mock_pagure, toddler): """ Assert that toddler will handle comments correctly. """ @@ -802,7 +791,6 @@ class TestProcessNewRepo: self.toddler = scm_request_processor.SCMRequestProcessor() self.toddler.pagure_io = Mock() self.toddler.dist_git = Mock() - self.toddler.anitya = Mock() self.toddler.ping_comment = "{maintainers}" def test_process_new_repo_missing_required_key(self): @@ -812,77 +800,12 @@ class TestProcessNewRepo: issue = { "id": 100, } - json = { - "repo": "+a", - "branch": "rawhide", - "namespace": "rpms", - "bug_id": "123", - "action": "new_repo", - "sls": {"rawhide": "2050-06-01"}, - "monitor": "monitoring", - } - self.toddler.process_new_repo(issue, json) + self.toddler.process_new_repo(issue, {}) self.toddler.pagure_io.close_issue.assert_called_with( 100, namespace=scm_request_processor.PROJECT_NAMESPACE, - message="Invalid body, missing required field: upstreamurl", - reason="Invalid", - ) - - def test_process_new_repo_missing_required_key_for_monitor(self): - """ - Assert that ticket will be closed if required key for monitor - is missing in request. - """ - issue = { - "id": 100, - } - json = { - "repo": "+a", - "branch": "rawhide", - "namespace": "rpms", - "bug_id": "123", - "action": "new_repo", - "sls": {"rawhide": "2050-06-01"}, - "monitor": "monitoring", - "upstreamurl": "", - "backend": "GitLab", - } - self.toddler.process_new_repo(issue, json) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message="Invalid body, missing required field: project_name", - reason="Invalid", - ) - - def test_process_new_repo_monitor_accepts_different_options(self): - """ - Assert that ticket will be closed if required key for monitor - is missing in request. - """ - issue = { - "id": 100, - } - json = { - "repo": "+a", - "branch": "rawhide", - "namespace": "rpms", - "bug_id": "123", - "action": "new_repo", - "sls": {"rawhide": "2050-06-01"}, - "monitor": "monitoring11", - "upstreamurl": "", - "backend": "GitLab", - } - self.toddler.process_new_repo(issue, json) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message="Invalid body, missing required field: project_name", + message="Invalid body, missing required field: repo", reason="Invalid", ) @@ -899,8 +822,40 @@ class TestProcessNewRepo: "bug_id": "123", "action": "new_repo", "sls": {"rawhide": "2050-06-01"}, - "monitor": "no-monitoring", - "upstreamurl": "", + "monitor": "monitor", + } + + self.toddler.process_new_repo(issue, json) + + error = ( + "The repository name is invalid. It must be at least two " + "characters long with only letters, numbers, hyphens, " + "underscores, plus signs, and/or periods. Please note that " + "the project cannot start with a period or a plus sign. " + "Repository name can't be longer than 64 characters." + ) + + self.toddler.pagure_io.close_issue.assert_called_with( + 100, + namespace=scm_request_processor.PROJECT_NAMESPACE, + message=error, + reason="Invalid", + ) + + def test_process_new_repo_long_repo_name(self): + """ + Assert that ticket will be closed if provided repository name is invalid. + """ + issue = {"id": 100, "user": {"name": "zlopez"}} + + json = { + "repo": "".join("a" for _ in range(65)), + "branch": "rawhide", + "namespace": "rpms", + "bug_id": "123", + "action": "new_repo", + "sls": {"rawhide": "2050-06-01"}, + "monitor": "monitor", } self.toddler.process_new_repo(issue, json) @@ -933,8 +888,7 @@ class TestProcessNewRepo: "bug_id": "", "action": "new_repo", "sls": {"rawhide": "2050-06-01"}, - "monitor": "no-monitoring", - "upstreamurl": "", + "monitor": "monitor", } self.toddler.dist_git.get_project.return_value = None @@ -963,8 +917,7 @@ class TestProcessNewRepo: "bug_id": "123", "action": "new_repo", "sls": {"rawhide": "2050-06-01"}, - "monitor": "no-monitoring", - "upstreamurl": "", + "monitor": "monitor", } self.toddler.dist_git.get_project.return_value = None @@ -998,8 +951,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {"rawhide": "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1009,7 +961,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } @@ -1039,8 +990,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {"rawhide": "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1050,7 +1000,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } @@ -1071,10 +1020,7 @@ class TestProcessNewRepo: comment=message, ) - @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug" - ) - def test_process_new_repo_master_branch(self, mock_validate_review_bug): + def test_process_new_repo_master_branch(self): """ Assert that ticket will be closed when branch is set to master branch. Master branch is no longer allowed. @@ -1087,8 +1033,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {"rawhide": "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1098,7 +1043,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } self.toddler.dist_git.get_project.return_value = None @@ -1124,8 +1068,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {"rawhide": "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1135,7 +1078,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } self.toddler.process_new_repo(issue, json) @@ -1178,12 +1120,11 @@ class TestProcessNewRepo: } repo = "repo" - bug_id = "11" + bug_id = "" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" - exception = False + monitor = "monitor" + exception = True json = { "repo": repo, "branch": branch, @@ -1192,7 +1133,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } dist_git_url = "https://src.fp.o" @@ -1201,12 +1141,9 @@ class TestProcessNewRepo: self.toddler.pagure_io.get_project_contributors.return_value = { "users": {"admin": [user], "commit": [], "ticket": []} } - self.toddler.validation_comment = "valid" - self.toddler.validate_review_bug = Mock() # Method to test - with patch("toddlers.plugins.scm_request_processor.bugzilla_system"): - self.toddler.process_new_repo(issue, json) + self.toddler.process_new_repo(issue, json) # asserts self.toddler.pagure_io.add_comment_to_issue.assert_called_with( @@ -1239,8 +1176,7 @@ class TestProcessNewRepo: bug_id = "" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = True json = { "repo": repo, @@ -1250,7 +1186,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } dist_git_url = "https://src.fp.o" @@ -1268,10 +1203,9 @@ class TestProcessNewRepo: @patch("toddlers.plugins.scm_request_processor.bugzilla_system") @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", - return_value=True, + "toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug" ) - def test_process_new_repo_project_exists(self, mock_validate_request, mock_bz): + def test_process_new_repo_project_exists(self, mock_validate_review_bug, mock_bz): """ Assert that ticket will be processed correctly when repo already exists in dist git. @@ -1284,7 +1218,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {"rawhide": "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1295,7 +1229,6 @@ class TestProcessNewRepo: "sls": sls, "monitor": monitor, "exception": exception, - "upstreamurl": "", } dist_git_url = "https://src.fp.o" @@ -1337,7 +1270,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1348,7 +1281,6 @@ class TestProcessNewRepo: "sls": sls, "monitor": monitor, "exception": exception, - "upstreamurl": "", } self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}} @@ -1390,314 +1322,6 @@ class TestProcessNewRepo: ) mock_bz.change_bug_status.assert_called_with(bug_id, "RELEASE_PENDING", message) - @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", - return_value=True, - ) - def test_process_new_repo_monitoring_project_created_successfully_package_exist( - self, - mock_validate_request, - ): - """ - Assert that ticket will be processed with correct Monitoring message - when project and package exists. - """ - # Preparation - user = "zlopez" - issue = { - "id": 100, - "user": {"name": user}, - } - - repo = "repo" - branch = "main" - namespace = "tests" - bug_id = "" - action = "new_repo" - sls = {branch: "2050-06-01"} - monitor = "monitoring" - upstreamurl = "" - backend = "custom" - distibution = "Fedora" - project_name = "test_project" - exception = False - json = { - "repo": repo, - "branch": branch, - "namespace": namespace, - "bug_id": bug_id, - "action": action, - "sls": sls, - "monitor": monitor, - "upstreamurl": upstreamurl, - "backend": backend, - "distribution": distibution, - "project_name": project_name, - "exception": exception, - } - dist_git_url = "https://src.fp.o" - self.toddler.dist_git._pagure_url = dist_git_url - self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}} - anitya_project_url = "https://release-monitoring.org/project/123" - self.toddler.anitya.does_project_exists_in_anitya = Mock( - return_value=anitya_project_url - ) - self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=True) - project_msg = ( - "Anitya project is accessible by this link \n`{0}`\n " - "you can modify it manually.".format(anitya_project_url) - ) - - self.toddler.process_new_repo(issue, json) - - self.toddler.dist_git.set_monitoring_status.assert_called_with( - namespace, repo, monitor - ) - monitoring_msg = "\nMonitoring:\n{0}\n".format(project_msg) - - message = "The Pagure repository was created at {0}/{1}/{2}{3}".format( - dist_git_url, namespace, repo, monitoring_msg - ) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message=message, - reason="Processed", - ) - - @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", - return_value=True, - ) - def test_process_new_repo_monitoring_project_was_not_created( - self, - mock_validate_request, - ): - """ - Assert that ticket will be processed with correct Monitoring message - when project and package exists. - """ - # Preparation - user = "zlopez" - issue = { - "id": 100, - "user": {"name": user}, - } - - repo = "repo" - branch = "main" - namespace = "tests" - bug_id = "" - action = "new_repo" - sls = {branch: "2050-06-01"} - monitor = "monitoring" - upstreamurl = "" - backend = "custom" - distibution = "Fedora" - project_name = "test_project" - exception = False - json = { - "repo": repo, - "branch": branch, - "namespace": namespace, - "bug_id": bug_id, - "action": action, - "sls": sls, - "monitor": monitor, - "upstreamurl": upstreamurl, - "backend": backend, - "distribution": distibution, - "project_name": project_name, - "exception": exception, - } - dist_git_url = "https://src.fp.o" - self.toddler.dist_git._pagure_url = dist_git_url - self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}} - self.toddler.anitya.does_project_exists_in_anitya = Mock(return_value=None) - self.toddler.anitya.create_project_in_anitya = Mock(return_value=None) - project_msg = ( - "Wasn't able to create project in Anitya. " - "You can create it manually on: `https://release-monitoring.org`" - ) - - self.toddler.process_new_repo(issue, json) - - self.toddler.dist_git.set_monitoring_status.assert_called_with( - namespace, repo, monitor - ) - monitoring_msg = "\nMonitoring:\n{0}\n".format(project_msg) - - message = "The Pagure repository was created at {0}/{1}/{2}{3}".format( - dist_git_url, namespace, repo, monitoring_msg - ) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message=message, - reason="Processed", - ) - - @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", - return_value=True, - ) - def test_process_new_repo_monitoring_creating_package( - self, - mock_validate_request, - ): - """ - Assert that ticket will be processed with correct Monitoring message - when project and package exists. - """ - # Preparation - user = "zlopez" - issue = { - "id": 100, - "user": {"name": user}, - } - - repo = "repo" - branch = "main" - namespace = "tests" - bug_id = "" - action = "new_repo" - sls = {branch: "2050-06-01"} - monitor = "monitoring" - upstreamurl = "" - backend = "custom" - distibution = "Fedora" - project_name = "test_project" - exception = False - json = { - "repo": repo, - "branch": branch, - "namespace": namespace, - "bug_id": bug_id, - "action": action, - "sls": sls, - "monitor": monitor, - "upstreamurl": upstreamurl, - "backend": backend, - "distribution": distibution, - "project_name": project_name, - "exception": exception, - } - dist_git_url = "https://src.fp.o" - self.toddler.dist_git._pagure_url = dist_git_url - self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}} - anitya_project_url = "https://release-monitoring.org/project/123" - self.toddler.anitya.does_project_exists_in_anitya = Mock( - return_value=anitya_project_url - ) - self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=False) - self.toddler.anitya.create_package_in_anitya = Mock(return_value="Success") - project_msg = ( - "Anitya project is accessible by this link \n`{0}`\n " - "you can modify it manually.".format(anitya_project_url) - ) - package_msg = "Package was created in Anitya" - - self.toddler.process_new_repo(issue, json) - - self.toddler.dist_git.set_monitoring_status.assert_called_with( - namespace, repo, monitor - ) - monitoring_msg = "\nMonitoring:\n{0}\n{1}".format(project_msg, package_msg) - - message = "The Pagure repository was created at {0}/{1}/{2}{3}".format( - dist_git_url, namespace, repo, monitoring_msg - ) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message=message, - reason="Processed", - ) - - @patch( - "toddlers.plugins.scm_request_processor.SCMRequestProcessor._validate_new_repo_request", - return_value=True, - ) - def test_process_new_repo_monitoring_creating_package_fails( - self, - mock_validate_request, - ): - """ - Assert that ticket will be processed with correct Monitoring message - when project and package exists. - """ - # Preparation - user = "zlopez" - issue = { - "id": 100, - "user": {"name": user}, - } - - repo = "repo" - branch = "main" - namespace = "tests" - bug_id = "" - action = "new_repo" - sls = {branch: "2050-06-01"} - monitor = "monitoring" - upstreamurl = "" - backend = "custom" - distibution = "Fedora" - project_name = "test_project" - exception = False - json = { - "repo": repo, - "branch": branch, - "namespace": namespace, - "bug_id": bug_id, - "action": action, - "sls": sls, - "monitor": monitor, - "upstreamurl": upstreamurl, - "backend": backend, - "distribution": distibution, - "project_name": project_name, - "exception": exception, - } - dist_git_url = "https://src.fp.o" - self.toddler.dist_git._pagure_url = dist_git_url - self.toddler.dist_git.get_project.return_value = {"access_users": {"owner": []}} - anitya_project_url = "https://release-monitoring.org/project/123" - self.toddler.anitya.does_project_exists_in_anitya = Mock( - return_value=anitya_project_url - ) - self.toddler.anitya.does_package_exists_in_anitya = Mock(return_value=False) - response_msg = "Unauthorized, access token is incorrect." - self.toddler.anitya.create_package_in_anitya = Mock(return_value=response_msg) - project_msg = ( - "Anitya project is accessible by this link \n`{0}`\n " - "you can modify it manually.".format(anitya_project_url) - ) - package_msg = "Package wasn't created in Anitya, reason: `{0}`.".format( - response_msg - ) - - self.toddler.process_new_repo(issue, json) - - self.toddler.dist_git.set_monitoring_status.assert_called_with( - namespace, repo, monitor - ) - monitoring_msg = "\nMonitoring:\n{0}\n{1}".format(project_msg, package_msg) - - message = "The Pagure repository was created at {0}/{1}/{2}{3}".format( - dist_git_url, namespace, repo, monitoring_msg - ) - - self.toddler.pagure_io.close_issue.assert_called_with( - 100, - namespace=scm_request_processor.PROJECT_NAMESPACE, - message=message, - reason="Processed", - ) - @patch("toddlers.plugins.scm_request_processor.bugzilla_system") @patch( "toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug" @@ -1717,8 +1341,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1728,7 +1351,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } self.toddler.branch_slas = {"rawhide": {"rawhide": "2050-06-01"}} @@ -1789,8 +1411,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" - upstreamurl = "" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1800,7 +1421,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": upstreamurl, "exception": exception, } @@ -1853,7 +1473,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1863,7 +1483,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": "", "exception": exception, } @@ -1923,7 +1542,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -1933,7 +1552,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": "", "exception": exception, } @@ -2009,7 +1627,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -2019,7 +1637,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": "", "exception": exception, } @@ -2086,7 +1703,7 @@ class TestProcessNewRepo: bug_id = "123" action = "new_repo" sls = {branch: "2050-06-01"} - monitor = "no-monitoring" + monitor = "monitor" exception = False json = { "repo": repo, @@ -2096,7 +1713,6 @@ class TestProcessNewRepo: "action": action, "sls": sls, "monitor": monitor, - "upstreamurl": "", "exception": exception, } diff --git a/tests/plugins/test_unretire_packages.py b/tests/plugins/test_unretire_packages.py deleted file mode 100644 index b5af785..0000000 --- a/tests/plugins/test_unretire_packages.py +++ /dev/null @@ -1,1023 +0,0 @@ -""" -Unit tests for `toddler.plugins.unretire_packages` -""" - -import logging -from unittest.mock import MagicMock, Mock, patch - -import arrow -import git -import koji -from pagure_messages.issue_schema import IssueNewV1 -import pytest - -from toddlers.exceptions import ValidationError -import toddlers.plugins.unretire_packages as unretire_packages - - -class TestAcceptTopic: - """ - Test class for `toddler.plugins.unretire_packages.UnretirePackages.accepts_topic` - method. - """ - - toddler_cls = unretire_packages.UnretirePackages - - def test_accepts_topic_invalid(self, toddler): - """ - Assert that invalid topic is not accepted. - """ - assert toddler.accepts_topic("foo.bar") is False - - @pytest.mark.parametrize( - "topic", - [ - "io.pagure.*.pagure.issue.new", - "io.pagure.stg.pagure.issue.new", - "io.pagure.prod.pagure.issue.new", - ], - ) - def test_accepts_topic_valid(self, topic, toddler): - assert toddler.accepts_topic(topic) - - -class TestProcess: - """ - Test class for `toddlers.plugins.unretire_packages.UnretirePackages.process` method - """ - - toddler_cls = unretire_packages.UnretirePackages - - def test_process_invalid_project(self, caplog, toddler): - """ - Asserts that messages from other projects than `` will be skipped. - """ - caplog.set_level(logging.INFO) - - body = { - "project": {"fullname": "foo/bar"}, - "issue": {"user": {"name": "zlopez"}}, - } - msg = IssueNewV1(body=body) - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages.process_ticket" - ) as mock_process_ticket: - toddler.process({}, msg) - mock_process_ticket.assert_not_called() - - assert ( - caplog.records[-1].message - == "The message doesn't belong to project releng/fedora-scm-requests. Skipping message." - ) - - def test_process_issue_not_open(self, caplog, toddler): - """ - Assert that message with closed issues will be skipped. - """ - caplog.set_level(logging.INFO) - - body = { - "project": {"fullname": unretire_packages.PROJECT_NAMESPACE}, - "issue": { - "id": 100, - "status": "Closed", - }, - } - msg = IssueNewV1(body=body) - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages.process_ticket" - ) as mock_process_ticket: - toddler.process({}, msg) - - mock_process_ticket.assert_not_called() - - assert ( - caplog.records[-1].message == "The issue 100 is not open. Skipping message." - ) - - def test_process_keyword_not_in_title(self, caplog, toddler): - """ - Assert that message without keyword in title will be skipped. - """ - caplog.set_level(logging.INFO) - - body = { - "project": {"fullname": unretire_packages.PROJECT_NAMESPACE}, - "issue": { - "id": 100, - "status": "Open", - "title": "New Repo", - }, - } - msg = IssueNewV1(body=body) - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages.process_ticket" - ) as mock_process_ticket: - toddler.process({}, msg) - - mock_process_ticket.assert_not_called() - - assert ( - caplog.records[-1].message - == "The issue doesn't contain keyword 'unretire' in the title 'New Repo'" - ) - - @patch("toddlers.utils.requests.make_session") - @patch("toddlers.utils.bodhi.set_bodhi") - @patch("toddlers.utils.pagure.set_pagure") - @patch("toddlers.utils.bugzilla_system.set_bz") - @patch("koji.ClientSession") - def test_process_base_exception_appeared( - self, mock_koji, mock_bz, mock_pagure, mock_bodhi, session_mock, caplog, toddler - ): - """ - Assert that if base exception appeared, it will be processed correctly. - """ - issue = { - "id": 100, - "status": "Open", - "title": "Unretire package_name", - "user": {"name": "amedvede"}, - } - body = { - "project": {"fullname": unretire_packages.PROJECT_NAMESPACE}, - "issue": issue, - } - config = { - "dist_git_url": "https://src.fedoraproject.org", - "dist_git_token": "Private API Key", - "temp_folder": "/var/tmp", - } - msg = IssueNewV1(body=body) - - mock_pagure_io = Mock() - mock_pagure.return_value = mock_pagure_io - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages.process_ticket" - ) as mock_process_ticket: - mock_process_ticket.side_effect = Exception("test") - toddler.process(config, msg) - mock_process_ticket.assert_called_with(issue) - - mock_pagure_io.add_comment_to_issue.assert_called_once() - - @patch("toddlers.utils.requests.make_session") - @patch("toddlers.utils.bodhi.set_bodhi") - @patch("toddlers.utils.pagure.set_pagure") - @patch("toddlers.utils.bugzilla_system.set_bz") - @patch("koji.ClientSession") - def test_process( - self, mock_koji, mock_bugzilla, mock_pagure, mock_bodhi, session_mock, toddler - ): - """ - Assert that message toddler will be initialized correctly, if message passes - initial processing. - """ - issue = { - "id": 100, - "status": "Open", - "title": "Unretire package", - "user": {"name": "amedvede"}, - } - body = { - "project": {"fullname": unretire_packages.PROJECT_NAMESPACE}, - "issue": issue, - } - msg = IssueNewV1(body=body) - config = { - "dist_git_url": "dist_git_url", - "dist_git_token": "dist_git_token", - "kojihub_url": "koji", - "pagure_url": "https://pagure.io", - "pagure_api_key": "some api key", - "bugzilla_url": "https://bugzilla.redhat.com/", - "bugzilla_api_key": "some api key", - } - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages.process_ticket" - ) as mock_process_ticket: - toddler.process(config, msg) - - mock_process_ticket.assert_called_with(issue) - - mock_pagure.assert_called_with(config) - mock_bugzilla.assert_called_with(config) - mock_bodhi.assert_called_with(config) - session_mock.assert_called() - - -class TestProcessTicket: - """ - Test class for `toddlers.plugin.unretire_packages.UnretirePackages.process_ticket` - method. - """ - - def setup_method(self): - """ - Initialize toddler - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.temp_dir = Mock() - self.toddler.git_repo = MagicMock() - self.toddler.pagure_io = MagicMock() - self.toddler.pagure_user = "bot" - self.toddler.koji_session = MagicMock() - self.toddler.bodhi = MagicMock() - self.toddler.oidc_distgit_token = "token" - - @patch("json.loads") - def test_process_ticket_issue_body_is_invalid_json(self, json_loads): - json_loads.side_effect = ValueError("test") - issue = {"id": 100, "content": "issue body", "full_url": "ticket_url"} - err_msg = "Invalid JSON provided" - - self.toddler.process_ticket(issue) - self.toddler.pagure_io.close_issue.assert_called_once_with( - issue["id"], - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - @patch("json.loads") - def test_process_ticket_package_doesnt_exist(self, json_loads_mock): - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "amedvede", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - err_msg = "Package repository doesnt exist. Try to repeat request." - self.toddler._does_url_exist = MagicMock(return_value=False) - - self.toddler.process_ticket(issue) - self.toddler.pagure_io.close_issue.assert_called_once_with( - issue["id"], - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - @patch("json.loads") - @patch("toddlers.plugins.unretire_packages.git.clone_repo") - @patch("tempfile.TemporaryDirectory") - @patch( - "toddlers.plugins.unretire_packages.UnretirePackages._does_url_exist", - return_value=True, - ) - def test_process_ticket_git_error_happened( - self, - mock_does_url_exist, - mock_tmp_dir, - mock_clone_repo, - json_loads_mock, - caplog, - ): - caplog.set_level(logging.INFO) - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "amedvede", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - err_msg = "Something went wrong during cloning git repository." - mock_clone_repo.side_effect = git.GitCommandError("Error cloning") - - self.toddler.process_ticket(issue) - assert caplog.records[-1].message == err_msg - self.toddler.pagure_io.close_issue.assert_called_once_with( - issue["id"], - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - @patch("json.loads") - @patch("toddlers.plugins.unretire_packages.git.clone_repo") - @patch("tempfile.TemporaryDirectory") - @patch( - "toddlers.plugins.unretire_packages.UnretirePackages._does_url_exist", - return_value=True, - ) - def test_process_ticket_package_is_not_ready_for_unretirement( - self, - mock_does_url_exist, - mock_tmp_dir, - mock_clone_repo, - json_loads_mock, - caplog, - ): - caplog.set_level(logging.DEBUG) - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "amedvede", - "branches": ["f37", "f38", "f39", "f40"], - "review_bugzilla": "242142144", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - last_log_msg = "Verifying if package is ready for unretirement." - self.toddler.bodhi.get_active_branches.return_value = ["f38", "f39", "f40"] - mock_clone_repo.return_value.does_branch_exist.return_value = True - mock_clone_repo.return_value.does_branch_contains_file.return_value = True - self.toddler._is_package_ready_for_unretirement = MagicMock(return_value=False) - - self.toddler.process_ticket(issue) - assert caplog.records[-1].message == last_log_msg - self.toddler._is_package_ready_for_unretirement.assert_called_with( - issue_id=issue["id"], - branches=["f38", "f39", "f40"], - review_bugzilla="242142144", - ) - self.toddler.git_repo.revert_last_commit.assert_not_called() - - @patch("toddlers.plugins.unretire_packages.git.clone_repo") - @patch("tempfile.TemporaryDirectory") - @patch("json.loads") - def test_process_ticket_no_maintainer_fas( - self, json_loads_mock, temp_dir_mock, mock_clone_repo, caplog - ): - caplog.set_level(logging.INFO) - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "", - "branches": ["f37", "f38", "f39", "f40"], - "review_bugzilla": "242142144", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - err_msg = "Package is ophaned, but maintainer fas is not provided." - self.toddler._does_url_exist = MagicMock(return_value=True) - self.toddler.bodhi.get_active_branches.return_value = ["f38", "f39", "f40"] - mock_clone_repo.return_value.does_branch_exist.return_value = True - mock_clone_repo.return_value.does_branch_contains_file.return_value = True - self.toddler._is_package_ready_for_unretirement = MagicMock(return_value=True) - self.toddler._check_tags_to_unblock = MagicMock(return_value=False) - self.toddler.pagure_io.is_project_orphaned = MagicMock(return_value=True) - - self.toddler.process_ticket(issue) - self.toddler.pagure_io.close_issue.assert_called_once_with( - issue_id=issue["id"], - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - @patch("toddlers.plugins.unretire_packages.git.clone_repo") - @patch("tempfile.TemporaryDirectory") - @patch("json.loads") - def test_process_ticket_koji_error( - self, json_loads_mock, temp_dir_mock, mock_clone_repo, caplog - ): - caplog.set_level(logging.INFO) - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "sososo", - "branches": ["f37", "f38", "f39", "f40"], - "review_bugzilla": "242142144", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - err_msg = "Not able to unblock `f38` tag on koji." - self.toddler._does_url_exist = MagicMock(return_value=True) - self.toddler.bodhi.get_active_branches.return_value = ["f38", "f39", "f40"] - mock_clone_repo.return_value.does_branch_exist.return_value = True - mock_clone_repo.return_value.does_branch_contains_file.return_value = True - self.toddler._is_package_ready_for_unretirement = MagicMock(return_value=True) - self.toddler._check_tags_to_unblock = MagicMock(return_value=True) - self.toddler.koji_session.packageListUnblock = MagicMock( - side_effect=koji.GenericError - ) - self.toddler.pagure_io.is_project_orphaned = MagicMock() - - self.toddler.process_ticket(issue) - self.toddler.pagure_io.close_issue.assert_called_once_with( - issue_id=issue["id"], - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - self.toddler.pagure_io.is_project_orphaned.assert_not_called() - - @patch("toddlers.plugins.unretire_packages.git.clone_repo") - @patch("tempfile.TemporaryDirectory") - @patch("json.loads") - def test_process_ticket_success( - self, json_loads_mock, temp_dir_mock, mock_clone_repo, caplog - ): - caplog.set_level(logging.INFO) - issue_body = { - "name": "pkg_name", - "type": "rpms", - "maintainer": "amedvede", - "branches": ["f37", "f38", "f39", "f40"], - "review_bugzilla": "242142144", - } - json_loads_mock.return_value = issue_body - issue = { - "id": 100, - "content": "issue body", - "full_url": "ticket_url", - } - self.toddler._does_url_exist = MagicMock(return_value=True) - self.toddler.bodhi.get_active_branches.return_value = ["f38", "f39", "f40"] - mock_clone_repo.return_value.does_branch_exist.return_value = True - mock_clone_repo.return_value.does_branch_contains_file.return_value = True - self.toddler._is_package_ready_for_unretirement = MagicMock(return_value=True) - self.toddler._check_tags_to_unblock = MagicMock(return_value=True) - self.toddler.koji_session.packageListUnblock = MagicMock() - self.toddler._unblock_tags_on_koji = MagicMock() - self.toddler.pagure_io.is_project_orphaned = MagicMock(return_value=True) - self.toddler.pagure_io.assign_maintainer_to_project = MagicMock() - - self.toddler.process_ticket(issue) - self.toddler._is_package_ready_for_unretirement.assert_called_once_with( - issue_id=issue["id"], - branches=["f38", "f39", "f40"], - review_bugzilla="242142144", - ) - self.toddler._check_tags_to_unblock.assert_called_once_with( - ["f38", "f39", "f40"], "pkg_name" - ) - self.toddler.pagure_io.is_project_orphaned.assert_called_once_with( - namespace=issue_body["type"], repo=issue_body["name"] - ) - self.toddler.pagure_io.assign_maintainer_to_project.assert_called_once_with( - namespace=issue_body["type"], - repo=issue_body["name"], - maintainer_fas="amedvede", - ) - package = "rpms/pkg_name" - assert caplog.records[-1].message == "Package {0} is assigned to {1}".format( - package, issue_body["maintainer"] - ) - - -class TestIsPackageReadyForUnretirement: - """ - Test class for `toddlers.plugin.unretire_packages.UnreturePackages - ._is_package_ready_for_unretirement` method. - """ - - def setup_method(self): - """ - Initialize toddler - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.pagure_io = Mock() - - @patch( - "toddlers.plugins.unretire_packages.UnretirePackages." "_verify_bugzilla_ticket" - ) - @patch( - "toddlers.plugins.unretire_packages.UnretirePackages." - "_verify_package_not_retired_for_reason" - ) - def test_is_package_ready_for_unretirement_success( - self, verify_reason_mock, need_to_check_bz_mock - ): - """ - Assert that method process correctly. - """ - issue_id = 55 - branches = ["f32", "f35"] - review_bugzilla = "242142144" - - assert ( - self.toddler._is_package_ready_for_unretirement( - issue_id, branches, review_bugzilla - ) - is True - ) - verify_reason_mock.assert_called_with(branches=branches) - need_to_check_bz_mock.assert_called_with( - review_bugzilla=review_bugzilla, branches=branches - ) - - def test_is_package_ready_for_unretirement_legal_issue(self): - """ - Assert that package won't be verified if it has legal of license issue - """ - issue_id = 55 - branches = ["rawhide", "some_branch"] - review_bugzilla = "242142144" - err_msg = "Package was retired for a reason: legal of license issue." - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages" - "._verify_package_not_retired_for_reason", - side_effect=ValidationError(err_msg), - ): - assert ( - self.toddler._is_package_ready_for_unretirement( - issue_id, branches, review_bugzilla - ) - is False - ) - self.toddler.pagure_io.close_issue.assert_called_with( - issue_id=issue_id, - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - @patch( - "toddlers.plugins.unretire_packages.UnretirePackages." - "_verify_package_not_retired_for_reason" - ) - def test_is_package_ready_for_unretirement_date_issue(self, verify_reason_mock): - """ - Assert that package won't be verified if retire date is not correct. - """ - issue_id = 55 - branches = ["rawhide", "some_branch"] - review_bugzilla = "242142144" - err_msg = "Couldn't get a date of the retire commit." - - with patch( - "toddlers.plugins.unretire_packages.UnretirePackages" - "._verify_bugzilla_ticket", - side_effect=ValidationError(err_msg), - ): - assert ( - self.toddler._is_package_ready_for_unretirement( - issue_id, branches, review_bugzilla - ) - is False - ) - self.toddler.pagure_io.close_issue.assert_called_with( - issue_id=issue_id, - namespace=unretire_packages.PROJECT_NAMESPACE, - message=err_msg, - reason="Invalid", - ) - - -class TestVerifyPackageNotRetiredForReason: - """ - Test class for `toddlers.plugin.unretire_packages.UnreturePackages - ._verify_package_not_retired_for_reason` method. - """ - - def setup_method(self): - """ - Initialize toddler. - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.git_repo = Mock() - - def test_verify_package_not_retired_for_reason_success(self): - """ - Assert that method process correctly if commit message doesn't contain forbidden keyword. - """ - branches = ["rawhide", "some_branch"] - self.toddler.git_repo.get_last_commit_message.return_value = ( - "everything ok issue" - ) - - self.toddler._verify_package_not_retired_for_reason(branches) - - def test_verify_package_not_retired_for_reason_reason_exist(self): - """ - Assert that method raise Exception if commit message contain forbidden keyword. - """ - err_msg = "Package was retired for a reason: legal or license issue." - branches = ["rawhide", "some_branch"] - - self.toddler.git_repo.get_last_commit_message.return_value = "legal issue" - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_package_not_retired_for_reason(branches) - - -class TestVerifyBugzillaTicket: - """ - Test class for `toddlers.plugin.unretire_packages.UnreturePackages - ._verify_bugzilla_ticket` method. - """ - - def setup_method(self): - """ - Initialize toddler. - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.git_repo = Mock() - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_date_is_none(self, get_bug_mock): - """ - Assert that method will raise Exception if git won't be able to find a date. - """ - err_msg = "Couldn't get a date of the retire commit." - branches = ["rawhide", "some_branch"] - bugzilla_review = "321321312" - - self.toddler.git_repo.get_last_commit_date.return_value = None - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket( - review_bugzilla=bugzilla_review, branches=branches - ) - - get_bug_mock.assert_not_called() - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_no_need_to_test_bz(self, get_bug_mock): - review_bugzilla = "" - branches = ["rawhide", "some_branch"] - commit_date = arrow.utcnow().shift(days=-3) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - get_bug_mock.assert_not_called() - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_bugzilla_id_is_missing(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - review_bugzilla = "" - branches = ["rawhide", "some_branch"] - err_msg = "Bugzilla url is missing, please add it and recreate the ticket." - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch( - "toddlers.plugins.unretire_packages.bugzilla_system.get_bug", - side_effect=Exception("bug error"), - ) - def test_verify_bugzilla_ticket_bug_getting_error(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "lalalala" - err_msg = ( - "The Bugzilla bug could not be verified. The following error was encountered: " - "bug error" - ) - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch( - "toddlers.plugins.unretire_packages.bugzilla_system.get_bug", - return_value=None, - ) - def test_verify_bugzilla_ticket_bug_is_none(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "lalalala" - err_msg = "Bugzilla can't get the bug by bug id, fix bugzilla url." - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_bug_product_is_not_fedora(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "321321321321" - bug = MagicMock() - bug.product = "Some product" - err_msg = ( - "The bugzilla bug is for '{0}', but request should be for 'Fedora'.".format( - bug.product - ) - ) - get_bug_mock.return_value = bug - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_bug_does_not_have_required_flag(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "32131321" - err_msg = ( - "Tag fedora-review is missing on bugzilla, get it and recreate the ticket." - ) - - bug = MagicMock() - bug.product = "Fedora" - bug.get_flags.side_effect = TypeError - get_bug_mock.return_value = bug - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_wrong_flag_status(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "3213213213" - err_msg = "Flag fedora-review has wrong status, need to be +" - bug = MagicMock() - bug.product = "Fedora" - bug.get_flags.return_value = [ - { - "status": "-", - }, - ] - get_bug_mock.return_value = bug - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - @patch("toddlers.plugins.unretire_packages.bugzilla_system.get_bug") - def test_verify_bugzilla_ticket_success(self, get_bug_mock): - """ - Assert that method will raise Exception if url is missing in issue body. - """ - branches = ["rawhide", "some_branch"] - review_bugzilla = "3213213213" - bug = MagicMock() - bug.product = "Fedora" - bug.get_flags.return_value = [ - { - "status": "+", - }, - ] - get_bug_mock.return_value = bug - commit_date = arrow.utcnow().shift(days=-100) - arrow.get = MagicMock(return_value=commit_date) - self.toddler.git_repo.get_last_commit_date.return_value = commit_date - - self.toddler._verify_bugzilla_ticket(review_bugzilla, branches) - - -class TestIsNeedToUnblockTagsOnKoji: - """ - Test class for `toddlers.plugin.unretire_packages.UnreturePackages - ._is_need_to_unblock_tags_on_koji` method. - """ - - def setup_method(self): - """ - Initialize toddler. - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.koji_session = Mock() - - def test_is_need_to_unblock_tags_on_koji_package_does_not_have_tags(self): - """ - Assert error during processing if method cant find tags of the package. - """ - tags_to_unblock = ["f37"] - repo = "some name" - err_msg = "Package doesn't have tags on koji." - - self.toddler.koji_session.listTags.return_value = None - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._check_tags_to_unblock(tags_to_unblock, repo) - - def test_is_need_to_unblock_tags_on_koji_cant_find_the_package(self): - """ - Assert error during processing if koji not able to find the package. - """ - tags_to_unblock = ["f37"] - repo = "some name" - err_msg = "Package doesn't exist on koji." - - self.toddler.koji_session.listTags.side_effect = koji.GenericError - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._check_tags_to_unblock(tags_to_unblock, repo) - - def test_is_need_to_unblock_tags_on_koji_request_tags_that_are_not_exist_on_koji( - self, - ): - """ - Assert error during processing, tags are requested to unblock are not exist on koji. - """ - tags_to_unblock = ["f37"] - repo = "some name" - err_msg = "Request to unblock tags that don't exist on koji." - - self.toddler.koji_session.listTags.return_value = [ - { - "id": 1, - "locked": True, - "name": "dist-f33", - }, - { - "id": 2, - "locked": False, - "name": "dist-f34", - }, - ] - - with pytest.raises(ValidationError, match=err_msg): - self.toddler._check_tags_to_unblock(tags_to_unblock, repo) - - def test_is_need_to_unblock_tags_on_koji_true(self): - """ - Assert that is need to unblock method process correctly and will return right value. - """ - tags_to_unblock = ["f37"] - repo = "some name" - - self.toddler.koji_session.listTags.return_value = [ - { - "id": 1, - "locked": True, - "name": "dist-f37", - }, - { - "id": 2, - "locked": False, - "name": "dist-f34", - }, - ] - - assert self.toddler._check_tags_to_unblock(tags_to_unblock, repo) is True - - def test_is_need_to_unblock_tags_on_koji_false(self): - """ - Assert that is need to unblock method process correctly and will return right value. - """ - tags_to_unblock = ["f37"] - repo = "some name" - - self.toddler.koji_session.listTags.return_value = [ - { - "id": 1, - "locked": False, - "name": "dist-f37", - }, - { - "id": 2, - "locked": False, - "name": "dist-f34", - }, - ] - - assert self.toddler._check_tags_to_unblock(tags_to_unblock, repo) is False - - -class TestNsConvertor: - """ - Test class for `toddlers.plugins.unretire_packages.UnretirePackages. - _ns_convertor` method.` - """ - - def setup_method(self): - self.toddler = unretire_packages.UnretirePackages() - - def test_ns_convertor(self): - assert self.toddler._ns_convertor(None) is None - assert self.toddler._ns_convertor("") == "" - assert self.toddler._ns_convertor("rpm") == "rpms" - assert self.toddler._ns_convertor("test") == "tests" - assert self.toddler._ns_convertor("flatpak") == "flatpaks" - assert self.toddler._ns_convertor("module") == "modules" - assert self.toddler._ns_convertor("rpms") == "rpms" - - -class TestIsUrlExist: - """ - Test class for `toddlers.plugins.unretire_packages.UnretirePackages. - _is_url_exist` method.` - """ - - def setup_method(self): - """ - Initialize toddler. - """ - self.toddler = unretire_packages.UnretirePackages() - self.toddler.requests_session = MagicMock() - - def test_is_url_exist_connection_error(self): - """ - Assert that when Connection error appear function will return False - """ - url = "some url" - self.toddler.requests_session.get = MagicMock(side_effect=ConnectionError) - assert self.toddler._does_url_exist(url) is False - - def test_is_url_exist_response_code_okay(self): - """ - Assert that when function run correctly, True is returned. - """ - url = "some url" - self.toddler.requests_session.get = MagicMock() - self.toddler.requests_session.get.return_value.status_code = 200 - assert self.toddler._does_url_exist(url) is True - - def test_is_url_exist_response_code_not_okay(self): - """ - Assert that when status of request diff from 200, False is returned. - """ - url = "some url" - self.toddler.requests_session.get = MagicMock() - self.toddler.requests_session.get.return_value.status_code = 404 - assert self.toddler._does_url_exist(url) is False - - -class TestMain: - """ - Test class for `toddlers.plugins.unretire_packages.main` function. - """ - - def test_main_no_args(self, capsys): - """Assert that help is printed if no arg is provided.""" - with pytest.raises(SystemExit): - unretire_packages.main([]) - - out, err = capsys.readouterr() - assert out == "" - # Expecting something along these lines, but don't make the test too tight: - # - # usage: pytest [-h] [--dry-run] [-q | --debug] conf [username] - # pytest: error: the following arguments are required: conf - assert err.startswith("usage:") - assert "error: the following arguments are required:" in err - - @patch("tomllib.load") - @patch("toddlers.plugins.unretire_packages.UnretirePackages.process") - @patch("toddlers.plugins.unretire_packages.pagure") - def test_main(self, mock_pagure, mock_process, mock_toml): - """Assert that main is initializing config and message correctly.""" - # Preparation - mock_toml.return_value = {} - mock_pagure_io = Mock() - mock_issue = {"id": 100, "user": {"name": "amedvede"}} - mock_pagure_io.get_issue.return_value = mock_issue - - mock_pagure.set_pagure.return_value = mock_pagure_io - - # Function to test - unretire_packages.main(["--config", "test.cfg", "100"]) - - # Assertions - mock_toml.assert_called_with("test.cfg") - mock_pagure.set_pagure.assert_called_with({}) - mock_pagure_io.get_issue.assert_called_with( - 100, unretire_packages.PROJECT_NAMESPACE - ) - body = { - "project": {"fullname": unretire_packages.PROJECT_NAMESPACE}, - "issue": mock_issue, - } - message = IssueNewV1(body=body) - mock_process.assert_called_with(config={}, message=message) diff --git a/tests/utils/test_anitya.py b/tests/utils/test_anitya.py deleted file mode 100644 index 15ef487..0000000 --- a/tests/utils/test_anitya.py +++ /dev/null @@ -1,410 +0,0 @@ -""" -Unit tests for `toddlers.utils.anitya`. -""" - -from unittest.mock import Mock - -import pytest - -import toddlers.utils.anitya as anitya - - -class TestAnityaSetAnitya: - """ - Test class for `toddlers.anitya.set_anitya` function. - """ - - def test_set_anitya(self): - """ - Test initialization of anitya module. - """ - config = { - "anitya_endpoint": "https://release-monitoring.org", - "anitya_access_token": "TOKEN", - } - anitya_obj = anitya.set_anitya(config) - - assert anitya_obj._anitya_endpoint == config.get("anitya_endpoint") - assert anitya_obj._anitya_token == config.get("anitya_access_token") - assert anitya_obj._requests_session - - def test_set_anitya_no_anitya_url(self): - """ - Test initialization of anitya module without required config value. - """ - with pytest.raises( - ValueError, match=r"No anitya endpoint found in config file" - ): - anitya.set_anitya({}) - - def test_set_anitya_no_anitya_api_key(self): - """ - Test initialization of anitya module without required config value. - """ - with pytest.raises( - ValueError, match=r"No anitya access token found in config file" - ): - config = {"anitya_endpoint": "https://anitya.io"} - anitya.set_anitya(config) - - -class TestAnityaDoesProjectExistInAnitya: - """ - Test class for - `toddlers.anitya.Anitya.does_project_exists_in_anitya` method. - """ - - def setup_method(self): - """ - Setup method for test class. - """ - config = { - "anitya_endpoint": "https://release-monitoring.org", - "anitya_access_token": "TOKEN", - } - self.anitya_obj = anitya.set_anitya(config) - self.anitya_obj._requests_session = Mock() - self.anitya_obj.remove_trailing_slashes_from_url = Mock( - return_value="https://release-monitoring.org/api/v2/projects/" - ) - - def test_does_project_exists_in_anitya(self): - """ - Assert that method will return correct response about project exists in anitya. - """ - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "amedvede_project" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "items": [ - { - "id": 123, - "name": project_name, - } - ], - "total_items": 1, - } - params = {"name": project_name} - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_project_exists_in_anitya(project_name) - - assert result == "https://release-monitoring.org/project/123" - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - def test_does_project_exists_in_anitya_project_not_found(self): - """ - Assert that method will return correct response about project does not exist in anitya. - """ - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "amedvede_project" - mock_response = Mock() - mock_response.status_code = 404 - params = {"name": project_name} - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_project_exists_in_anitya(project_name) - - assert result is None - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - def test_does_project_exists_in_anitya_empty_items(self): - """Assert that method will return correct response about project not found in anitya.""" - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "amedvede_project" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {"items": [], "total_items": 0} - params = {"name": project_name} - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_project_exists_in_anitya(project_name) - - assert result is None - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - def test_does_project_exists_in_anitya_wrong_structure(self): - """ - Assert that method will return correct response about project has wrong structure in anitya. - """ - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "amedvede_project" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "items": [ - { - "wrong": "structure", - } - ], - "total_items": 1, - } - params = {"name": project_name} - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_project_exists_in_anitya(project_name) - - assert result is None - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - -class TestAnityaDoesPackageExistInAnitya: - """ - Test class for `toddlers.anitya.Anitya.does_package_exists_in_anitya` method. - """ - - def setup_method(self): - """ - Setup method for test class. - """ - config = { - "anitya_endpoint": "https://release-monitoring.org", - "anitya_access_token": "TOKEN", - } - self.anitya_obj = anitya.set_anitya(config) - self.anitya_obj._requests_session = Mock() - self.anitya_obj.remove_trailing_slashes_from_url = Mock( - return_value="https://release-monitoring.org/api/v2/packages/" - ) - - @pytest.mark.parametrize( - "project_name, expected_project_name, expected_result", - [ - ("nice_project", "nice_project", True), - ("nice_project", "bad_project", False), - ], - ) - def test_does_package_exists_in_anitya( - self, project_name, expected_project_name, expected_result - ): - """ - Assert that method will return correct response about package exists in anitya - and his project name is same with expected. - """ - endpoint = "https://release-monitoring.org/api/v2/packages/" - package_name = "amedvede_package" - distribution = "Fedora" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "items": [ - { - "name": package_name, - "project": project_name, - } - ], - "total_items": 1, - } - params = { - "name": package_name, - "distribution": distribution, - } - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_package_exists_in_anitya( - package_name, distribution, expected_project_name - ) - - assert result is expected_result # package and project name the same - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - def test_does_package_exists_in_anitya_not_found(self): - """ - Assert that method will return correct response when package does not exist in anitya. - """ - endpoint = "https://release-monitoring.org/api/v2/packages/" - package_name = "amedvede_package" - project_name = "different name" - distribution = "Fedora" - mock_response = Mock() - mock_response.status_code = 202 - params = { - "name": package_name, - "distribution": distribution, - } - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_package_exists_in_anitya( - package_name, distribution, project_name - ) - - assert result is False # package and project name are different - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - def test_does_package_exists_in_anitya_found_zero_items(self): - """ - Assert that method will return correct response when response code is correct, - but response does not contain items. - """ - endpoint = "https://release-monitoring.org/api/v2/packages/" - package_name = "amedvede_package" - project_name = "different name" - distribution = "Fedora" - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {"items": [], "total_items": 0} - params = { - "name": package_name, - "distribution": distribution, - } - self.anitya_obj._requests_session.get.return_value = mock_response - - result = self.anitya_obj.does_package_exists_in_anitya( - package_name, distribution, project_name - ) - - assert result is False # package and project name are different - self.anitya_obj._requests_session.get.assert_called_once_with( - endpoint, params=params - ) - - -class TestAnityaCreateProjectInAnitya: - """ - Test class for `toddlers.anitya.Anitya.create_project_in_anitya` method. - """ - - def setup_method(self): - """ - Setup method for test class. - """ - config = { - "anitya_endpoint": "https://release-monitoring.org", - "anitya_access_token": "TOKEN", - } - self.anitya_obj = anitya.set_anitya(config) - self.anitya_obj._requests_session = Mock() - self.anitya_obj.remove_trailing_slashes_from_url = Mock( - return_value="https://release-monitoring.org/api/v2/projects/" - ) - - def test_create_project_in_anitya_successful_creation(self): - """ - Assert that method will return correct response when project is created. - """ - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "project" - homepage = "https://project.com" - backend = "GitHub" - test_data = { - "name": project_name, - "homepage": homepage, - "backend": backend, - } - response_json = {"id": 123} - mock_response = Mock() - mock_response.status_code = 201 - mock_response.json.return_value = response_json - self.anitya_obj._requests_session.post.return_value = mock_response - - result = self.anitya_obj.create_project_in_anitya( - project_name, homepage, backend - ) - - assert result == "https://release-monitoring.org/project/123" - self.anitya_obj._requests_session.post.assert_called_once_with( - url=endpoint, - data=test_data, - headers={"Authorization": "token TOKEN"}, - ) - - def test_create_project_in_anitya_fail(self): - """ - Assert that method will return correct response when project is not created. - """ - endpoint = "https://release-monitoring.org/api/v2/projects/" - project_name = "project" - homepage = "https://project.com" - backend = "GitHub" - test_data = { - "name": project_name, - "homepage": homepage, - "backend": backend, - } - mock_response = Mock() - mock_response.status_code = 400 - self.anitya_obj._requests_session.post.return_value = mock_response - - result = self.anitya_obj.create_project_in_anitya( - project_name, homepage, backend - ) - - assert result is None - self.anitya_obj._requests_session.post.assert_called_once_with( - url=endpoint, - data=test_data, - headers={"Authorization": "token TOKEN"}, - ) - - -class TestAnityaCreatePackageInAnitya: - """ - Test class for `toddlers.anitya.Anitya.create_package_in_anitya` method. - """ - - def setup_method(self): - """ - Setup method for test class. - """ - config = { - "anitya_endpoint": "https://release-monitoring.org", - "anitya_access_token": "TOKEN", - } - self.anitya_obj = anitya.set_anitya(config) - self.anitya_obj._requests_session = Mock() - self.anitya_obj.remove_trailing_slashes_from_url = Mock( - return_value="https://release-monitoring.org/api/v2/packages/" - ) - - @pytest.mark.parametrize( - "response_code, expected_result", - [ - (201, "Success"), - (400, "Bad Request, some necessary arguments were not provided."), - (401, "Unauthorized, access token is incorrect."), - (409, "Conflict, package already exists."), - (404, None), - ], - ) - def test_create_package_in_anitya(self, response_code, expected_result): - """ - Assert that method will return correct response when package is created. - """ - endpoint = "https://release-monitoring.org/api/v2/packages/" - package_name = "test_package" - project_name = "test_project" - distribution = "Fedora" - project_ecosystem = "https://project.com" - test_data = { - "package_name": package_name, - "project_name": project_name, - "distribution": distribution, - "project_ecosystem": project_ecosystem, - } - mock_response = Mock() - mock_response.status_code = response_code - self.anitya_obj._requests_session.post.return_value = mock_response - - result = self.anitya_obj.create_package_in_anitya( - package_name, project_name, distribution, project_ecosystem - ) - - assert result == expected_result - self.anitya_obj._requests_session.post.assert_called_once_with( - url=endpoint, - data=test_data, - headers={"Authorization": "token TOKEN"}, - ) diff --git a/tests/utils/test_git.py b/tests/utils/test_git.py index 5b9bf75..0cb86c5 100644 --- a/tests/utils/test_git.py +++ b/tests/utils/test_git.py @@ -2,7 +2,7 @@ Unit tests for `toddlers.utils.git`. """ -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import call, MagicMock, Mock, patch import pytest @@ -290,28 +290,24 @@ class TestGitRepoRevertLastCommit: Assert that revert last commit process correctly. """ mock_origin = MagicMock() - mock_origin.url = "https://example.com" self.repo.repo.remote.return_value = mock_origin - mock_git_cmd = MagicMock() - self.repo.repo.git = mock_git_cmd - self.repo.revert_last_commit("Revert message", "bot", "token", "feature_branch") + self.repo.revert_last_commit("Revert message", "feature_branch") self.repo.repo.git.checkout.assert_called_once_with("feature_branch") - self.repo.repo.git.revert.assert_called_once_with("HEAD", no_edit=True) - self.repo.repo.git.commit.assert_called_once_with( - "--amend", "-m", "Revert message" - ) - - mock_git_cmd.push.assert_called_once_with( - "-u", "https://bot:token@example.com", "feature_branch" + self.repo.repo.git.execute.assert_has_calls( + [ + call(["git", "revert", "--no-edit", "HEAD"]), + call(["git", "commit", "--amend", "-m", "Revert message"]), + ] ) + mock_origin.push.assert_called_once() def test_revert_last_commit_revert_exception(self): mock_origin = MagicMock() self.repo.repo.remote.return_value = mock_origin - self.repo.repo.git.revert.side_effect = Exception("Revert error") + self.repo.repo.git.execute.side_effect = Exception("Revert error") - self.repo.revert_last_commit("Revert message", "bot", "token", "feature_branch") + self.repo.revert_last_commit("Revert message", "feature_branch") mock_origin.push.assert_not_called() diff --git a/toddlers.toml.example b/toddlers.toml.example index d81378b..e4d7bad 100644 --- a/toddlers.toml.example +++ b/toddlers.toml.example @@ -253,9 +253,6 @@ monitor_choices = ['no-monitoring', 'monitoring', 'monitoring-with-scratch'] ping_comment = "This request wants to skip bugzilla validation! {maintainers} could you check if this is correct? If yes, please respond to this ticket with 'valid' comment" # This is a OIDC token that allows pagure_user to push changes to dist git oidc_distgit_token = "OIDC token used to push git changes using pagure_user" -# Anitya access token and endpoint for managing project in release-monitoring -anitya_access_token = "API token for Anitya" -anitya_endpoint = "https://release-monitoring.org" # Pagure mapping to bugzilla diff --git a/toddlers/plugins/scm_request_processor.py b/toddlers/plugins/scm_request_processor.py index 7a388bd..f6b230b 100644 --- a/toddlers/plugins/scm_request_processor.py +++ b/toddlers/plugins/scm_request_processor.py @@ -24,14 +24,7 @@ import tomllib from toddlers.base import ToddlerBase from toddlers.exceptions import ValidationError -from toddlers.utils import ( - anitya, - bugzilla_system, - fedora_account, - git, - pagure, - requests, -) +from toddlers.utils import bugzilla_system, fedora_account, git, pagure, requests # Regex for branch name validation STREAM_NAME_REGEX = r"^[a-zA-Z0-9.\-_+]+$" @@ -107,9 +100,6 @@ class SCMRequestProcessor(ToddlerBase): # for toddler pagure_user: str = "" - # Anitya object to work with Anitya - anitya: anitya.Anitya - def accepts_topic(self, topic: str) -> bool: """Returns a boolean whether this toddler is interested in messages from this specific topic. @@ -197,9 +187,6 @@ class SCMRequestProcessor(ToddlerBase): _log.info("Setting up connection to Bugzilla") bugzilla_system.set_bz(config) - _log.info("Setting up connection to Anitya") - self.anitya = anitya.set_anitya(config) - try: if message.topic.endswith("pagure.issue.comment.added"): self.process_comment(issue) @@ -463,12 +450,6 @@ class SCMRequestProcessor(ToddlerBase): "namespace", "sls", "monitor", - "upstreamurl", - ] - required_keys_for_monitor = [ - "backend", - "project_name", - "distribution", ] for key in required_keys: if key not in issue_body_json.keys(): @@ -480,18 +461,6 @@ class SCMRequestProcessor(ToddlerBase): ) return - monitor = issue_body_json.get("monitor", "").strip() - if monitor != "no-monitoring": - for key in required_keys_for_monitor: - if key not in issue_body_json.keys(): - self.pagure_io.close_issue( - issue["id"], - namespace=PROJECT_NAMESPACE, - message="Invalid body, missing required field: {}".format(key), - reason="Invalid", - ) - return - # Validate the request first if self._validate_new_repo_request(issue, issue_body_json): _log.info("Ticket passed all validations. Creating repository.") @@ -667,7 +636,6 @@ class SCMRequestProcessor(ToddlerBase): branch_name = issue_body_json.get("branch", "").strip() description = issue_body_json.get("description", "").strip() upstreamurl = issue_body_json.get("upstreamurl", "").strip() - monitor = issue_body_json.get("monitor", "").strip() if namespace in ["rpms", "container"]: default_branch = "rawhide" @@ -768,50 +736,6 @@ class SCMRequestProcessor(ToddlerBase): 'You may commit to the branch "{1}" in about ' "10 minutes.".format(dist_git_url, branch_name) ) - - if monitor != "no-monitoring": - _log.info("- Checking if project {0} exists in Anitya".format(repo)) - backend = issue_body_json["backend"].strip() - distribution = issue_body_json["distribution"].strip() - project_name = issue_body_json["project_name"].strip() - - monitoring_message = "" - project_msg = "" - package_msg = "" - anitya_project_url = self.anitya.does_project_exists_in_anitya(project_name) - if anitya_project_url is None: - anitya_project_url = self.anitya.create_project_in_anitya( - repo, upstreamurl, backend - ) - if anitya_project_url is None: - project_msg = ( - "Wasn't able to create project in Anitya. " - "You can create it manually on: `https://release-monitoring.org`" - ) - else: - project_msg = ( - "Anitya project is accessible by this link \n`{0}`\n " - "you can modify it manually." - ).format(anitya_project_url) - package_exists = self.anitya.does_package_exists_in_anitya( - repo, project_name, distribution - ) - if not package_exists: - response_msg = self.anitya.create_package_in_anitya( - repo, project_name, distribution, upstreamurl - ) - if response_msg != "Success": - package_msg = ( - "Package wasn't created in Anitya, reason: `{0}`.".format( - response_msg - ) - ) - else: - package_msg = "Package was created in Anitya" - - monitoring_message = project_msg + "\n" + package_msg - new_repo_comment = new_repo_comment + "\nMonitoring:\n" + monitoring_message - self.pagure_io.close_issue( issue["id"], namespace=PROJECT_NAMESPACE, diff --git a/toddlers/plugins/unretire_packages.py b/toddlers/plugins/unretire_packages.py deleted file mode 100644 index 619e9d2..0000000 --- a/toddlers/plugins/unretire_packages.py +++ /dev/null @@ -1,581 +0,0 @@ -""" -This is a script to automate unretirement of package automatically, when ticket is created. - -Authors: Anton Medvedev - -""" - -import argparse -import json -import logging -import re -import sys -import tempfile -import traceback -from typing import Optional - -import arrow -from fedora_messaging.api import Message -from git import GitCommandError -import koji -from pagure_messages.issue_schema import IssueNewV1 -import tomllib - -from toddlers.base import ToddlerBase -from toddlers.exceptions import ValidationError -from toddlers.utils import bodhi, bugzilla_system, git, pagure, requests - - -# Where to look for unretire request tickets -PROJECT_NAMESPACE = "releng/fedora-scm-requests" -# Keyword that will be searched for in the issue title -UNRETIRE_KEYWORD = "unretire" -# RPM package prefix, that will be searched in the issue title -RPM_PREFIX = "rpms/" -# Forbidden keywords for commit message -FORBIDDEN_KEYWORDS_FOR_COMMIT_MESSAGE = ["legal", "license"] -# Time difference limit not getting Bugzilla url -TIME_DIFFERENCE_LIMIT = 56 # 8 weeks in days -# Package retirement process url -PACKAGE_RETIREMENT_PROCESS_URL = ( - "https://docs.fedoraproject.org/en-US/package-maintainers" - "/Package_Retirement_Process/#claiming" -) -# Fedora review bugzilla flag -FEDORA_REVIEW_FLAG_NAME = "fedora-review" -# Koji hub url -KOJIHUB_URL = "https://koji.fedoraproject.org/kojihub" - -_log = logging.getLogger(__name__) - - -class UnretirePackages(ToddlerBase): - """ - Listen for new tickets in https://pagure.io/releng/fedora-scm-requests/issues - and process then, either by unretiring a package or rejecting the ticket - """ - - name: str = "unretire_packages" - - amqp_topics: list = ["io.pagure.*.pagure.issue.new"] - - # Path to temporary dir - temp_dir: str = "" - - # Requests session - requests_session: requests.requests.Session - - # Dist-git base url - dist_git_base: Optional[str] = "" - - # Pagure object connected to pagure.io - pagure_io: pagure.Pagure - - # Pagure user that will be creating the comments on pagure - # for toddler - pagure_user: str = "" - - # Git repo object - git_repo: git.GitRepo - - # Koji session object - koji_session: koji.ClientSession - - # Bodhi object - bodhi: bodhi.Bodhi - - # OIDC distgit token - oidc_distgit_token: str - - def accepts_topic(self, topic: str) -> bool: - """ - Returns a boolean whether this toddler is interested in messages - from this specific topic. - - :arg topic: Topic to check - - :returns: True if topic is accepted, False otherwise - """ - if topic.startswith("io.pagure."): - if topic.endswith("pagure.issue.new"): - return True - - return False - - def process( - self, - config: dict, - message: Message, - ) -> None: - """ - Process a given message. - - :arg config: Toddlers configuration - :arg message: Message to process - """ - _log.debug( - "Processing message:\n{0}".format(json.dumps(message.body, indent=2)) - ) - project_name = message.body["project"]["fullname"] - - if project_name != PROJECT_NAMESPACE: - _log.info( - "The message doesn't belong to project {0}. Skipping message.".format( - PROJECT_NAMESPACE - ) - ) - return - - issue = message.body["issue"] - - if issue["status"] != "Open": - _log.info( - "The issue {0} is not open. Skipping message.".format(issue["id"]) - ) - return - - issue_title = issue["title"] - words_in_issue_title = issue_title.split() - if UNRETIRE_KEYWORD != words_in_issue_title[0].lower(): - _log.info( - "The issue doesn't contain keyword '{0}' in the title '{1}'" - "".format(UNRETIRE_KEYWORD, issue_title) - ) - return - - _log.debug("Getting temp_folder name from config.") - self.temp_dir = config.get("temp_folder", "") - - _log.debug("Creating a request session.") - self.requests_session = requests.make_session() - - _log.debug("Getting dist-git url from config.") - self.dist_git_base = config.get("dist_git_url") - - _log.debug("Setting up connection to Pagure") - self.pagure_io = pagure.set_pagure(config) - self.pagure_user = config.get("pagure_user", "") - - _log.debug("Setting up connection to Bugzilla") - bugzilla_system.set_bz(config) - - _log.debug("Setting up session with Koji") - self.koji_session = koji.ClientSession(KOJIHUB_URL) - - _log.debug("Setting up bodhi session") - self.bodhi = bodhi.set_bodhi(config) - - _log.debug("Getting OIDC distgit token from config.") - self.oidc_distgit_token = config.get("oidc_distgit_token", "") - - try: - self.process_ticket(issue) - except BaseException: - self.pagure_io.add_comment_to_issue( - issue["id"], - namespace=PROJECT_NAMESPACE, - comment=( - "Error happened during processing:\n" "```\n" "{0}\n" "```\n" - ).format(traceback.format_exc()), - ) - - def process_ticket(self, issue: dict) -> None: - """ - Process a single ticket - - :arg issue: A dictionary containing the issue - """ - _log.info("Handling pagure releng ticket '{0}'".format(issue["full_url"])) - try: - # If a ValueError is raised, that means it isn't valid JSON - issue_body = json.loads(issue["content"].strip("`").strip("\n")) - except ValueError: - _log.info("Invalid JSON in ticket. Closing '{0}'".format(issue["full_url"])) - self.pagure_io.close_issue( - issue["id"], - namespace=PROJECT_NAMESPACE, - message="Invalid JSON provided", - reason="Invalid", - ) - return - - package_name = issue_body["name"] - package_ns = issue_body["type"] - maintainer_fas = issue_body["maintainer"] - - package_ns = self._ns_convertor(package_ns) - - package_url = "{0}/{1}/{2}.git".format( - self.dist_git_base, package_ns, package_name - ) - - _log.debug("Verifying that package repository actually exist.") - if not self._does_url_exist(package_url): - msg = "Package repository doesnt exist. Try to repeat request." - _log.info(msg) - self.pagure_io.close_issue( - issue["id"], - namespace=PROJECT_NAMESPACE, - message=msg, - reason="Invalid", - ) - return - - _log.debug("Creating temporary directory") - with tempfile.TemporaryDirectory(dir=self.temp_dir) as tmp_dir: - _log.info("Cloning repo into dir with name '{0}'".format(self.temp_dir)) - try: - self.git_repo = git.clone_repo(package_url, tmp_dir) - except GitCommandError: - message = "Something went wrong during cloning git repository." - _log.info(message) - self.pagure_io.close_issue( - issue["id"], - namespace=PROJECT_NAMESPACE, - message=message, - reason="Invalid", - ) - return - - branches = issue_body["branches"] - - _log.debug("Getting active branches") - active_branches = self.bodhi.get_active_branches() - - filtered_branches = [ - branch for branch in branches if branch in active_branches - ] - - final_list_of_branches = [] - deadpackage_file_path = "dead.package" - _log.debug("Verifying that branches are actually exists.") - _log.debug( - "Verifying that branches are actually retired (have a `dead.package` file)." - ) - for branch in filtered_branches: - if self.git_repo.does_branch_exist(branch): - if self.git_repo.does_branch_contains_file( - branch, deadpackage_file_path - ): - final_list_of_branches.append(branch) - - _log.debug("Verifying if package is ready for unretirement.") - if not self._is_package_ready_for_unretirement( - issue_id=issue["id"], - branches=final_list_of_branches, - review_bugzilla=issue_body["review_bugzilla"], - ): - return - - _log.debug("Reverting retire commit") - revert_commit_message = "Unretirement request: {0}".format( - issue["full_url"] - ) - for branch in final_list_of_branches: - self.git_repo.revert_last_commit( - message=revert_commit_message, - user=self.pagure_user, - token=self.oidc_distgit_token, - branch=branch, - ) - - _log.debug("Unblocking tags on Koji.") - if self._check_tags_to_unblock(final_list_of_branches, package_name): - _log.debug("Unblocking tags in koji.") - for tag in final_list_of_branches: - try: - self.koji_session.packageListUnblock( - taginfo=tag, pkginfo=package_name - ) - except koji.GenericError: - msg = "Not able to unblock `{0}` tag on koji.".format(tag) - self.pagure_io.close_issue( - issue_id=issue["id"], - namespace=PROJECT_NAMESPACE, - message=msg, - reason="Invalid", - ) - return - - _log.debug("Verifying package is not orphan.") - if self.pagure_io.is_project_orphaned( - namespace=package_ns, repo=package_name - ): - if maintainer_fas == "": - msg = "Package is ophaned, but maintainer fas is not provided." - self.pagure_io.close_issue( - issue_id=issue["id"], - namespace=PROJECT_NAMESPACE, - message=msg, - reason="Invalid", - ) - return - self.pagure_io.assign_maintainer_to_project( - namespace=package_ns, - repo=package_name, - maintainer_fas=maintainer_fas, - ) - - _log.info( - "Package {0} is assigned to {1}".format( - f"{package_ns}/{package_name}", maintainer_fas - ) - ) - return - - def _is_package_ready_for_unretirement( - self, issue_id: int, branches: list, review_bugzilla: str - ) -> bool: - """ - Verify that package is ready for unretirement. - - :arg issue_id: An int value of issue ID. - :arg branches: A list containing branches that need to be unretired. - :arg review_bugzilla: A str contain url on bugzilla review. - - :returns: Bool value whether the package was verified. - """ - try: - _log.debug("Verifying the reason of retirement.") - self._verify_package_not_retired_for_reason(branches=branches) - _log.debug("Verifying the date of retirement.") - self._verify_bugzilla_ticket( - review_bugzilla=review_bugzilla, branches=branches - ) - except ValidationError as error: - self.pagure_io.close_issue( - issue_id=issue_id, - namespace=PROJECT_NAMESPACE, - message=str(error), - reason="Invalid", - ) - return False - return True - - def _verify_package_not_retired_for_reason(self, branches: list): - """ - Verify that commit message does not contain forbidden keywords. - - Raises: - `toddler.exceptions.ValidationError`: When retirement reason wasn't verified - """ - _log.debug("Verifying that issue message doesn't contain forbidden keywords") - - for branch in branches: - last_commit_message = self.git_repo.get_last_commit_message(branch) - if any( - re.search(forbidden_keyword, str(last_commit_message).lower()) - for forbidden_keyword in FORBIDDEN_KEYWORDS_FOR_COMMIT_MESSAGE - ): - raise ValidationError( - "Package was retired for a reason: legal or license issue." - ) - - def _verify_bugzilla_ticket(self, review_bugzilla, branches): - """ - Verify if last commit was made more than 8 weeks ago, need to request a bugzilla ticket. - """ - _log.debug("Verifying that retire commit was made less than 8 weeks ago.") - - is_need_to_verify_bz = False - - for branch in branches: - last_commit_date = self.git_repo.get_last_commit_date(branch) - if last_commit_date is None: - raise ValidationError("Couldn't get a date of the retire commit.") - else: - last_commit_date = arrow.get(last_commit_date) - - current_time = arrow.utcnow() - - time_diff_in_days = (current_time - last_commit_date).days - - if time_diff_in_days > TIME_DIFFERENCE_LIMIT: - is_need_to_verify_bz = True - - if not is_need_to_verify_bz: - return - - if review_bugzilla == "": - raise ValidationError( - "Bugzilla url is missing, please add it and recreate the ticket." - ) - - bug_id = review_bugzilla - - _log.debug("Getting the bug object from bugzilla.") - try: - bug = bugzilla_system.get_bug(bug_id) - except Exception as error: - raise ValidationError( - "The Bugzilla bug could not be verified. The following " - "error was encountered: {0}".format(str(error)) - ) - - if bug is None: - raise ValidationError( - "Bugzilla can't get the bug by bug id, fix bugzilla url." - ) - - if bug.product != "Fedora": - raise ValidationError( - "The bugzilla bug is for '{0}', " - "but request should be for 'Fedora'.".format(bug.product) - ) - - try: - _log.info("Getting {0} flag from bug".format(FEDORA_REVIEW_FLAG_NAME)) - fedora_review_flag = bug.get_flags(FEDORA_REVIEW_FLAG_NAME) - fedora_review_flag_status = fedora_review_flag[0]["status"] - - if fedora_review_flag_status != "+": - raise ValidationError( - "Flag fedora-review has wrong status, need to be +" - ) - except TypeError: - raise ValidationError( - "Tag fedora-review is missing on bugzilla, get it and recreate the ticket." - ) - - def _check_tags_to_unblock(self, tags_to_unblock: list, repo: str) -> bool: - """ - Check if at least one of the tags requested to be unblocked are really blocked. - - :arg tags_to_unblock: List of branch names - :arg repo: Name of package - - :returns: Bool value whether program need to unblock tags - """ - _log.debug("Verifying that tags are blocked on koji.") - try: - package_tags = self.koji_session.listTags(package=repo) - if not package_tags: - raise ValidationError("Package doesn't have tags on koji.") - tags_that_suppose_to_be_blocked = [] - - for tag in package_tags: - prefix = "dist-" - if tag["name"].startswith(prefix): - tag_name = tag["name"][len(prefix) :] # noqa: E203 - if tag_name in tags_to_unblock: - tags_that_suppose_to_be_blocked.append(tag) - - if len(tags_that_suppose_to_be_blocked) == 0: - raise ValidationError( - "Request to unblock tags that don't exist on koji." - ) - return any([tag["locked"] for tag in tags_that_suppose_to_be_blocked]) - except koji.GenericError: - raise ValidationError("Package doesn't exist on koji.") - - def _does_url_exist(self, url: str) -> bool: - """ - Check whether url exist. - - :arg url: Url that might exist - - :returns: True if url exist, otherwise False - """ - try: - response = self.requests_session.get(url) - except ConnectionError: - return False - return response.status_code == 200 - - @staticmethod - def _ns_convertor(namespace): - ns_mapping = { - "rpm": "rpms", - "test": "tests", - "flatpak": "flatpaks", - "module": "modules", - } - namespace = ns_mapping[namespace] if namespace in ns_mapping else namespace - return namespace - - -def _get_arguments(args): - """Load and parse the CLI arguments. - - :arg args: Script arguments - - :returns: Parsed arguments - """ - parser = argparse.ArgumentParser( - description="Processor for Unretire packages, handling tickets from '{}'".format( - PROJECT_NAMESPACE - ) - ) - - parser.add_argument( - "ticket", - type=int, - help="Number of ticket to process", - ) - - parser.add_argument( - "--config", - help="Configuration file", - ) - - parser.add_argument( - "--debug", - action="store_const", - dest="log_level", - const=logging.DEBUG, - default=logging.INFO, - help="Enable debugging output", - ) - return parser.parse_args(args) - - -def _setup_logging(log_level: int) -> None: - """ - Set up the logging level. - - :arg log_level: Log level to set - """ - handlers = [] - - _log.setLevel(log_level) - # We want all messages logged at level INFO or lower to be printed to stdout - info_handler = logging.StreamHandler(stream=sys.stdout) - handlers.append(info_handler) - - if log_level == logging.INFO: - # In normal operation, don't decorate messages - for handler in handlers: - handler.setFormatter(logging.Formatter("%(message)s")) - - logging.basicConfig(level=log_level, handlers=handlers) - - -def main(args): - """Main function""" - args = _get_arguments(args) - _setup_logging(log_level=args.log_level) - _log.info("hello i'm starting work") - - config = tomllib.load(args.config) - - ticket = args.ticket - - pagure_io = pagure.set_pagure(config) - issue = pagure_io.get_issue(ticket, PROJECT_NAMESPACE) - - # Convert issue to message - body = {"project": {"fullname": PROJECT_NAMESPACE}, "issue": issue} - message = IssueNewV1(body=body) - _log.debug("Message prepared: {}".format(message.body)) - - UnretirePackages().process( - config=config, - message=message, - ) - - -if __name__ == "__main__": # pragma: no cover - try: - main(sys.argv[1:]) - except KeyboardInterrupt: - pass diff --git a/toddlers/utils/anitya.py b/toddlers/utils/anitya.py deleted file mode 100644 index 6c371be..0000000 --- a/toddlers/utils/anitya.py +++ /dev/null @@ -1,212 +0,0 @@ -""" -This module is a wrapper for Anitya. It uses Anitya API to communicate and configure -release monitoring instance. -To work with it, you need to set it up by calling `set_anitya`. - -Examples: - from utils import anitya - - anitya_config = { - "anitya_endpoint": "https://release-monitoring.org/", - "anitya_access_token": "secret TOKEN", - } - - anitya_obj = anitya.set_anitya(config) - anitya_obj.create_project_in_anitya("", "", "") -""" - -import logging -from typing import Optional - -from toddlers.utils import requests - -log = logging.getLogger(__name__) - - -def set_anitya(config): - """ - Set the connection to the Anitya API. - - Params: - config: Configuration dictionary. - """ - return Anitya(config) - - -class Anitya(object): - """ - Object that works with Anitya. - """ - - # URL to Anitya - _anitya_endpoint: str = "" - # API TOKEN to Anitya - _anitya_access_token: Optional[str] = None - # Request Session object used for communication - _requests_session: requests.requests.Session - - def __init__(self, config): - """ - Initialize the Anitya class. - - Params: - config (dict): A configuration with anitya_endpoint and anitya_access_token keys. - - Raises: - ValueError: If no pagure_api_key is provided. - """ - self._anitya_endpoint = config.get("anitya_endpoint", "").removesuffix("/") - if not self._anitya_endpoint: - raise ValueError("No anitya endpoint found in config file") - - self._anitya_token = config.get("anitya_access_token", "") - if not self._anitya_token: - raise ValueError("No anitya access token found in config file") - - self._requests_session = requests.make_session(timeout=300) - - def does_project_exists_in_anitya(self, project_name: str) -> Optional[str]: - """ - Check if project exists in Anitya. - - Params: - project_name (str): The name of the project. - - Returns: - Optional[str]: project URL if it exists in Anitya, otherwise None. - """ - projects_params = { - "name": project_name, - } - endpoint = self._anitya_endpoint + "/api/v2/projects/" - projects_response = self._requests_session.get(endpoint, params=projects_params) - if projects_response.status_code != 200: - log.debug("Project '{0}' not found in Anitya.".format(project_name)) - return None - - response_json = projects_response.json() - item_count = response_json["total_items"] - if item_count == 0: - log.debug("Project '{0}' not found in Anitya.".format(project_name)) - return None - - try: - project_id = response_json["items"][0]["id"] - project_url = "{0}/project/{1}".format(self._anitya_endpoint, project_id) - return project_url - except (KeyError, IndexError): - return None - - def does_package_exists_in_anitya( - self, package_name: str, distribution: str, project_name: str - ) -> bool: - """ - Check if package exists in Anitya. - - Params: - package_name (str): The name of the package. - distribution (str): The name of the distribution. - project_name (str): The name of the project. - - Returns: - False if package don't exist - False if package exist but his project is different from provided project name - True if package exist and his project is correct - """ - endpoint = self._anitya_endpoint + "/api/v2/packages/" - packages_params = { - "name": package_name, - "distribution": distribution, - } - packages_response = self._requests_session.get(endpoint, params=packages_params) - if packages_response.status_code != 200: - log.info("Package '{0}' not found in Anitya.".format(package_name)) - return False # Not able to find package - response_json = packages_response.json() - item_count = response_json["total_items"] - if item_count < 1: - log.info("Package '{0}' not found in Anitya.".format(package_name)) - return False - package_json = response_json["items"][0] - package_project_name = package_json["project"] - if package_project_name != project_name: - return False # Expected and actual project name are different - else: - return True # Expected and actual project name are the same - - def create_project_in_anitya( - self, - name: str, - homepage: str, - backend: str, - ) -> Optional[str]: - """ - Create a new project in Anitya. - - Params: - name (str): The name of the project. - homepage (str): The homepage of the project. - backend (str): The name of the backend. - - Returns: - The project URL if successful, otherwise None. - """ - headers = {"Authorization": "token " + self._anitya_token} - endpoint = self._anitya_endpoint + "/api/v2/projects/" - payload = { - "name": name, - "homepage": homepage, - "backend": backend, - } - log.info("Creating project '{0}' in Anitya.".format(name)) - response = self._requests_session.post( - url=endpoint, data=payload, headers=headers - ) - if response.status_code == 201: - project_json = response.json() - project_id = project_json["id"] - project_url = "{0}/project/{1}".format(self._anitya_endpoint, project_id) - return project_url - else: - return None - - def create_package_in_anitya( - self, - package_name: str, - project_name: str, - distribution: str, - project_ecosystem: str, - ) -> Optional[str]: - """ - Create a new package in Anitya. - - Params: - package_name (str): The name of the package. - project_name (str): The name of the project. - distribution (str): The name of the distribution. - project_ecosystem (str): The name of the ecosystem. - - Returns: - Return message if status code is known, otherwise None. - """ - headers = {"Authorization": "token " + self._anitya_token} - endpoint = self._anitya_endpoint + "/api/v2/packages/" - payload = { - "package_name": package_name, - "project_name": project_name, - "distribution": distribution, - "project_ecosystem": project_ecosystem, - } - log.info("Creating package '{0}' in Anitya.".format(package_name)) - response = self._requests_session.post( - url=endpoint, data=payload, headers=headers - ) - if response.status_code == 400: - return "Bad Request, some necessary arguments were not provided." - elif response.status_code == 401: - return "Unauthorized, access token is incorrect." - elif response.status_code == 409: - return "Conflict, package already exists." - elif response.status_code == 201: - return "Success" - return None diff --git a/toddlers/utils/git.py b/toddlers/utils/git.py index c768b35..6e340f9 100644 --- a/toddlers/utils/git.py +++ b/toddlers/utils/git.py @@ -136,9 +136,7 @@ class GitRepo: except Exception: # Raised when branch name is not correct return None - def revert_last_commit( - self, message: str, user: str, token: str, branch: str = "rawhide" - ) -> None: + def revert_last_commit(self, message: str, branch: str = "rawhide") -> None: """ Revert last commit with message on requested branch. @@ -151,16 +149,10 @@ class GitRepo: self.repo.git.checkout(branch) # reverting last commit and changing the commit message - self.repo.git.revert("HEAD", no_edit=True) - self.repo.git.commit("--amend", "-m", message) - + self.repo.git.execute(["git", "revert", "--no-edit", "HEAD"]) + self.repo.git.execute(["git", "commit", "--amend", "-m", message]) origin = self.repo.remote("origin") - git_cmd = self.repo.git - - push_url = origin.url.replace( - "https://", "https://{0}:{1}@".format(user, token) - ) - git_cmd.push("-u", push_url, branch) + origin.push() except Exception as error: print(error, "\nSomething happened during reverting the last commit")