Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/test/issues_servicer_test.py b/api/test/issues_servicer_test.py
new file mode 100644
index 0000000..2c46f7c
--- /dev/null
+++ b/api/test/issues_servicer_test.py
@@ -0,0 +1,2693 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Tests for the issues servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import logging
+import sys
+import time
+import unittest
+from mock import ANY, Mock, patch
+
+from google.protobuf import empty_pb2
+
+from components.prpc import codes
+from components.prpc import context
+from components.prpc import server
+
+from api import issues_servicer
+from api import converters
+from api.api_proto import common_pb2
+from api.api_proto import issues_pb2
+from api.api_proto import issue_objects_pb2
+from api.api_proto import common_pb2
+from businesslogic import work_env
+from features import filterrules_helpers
+from features import send_notifications
+from framework import authdata
+from framework import exceptions
+from framework import framework_views
+from framework import monorailcontext
+from framework import permissions
+from search import frontendsearchpipeline
+from proto import tracker_pb2
+from proto import project_pb2
+from testing import fake
+from tracker import tracker_bizobj
+from services import service_manager
+from proto import tracker_pb2
+
+
+class IssuesServicerTest(unittest.TestCase):
+
+ NOW = 1234567890
+
+ def setUp(self):
+ self.cnxn = fake.MonorailConnection()
+ self.services = service_manager.Services(
+ config=fake.ConfigService(),
+ features=fake.FeaturesService(),
+ issue=fake.IssueService(),
+ issue_star=fake.IssueStarService(),
+ project=fake.ProjectService(),
+ spam=fake.SpamService(),
+ user=fake.UserService(),
+ usergroup=fake.UserGroupService())
+ self.project = self.services.project.TestAddProject(
+ 'proj', project_id=789, owner_ids=[111], contrib_ids=[222, 333])
+ self.user_1 = self.services.user.TestAddUser('owner@example.com', 111)
+ self.user_2 = self.services.user.TestAddUser('approver2@example.com', 222)
+ self.user_3 = self.services.user.TestAddUser('approver3@example.com', 333)
+ self.user_4 = self.services.user.TestAddUser('nonmember@example.com', 444)
+ self.issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj',
+ opened_timestamp=self.NOW, issue_id=1001)
+ self.issue_2 = fake.MakeTestIssue(
+ 789, 2, 'sum', 'New', 111, project_name='proj', issue_id=1002)
+ self.issue_1.blocked_on_iids.append(self.issue_2.issue_id)
+ self.issue_1.blocked_on_ranks.append(sys.maxint)
+ self.services.issue.TestAddIssue(self.issue_1)
+ self.services.issue.TestAddIssue(self.issue_2)
+ self.issues_svcr = issues_servicer.IssuesServicer(
+ self.services, make_rate_limiter=False)
+ self.prpc_context = context.ServicerContext()
+ self.prpc_context.set_code(server.StatusCode.OK)
+ self.auth = authdata.AuthData(user_id=333, email='approver3@example.com')
+
+ self.fd_1 = tracker_pb2.FieldDef(
+ field_name='FirstField', field_id=1,
+ field_type=tracker_pb2.FieldTypes.STR_TYPE,
+ applicable_type='')
+ self.fd_2 = tracker_pb2.FieldDef(
+ field_name='SecField', field_id=2,
+ field_type=tracker_pb2.FieldTypes.INT_TYPE,
+ applicable_type='')
+ self.fd_3 = tracker_pb2.FieldDef(
+ field_name='LegalApproval', field_id=3,
+ field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE,
+ applicable_type='')
+ self.fd_4 = tracker_pb2.FieldDef(
+ field_name='UserField', field_id=4,
+ field_type=tracker_pb2.FieldTypes.USER_TYPE,
+ applicable_type='')
+ self.fd_5 = tracker_pb2.FieldDef(
+ field_name='DogApproval', field_id=5,
+ field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE,
+ applicable_type='')
+
+ def CallWrapped(self, wrapped_handler, *args, **kwargs):
+ return wrapped_handler.wrapped(self.issues_svcr, *args, **kwargs)
+
+ def testGetProjectIssueIDsAndConfig_OnlyOneProjectName(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [
+ common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(local_id=2),
+ common_pb2.IssueRef(project_name='proj', local_id=3),
+ ]
+ project, issue_ids, config = self.issues_svcr._GetProjectIssueIDsAndConfig(
+ mc, issue_refs)
+ self.assertEqual(project, self.project)
+ self.assertEqual(issue_ids, [self.issue_1.issue_id, self.issue_2.issue_id])
+ self.assertEqual(
+ config,
+ self.services.config.GetProjectConfig(
+ self.cnxn, self.project.project_id))
+
+ def testGetProjectIssueIDsAndConfig_NoProjectName(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [
+ common_pb2.IssueRef(local_id=2),
+ common_pb2.IssueRef(local_id=3),
+ ]
+ with self.assertRaises(exceptions.InputException):
+ self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
+
+ def testGetProjectIssueIDsAndConfig_MultipleProjectNames(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [
+ common_pb2.IssueRef(project_name='proj', local_id=2),
+ common_pb2.IssueRef(project_name='proj2', local_id=3),
+ ]
+ with self.assertRaises(exceptions.InputException):
+ self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
+
+ def testGetProjectIssueIDsAndConfig_MissingLocalId(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [
+ common_pb2.IssueRef(project_name='proj'),
+ common_pb2.IssueRef(project_name='proj', local_id=3),
+ ]
+ with self.assertRaises(exceptions.InputException):
+ self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
+
+ def testCreateIssue_Normal(self):
+ """We can create an issue."""
+ request = issues_pb2.CreateIssueRequest(
+ project_name='proj',
+ issue=issue_objects_pb2.Issue(
+ project_name='proj', local_id=1, summary='sum'))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ response = self.CallWrapped(self.issues_svcr.CreateIssue, mc, request)
+
+ self.assertEqual('proj', response.project_name)
+
+ def testGetIssue_Normal(self):
+ """We can get an issue."""
+ request = issues_pb2.GetIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
+
+ actual = response.issue
+ self.assertEqual('proj', actual.project_name)
+ self.assertEqual(1, actual.local_id)
+ self.assertEqual(1, len(actual.blocked_on_issue_refs))
+ self.assertEqual('proj', actual.blocked_on_issue_refs[0].project_name)
+ self.assertEqual(2, actual.blocked_on_issue_refs[0].local_id)
+
+ def testGetIssue_Moved(self):
+ """We can get a moved issue."""
+ self.services.project.TestAddProject(
+ 'other', project_id=987, owner_ids=[111], contrib_ids=[111])
+ issue = fake.MakeTestIssue(987, 200, 'sum', 'New', 111, issue_id=1010)
+ self.services.issue.TestAddIssue(issue)
+ self.services.issue.TestAddMovedIssueRef(789, 404, 987, 200)
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ request = issues_pb2.GetIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 404
+
+ response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
+
+ ref = response.moved_to_ref
+ self.assertEqual(200, ref.local_id)
+ self.assertEqual('other', ref.project_name)
+
+ @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
+ def testListIssues(self, mock_pipeline):
+ """We can get a list of issues from a search."""
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+ users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, [111])
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+
+ instance = Mock(
+ spec=True, visible_results=[self.issue_1, self.issue_2],
+ users_by_id=users_by_id, harmonized_config=config,
+ pagination=Mock(total_count=2))
+ mock_pipeline.return_value = instance
+ instance.SearchForIIDs = Mock()
+ instance.MergeAndSortIssues = Mock()
+ instance.Paginate = Mock()
+
+ request = issues_pb2.ListIssuesRequest(query='',project_names=['proj'])
+ response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
+
+ actual_issue_1 = response.issues[0]
+ self.assertEqual(actual_issue_1.owner_ref.user_id, 111)
+ self.assertEqual('owner@example.com', actual_issue_1.owner_ref.display_name)
+ self.assertEqual(actual_issue_1.local_id, 1)
+
+ actual_issue_2 = response.issues[1]
+ self.assertEqual(actual_issue_2.owner_ref.user_id, 111)
+ self.assertEqual('owner@example.com', actual_issue_2.owner_ref.display_name)
+ self.assertEqual(actual_issue_2.local_id, 2)
+ self.assertEqual(2, response.total_results)
+
+ # TODO(zhangtiff): Add tests for ListIssues + canned queries.
+
+ @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
+ def testListIssues_IncludesAttachmentCount(self, mock_pipeline):
+ """Ensure ListIssues includes correct attachment counts."""
+
+ # Add an attachment to one of the issues so we can check attachment counts.
+ issue_3 = fake.MakeTestIssue(
+ 789, 3, 'sum', 'New', 111, project_name='proj', issue_id=2003,
+ attachment_count=1)
+ issue_4 = fake.MakeTestIssue(
+ 789, 4, 'sum', 'New', 111, project_name='proj', issue_id=2004,
+ attachment_count=-10)
+ self.services.issue.TestAddIssue(issue_3)
+ self.services.issue.TestAddIssue(issue_4)
+
+ # Request the list of issues.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+ mc.LookupLoggedInUserPerms(self.project)
+
+ users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, [111])
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+ instance = Mock(
+ spec=True, visible_results=[
+ self.issue_1, self.issue_2, issue_3, issue_4],
+ users_by_id=users_by_id, harmonized_config=config,
+ pagination=Mock(total_count=4))
+ mock_pipeline.return_value = instance
+ instance.SearchForIIDs = Mock()
+ instance.MergeAndSortIssues = Mock()
+ instance.Paginate = Mock()
+
+ request = issues_pb2.ListIssuesRequest(query='', project_names=['proj'])
+ response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
+
+ # Ensure attachment counts match what we expect.
+ actual_issue_1 = response.issues[0]
+ self.assertEqual(actual_issue_1.attachment_count, 0)
+ self.assertEqual(actual_issue_1.local_id, 1)
+
+ actual_issue_2 = response.issues[1]
+ self.assertEqual(actual_issue_2.attachment_count, 0)
+ self.assertEqual(actual_issue_2.local_id, 2)
+
+ actual_issue_3 = response.issues[2]
+ self.assertEqual(actual_issue_3.attachment_count, 1)
+ self.assertEqual(actual_issue_3.local_id, 3)
+
+ actual_issue_4 = response.issues[3]
+ # NOTE(pawalls): It is not possible to test for presence in Proto3. Instead
+ # we test for default value here though it is semantically different
+ # and not quite the behavior we care about.
+ self.assertEqual(actual_issue_4.attachment_count, 0)
+ self.assertEqual(actual_issue_4.local_id, 4)
+
+ @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
+ def testListIssues_No_visible_results(self, mock_pipeline):
+ """Ensure ListIssues handles the no visible results case."""
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester=None, auth=None)
+ users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, [111])
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+
+ instance = Mock(
+ spec=True,
+ users_by_id=users_by_id,
+ harmonized_config=config,
+ # When there are no results, these default to None.
+ visible_results=None,
+ pagination=None)
+ mock_pipeline.return_value = instance
+ instance.SearchForIIDs = Mock()
+ instance.MergeAndSortIssues = Mock()
+ instance.Paginate = Mock()
+
+ request = issues_pb2.ListIssuesRequest(query='', project_names=['proj'])
+ response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
+
+ self.assertEqual(len(response.issues), 0)
+
+ def testListReferencedIssues(self):
+ """We can get the referenced issues that exist."""
+ self.services.project.TestAddProject(
+ 'other-proj', project_id=788, owner_ids=[111])
+ other_issue = fake.MakeTestIssue(
+ 788, 1, 'sum', 'Fixed', 111, project_name='other-proj', issue_id=78801)
+ self.services.issue.TestAddIssue(other_issue)
+ # We ignore project_names or local_ids that don't exist in our DB.
+ request = issues_pb2.ListReferencedIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='other-proj', local_id=1),
+ common_pb2.IssueRef(project_name='other-proj', local_id=2),
+ common_pb2.IssueRef(project_name='ghost-proj', local_id=1)
+ ]
+ )
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListReferencedIssues, mc, request)
+ self.assertEqual(len(response.closed_refs), 1)
+ self.assertEqual(len(response.open_refs), 1)
+ self.assertEqual(
+ issue_objects_pb2.Issue(
+ local_id=1,
+ project_name='other-proj',
+ summary='sum',
+ status_ref=common_pb2.StatusRef(
+ status='Fixed'),
+ owner_ref=common_pb2.UserRef(
+ user_id=111,
+ display_name='owner@example.com'),
+ reporter_ref=common_pb2.UserRef(
+ user_id=111,
+ display_name='owner@example.com')),
+ response.closed_refs[0])
+ self.assertEqual(
+ issue_objects_pb2.Issue(
+ local_id=1,
+ project_name='proj',
+ summary='sum',
+ status_ref=common_pb2.StatusRef(
+ status='New',
+ means_open=True),
+ owner_ref=common_pb2.UserRef(
+ user_id=111,
+ display_name='owner@example.com'),
+ blocked_on_issue_refs=[common_pb2.IssueRef(
+ project_name='proj',
+ local_id=2)],
+ reporter_ref=common_pb2.UserRef(
+ user_id=111,
+ display_name='owner@example.com'),
+ opened_timestamp=self.NOW,
+ component_modified_timestamp=self.NOW,
+ status_modified_timestamp=self.NOW,
+ owner_modified_timestamp=self.NOW),
+ response.open_refs[0])
+
+ def testListReferencedIssues_MissingInput(self):
+ request = issues_pb2.ListReferencedIssuesRequest(
+ issue_refs=[common_pb2.IssueRef(local_id=1)])
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.ListReferencedIssues, mc, request)
+
+ def testListApplicableFieldDefs_EmptyIssueRefs(self):
+ request = issues_pb2.ListApplicableFieldDefsRequest()
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.ListApplicableFieldDefs, mc, request)
+ self.assertEqual(response, issues_pb2.ListApplicableFieldDefsResponse())
+
+ def testListApplicableFieldDefs_CrossProjectRequest(self):
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj2', local_id=2)]
+ request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.ListApplicableFieldDefs, mc, request)
+
+ def testListApplicableFieldDefs_MissingProjectName(self):
+ issue_refs = [common_pb2.IssueRef(local_id=1),
+ common_pb2.IssueRef(local_id=2)]
+ request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.ListApplicableFieldDefs, mc, request)
+
+ def testListApplicableFieldDefs_Normal(self):
+ self.issue_1.labels = ['Type-Feedback']
+ self.issue_2.approval_values = [
+ tracker_pb2.ApprovalValue(approval_id=self.fd_3.field_id)]
+ self.fd_1.applicable_type = 'Defect' # not applicable
+ self.fd_2.applicable_type = 'feedback' # applicable
+ self.fd_3.applicable_type = 'ignored' # is APPROVAL_TYPE, applicable
+ self.fd_4.applicable_type = '' # applicable
+ self.fd_5.applicable_type = '' # is APPROVAl_TYPE, not applicable
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_1, self.fd_2, self.fd_3, self.fd_4, self.fd_5])
+ self.services.config.StoreConfig(self.cnxn, config)
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]
+ request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.ListApplicableFieldDefs, mc, request)
+ converted_field_defs = [converters.ConvertFieldDef(fd, [], {}, config, True)
+ for fd in [self.fd_2, self.fd_3, self.fd_4]]
+ self.assertEqual(response, issues_pb2.ListApplicableFieldDefsResponse(
+ field_defs=converted_field_defs))
+
+ def testUpdateIssue_Denied_Edit(self):
+ """We reject requests to update an issue when the user lacks perms."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.delta.summary.value = 'new summary'
+
+ # Anon user can never update.
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # Signed in user cannot view this issue.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ self.issue_1.labels = ['Restrict-View-CoreTeam']
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # Signed in user cannot edit this issue.
+ self.issue_1.labels = ['Restrict-EditIssue-CoreTeam']
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_JustAComment(self, _fake_pasicn):
+ """We check AddIssueComment when the user is only commenting."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.comment_content = 'Foo'
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ # Note: no delta.
+
+ # Anon user can never update.
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # Signed in user cannot view this issue.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ self.issue_1.labels = ['Restrict-View-CoreTeam']
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # Signed in user cannot edit this issue, but they can still comment.
+ self.issue_1.labels = ['Restrict-EditIssue-CoreTeam']
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # Signed in user cannot post even a text comment.
+ self.issue_1.labels = ['Restrict-AddIssueComment-CoreTeam']
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_Normal(self, fake_pasicn):
+ """We can update an issue."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.delta.summary.value = 'New summary'
+ request.delta.label_refs_add.extend([
+ common_pb2.LabelRef(label='Hot')])
+ request.comment_content = 'test comment'
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ actual = response.issue
+ # Intended stuff was changed.
+ self.assertEqual(1, len(actual.label_refs))
+ self.assertEqual('Hot', actual.label_refs[0].label)
+ self.assertEqual('New summary', actual.summary)
+
+ # Other stuff didn't change.
+ self.assertEqual('proj', actual.project_name)
+ self.assertEqual(1, actual.local_id)
+ self.assertEqual(1, len(actual.blocked_on_issue_refs))
+ self.assertEqual('proj', actual.blocked_on_issue_refs[0].project_name)
+ self.assertEqual(2, actual.blocked_on_issue_refs[0].local_id)
+
+ # A comment was added.
+ fake_pasicn.assert_called_once()
+ comments = self.services.issue.GetCommentsForIssue(
+ self.cnxn, self.issue_1.issue_id)
+ self.assertEqual(2, len(comments))
+ self.assertEqual('test comment', comments[1].content)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_CommentOnly(self, fake_pasicn):
+ """We can update an issue with a comment w/o making any other changes."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.comment_content = 'test comment'
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # A comment was added.
+ fake_pasicn.assert_called_once()
+ comments = self.services.issue.GetCommentsForIssue(
+ self.cnxn, self.issue_1.issue_id)
+ self.assertEqual(2, len(comments))
+ self.assertEqual('test comment', comments[1].content)
+ self.assertFalse(comments[1].is_description)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_CommentWithAttachments(self, fake_pasicn):
+ """We can update an issue with a comment and attachments."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.comment_content = 'test comment'
+ request.uploads.extend([
+ issue_objects_pb2.AttachmentUpload(
+ filename='a.txt',
+ content='aaaaa')])
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # A comment with an attachment was added.
+ fake_pasicn.assert_called_once()
+ comments = self.services.issue.GetCommentsForIssue(
+ self.cnxn, self.issue_1.issue_id)
+ self.assertEqual(2, len(comments))
+ self.assertEqual('test comment', comments[1].content)
+ self.assertFalse(comments[1].is_description)
+ self.assertEqual(1, len(comments[1].attachments))
+ self.assertEqual('a.txt', comments[1].attachments[0].filename)
+ self.assertEqual(5, self.project.attachment_bytes_used)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_Description(self, fake_pasicn):
+ """We can update an issue's description."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.comment_content = 'new description'
+ request.is_description = True
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ # A comment was added.
+ fake_pasicn.assert_called_once()
+ comments = self.services.issue.GetCommentsForIssue(
+ self.cnxn, self.issue_1.issue_id)
+ self.assertEqual(2, len(comments))
+ self.assertEqual('new description', comments[1].content)
+ self.assertTrue(comments[1].is_description)
+
+ @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
+ def testUpdateIssue_NoOp(self, fake_pasicn):
+ """We gracefully ignore requests that have no delta or comment."""
+ request = issues_pb2.UpdateIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
+
+ actual = response.issue
+ # Other stuff didn't change.
+ self.assertEqual('proj', actual.project_name)
+ self.assertEqual(1, actual.local_id)
+ self.assertEqual('sum', actual.summary)
+ self.assertEqual('New', actual.status_ref.status)
+
+ # No comment was added.
+ fake_pasicn.assert_not_called()
+ comments = self.services.issue.GetCommentsForIssue(
+ self.cnxn, self.issue_1.issue_id)
+ self.assertEqual(1, len(comments))
+
+ def testStarIssue_Denied(self):
+ """We reject requests to star an issue if the user lacks perms."""
+ request = issues_pb2.StarIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.starred = True
+
+ # Anon user cannot star an issue.
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
+
+ # User star an issue that they cannot view.
+ self.issue_1.labels = ['Restrict-View-CoreTeam']
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
+
+ # The issue was not actually starred.
+ self.assertEqual(0, self.issue_1.star_count)
+
+ def testStarIssue_Normal(self):
+ """Users can star and unstar issues."""
+ request = issues_pb2.StarIssueRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.starred = True
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ # First, star it.
+ response = self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
+ self.assertEqual(1, response.star_count)
+
+ # Then, unstar it.
+ request.starred = False
+ response = self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
+ self.assertEqual(0, response.star_count)
+
+ def testIsIssueStared_Anon(self):
+ """Anon users can't star issues, so they always get back False."""
+ request = issues_pb2.IsIssueStarredRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
+ self.assertFalse(response.is_starred)
+
+ def testIsIssueStared_Denied(self):
+ """Users can't ask about an issue that they cannot currently view."""
+ request = issues_pb2.IsIssueStarredRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ self.issue_1.labels = ['Restrict-View-CoreTeam']
+
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
+
+ def testIsIssueStared_Normal(self):
+ """Users can star and unstar issues."""
+ request = issues_pb2.IsIssueStarredRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ # It is not initially starred by this user.
+ response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
+ self.assertFalse(response.is_starred)
+
+ # If we star it, we get response True.
+ self.services.issue_star.SetStar(
+ self.cnxn, self.services, 'fake config', self.issue_1.issue_id,
+ 333, True)
+ response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
+ self.assertTrue(response.is_starred)
+
+ def testListStarredIssues_Anon(self):
+ """Users can't see their starred issues until they sign in."""
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.ListStarredIssues, mc, {})
+ # Assert that response has an empty list
+ self.assertEqual(0, len(response.starred_issue_refs))
+
+ def testListStarredIssues_Normal(self):
+ """User can access which issues they've starred."""
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ # First, star some issues
+ self.services.issue_star.SetStar(
+ self.cnxn, self.services, 'fake config', self.issue_1.issue_id,
+ 333, True)
+ self.services.issue_star.SetStar(
+ self.cnxn, self.services, 'fake config', self.issue_2.issue_id,
+ 333, True)
+
+ # Now test that user can retrieve their star in a list
+ response = self.CallWrapped(self.issues_svcr.ListStarredIssues, mc, {})
+ self.assertEqual(2, len(response.starred_issue_refs))
+
+ def testListComments_Normal(self):
+ """We can get comments on an issue."""
+ comment = tracker_pb2.IssueComment(
+ user_id=111, timestamp=self.NOW, content='second',
+ project_id=789, issue_id=self.issue_1.issue_id, sequence=1)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+ request = issues_pb2.ListCommentsRequest()
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+
+ response = self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+
+ actual_0 = response.comments[0]
+ actual_1 = response.comments[1]
+ expected_0 = issue_objects_pb2.Comment(
+ project_name='proj', local_id=1, sequence_num=0, is_deleted=False,
+ commenter=common_pb2.UserRef(
+ user_id=111, display_name='owner@example.com'),
+ timestamp=self.NOW, content='sum', is_spam=False,
+ description_num=1, can_delete=True, can_flag=True)
+ expected_1 = issue_objects_pb2.Comment(
+ project_name='proj', local_id=1, sequence_num=1, is_deleted=False,
+ commenter=common_pb2.UserRef(
+ user_id=111, display_name='owner@example.com'),
+ timestamp=self.NOW, content='second', can_delete=True, can_flag=True)
+ self.assertEqual(expected_0, actual_0)
+ self.assertEqual(expected_1, actual_1)
+
+ def testListActivities_Normal(self):
+ """We can get issue activity."""
+ self.services.user.TestAddUser('user@example.com', 444)
+
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_1])
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ comment = tracker_pb2.IssueComment(
+ user_id=444, timestamp=self.NOW, content='c1',
+ project_id=789, issue_id=self.issue_1.issue_id, sequence=1)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+
+ self.services.project.TestAddProject(
+ 'proj2', project_id=790, owner_ids=[111], contrib_ids=[222, 333])
+ issue_2 = fake.MakeTestIssue(
+ 790, 1, 'sum', 'New', 444, project_name='proj2',
+ opened_timestamp=self.NOW, issue_id=2001)
+ comment_2 = tracker_pb2.IssueComment(
+ user_id=444, timestamp=self.NOW, content='c2',
+ project_id=790, issue_id=issue_2.issue_id, sequence=1)
+ self.services.issue.TestAddComment(comment_2, issue_2.local_id)
+ self.services.issue.TestAddIssue(issue_2)
+
+ issue_3 = fake.MakeTestIssue(
+ 790, 2, 'sum', 'New', 111, project_name='proj2',
+ opened_timestamp=self.NOW, issue_id=2002, labels=['Restrict-View-Foo'])
+ comment_3 = tracker_pb2.IssueComment(
+ user_id=444, timestamp=self.NOW, content='c3',
+ project_id=790, issue_id=issue_3.issue_id, sequence=1)
+ self.services.issue.TestAddComment(comment_3, issue_3.local_id)
+ self.services.issue.TestAddIssue(issue_3)
+
+ request = issues_pb2.ListActivitiesRequest()
+ request.user_ref.user_id = 444
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.ListActivities, mc, request)
+
+ self.maxDiff = None
+ self.assertEqual([
+ issue_objects_pb2.Comment(
+ project_name='proj',
+ local_id=1,
+ commenter=common_pb2.UserRef(
+ user_id=444, display_name='user@example.com'),
+ timestamp=self.NOW,
+ content='c1',
+ sequence_num=1,
+ can_delete=True,
+ can_flag=True),
+ issue_objects_pb2.Comment(
+ project_name='proj2',
+ local_id=1,
+ commenter=common_pb2.UserRef(
+ user_id=444, display_name='user@example.com'),
+ timestamp=self.NOW,
+ content='sum',
+ description_num=1,
+ can_delete=True,
+ can_flag=True),
+ issue_objects_pb2.Comment(
+ project_name='proj2',
+ local_id=1,
+ commenter=common_pb2.UserRef(
+ user_id=444, display_name='user@example.com'),
+ timestamp=self.NOW,
+ content='c2',
+ sequence_num=1,
+ can_delete=True,
+ can_flag=True)],
+ sorted(
+ response.comments,
+ key=lambda c: (c.project_name, c.local_id, c.sequence_num)))
+ self.assertEqual([
+ issue_objects_pb2.IssueSummary(
+ project_name='proj',
+ local_id=1,
+ summary='sum'),
+ issue_objects_pb2.IssueSummary(
+ project_name='proj2',
+ local_id=1,
+ summary='sum')],
+ sorted(
+ response.issue_summaries,
+ key=lambda issue: (issue.project_name, issue.local_id)))
+
+ def testListActivities_Amendment(self):
+ self.services.user.TestAddUser('user@example.com', 444)
+
+ comment = tracker_pb2.IssueComment(
+ user_id=444,
+ timestamp=self.NOW,
+ amendments=[tracker_bizobj.MakeOwnerAmendment(111, 222)],
+ project_id=789,
+ issue_id=self.issue_1.issue_id,
+ content='',
+ sequence=1)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+
+ request = issues_pb2.ListActivitiesRequest()
+ request.user_ref.user_id = 444
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.ListActivities, mc, request)
+
+ self.assertEqual([
+ issue_objects_pb2.Comment(
+ project_name='proj',
+ local_id=1,
+ commenter=common_pb2.UserRef(
+ user_id=444, display_name='user@example.com'),
+ timestamp=self.NOW,
+ content='',
+ sequence_num=1,
+ amendments=[issue_objects_pb2.Amendment(
+ field_name="Owner",
+ new_or_delta_value="ow...@example.com")],
+ can_delete=True,
+ can_flag=True)],
+ sorted(
+ response.comments,
+ key=lambda c: (c.project_name, c.local_id, c.sequence_num)))
+ self.assertEqual([
+ issue_objects_pb2.IssueSummary(
+ project_name='proj',
+ local_id=1,
+ summary='sum')],
+ sorted(
+ response.issue_summaries,
+ key=lambda issue: (issue.project_name, issue.local_id)))
+
+ @patch('testing.fake.IssueService.SoftDeleteComment')
+ def testDeleteComment_Invalid(self, fake_softdeletecomment):
+ """We reject requests to delete a non-existent comment."""
+ # Note: no comments added to self.issue_1 after the description.
+ request = issues_pb2.DeleteCommentRequest(
+ issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
+ sequence_num=2, delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ with self.assertRaises(exceptions.NoSuchCommentException):
+ self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
+
+ fake_softdeletecomment.assert_not_called()
+
+ def testDeleteComment_Normal(self):
+ """An authorized user can delete and undelete a comment."""
+ comment_1 = tracker_pb2.IssueComment(
+ project_id=789, issue_id=self.issue_1.issue_id, content='one')
+ self.services.issue.TestAddComment(comment_1, 1)
+ comment_2 = tracker_pb2.IssueComment(
+ project_id=789, issue_id=self.issue_1.issue_id, content='two',
+ user_id=222)
+ self.services.issue.TestAddComment(comment_2, 1)
+
+ # Delete a comment.
+ request = issues_pb2.DeleteCommentRequest(
+ issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
+ sequence_num=2, delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ response = self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
+
+ self.assertTrue(isinstance(response, empty_pb2.Empty))
+ self.assertEqual(111, comment_2.deleted_by)
+
+ # Undelete a comment.
+ request.delete=False
+
+ response = self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
+
+ self.assertTrue(isinstance(response, empty_pb2.Empty))
+ self.assertEqual(None, comment_2.deleted_by)
+
+ @patch('testing.fake.IssueService.SoftDeleteComment')
+ def testDeleteComment_Denied(self, fake_softdeletecomment):
+ """An unauthorized user cannot delete a comment."""
+ comment_1 = tracker_pb2.IssueComment(
+ project_id=789, issue_id=self.issue_1.issue_id, content='one',
+ user_id=222)
+ self.services.issue.TestAddComment(comment_1, 1)
+
+ request = issues_pb2.DeleteCommentRequest(
+ issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
+ sequence_num=1, delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com')
+
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
+
+ fake_softdeletecomment.assert_not_called()
+ self.assertIsNone(comment_1.deleted_by)
+
+ def testUpdateApproval_MissingFieldDef(self):
+ """Missing Approval Field Def throwns exception."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ approval_delta = issue_objects_pb2.ApprovalDelta(
+ status=issue_objects_pb2.REVIEW_REQUESTED)
+ request = issues_pb2.UpdateApprovalRequest(
+ issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta)
+
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+
+ with self.assertRaises(exceptions.NoSuchFieldDefException):
+ self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
+
+ def testBulkUpdateApprovals_EmptyIssueRefs(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
+ approval_delta=issue_objects_pb2.ApprovalDelta())
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ def testBulkUpdateApprovals_NoProjectName(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [common_pb2.IssueRef(local_id=1),
+ common_pb2.IssueRef(local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs,
+ field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
+ approval_delta=issue_objects_pb2.ApprovalDelta())
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ def testBulkUpdateApprovals_CrossProjectRequest(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [common_pb2.IssueRef(project_name='p1', local_id=1),
+ common_pb2.IssueRef(project_name='p2', local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs,
+ field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
+ approval_delta=issue_objects_pb2.ApprovalDelta())
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ def testBulkUpdateApprovals_NoSuchFieldDef(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs,
+ field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
+ approval_delta=issue_objects_pb2.ApprovalDelta())
+ with self.assertRaises(exceptions.NoSuchFieldDefException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ def testBulkUpdateApprovals_AnonDenied(self):
+ """Anon user cannot make any updates"""
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_3])
+ self.services.config.StoreConfig(self.cnxn, config)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ approval_delta = issue_objects_pb2.ApprovalDelta()
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs, field_ref=field_ref,
+ approval_delta=approval_delta)
+
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ def testBulkUpdateApprovals_UserLacksViewPerms(self):
+ """User who cannot view issue cannot update issue."""
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_3])
+ self.services.config.StoreConfig(self.cnxn, config)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ approval_delta = issue_objects_pb2.ApprovalDelta()
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs, field_ref=field_ref,
+ approval_delta=approval_delta)
+
+ self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ @patch('time.time')
+ @patch('businesslogic.work_env.WorkEnv.BulkUpdateIssueApprovals')
+ @patch('businesslogic.work_env.WorkEnv.GetIssueRefs')
+ def testBulkUpdateApprovals_Normal(
+ self, mockGetIssueRefs, mockBulkUpdateIssueApprovals, mockTime):
+ """Issue approvals that can be updated are updated and returned."""
+ mockTime.return_value = 12345
+ mockGetIssueRefs.return_value = {1001: ('proj', 1), 1002: ('proj', 2)}
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_3])
+ self.services.config.StoreConfig(self.cnxn, config)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs, field_ref=field_ref,
+ approval_delta=issue_objects_pb2.ApprovalDelta(
+ status=issue_objects_pb2.APPROVED),
+ comment_content='new bulk comment')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.BulkUpdateApprovals, mc, request)
+ self.assertEqual(
+ response,
+ issues_pb2.BulkUpdateApprovalsResponse(
+ issue_refs=[common_pb2.IssueRef(project_name='proj', local_id=1),
+ common_pb2.IssueRef(project_name='proj', local_id=2)]))
+
+ approval_delta = tracker_pb2.ApprovalDelta(
+ status=tracker_pb2.ApprovalStatus.APPROVED,
+ setter_id=444, set_on=12345)
+ mockBulkUpdateIssueApprovals.assert_called_once_with(
+ [1001, 1002], 3, self.project, approval_delta,
+ 'new bulk comment', send_email=False)
+
+ @patch('businesslogic.work_env.WorkEnv.BulkUpdateIssueApprovals')
+ @patch('businesslogic.work_env.WorkEnv.GetIssueRefs')
+ def testBulkUpdateApprovals_EmptyDelta(
+ self, mockGetIssueRefs, mockBulkUpdateIssueApprovals):
+ """Bulk update approval requests don't fail with an empty approval delta."""
+ mockGetIssueRefs.return_value = {1001: ('proj', 1)}
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789,
+ field_defs=[self.fd_3])
+ self.services.config.StoreConfig(self.cnxn, config)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1)]
+ request = issues_pb2.BulkUpdateApprovalsRequest(
+ issue_refs=issue_refs, field_ref=field_ref,
+ comment_content='new bulk comment',
+ send_email=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ self.CallWrapped(
+ self.issues_svcr.BulkUpdateApprovals, mc, request)
+
+ approval_delta = tracker_pb2.ApprovalDelta()
+ mockBulkUpdateIssueApprovals.assert_called_once_with(
+ [1001], 3, self.project, approval_delta,
+ 'new bulk comment', send_email=True)
+
+
+ @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
+ @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
+ def testUpdateApproval(self, _mockPrepareAndSend, mockUpdateIssueApproval):
+ """We can update an approval."""
+
+ av_3 = tracker_pb2.ApprovalValue(
+ approval_id=3,
+ status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW,
+ approver_ids=[333]
+ )
+ self.issue_1.approval_values = [av_3]
+
+ config = self.services.config.GetProjectConfig(
+ self.cnxn, 789)
+ config.field_defs = [self.fd_1, self.fd_3]
+
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ approval_delta = issue_objects_pb2.ApprovalDelta(
+ status=issue_objects_pb2.REVIEW_REQUESTED,
+ approver_refs_add=[
+ common_pb2.UserRef(user_id=222, display_name='approver2@example.com')
+ ],
+ field_vals_add=[
+ issue_objects_pb2.FieldValue(
+ field_ref=common_pb2.FieldRef(field_name='FirstField'),
+ value='string')
+ ]
+ )
+
+ request = issues_pb2.UpdateApprovalRequest(
+ issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta,
+ comment_content='Well, actually'
+ )
+ request.issue_ref.project_name = 'proj'
+ request.issue_ref.local_id = 1
+ request.uploads.extend([
+ issue_objects_pb2.AttachmentUpload(
+ filename='a.txt',
+ content='aaaaa')])
+ request.kept_attachments.extend([1, 2, 3])
+ request.send_email = True
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+
+ mockUpdateIssueApproval.return_value = [
+ tracker_pb2.ApprovalValue(
+ approval_id=3,
+ status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
+ setter_id=333,
+ approver_ids=[333, 222]),
+ 'comment_pb',
+ {}, # Fake issue.
+ ]
+
+ actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
+
+ expected = issues_pb2.UpdateApprovalResponse()
+ expected.approval.CopyFrom(
+ issue_objects_pb2.Approval(
+ field_ref=common_pb2.FieldRef(
+ field_id=3,
+ field_name='LegalApproval',
+ type=common_pb2.APPROVAL_TYPE),
+ approver_refs=[
+ common_pb2.UserRef(
+ user_id=333, display_name='approver3@example.com'),
+ common_pb2.UserRef(
+ user_id=222, display_name='approver2@example.com')
+ ],
+ status=issue_objects_pb2.REVIEW_REQUESTED,
+ setter_ref=common_pb2.UserRef(
+ user_id=333, display_name='approver3@example.com'),
+ phase_ref=issue_objects_pb2.PhaseRef()
+ )
+ )
+
+ work_env.WorkEnv(mc, self.services).UpdateIssueApproval.\
+ assert_called_once_with(
+ self.issue_1.issue_id, 3, ANY, u'Well, actually', False,
+ attachments=[(u'a.txt', 'aaaaa', 'text/plain')], send_email=True,
+ kept_attachments=[1, 2, 3])
+ self.assertEqual(expected, actual)
+
+ @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
+ @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
+ def testUpdateApproval_IsDescription(
+ self, _mockPrepareAndSend, mockUpdateIssueApproval):
+ """We can update an approval survey."""
+
+ av_3 = tracker_pb2.ApprovalValue(approval_id=3)
+ self.issue_1.approval_values = [av_3]
+
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+ config.field_defs = [self.fd_3]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+ approval_delta = issue_objects_pb2.ApprovalDelta()
+
+ request = issues_pb2.UpdateApprovalRequest(
+ issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta,
+ comment_content='Better response.', is_description=True)
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+
+ mockUpdateIssueApproval.return_value = [
+ tracker_pb2.ApprovalValue(approval_id=3),
+ 'comment_pb',
+ {}, # Fake issue.
+ ]
+
+ actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
+
+ expected = issues_pb2.UpdateApprovalResponse()
+ expected.approval.CopyFrom(
+ issue_objects_pb2.Approval(
+ field_ref=common_pb2.FieldRef(
+ field_id=3,
+ field_name='LegalApproval',
+ type=common_pb2.APPROVAL_TYPE),
+ phase_ref=issue_objects_pb2.PhaseRef()
+ )
+ )
+
+ work_env.WorkEnv(mc, self.services
+ ).UpdateIssueApproval.assert_called_once_with(
+ self.issue_1.issue_id, 3,
+ tracker_pb2.ApprovalDelta(),
+ u'Better response.', True, attachments=[], send_email=False,
+ kept_attachments=[])
+ self.assertEqual(expected, actual)
+
+ @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
+ @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
+ def testUpdateApproval_EmptyDelta(
+ self, _mockPrepareAndSend, mockUpdateIssueApproval):
+ self.issue_1.approval_values = [tracker_pb2.ApprovalValue(approval_id=3)]
+
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+ config.field_defs = [self.fd_3]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ field_ref = common_pb2.FieldRef(field_name='LegalApproval')
+
+ request = issues_pb2.UpdateApprovalRequest(
+ issue_ref=issue_ref, field_ref=field_ref,
+ comment_content='Better response.', is_description=True)
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+
+ mockUpdateIssueApproval.return_value = [
+ tracker_pb2.ApprovalValue(approval_id=3),
+ 'comment_pb',
+ {}, # Fake issue.
+ ]
+
+ actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
+
+ approval_value = issue_objects_pb2.Approval(
+ field_ref=common_pb2.FieldRef(
+ field_id=3,
+ field_name='LegalApproval',
+ type=common_pb2.APPROVAL_TYPE),
+ phase_ref=issue_objects_pb2.PhaseRef()
+ )
+ expected = issues_pb2.UpdateApprovalResponse(approval=approval_value)
+ self.assertEqual(expected, actual)
+
+ mockUpdateIssueApproval.assert_called_once_with(
+ self.issue_1.issue_id, 3,
+ tracker_pb2.ApprovalDelta(),
+ u'Better response.', True, attachments=[], send_email=False,
+ kept_attachments=[])
+
+ @patch('businesslogic.work_env.WorkEnv.ConvertIssueApprovalsTemplate')
+ def testConvertIssueApprovalsTemplate(self, mockWorkEnvConvertApprovals):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+ request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
+ issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
+ template_name='template_name', comment_content='CHICKEN',
+ send_email=True)
+ response = self.CallWrapped(
+ self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
+ config = self.services.config.GetProjectConfig(self.cnxn, 789)
+ mockWorkEnvConvertApprovals.assert_called_once_with(
+ config, self.issue_1, 'template_name', request.comment_content,
+ send_email=request.send_email)
+ self.assertEqual(
+ response.issue,
+ issue_objects_pb2.Issue(
+ project_name='proj',
+ local_id=1,
+ summary='sum',
+ owner_ref=common_pb2.UserRef(
+ user_id=111, display_name='owner@example.com'),
+ status_ref=common_pb2.StatusRef(status='New', means_open=True),
+ blocked_on_issue_refs=[
+ common_pb2.IssueRef(project_name='proj', local_id=2)],
+ reporter_ref=common_pb2.UserRef(
+ user_id=111, display_name='owner@example.com'),
+ opened_timestamp=self.NOW,
+ component_modified_timestamp=self.NOW,
+ status_modified_timestamp=self.NOW,
+ owner_modified_timestamp=self.NOW,
+ ))
+
+ def testConvertIssueApprovalsTemplate_MissingRequiredFields(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver3@example.com',
+ auth=self.auth)
+ request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
+ issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1))
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(
+ self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
+
+ request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
+ template_name='name')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(
+ self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_RequiredFields(self, mockSnapshotCountsQuery):
+ """Test that timestamp is required at all times.
+ And that label_prefix is required when group_by is 'label'.
+ """
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ # Test timestamp is required.
+ request = issues_pb2.IssueSnapshotRequest(project_name='proj')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ # Test project_name or hotlist_id is required.
+ request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109)
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ # Test label_prefix is required when group_by is 'label'.
+ request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
+ project_name='proj', group_by='label')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ mockSnapshotCountsQuery.assert_not_called()
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_Basic(self, mockSnapshotCountsQuery):
+ """Tests the happy path case."""
+ request = issues_pb2.IssueSnapshotRequest(
+ timestamp=1531334109, project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = ({'total': 123}, [], True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(123, response.snapshot_count[0].count)
+ self.assertEqual(0, len(response.unsupported_field))
+ self.assertTrue(response.search_limit_reached)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ '', query=None, canned_query=None, label_prefix='', hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ @patch('search.searchpipeline.ReplaceKeywordsWithUserIDs')
+ @patch('features.savedqueries_helpers.SavedQueryIDToCond')
+ def testSnapshotCounts_ReplacesKeywords(self, mockSavedQueryIDToCond,
+ mockReplaceKeywordsWithUserIDs,
+ mockSnapshotCountsQuery):
+ """Tests that canned query is unpacked and keywords in query and canned
+ query are replaced with user IDs."""
+ request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
+ project_name='proj', query='owner:me', canned_query=3)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSavedQueryIDToCond.return_value = 'cc:me'
+ mockReplaceKeywordsWithUserIDs.side_effect = [
+ ('cc:2345', []), ('owner:1234', [])]
+ mockSnapshotCountsQuery.return_value = ({'total': 789}, [], False)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(789, response.snapshot_count[0].count)
+ self.assertEqual(0, len(response.unsupported_field))
+ self.assertFalse(response.search_limit_reached)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ '', query='owner:1234', canned_query='cc:2345', label_prefix='',
+ hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_GroupByLabel(self, mockSnapshotCountsQuery):
+ """Tests grouping by label with label_prefix and a query.
+ But no canned_query.
+ """
+ request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
+ project_name='proj', group_by='label', label_prefix='Type',
+ query='rutabaga:rutabaga')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = (
+ {'label1': 123, 'label2': 987},
+ ['rutabaga'],
+ True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(2, len(response.snapshot_count))
+ self.assertEqual('label1', response.snapshot_count[0].dimension)
+ self.assertEqual(123, response.snapshot_count[0].count)
+ self.assertEqual('label2', response.snapshot_count[1].dimension)
+ self.assertEqual(987, response.snapshot_count[1].count)
+ self.assertEqual(1, len(response.unsupported_field))
+ self.assertEqual('rutabaga', response.unsupported_field[0])
+ self.assertTrue(response.search_limit_reached)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ 'label', label_prefix='Type', query='rutabaga:rutabaga',
+ canned_query=None, hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_GroupByComponent(self, mockSnapshotCountsQuery):
+ """Tests grouping by component with a query and a canned_query."""
+ request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
+ project_name='proj', group_by='component',
+ query='rutabaga:rutabaga', canned_query=2)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = (
+ {'component1': 123, 'component2': 987},
+ ['rutabaga'],
+ True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(2, len(response.snapshot_count))
+ self.assertEqual('component1', response.snapshot_count[0].dimension)
+ self.assertEqual(123, response.snapshot_count[0].count)
+ self.assertEqual('component2', response.snapshot_count[1].dimension)
+ self.assertEqual(987, response.snapshot_count[1].count)
+ self.assertEqual(1, len(response.unsupported_field))
+ self.assertEqual('rutabaga', response.unsupported_field[0])
+ self.assertTrue(response.search_limit_reached)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ 'component', label_prefix='', query='rutabaga:rutabaga',
+ canned_query='is:open', hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_GroupByOpen(self, mockSnapshotCountsQuery):
+ """Tests grouping by open with a query."""
+ request = issues_pb2.IssueSnapshotRequest(
+ timestamp=1531334109, project_name='proj', group_by='open')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = (
+ {'Opened': 100, 'Closed': 23}, [], True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(2, len(response.snapshot_count))
+ self.assertEqual('Opened', response.snapshot_count[0].dimension)
+ self.assertEqual(100, response.snapshot_count[0].count)
+ self.assertEqual('Closed', response.snapshot_count[1].dimension)
+ self.assertEqual(23, response.snapshot_count[1].count)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ 'open', label_prefix='', query=None, canned_query=None, hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_GroupByStatus(self, mockSnapshotCountsQuery):
+ """Tests grouping by status with a query."""
+ request = issues_pb2.IssueSnapshotRequest(
+ timestamp=1531334109, project_name='proj', group_by='status')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = (
+ {'Accepted': 100, 'Fixed': 23}, [], True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(2, len(response.snapshot_count))
+ self.assertEqual('Fixed', response.snapshot_count[0].dimension)
+ self.assertEqual(23, response.snapshot_count[0].count)
+ self.assertEqual('Accepted', response.snapshot_count[1].dimension)
+ self.assertEqual(100, response.snapshot_count[1].count)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ 'status', label_prefix='', query=None, canned_query=None, hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_GroupByOwner(self, mockSnapshotCountsQuery):
+ """Tests grouping by status with a query."""
+ request = issues_pb2.IssueSnapshotRequest(
+ timestamp=1531334109, project_name='proj', group_by='owner')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = ({111: 100}, [], True)
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(1, len(response.snapshot_count))
+ self.assertEqual('owner@example.com', response.snapshot_count[0].dimension)
+ self.assertEqual(100, response.snapshot_count[0].count)
+ mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
+ 'owner', label_prefix='', query=None, canned_query=None, hotlist=None)
+
+ @patch('businesslogic.work_env.WorkEnv.GetHotlist')
+ @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
+ def testSnapshotCounts_WithHotlist(self, mockSnapshotCountsQuery,
+ mockGetHotlist):
+ """Tests grouping by status with a hotlist."""
+ request = issues_pb2.IssueSnapshotRequest(
+ timestamp=1531334109, hotlist_id=19191)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mockSnapshotCountsQuery.return_value = ({'total': 123}, [], True)
+ fake_hotlist = fake.Hotlist('hotlist_rutabaga', 19191)
+ mockGetHotlist.return_value = fake_hotlist
+
+ response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
+
+ self.assertEqual(1, len(response.snapshot_count))
+ self.assertEqual('total', response.snapshot_count[0].dimension)
+ self.assertEqual(123, response.snapshot_count[0].count)
+ mockSnapshotCountsQuery.assert_called_once_with(None, 1531334109,
+ '', label_prefix='', query=None, canned_query=None,
+ hotlist=fake_hotlist)
+
+ def AddField(self, name, field_type_str):
+ kwargs = {
+ 'cnxn': self.cnxn,
+ 'project_id': self.project.project_id,
+ 'field_name': name,
+ 'field_type_str': field_type_str}
+ kwargs.update(
+ {
+ arg: None for arg in (
+ 'applic_type', 'applic_pred', 'is_required', 'is_niche',
+ 'is_multivalued', 'min_value', 'max_value', 'regex',
+ 'needs_member', 'needs_perm', 'grants_perm', 'notify_on',
+ 'date_action_str', 'docstring')
+ })
+ kwargs.update({arg: [] for arg in ('admin_ids', 'editor_ids')})
+
+ return self.services.config.CreateFieldDef(**kwargs)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_NoDerivedFields(self, mockGetFilterRules):
+ """When no rules match, we respond with just owner availability."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ label_refs_add=[common_pb2.LabelRef(label='foo')])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule('label:bar', add_labels=['baz'])]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.PresubmitIssueResponse(
+ owner_availability="User never visited",
+ owner_availability_state="never"),
+ response)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_IncompleteOwnerEmail(self, mockGetFilterRules):
+ """User is in the process of typing in the proposed owner."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(display_name='owner@examp'))
+
+ mockGetFilterRules.return_value = []
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ actual = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.PresubmitIssueResponse(),
+ actual)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_NewIssue(self, mockGetFilterRules):
+ """Proposed owner has a vacation message set."""
+ self.user_1.vacation_message = 'In Galapagos Islands'
+ issue_ref = common_pb2.IssueRef(project_name='proj')
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ label_refs_add=[common_pb2.LabelRef(label='foo')])
+
+ mockGetFilterRules.return_value = []
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.PresubmitIssueResponse(
+ owner_availability='In Galapagos Islands',
+ owner_availability_state='none'),
+ response)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_OwnerVacation(self, mockGetFilterRules):
+ """Proposed owner has a vacation message set."""
+ self.user_1.vacation_message = 'In Galapagos Islands'
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ label_refs_add=[common_pb2.LabelRef(label='foo')])
+
+ mockGetFilterRules.return_value = []
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.PresubmitIssueResponse(
+ owner_availability='In Galapagos Islands',
+ owner_availability_state='none'),
+ response)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_OwnerIsAvailable(self, mockGetFilterRules):
+ """Proposed owner not on vacation and has visited recently."""
+ self.user_1.last_visit_timestamp = int(time.time())
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ label_refs_add=[common_pb2.LabelRef(label='foo')])
+
+ mockGetFilterRules.return_value = []
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.PresubmitIssueResponse(
+ owner_availability='',
+ owner_availability_state=''),
+ response)
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_DerivedLabels(self, mockGetFilterRules):
+ """Test that we can match label rules and return derived labels."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ label_refs_add=[common_pb2.LabelRef(label='foo')])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule('label:foo', add_labels=['bar', 'baz'])]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='bar',
+ why='Added by rule: IF label:foo THEN ADD LABEL'),
+ common_pb2.ValueAndWhy(
+ value='baz',
+ why='Added by rule: IF label:foo THEN ADD LABEL')],
+ [vnw for vnw in response.derived_labels])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_DerivedOwner(self, mockGetFilterRules):
+ """Test that we can match component rules and return derived owners."""
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Foo', 'Foo Docstring', False,
+ [], [], 0, 111, [])
+ self.issue_1.owner_id = 0
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ comp_refs_add=[common_pb2.ComponentRef(path='Foo')])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule('component:Foo', default_owner_id=222)]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='approver2@example.com',
+ why='Added by rule: IF component:Foo THEN SET DEFAULT OWNER')],
+ [vnw for vnw in response.derived_owners])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_DerivedCCs(self, mockGetFilterRules):
+ """Test that we can match field rules and return derived cc emails."""
+ field_id = self.AddField('Foo', 'ENUM_TYPE')
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ field_vals_add=[issue_objects_pb2.FieldValue(
+ value='Bar', field_ref=common_pb2.FieldRef(field_id=field_id))])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule('Foo=Bar', add_cc_ids=[222, 333])]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='approver2@example.com',
+ why='Added by rule: IF Foo=Bar THEN ADD CC'),
+ common_pb2.ValueAndWhy(
+ value='approver3@example.com',
+ why='Added by rule: IF Foo=Bar THEN ADD CC')],
+ [vnw for vnw in response.derived_ccs])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_DerivedCCsNonMember(self, mockGetFilterRules):
+ """Test that we can return obscured cc emails to non-members."""
+ field_id = self.AddField('Foo', 'ENUM_TYPE')
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111),
+ field_vals_add=[issue_objects_pb2.FieldValue(
+ value='Bar', field_ref=common_pb2.FieldRef(field_id=field_id))])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule('Foo=Bar', add_cc_ids=[222, 333])]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [
+ common_pb2.ValueAndWhy(
+ value='appro...@example.com',
+ why='Added by rule: IF Foo=Bar THEN ADD CC'),
+ common_pb2.ValueAndWhy(
+ value='appro...@example.com',
+ why='Added by rule: IF Foo=Bar THEN ADD CC')
+ ], [vnw for vnw in response.derived_ccs])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_Warnings(self, mockGetFilterRules):
+ """Test that we can match owner rules and return warnings."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=111))
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule(
+ 'owner:owner@example.com', warning='Owner is too busy')]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='Owner is too busy',
+ why='Added by rule: IF owner:owner@example.com THEN ADD WARNING')],
+ [vnw for vnw in response.warnings])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_Errors(self, mockGetFilterRules):
+ """Test that we can match owner rules and return errors."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta(
+ owner_ref=common_pb2.UserRef(user_id=222),
+ cc_refs_add=[
+ common_pb2.UserRef(user_id=111),
+ common_pb2.UserRef(user_id=333)])
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule(
+ 'cc:owner@example.com', error='Owner is not to be disturbed')]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='Owner is not to be disturbed',
+ why='Added by rule: IF cc:owner@example.com THEN ADD ERROR')],
+ [vnw for vnw in response.errors])
+
+ @patch('testing.fake.FeaturesService.GetFilterRules')
+ def testPresubmitIssue_Errors_ExistingOwner(self, mockGetFilterRules):
+ """Test that we apply the rules to the issue + delta, not only delta."""
+ issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
+ issue_delta = issue_objects_pb2.IssueDelta()
+
+ mockGetFilterRules.return_value = [
+ filterrules_helpers.MakeRule(
+ 'owner:owner@example.com', error='Owner is not to be disturbed')]
+
+ request = issues_pb2.PresubmitIssueRequest(
+ issue_ref=issue_ref, issue_delta=issue_delta)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
+
+ self.assertEqual(
+ [common_pb2.ValueAndWhy(
+ value='Owner is not to be disturbed',
+ why='Added by rule: IF owner:owner@example.com THEN ADD ERROR')],
+ [vnw for vnw in response.errors])
+
+ def testRerankBlockedOnIssues_SplitBelow(self):
+ issues = []
+ for idx in range(3, 6):
+ issues.append(fake.MakeTestIssue(
+ 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
+ self.services.issue.TestAddIssue(issues[-1])
+ self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
+ self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
+
+ request = issues_pb2.RerankBlockedOnIssuesRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ moved_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=2),
+ target_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=4),
+ split_above=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.RerankBlockedOnIssues, mc, request)
+
+ self.assertEqual(
+ [3, 4, 2, 5],
+ [blocked_on_ref.local_id
+ for blocked_on_ref in response.blocked_on_issue_refs])
+
+ def testRerankBlockedOnIssues_SplitAbove(self):
+ self.project.committer_ids.append(222)
+ issues = []
+ for idx in range(3, 6):
+ issues.append(fake.MakeTestIssue(
+ 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
+ self.services.issue.TestAddIssue(issues[-1])
+ self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
+ self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
+
+ request = issues_pb2.RerankBlockedOnIssuesRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ moved_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=2),
+ target_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=4),
+ split_above=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.RerankBlockedOnIssues, mc, request)
+
+ self.assertEqual(
+ [3, 2, 4, 5],
+ [blocked_on_ref.local_id
+ for blocked_on_ref in response.blocked_on_issue_refs])
+
+ def testRerankBlockedOnIssues_CantEditIssue(self):
+ self.project.committer_ids.append(222)
+ issues = []
+ for idx in range(3, 6):
+ issues.append(fake.MakeTestIssue(
+ 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
+ self.services.issue.TestAddIssue(issues[-1])
+ self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
+ self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
+
+ self.issue_1.labels = ['Restrict-EditIssue-Foo']
+
+ request = issues_pb2.RerankBlockedOnIssuesRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ moved_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=2),
+ target_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=4),
+ split_above=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.RerankBlockedOnIssues, mc, request)
+
+ def testRerankBlockedOnIssues_ComplexPermissions(self):
+ """We can rerank blocked on issues, regardless of perms on other issues.
+
+ If Issue 1 is blocked on Issue 3 and Issue 4, we should be able to reorder
+ them as long as we have permission to edit Issue 1, even if we don't have
+ permission to view or edit Issues 3 or 4.
+ """
+ # Issue 3 is in proj2, which we don't have access to.
+ project_2 = self.services.project.TestAddProject(
+ 'proj2', project_id=790, owner_ids=[222], contrib_ids=[333])
+ project_2.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ issue_3 = fake.MakeTestIssue(
+ 790, 3, 'sum', 'New', 111, project_name='proj2', issue_id=1003)
+
+ # Issue 4 requires a permission we don't have in order to edit it.
+ issue_4 = fake.MakeTestIssue(
+ 789, 4, 'sum', 'New', 111, project_name='proj', issue_id=1004)
+ issue_4.labels = ['Restrict-EditIssue-Foo']
+
+ self.services.issue.TestAddIssue(issue_3)
+ self.services.issue.TestAddIssue(issue_4)
+
+ self.issue_1.blocked_on_iids = [1003, 1004]
+ self.issue_1.blocked_on_ranks = [2, 1]
+
+ request = issues_pb2.RerankBlockedOnIssuesRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ moved_ref=common_pb2.IssueRef(
+ project_name='proj2',
+ local_id=3),
+ target_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=4),
+ split_above=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.RerankBlockedOnIssues, mc, request)
+
+ self.assertEqual(
+ [4, 3],
+ [blocked_on_ref.local_id
+ for blocked_on_ref in response.blocked_on_issue_refs])
+
+ def testDeleteIssue_Delete(self):
+ """We can delete an issue."""
+ issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
+ self.assertFalse(issue.deleted)
+
+ request = issues_pb2.DeleteIssueRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.DeleteIssue, mc, request)
+
+ issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
+ self.assertTrue(issue.deleted)
+
+ def testDeleteIssue_Undelete(self):
+ """We can undelete an issue."""
+ self.services.issue.SoftDeleteIssue(
+ self.cnxn, self.project.project_id, 1, True, self.services.user)
+ issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
+ self.assertTrue(issue.deleted)
+
+ request = issues_pb2.DeleteIssueRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ delete=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.DeleteIssue, mc, request)
+
+ issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
+ self.assertFalse(issue.deleted)
+
+ def testDeleteIssueComment_Delete(self):
+ """We can delete an issue comment."""
+ comment = tracker_pb2.IssueComment(
+ project_id=self.project.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=111,
+ content='Foo',
+ timestamp=12345)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+
+ request = issues_pb2.DeleteIssueCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
+
+ comment = self.services.issue.GetComment(self.cnxn, comment.id)
+ self.assertEqual(111, comment.deleted_by)
+
+ def testDeleteIssueComment_Undelete(self):
+ """We can undelete an issue comment."""
+ comment = tracker_pb2.IssueComment(
+ project_id=self.project.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=111,
+ content='Foo',
+ timestamp=12345,
+ deleted_by=111)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+
+ request = issues_pb2.DeleteIssueCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ delete=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
+
+ comment = self.services.issue.GetComment(self.cnxn, comment.id)
+ self.assertIsNone(comment.deleted_by)
+
+ def testDeleteIssueComment_InvalidSequenceNum(self):
+ """We can handle invalid sequence numbers."""
+ request = issues_pb2.DeleteIssueCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
+
+ def testDeleteAttachment_Delete(self):
+ """We can delete an issue comment attachment."""
+ comment = tracker_pb2.IssueComment(
+ project_id=self.project.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=111,
+ content='Foo',
+ timestamp=12345)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+ attachment = tracker_pb2.Attachment()
+ self.services.issue.TestAddAttachment(attachment, comment.id, 1)
+
+ request = issues_pb2.DeleteAttachmentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ attachment_id=attachment.attachment_id,
+ delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(
+ self.issues_svcr.DeleteAttachment, mc, request)
+
+ self.assertTrue(attachment.deleted)
+
+ def testDeleteAttachment_Undelete(self):
+ """We can undelete an issue comment attachment."""
+ comment = tracker_pb2.IssueComment(
+ project_id=self.project.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=111,
+ content='Foo',
+ timestamp=12345,
+ deleted_by=111)
+ self.services.issue.TestAddComment(comment, self.issue_1.local_id)
+ attachment = tracker_pb2.Attachment(deleted=True)
+ self.services.issue.TestAddAttachment(attachment, comment.id, 1)
+
+ request = issues_pb2.DeleteAttachmentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ attachment_id=attachment.attachment_id,
+ delete=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(
+ self.issues_svcr.DeleteAttachment, mc, request)
+
+ self.assertFalse(attachment.deleted)
+
+ def testDeleteAttachment_InvalidSequenceNum(self):
+ """We can handle invalid sequence numbers."""
+ request = issues_pb2.DeleteAttachmentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ attachment_id=1234,
+ delete=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(
+ self.issues_svcr.DeleteAttachment, mc, request)
+
+ def testFlagIssues_Normal(self):
+ """Test that an user can flag an issue as spam."""
+ self.services.user.TestAddUser('user@example.com', 999)
+
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=2)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ issue_id = self.issue_1.issue_id
+ self.assertEqual(
+ [999], self.services.spam.reports_by_issue_id[issue_id])
+ self.assertNotIn(
+ 999, self.services.spam.manual_verdicts_by_issue_id[issue_id])
+
+ issue_id2 = self.issue_2.issue_id
+ self.assertEqual(
+ [999], self.services.spam.reports_by_issue_id[issue_id2])
+ self.assertNotIn(
+ 999, self.services.spam.manual_verdicts_by_issue_id[issue_id2])
+
+ def testFlagIssues_Unflag(self):
+ """Test that we can un-flag an issue as spam."""
+ self.services.spam.FlagIssues(
+ self.cnxn, self.services.issue, [self.issue_1], 111, True)
+ self.services.spam.RecordManualIssueVerdicts(
+ self.cnxn, self.services.issue, [self.issue_1], 111, True)
+
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1)],
+ flag=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ issue_id = self.issue_1.issue_id
+ self.assertEqual([], self.services.spam.reports_by_issue_id[issue_id])
+ self.assertFalse(
+ self.services.spam.manual_verdicts_by_issue_id[issue_id][111])
+
+ def testFlagIssues_OwnerAutoVerdict(self):
+ """Test that an owner can flag an issue as spam and it is a verdict."""
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ issue_id = self.issue_1.issue_id
+ self.assertEqual(
+ [111], self.services.spam.reports_by_issue_id[issue_id])
+ self.assertTrue(
+ self.services.spam.manual_verdicts_by_issue_id[issue_id][111])
+
+ def testFlagIssues_CommitterAutoVerdict(self):
+ """Test that an owner can flag an issue as spam and it is a verdict."""
+ self.services.user.TestAddUser('committer@example.com', 999)
+ self.services.project.TestAddProjectMembers(
+ [999], self.project, fake.COMMITTER_ROLE)
+
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='committer@example.com')
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ issue_id = self.issue_1.issue_id
+ self.assertEqual(
+ [999], self.services.spam.reports_by_issue_id[issue_id])
+ self.assertTrue(
+ self.services.spam.manual_verdicts_by_issue_id[issue_id][999])
+
+ def testFlagIssues_ContributorAutoVerdict(self):
+ """Test that an owner can flag an issue as spam and it is a verdict."""
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ issue_id = self.issue_1.issue_id
+ self.assertEqual(
+ [222], self.services.spam.reports_by_issue_id[issue_id])
+ self.assertTrue(
+ self.services.spam.manual_verdicts_by_issue_id[issue_id][222])
+
+ def testFlagIssues_NotAllowed(self):
+ """Test that anon users cannot flag issues as spam."""
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ self.assertEqual(
+ [], self.services.spam.reports_by_issue_id[self.issue_1.issue_id])
+ self.assertEqual({}, self.services.spam.manual_verdicts_by_issue_id)
+
+ def testFlagIssues_CrossProjectNotAllowed(self):
+ """Test that cross-project requests are rejected."""
+ request = issues_pb2.FlagIssuesRequest(
+ issue_refs=[
+ common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ common_pb2.IssueRef(
+ project_name='proj2',
+ local_id=2)],
+ flag=True)
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ self.assertEqual(
+ [], self.services.spam.reports_by_issue_id[self.issue_1.issue_id])
+ self.assertEqual({}, self.services.spam.manual_verdicts_by_issue_id)
+
+ def testFlagIssues_MissingIssueRefs(self):
+ request = issues_pb2.FlagIssuesRequest(flag=True)
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
+
+ def testFlagComment_InvalidSequenceNumber(self):
+ """Test that we reject requests with invalid sequence numbers."""
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ def testFlagComment_Normal(self):
+ """Test that an user can flag a comment as spam."""
+ self.services.user.TestAddUser('user@example.com', 999)
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=111,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertEqual([999], comment_reports[self.issue_1.issue_id][comment.id])
+ self.assertNotIn(999, manual_verdicts[comment.id])
+
+ def testFlagComment_Unflag(self):
+ """Test that we can un-flag a comment as spam."""
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=999,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ self.services.spam.FlagComment(
+ self.cnxn, self.issue_1, comment.id, 999, 111, True)
+ self.services.spam.RecordManualCommentVerdict(
+ self.cnxn, self.services.issue, self.services.user, comment.id, 111,
+ True)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=False)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertEqual([], comment_reports[self.issue_1.issue_id][comment.id])
+ self.assertFalse(manual_verdicts[comment.id][111])
+
+ def testFlagComment_OwnerAutoVerdict(self):
+ """Test that an owner can flag a comment as spam and it is a verdict."""
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=999,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertEqual([111], comment_reports[self.issue_1.issue_id][comment.id])
+ self.assertTrue(manual_verdicts[comment.id][111])
+
+ def testFlagComment_CommitterAutoVerdict(self):
+ """Test that an owner can flag an issue as spam and it is a verdict."""
+ self.services.user.TestAddUser('committer@example.com', 999)
+ self.services.project.TestAddProjectMembers(
+ [999], self.project, fake.COMMITTER_ROLE)
+
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=999,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='committer@example.com')
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertEqual([999], comment_reports[self.issue_1.issue_id][comment.id])
+ self.assertTrue(manual_verdicts[comment.id][999])
+
+ def testFlagComment_ContributorAutoVerdict(self):
+ """Test that an owner can flag an issue as spam and it is a verdict."""
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=999,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertEqual([222], comment_reports[self.issue_1.issue_id][comment.id])
+ self.assertTrue(manual_verdicts[comment.id][222])
+
+ def testFlagComment_NotAllowed(self):
+ """Test that anon users cannot flag issues as spam."""
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=999,
+ issue_id=self.issue_1.issue_id)
+ self.services.issue.TestAddComment(comment, 1)
+
+ request = issues_pb2.FlagCommentRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ sequence_num=1,
+ flag=True)
+ mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
+
+ comment_reports = self.services.spam.comment_reports_by_issue_id
+ manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
+ self.assertNotIn(comment.id, comment_reports[self.issue_1.issue_id])
+ self.assertEqual({}, manual_verdicts[comment.id])
+
+ def testListIssuePermissions_Normal(self):
+ issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
+ self.services.issue.TestAddIssue(issue_1)
+
+ request = issues_pb2.ListIssuePermissionsRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user@example.com')
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListIssuePermissions, mc, request)
+ self.assertEqual(
+ issues_pb2.ListIssuePermissionsResponse(
+ permissions=[
+ 'addissuecomment',
+ 'createissue',
+ 'deleteown',
+ 'flagspam',
+ 'setstar',
+ 'view']),
+ response)
+
+ def testListIssuePermissions_DeletedIssue(self):
+ issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
+ issue_1.deleted = True
+ self.services.issue.TestAddIssue(issue_1)
+
+ request = issues_pb2.ListIssuePermissionsRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListIssuePermissions, mc, request)
+ self.assertEqual(
+ issues_pb2.ListIssuePermissionsResponse(permissions=['view']),
+ response)
+
+ def testListIssuePermissions_CanViewDeletedIssue(self):
+ issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
+ issue_1.deleted = True
+ self.services.issue.TestAddIssue(issue_1)
+
+ request = issues_pb2.ListIssuePermissionsRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListIssuePermissions, mc, request)
+ self.assertEqual(
+ issues_pb2.ListIssuePermissionsResponse(permissions=[
+ 'deleteissue',
+ 'view']),
+ response)
+
+ def testListIssuePermissions_IssueRestrictions(self):
+ issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
+ issue_1.labels = ['Restrict-SetStar-CustomPerm']
+ self.services.issue.TestAddIssue(issue_1)
+
+ request = issues_pb2.ListIssuePermissionsRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListIssuePermissions, mc, request)
+ self.assertEqual(
+ issues_pb2.ListIssuePermissionsResponse(
+ permissions=[
+ 'addissuecomment',
+ 'createissue',
+ 'deleteown',
+ 'flagspam',
+ 'verdictspam',
+ 'view']),
+ response)
+
+ def testListIssuePermissions_IssueGrantedPerms(self):
+ self.services.config.CreateFieldDef(
+ self.cnxn, 789, 'Field Name', 'USER_TYPE', None, None, None, None, None,
+ None, None, None, None, None, 'CustomPerm', None, None, 'Docstring', [],
+ [])
+ issue_1 = fake.MakeTestIssue(
+ 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
+ issue_1.labels = ['Restrict-SetStar-CustomPerm']
+ issue_1.field_values = [tracker_pb2.FieldValue(user_id=222, field_id=123)]
+ self.services.issue.TestAddIssue(issue_1)
+
+ request = issues_pb2.ListIssuePermissionsRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1))
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='approver2@example.com')
+
+ response = self.CallWrapped(
+ self.issues_svcr.ListIssuePermissions, mc, request)
+ self.assertEqual(
+ issues_pb2.ListIssuePermissionsResponse(
+ permissions=[
+ 'addissuecomment',
+ 'createissue',
+ 'customperm',
+ 'deleteown',
+ 'flagspam',
+ 'setstar',
+ 'verdictspam',
+ 'view']),
+ response)
+
+ @patch('services.tracker_fulltext.IndexIssues')
+ @patch('services.tracker_fulltext.UnindexIssues')
+ def testMoveIssue_Normal(self, _mock_index, _mock_unindex):
+ issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
+ self.services.issue.TestAddIssue(issue)
+ self.project.owner_ids = [111]
+ target_project = self.services.project.TestAddProject(
+ 'dest', project_id=988, committer_ids=[111])
+
+ request = issues_pb2.MoveIssueRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ target_project_name='dest')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.MoveIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.MoveIssueResponse(
+ new_issue_ref=common_pb2.IssueRef(
+ project_name='dest',
+ local_id=1)),
+ response)
+
+ moved_issue = self.services.issue.GetIssueByLocalID(self.cnxn,
+ target_project.project_id, 1)
+ self.assertEqual(target_project.project_id, moved_issue.project_id)
+ self.assertEqual(issue.summary, moved_issue.summary)
+ self.assertEqual(moved_issue.reporter_id, 111)
+
+ @patch('services.tracker_fulltext.IndexIssues')
+ def testCopyIssue_Normal(self, _mock_index):
+ issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
+ self.services.issue.TestAddIssue(issue)
+ self.project.owner_ids = [111]
+
+ request = issues_pb2.CopyIssueRequest(
+ issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=1),
+ target_project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.issues_svcr.CopyIssue, mc, request)
+
+ self.assertEqual(
+ issues_pb2.CopyIssueResponse(
+ new_issue_ref=common_pb2.IssueRef(
+ project_name='proj',
+ local_id=3)),
+ response)
+
+ copied_issue = self.services.issue.GetIssueByLocalID(self.cnxn,
+ self.project.project_id, 3)
+ self.assertEqual(self.project.project_id, copied_issue.project_id)
+ self.assertEqual(issue.summary, copied_issue.summary)
+ self.assertEqual(copied_issue.reporter_id, 111)