diff --git a/tests/plugins/test_scm_request_processor.py b/tests/plugins/test_scm_request_processor.py index fc51f87..f129663 100644 --- a/tests/plugins/test_scm_request_processor.py +++ b/tests/plugins/test_scm_request_processor.py @@ -2,9 +2,11 @@ Unit tests for `toddlers.plugins.scm_request_processor` """ import json -from unittest.mock import call, patch, Mock +from unittest.mock import call, patch, MagicMock, Mock import logging +import re +import arrow from pagure_messages.issue_schema import IssueNewV1 import pytest @@ -1406,18 +1408,12 @@ class TestCreateNewBranch: "users": { "admin": [], "commit": [], - "collaborators": [{ - "user": "", - "branches": "rawhide" - }] + "collaborators": [] }, "groups": { "admin": ["group"], "commit": [], - "collaborators": [{ - "user": "", - "branches": "rawhide" - }] + "collaborators": [] } } mock_pdc.get_branch.return_value = None @@ -1439,3 +1435,1194 @@ class TestCreateNewBranch: message="zlopez is not a maintainer of the {0} package".format(repo), reason="Invalid" ) + + @patch("toddlers.plugins.scm_request_processor.TemporaryDirectory") + @patch("toddlers.plugins.scm_request_processor.git") + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.pdc") + def test_create_new_branch_default_branch_missing(self, mock_pdc, mock_fedora_account, mock_git, mock_temp_dir): + """ + Assert that ticket will be closed if there is no default branch in project and + new branch in git should be created. + """ + issue = { + "id": 100, + "user": { + "name": "zlopez" + } + } + + repo = "repo" + branch = "rawhide" + namespace = "rpms" + action = "new_branch" + sls = { + "rawhide": "2050-06-01" + } + json = { + "repo": repo, + "branch": branch, + "namespace": namespace, + "action": action, + "sls": sls, + } + self.toddler.dist_git.get_project_contributors.return_value = { + "users": { + "admin": [], + "commit": [], + "collaborators": [] + }, + "groups": { + "admin": ["group"], + "commit": [], + "collaborators": [] + } + } + self.toddler.dist_git.get_default_branch.return_value = None + self.toddler.dist_git._pagure_url = "https://fp.o" + mock_pdc.get_branch.return_value = None + mock_fedora_account.user_member_of.return_value = True + + mock_dir = MagicMock() + mock_dir.__enter__.return_value = "dir" + + mock_temp_dir.return_value = mock_dir + + self.toddler.create_new_branch(issue, json) + + # Asserts + self.toddler.dist_git.get_project_contributors.assert_called_with(namespace, repo) + + mock_pdc.get_branch.assert_called_with(repo, branch, namespace.strip().rstrip('s')) + + mock_fedora_account.user_member_of.assert_called_with( + mock_fedora_account.get_user_by_username(), "group" + ) + + mock_pdc.new_global_component.assert_called_with( + repo, "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo) + ) + + mock_pdc.new_branch.assert_called_with( + repo, branch, namespace.strip().rstrip('s') + ) + + mock_pdc.new_sla_to_branch.assert_called_with( + branch, "2050-06-01", repo, branch, namespace.strip().rstrip('s') + ) + + mock_git.clone_repo.assert_called_with( + "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo), "dir" + ) + + self.toddler.dist_git.get_default_branch.assert_called_with( + namespace, repo + ) + + self.toddler.pagure_io.close_issue.assert_called_with( + 100, namespace=scm_request_processor.PROJECT_NAMESPACE, + message="There is no default branch set for {0}/{1}".format(namespace, repo), + reason="Invalid" + ) + + @patch("toddlers.plugins.scm_request_processor.TemporaryDirectory") + @patch("toddlers.plugins.scm_request_processor.git") + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.pdc") + def test_create_new_branch_with_git(self, mock_pdc, mock_fedora_account, mock_git, mock_temp_dir): + """ + Assert that ticket will be processed and branch created in git. + """ + issue = { + "id": 100, + "user": { + "name": "zlopez" + } + } + + repo = "repo" + default_branch = "rawhide" + branch = "f36" + namespace = "rpms" + action = "new_branch" + sls = { + "rawhide": "2050-06-01" + } + json = { + "repo": repo, + "branch": branch, + "namespace": namespace, + "action": action, + "sls": sls, + } + self.toddler.dist_git.get_project_contributors.return_value = { + "users": { + "admin": [], + "commit": [], + "collaborators": [] + }, + "groups": { + "admin": ["group"], + "commit": [], + "collaborators": [] + } + } + self.toddler.dist_git.get_default_branch.return_value = default_branch + self.toddler.dist_git._pagure_url = "https://fp.o" + mock_pdc.get_branch.return_value = None + mock_fedora_account.user_member_of.return_value = True + + mock_dir = MagicMock() + mock_dir.__enter__.return_value = "dir" + mock_temp_dir.return_value = mock_dir + + mock_git_repo = Mock() + mock_git_repo.first_commit.return_value = "SHA256" + + mock_git.clone_repo.return_value = mock_git_repo + + self.toddler.create_new_branch(issue, json) + + # Asserts + self.toddler.dist_git.get_project_contributors.assert_called_with(namespace, repo) + + mock_pdc.get_branch.assert_called_with(repo, branch, namespace.strip().rstrip('s')) + + mock_fedora_account.user_member_of.assert_called_with( + mock_fedora_account.get_user_by_username(), "group" + ) + + mock_pdc.new_global_component.assert_called_with( + repo, "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo) + ) + + mock_pdc.new_branch.assert_called_with( + repo, branch, namespace.strip().rstrip('s') + ) + + mock_pdc.new_sla_to_branch.assert_called_with( + default_branch, "2050-06-01", repo, branch, namespace.strip().rstrip('s') + ) + + mock_git.clone_repo.assert_called_with( + "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo), "dir" + ) + + self.toddler.dist_git.get_default_branch.assert_called_with( + namespace, repo + ) + + mock_git_repo.first_commit.assert_called_with(default_branch) + + self.toddler.dist_git.new_branch.assert_called_with( + namespace, repo, branch, from_commit="SHA256" + ) + + + message = ('The branch was created in PDC and git. It ' + 'may take up to 10 minutes before you have ' + 'write access on the branch.') + + 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.fedora_account") + @patch("toddlers.plugins.scm_request_processor.pdc") + def test_create_new_branch_only_PDC(self, mock_pdc, mock_fedora_account): + """ + Assert that ticket will be processed and branch created in PDC. + """ + issue = { + "id": 100, + "user": { + "name": "zlopez" + } + } + + repo = "repo" + branch = "rawhide" + namespace = "rpms" + action = "new_branch" + sls = { + "rawhide": "2050-06-01" + } + json = { + "repo": repo, + "branch": branch, + "namespace": namespace, + "action": action, + "sls": sls, + "create_git_branch": False, + } + self.toddler.dist_git.get_project_contributors.return_value = { + "users": { + "admin": [], + "commit": [], + "collaborators": [] + }, + "groups": { + "admin": ["group"], + "commit": [], + "collaborators": [] + } + } + self.toddler.dist_git._pagure_url = "https://fp.o" + mock_pdc.get_branch.return_value = None + mock_fedora_account.user_member_of.return_value = True + + self.toddler.create_new_branch(issue, json) + + # Asserts + self.toddler.dist_git.get_project_contributors.assert_called_with(namespace, repo) + + mock_pdc.get_branch.assert_called_with(repo, branch, namespace.strip().rstrip('s')) + + mock_fedora_account.user_member_of.assert_called_with( + mock_fedora_account.get_user_by_username(), "group" + ) + + mock_pdc.new_global_component.assert_called_with( + repo, "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo) + ) + + mock_pdc.new_branch.assert_called_with( + repo, branch, namespace.strip().rstrip('s') + ) + + mock_pdc.new_sla_to_branch.assert_called_with( + branch, "2050-06-01", repo, branch, namespace.strip().rstrip('s') + ) + + message = ('The branch in PDC was created. Pagure is still processing ' + 'the request, but in about 10 minutes, you may create the ' + 'branch in Pagure using git.') + + 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.fedora_account") + @patch("toddlers.plugins.scm_request_processor.pdc") + def test_create_new_branch_comment_on_bug(self, mock_pdc, mock_fedora_account, mock_bz): + """ + Assert that ticket will be processed and comment added to bugzilla bug, if provided. + """ + issue = { + "id": 100, + "user": { + "name": "zlopez" + } + } + + repo = "repo" + branch = "rawhide" + namespace = "rpms" + action = "new_branch" + sls = { + "rawhide": "2050-06-01" + } + bug_id = "123" + json = { + "repo": repo, + "branch": branch, + "namespace": namespace, + "action": action, + "bug_id": bug_id, + "sls": sls, + "create_git_branch": False, + } + self.toddler.dist_git.get_project_contributors.return_value = { + "users": { + "admin": [], + "commit": [], + "collaborators": [] + }, + "groups": { + "admin": ["group"], + "commit": [], + "collaborators": [] + } + } + self.toddler.dist_git._pagure_url = "https://fp.o" + mock_pdc.get_branch.return_value = None + mock_fedora_account.user_member_of.return_value = True + + self.toddler.create_new_branch(issue, json) + + # Asserts + self.toddler.dist_git.get_project_contributors.assert_called_with(namespace, repo) + + mock_pdc.get_branch.assert_called_with(repo, branch, namespace.strip().rstrip('s')) + + mock_fedora_account.user_member_of.assert_called_with( + mock_fedora_account.get_user_by_username(), "group" + ) + + mock_pdc.new_global_component.assert_called_with( + repo, "{0}/{1}/{2}".format(self.toddler.dist_git._pagure_url, namespace, repo) + ) + + mock_pdc.new_branch.assert_called_with( + repo, branch, namespace.strip().rstrip('s') + ) + + mock_pdc.new_sla_to_branch.assert_called_with( + branch, "2050-06-01", repo, branch, namespace.strip().rstrip('s') + ) + + message = ('The branch in PDC was created. Pagure is still processing ' + 'the request, but in about 10 minutes, you may create the ' + 'branch in Pagure using git.') + + self.toddler.pagure_io.close_issue.assert_called_with( + 100, namespace=scm_request_processor.PROJECT_NAMESPACE, + message=message, + reason="Processed" + ) + + mock_bz.comment_on_bug.assert_called_with(bug_id, message) + + +class TestValidateReviewBug: + """ + Test class for `toddlers.plugins.scm_request_processor.SCMRequestProcessor.validate_review_bug` method. + """ + + def setup(self): + """ + Initialize toddler. + """ + self.toddler = scm_request_processor.SCMRequestProcessor() + self.toddler.pagure_io = Mock() + self.toddler.dist_git = Mock() + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_bug_retrieve_error(self, mock_bz): + """ + Assert that error will be raised when bug couldn't be retrieved. + """ + # Preparation + repo = "repo" + branch = "rawhide" + bug_id = "123" + + mock_bz.get_bug.side_effect = Exception("error") + + error = ('The Bugzilla bug could not be verified. The following ' + 'error was encountered: error') + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_no_bug(self, mock_bz): + """ + Assert that error will be raised when the bug is not found in bugzilla. + """ + # Preparation + repo = "repo" + branch = "rawhide" + bug_id = "123" + + mock_bz.get_bug.return_value = None + + error = 'The Bugzilla bug doesn\'t exist' + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_epel_branch_invalid_bug(self, mock_bz): + """ + Assert that error will be raised when the requested branch is for EPEL, but + the review bug isn't. + """ + # Preparation + repo = "repo" + branch = "epel8" + bug_id = "123" + + mock_bug = Mock() + mock_bug.product = "Fedora" + + mock_bz.get_bug.return_value = mock_bug + + error = ('The Bugzilla bug is for "Fedora" but the ' + 'requested branch is an EPEL branch') + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_not_epel_branch_epel_bug(self, mock_bz): + """ + Assert that error will be raised when the requested branch isn't for EPEL, but + the review bug is. + """ + # Preparation + repo = "repo" + branch = "f36" + bug_id = "123" + + mock_bug = Mock() + mock_bug.product = "Fedora EPEL" + + mock_bz.get_bug.return_value = mock_bug + + error = ('The Bugzilla bug is for "Fedora EPEL" but the ' + 'requested branch is "{0}"').format(branch) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_invalid_component(self, mock_bz): + """ + Assert that error will be raised when the namespace provided is not known. + """ + # Preparation + repo = "repo" + branch = "f36" + bug_id = "123" + + mock_bug = Mock() + mock_bug.component = None + + mock_bz.get_bug.return_value = mock_bug + + self.toddler.pagure_namespace_to_component = {} + + error = 'The Bugzilla bug provided is not the proper type' + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_invalid_product(self, mock_bz): + """ + Assert that error will be raised when the namespace provided is not a valid product. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = "invalid" + + mock_bz.get_bug.return_value = mock_bug + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = 'The Bugzilla bug provided is not for "{0}" or "{0}"'.format(namespace) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_not_assigned(self, mock_bz): + """ + Assert that error will be raised when the bug is not assigned to anybody. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = None + + mock_bz.get_bug.return_value = mock_bug + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = 'The Bugzilla bug provided is not assigned to anyone' + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_user_not_in_FAS(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug creator doesn't exists in FAS. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = None + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla review bug creator could not be found in ' + 'FAS. Make sure your FAS email address is the same as in ' + 'Bugzilla.' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_called_with(user) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_user_not_matching_requester(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug creator doesn't match the ticket requester. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": "not_zlopez" + } + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla review bug creator ' + 'didn\'t match the requester in Pagure.' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_called_with(user) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_reviewer_not_in_FAS(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug reviewer isn't in FAS. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "" + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.side_effect = [{ + "username": user + }, None] + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The email address "{0}" of the Bugzilla reviewer ' + 'is not tied to a user in FAS or FAS check failed. ' + 'Group membership can\'t be validated.'.format(user) + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(user) + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_reviewer_not_packager(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug reviewer is not in packager group. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "" + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user + } + mock_fas.user_member_of.return_value = False + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla bug\'s review ' + 'is approved by a user "{0}" that is ' + 'not a packager or FAS check failed'.format( + user) + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(user) + ]) + + mock_fas.user_member_of.assert_called_with( + {"username": user}, "packager" + ) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_submitter_not_packager(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug submitter is not in packager group. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "" + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user + } + mock_fas.user_member_of.side_effect = [True, False] + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla reporter "{0}"' + 'is not a packager'.format(user) + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(user) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user}, "packager"), + call({"username": user}, "packager"), + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_approved_by_creator(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug is approved by creator. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "", + "setter": user + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user + } + mock_fas.user_member_of.return_value = True + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla bug\'s review is approved ' + 'by the person creating the bug. This is ' + 'not allowed.' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(user) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user}, "packager"), + call({"username": user}, "packager"), + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_not_approved_by_assignee(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug is not approved by assignee. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = user + mock_bug.creator = user + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "", + "setter": "not@setter.com" + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user, + "emails": [] + } + mock_fas.user_member_of.return_value = True + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The review is not approved by ' + 'the assignee of the Bugzilla ' + 'bug' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(user) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user, "emails": []}, "packager"), + call({"username": user, "emails": []}, "packager"), + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_approved_in_past(self, mock_bz, mock_fas): + """ + Assert that error will be raised when bug was approved more than 60 days ago. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + assignee = "assignee" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = assignee + mock_bug.creator = user + + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "", + "setter": assignee, + "modification_date": arrow.utcnow().shift(days=-61).format("YYYY-MM-DDTHH-mm-ssZ") + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user, + } + mock_fas.user_member_of.return_value = True + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla bug\'s review ' + 'was approved over 60 days ago' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(assignee) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user}, "packager"), + call({"username": user}, "packager"), + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_flags_missing(self, mock_bz, mock_fas): + """ + Assert that error will be raised when the bug flags are missing. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.creator = user + mock_bug.flags = [] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user, + } + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + error = ( + 'The Bugzilla bug is not approved yet' + ) + + # Method to test + with pytest.raises(ValidationError, match=error): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user) + ]) + + @pytest.mark.parametrize( + "summary, error", + [ + ("", 'Invalid title for this Bugzilla bug (no ":" present)'), + ("Review: package", 'Invalid title for this Bugzilla bug (no "-" present)'), + ("Review: package - 1.0", ( + 'The package in the Bugzilla bug "package" doesn\'t match ' + 'the one provided "repo"' + ) + ), + ] + ) + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_invalid_summary(self, mock_bz, mock_fas, summary, error): + """ + Assert that error will be raised when bug summary is not correct. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + assignee = "assignee" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = assignee + mock_bug.creator = user + mock_bug.summary = summary + + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "+", + "setter": assignee, + "modification_date": arrow.utcnow().format("YYYY-MM-DDTHH-mm-ssZ") + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user, + } + mock_fas.user_member_of.return_value = True + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + # Method to test + with pytest.raises(ValidationError, match=re.escape(error)): + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(assignee) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user}, "packager"), + call({"username": user}, "packager"), + ]) + + @patch("toddlers.plugins.scm_request_processor.fedora_account") + @patch("toddlers.plugins.scm_request_processor.bugzilla_system") + def test_validate_review_bug_valid(self, mock_bz, mock_fas): + """ + Assert that no error will be raised when bug is valid. + """ + # Preparation + repo = "repo" + namespace = "rpms" + branch = "f36" + bug_id = "123" + user = "zlopez" + assignee = "assignee" + + mock_bug = Mock() + mock_bug.component = namespace + mock_bug.product = namespace + mock_bug.assigned_to = assignee + mock_bug.creator = user + mock_bug.summary = "Review: repo - Project for everyday work" + + mock_bug.flags = [ + { + "name": "fedora-review", + "status": "+", + "setter": assignee, + "modification_date": arrow.utcnow().format("YYYY-MM-DDTHH-mm-ssZ") + }, + { + "name": "random-flag", + } + ] + + mock_bz.get_bug.return_value = mock_bug + + mock_fas.get_user_by_email.return_value = { + "username": user, + } + mock_fas.user_member_of.return_value = True + + self.toddler.pagure_namespace_to_component = { + namespace: namespace + } + self.toddler.pagure_namespace_to_product = { + namespace: [namespace, namespace] + } + + # Method to test + self.toddler.validate_review_bug(bug_id, repo, branch, pagure_user=user) + + # Asserts + mock_bz.get_bug.assert_called_with(bug_id) + + mock_fas.get_user_by_email.assert_has_calls([ + call(user), call(assignee) + ]) + + mock_fas.user_member_of.assert_has_calls([ + call({"username": user}, "packager"), + call({"username": user}, "packager"), + ]) diff --git a/toddlers/plugins/scm_request_processor.py b/toddlers/plugins/scm_request_processor.py index 029144e..ae3d690 100644 --- a/toddlers/plugins/scm_request_processor.py +++ b/toddlers/plugins/scm_request_processor.py @@ -623,7 +623,7 @@ class SCMRequestProcessor(ToddlerBase): if create_git_branch: with TemporaryDirectory(dir=self.temp_dir) as tmp_dir: - repo = git.clone_repo(dist_git_url, tmp_dir) + git_repo = git.clone_repo(dist_git_url, tmp_dir) default_branch = self.dist_git.get_default_branch(namespace, repo) if not default_branch: self.pagure_io.close_issue( @@ -633,7 +633,7 @@ class SCMRequestProcessor(ToddlerBase): reason="Invalid" ) return - commit = repo.first_commit(default_branch) + commit = git_repo.first_commit(default_branch) self.dist_git.new_branch(namespace, repo, branch_name, from_commit=commit) new_branch_comment = ('The branch was created in PDC and git. It ' 'may take up to 10 minutes before you have ' @@ -650,7 +650,7 @@ class SCMRequestProcessor(ToddlerBase): reason="Processed" ) if bug_id: - bugzilla_system.comment_on_bug(bug_id, new_repo_comment) + bugzilla_system.comment_on_bug(bug_id, new_branch_comment) def validate_review_bug(self, bug_id: str, pkg: str, branch: str, namespace: str = 'rpms', pagure_user: Optional[str] = None @@ -669,7 +669,7 @@ class SCMRequestProcessor(ToddlerBase): pagure_user: a string of the requesting user's Pagure username """ try: - bug = bugzilla.get_bug(bug_id) + bug = bugzilla_system.get_bug(bug_id) except Exception as error: raise ValidationError( 'The Bugzilla bug could not be verified. The following ' @@ -680,7 +680,7 @@ class SCMRequestProcessor(ToddlerBase): # Check that the bug is valid bz_proper_component = self.pagure_namespace_to_component.get(namespace) bz_proper_products = self.pagure_namespace_to_product.get(namespace) - if namespace == 'rpms' and (branch != 'rawhide' or branch != 'main'): + if namespace == 'rpms' and (branch != 'rawhide' and branch != 'main'): if re.match(EPEL_REGEX, branch) and bug.product != 'Fedora EPEL': raise ValidationError( 'The Bugzilla bug is for "{0}" but the ' @@ -699,14 +699,14 @@ class SCMRequestProcessor(ToddlerBase): elif bug.assigned_to in ['', None, 'nobody@fedoraproject.org']: raise ValidationError( 'The Bugzilla bug provided is not assigned to anyone') + fas_submitter = fedora_account.get_user_by_email(bug.creator) + if not fas_submitter: + raise ValidationError( + 'The Bugzilla review bug creator could not be found in ' + 'FAS. Make sure your FAS email address is the same as in ' + 'Bugzilla.') if pagure_user: - fas_user = fedora_account.get_user_by_email(bug.creator) - if not fas_user: - raise ValidationError( - 'The Bugzilla review bug creator could not be found in ' - 'FAS. Make sure your FAS email address is the same as in ' - 'Bugzilla.') - if fas_user['username'] != pagure_user: + if fas_submitter['username'] != pagure_user: raise ValidationError('The Bugzilla review bug creator ' 'didn\'t match the requester in Pagure.') # Check if the review was approved and by whom @@ -726,18 +726,10 @@ class SCMRequestProcessor(ToddlerBase): 'is approved by a user "{0}" that is ' 'not a packager or FAS check failed'.format( bug.assigned_to)) - fas_submitter = self.get_fas_user_by_bz_email(bug.creator) - if not fas_submitter: - raise ValidationError( - 'The email address "{0}" of the Bugzilla submitter ' - 'is not tied to a user in FAS. Group membership ' - 'can\'t be validated.'.format(bug.creator)) - if not fedora_acount.user_member_of(fas_submitter, 'packager'): + if not fedora_account.user_member_of(fas_submitter, 'packager'): raise ValidationError('The Bugzilla reporter "{0}"' 'is not a packager'.format(bug.creator)) - # Setter will be an empty string and emails will not be shown - # if the user is not logged in. This is why we check for - # authentication here. + if flag['setter'] == bug.creator: error = ('The Bugzilla bug\'s review is approved ' 'by the person creating the bug. This is ' @@ -746,10 +738,10 @@ class SCMRequestProcessor(ToddlerBase): assigned_to_emails = [bug.assigned_to] - assigned_to_user = fedora_account.get_user_by_username(bug.assigned_to) - if assigned_to_user: + if "emails" in fas_reviewer: assigned_to_emails.append( - assigned_to_user["emails"]) + fas_reviewer["emails"]) + if flag['setter'] not in assigned_to_emails: raise ValidationError('The review is not approved by ' 'the assignee of the Bugzilla ' @@ -757,9 +749,8 @@ class SCMRequestProcessor(ToddlerBase): update_dt = flag.get('modification_date') if update_dt: - dt = datetime.strptime( - update_dt.value, '%Y%m%dT%H:%M:%S') - delta = datetime.utcnow().date() - dt.date() + dt = arrow.get(update_dt, 'YYYY-MM-DDTHH-mm-ssZ') + delta = arrow.utcnow().date() - dt.date() if delta.days > 60: raise ValidationError('The Bugzilla bug\'s review ' 'was approved over 60 days ago')