blob: ab35a9fa8dd23595885e263404f7d4e2bb2266dc [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2018 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Tests for the issues servicer."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Copybara854996b2021-09-07 19:36:02 +000010import sys
11import time
12import unittest
13from mock import ANY, Mock, patch
14
15from google.protobuf import empty_pb2
16
17from components.prpc import codes
18from components.prpc import context
Copybara854996b2021-09-07 19:36:02 +000019
20from api import issues_servicer
21from api import converters
22from api.api_proto import common_pb2
23from api.api_proto import issues_pb2
24from api.api_proto import issue_objects_pb2
25from api.api_proto import common_pb2
26from businesslogic import work_env
27from features import filterrules_helpers
28from features import send_notifications
29from framework import authdata
30from framework import exceptions
31from framework import framework_views
32from framework import monorailcontext
33from framework import permissions
34from search import frontendsearchpipeline
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010035from mrproto import tracker_pb2
36from mrproto import project_pb2
Copybara854996b2021-09-07 19:36:02 +000037from testing import fake
38from tracker import tracker_bizobj
39from services import service_manager
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010040from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000041
42class IssuesServicerTest(unittest.TestCase):
43
44 NOW = 1234567890
45
46 def setUp(self):
47 self.cnxn = fake.MonorailConnection()
48 self.services = service_manager.Services(
49 config=fake.ConfigService(),
50 features=fake.FeaturesService(),
51 issue=fake.IssueService(),
52 issue_star=fake.IssueStarService(),
53 project=fake.ProjectService(),
54 spam=fake.SpamService(),
55 user=fake.UserService(),
56 usergroup=fake.UserGroupService())
57 self.project = self.services.project.TestAddProject(
58 'proj', project_id=789, owner_ids=[111], contrib_ids=[222, 333])
59 self.user_1 = self.services.user.TestAddUser('owner@example.com', 111)
60 self.user_2 = self.services.user.TestAddUser('approver2@example.com', 222)
61 self.user_3 = self.services.user.TestAddUser('approver3@example.com', 333)
62 self.user_4 = self.services.user.TestAddUser('nonmember@example.com', 444)
63 self.issue_1 = fake.MakeTestIssue(
64 789, 1, 'sum', 'New', 111, project_name='proj',
65 opened_timestamp=self.NOW, issue_id=1001)
66 self.issue_2 = fake.MakeTestIssue(
67 789, 2, 'sum', 'New', 111, project_name='proj', issue_id=1002)
68 self.issue_1.blocked_on_iids.append(self.issue_2.issue_id)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010069 self.issue_1.blocked_on_ranks.append(sys.maxsize)
Copybara854996b2021-09-07 19:36:02 +000070 self.services.issue.TestAddIssue(self.issue_1)
71 self.services.issue.TestAddIssue(self.issue_2)
72 self.issues_svcr = issues_servicer.IssuesServicer(
73 self.services, make_rate_limiter=False)
74 self.prpc_context = context.ServicerContext()
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020075 self.prpc_context.set_code(codes.StatusCode.OK)
Copybara854996b2021-09-07 19:36:02 +000076 self.auth = authdata.AuthData(user_id=333, email='approver3@example.com')
77
78 self.fd_1 = tracker_pb2.FieldDef(
79 field_name='FirstField', field_id=1,
80 field_type=tracker_pb2.FieldTypes.STR_TYPE,
81 applicable_type='')
82 self.fd_2 = tracker_pb2.FieldDef(
83 field_name='SecField', field_id=2,
84 field_type=tracker_pb2.FieldTypes.INT_TYPE,
85 applicable_type='')
86 self.fd_3 = tracker_pb2.FieldDef(
87 field_name='LegalApproval', field_id=3,
88 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE,
89 applicable_type='')
90 self.fd_4 = tracker_pb2.FieldDef(
91 field_name='UserField', field_id=4,
92 field_type=tracker_pb2.FieldTypes.USER_TYPE,
93 applicable_type='')
94 self.fd_5 = tracker_pb2.FieldDef(
95 field_name='DogApproval', field_id=5,
96 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE,
97 applicable_type='')
98
99 def CallWrapped(self, wrapped_handler, *args, **kwargs):
100 return wrapped_handler.wrapped(self.issues_svcr, *args, **kwargs)
101
102 def testGetProjectIssueIDsAndConfig_OnlyOneProjectName(self):
103 mc = monorailcontext.MonorailContext(
104 self.services, cnxn=self.cnxn, requester='owner@example.com')
105 issue_refs = [
106 common_pb2.IssueRef(project_name='proj', local_id=1),
107 common_pb2.IssueRef(local_id=2),
108 common_pb2.IssueRef(project_name='proj', local_id=3),
109 ]
110 project, issue_ids, config = self.issues_svcr._GetProjectIssueIDsAndConfig(
111 mc, issue_refs)
112 self.assertEqual(project, self.project)
113 self.assertEqual(issue_ids, [self.issue_1.issue_id, self.issue_2.issue_id])
114 self.assertEqual(
115 config,
116 self.services.config.GetProjectConfig(
117 self.cnxn, self.project.project_id))
118
119 def testGetProjectIssueIDsAndConfig_NoProjectName(self):
120 mc = monorailcontext.MonorailContext(
121 self.services, cnxn=self.cnxn, requester='owner@example.com')
122 issue_refs = [
123 common_pb2.IssueRef(local_id=2),
124 common_pb2.IssueRef(local_id=3),
125 ]
126 with self.assertRaises(exceptions.InputException):
127 self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
128
129 def testGetProjectIssueIDsAndConfig_MultipleProjectNames(self):
130 mc = monorailcontext.MonorailContext(
131 self.services, cnxn=self.cnxn, requester='owner@example.com')
132 issue_refs = [
133 common_pb2.IssueRef(project_name='proj', local_id=2),
134 common_pb2.IssueRef(project_name='proj2', local_id=3),
135 ]
136 with self.assertRaises(exceptions.InputException):
137 self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
138
139 def testGetProjectIssueIDsAndConfig_MissingLocalId(self):
140 mc = monorailcontext.MonorailContext(
141 self.services, cnxn=self.cnxn, requester='owner@example.com')
142 issue_refs = [
143 common_pb2.IssueRef(project_name='proj'),
144 common_pb2.IssueRef(project_name='proj', local_id=3),
145 ]
146 with self.assertRaises(exceptions.InputException):
147 self.issues_svcr._GetProjectIssueIDsAndConfig(mc, issue_refs)
148
149 def testCreateIssue_Normal(self):
150 """We can create an issue."""
151 request = issues_pb2.CreateIssueRequest(
152 project_name='proj',
153 issue=issue_objects_pb2.Issue(
154 project_name='proj', local_id=1, summary='sum'))
155 mc = monorailcontext.MonorailContext(
156 self.services, cnxn=self.cnxn, requester='owner@example.com')
157
158 response = self.CallWrapped(self.issues_svcr.CreateIssue, mc, request)
159
160 self.assertEqual('proj', response.project_name)
161
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100162 @patch('businesslogic.work_env.WorkEnv.GetIssueMigratedID')
163 def testGetIssue_Normal(self, mockGetIssueMigratedID):
Copybara854996b2021-09-07 19:36:02 +0000164 """We can get an issue."""
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100165 mockGetIssueMigratedID.return_value = None
Copybara854996b2021-09-07 19:36:02 +0000166 request = issues_pb2.GetIssueRequest()
167 request.issue_ref.project_name = 'proj'
168 request.issue_ref.local_id = 1
169 mc = monorailcontext.MonorailContext(
170 self.services, cnxn=self.cnxn, requester='owner@example.com')
171 mc.LookupLoggedInUserPerms(self.project)
172
173 response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
174
175 actual = response.issue
176 self.assertEqual('proj', actual.project_name)
177 self.assertEqual(1, actual.local_id)
178 self.assertEqual(1, len(actual.blocked_on_issue_refs))
179 self.assertEqual('proj', actual.blocked_on_issue_refs[0].project_name)
180 self.assertEqual(2, actual.blocked_on_issue_refs[0].local_id)
181
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100182 @patch('businesslogic.work_env.WorkEnv.GetIssueMigratedID')
183 def testGetIssue_WithMigratedID(self, mockGetIssueMigratedID):
184 """We can get an issue."""
185 mockGetIssueMigratedID.return_value = '123'
186 request = issues_pb2.GetIssueRequest()
187 request.issue_ref.project_name = 'proj'
188 request.issue_ref.local_id = 1
189 mc = monorailcontext.MonorailContext(
190 self.services, cnxn=self.cnxn, requester='owner@example.com')
191 mc.LookupLoggedInUserPerms(self.project)
192
193 response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
194
195 actual = response.issue
196 self.assertEqual('proj', actual.project_name)
197 self.assertEqual(1, actual.local_id)
198 self.assertEqual(1, len(actual.blocked_on_issue_refs))
199 self.assertEqual('proj', actual.blocked_on_issue_refs[0].project_name)
200 self.assertEqual(2, actual.blocked_on_issue_refs[0].local_id)
201 self.assertEqual('123', actual.migrated_id)
202
Copybara854996b2021-09-07 19:36:02 +0000203 def testGetIssue_Moved(self):
204 """We can get a moved issue."""
205 self.services.project.TestAddProject(
206 'other', project_id=987, owner_ids=[111], contrib_ids=[111])
207 issue = fake.MakeTestIssue(987, 200, 'sum', 'New', 111, issue_id=1010)
208 self.services.issue.TestAddIssue(issue)
209 self.services.issue.TestAddMovedIssueRef(789, 404, 987, 200)
210
211 mc = monorailcontext.MonorailContext(
212 self.services, cnxn=self.cnxn, requester='owner@example.com')
213 mc.LookupLoggedInUserPerms(self.project)
214
215 request = issues_pb2.GetIssueRequest()
216 request.issue_ref.project_name = 'proj'
217 request.issue_ref.local_id = 404
218
219 response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
220
221 ref = response.moved_to_ref
222 self.assertEqual(200, ref.local_id)
223 self.assertEqual('other', ref.project_name)
224
225 @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
226 def testListIssues(self, mock_pipeline):
227 """We can get a list of issues from a search."""
228 mc = monorailcontext.MonorailContext(
229 self.services, cnxn=self.cnxn, requester='approver3@example.com',
230 auth=self.auth)
231 users_by_id = framework_views.MakeAllUserViews(
232 mc.cnxn, self.services.user, [111])
233 config = self.services.config.GetProjectConfig(self.cnxn, 789)
234
235 instance = Mock(
236 spec=True, visible_results=[self.issue_1, self.issue_2],
237 users_by_id=users_by_id, harmonized_config=config,
238 pagination=Mock(total_count=2))
239 mock_pipeline.return_value = instance
240 instance.SearchForIIDs = Mock()
241 instance.MergeAndSortIssues = Mock()
242 instance.Paginate = Mock()
243
244 request = issues_pb2.ListIssuesRequest(query='',project_names=['proj'])
245 response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
246
247 actual_issue_1 = response.issues[0]
248 self.assertEqual(actual_issue_1.owner_ref.user_id, 111)
249 self.assertEqual('owner@example.com', actual_issue_1.owner_ref.display_name)
250 self.assertEqual(actual_issue_1.local_id, 1)
251
252 actual_issue_2 = response.issues[1]
253 self.assertEqual(actual_issue_2.owner_ref.user_id, 111)
254 self.assertEqual('owner@example.com', actual_issue_2.owner_ref.display_name)
255 self.assertEqual(actual_issue_2.local_id, 2)
256 self.assertEqual(2, response.total_results)
257
258 # TODO(zhangtiff): Add tests for ListIssues + canned queries.
259
260 @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
261 def testListIssues_IncludesAttachmentCount(self, mock_pipeline):
262 """Ensure ListIssues includes correct attachment counts."""
263
264 # Add an attachment to one of the issues so we can check attachment counts.
265 issue_3 = fake.MakeTestIssue(
266 789, 3, 'sum', 'New', 111, project_name='proj', issue_id=2003,
267 attachment_count=1)
268 issue_4 = fake.MakeTestIssue(
269 789, 4, 'sum', 'New', 111, project_name='proj', issue_id=2004,
270 attachment_count=-10)
271 self.services.issue.TestAddIssue(issue_3)
272 self.services.issue.TestAddIssue(issue_4)
273
274 # Request the list of issues.
275 mc = monorailcontext.MonorailContext(
276 self.services, cnxn=self.cnxn, requester='approver3@example.com',
277 auth=self.auth)
278 mc.LookupLoggedInUserPerms(self.project)
279
280 users_by_id = framework_views.MakeAllUserViews(
281 mc.cnxn, self.services.user, [111])
282 config = self.services.config.GetProjectConfig(self.cnxn, 789)
283 instance = Mock(
284 spec=True, visible_results=[
285 self.issue_1, self.issue_2, issue_3, issue_4],
286 users_by_id=users_by_id, harmonized_config=config,
287 pagination=Mock(total_count=4))
288 mock_pipeline.return_value = instance
289 instance.SearchForIIDs = Mock()
290 instance.MergeAndSortIssues = Mock()
291 instance.Paginate = Mock()
292
293 request = issues_pb2.ListIssuesRequest(query='', project_names=['proj'])
294 response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
295
296 # Ensure attachment counts match what we expect.
297 actual_issue_1 = response.issues[0]
298 self.assertEqual(actual_issue_1.attachment_count, 0)
299 self.assertEqual(actual_issue_1.local_id, 1)
300
301 actual_issue_2 = response.issues[1]
302 self.assertEqual(actual_issue_2.attachment_count, 0)
303 self.assertEqual(actual_issue_2.local_id, 2)
304
305 actual_issue_3 = response.issues[2]
306 self.assertEqual(actual_issue_3.attachment_count, 1)
307 self.assertEqual(actual_issue_3.local_id, 3)
308
309 actual_issue_4 = response.issues[3]
310 # NOTE(pawalls): It is not possible to test for presence in Proto3. Instead
311 # we test for default value here though it is semantically different
312 # and not quite the behavior we care about.
313 self.assertEqual(actual_issue_4.attachment_count, 0)
314 self.assertEqual(actual_issue_4.local_id, 4)
315
316 @patch('search.frontendsearchpipeline.FrontendSearchPipeline')
317 def testListIssues_No_visible_results(self, mock_pipeline):
318 """Ensure ListIssues handles the no visible results case."""
319 mc = monorailcontext.MonorailContext(
320 self.services, cnxn=self.cnxn, requester=None, auth=None)
321 users_by_id = framework_views.MakeAllUserViews(
322 mc.cnxn, self.services.user, [111])
323 config = self.services.config.GetProjectConfig(self.cnxn, 789)
324
325 instance = Mock(
326 spec=True,
327 users_by_id=users_by_id,
328 harmonized_config=config,
329 # When there are no results, these default to None.
330 visible_results=None,
331 pagination=None)
332 mock_pipeline.return_value = instance
333 instance.SearchForIIDs = Mock()
334 instance.MergeAndSortIssues = Mock()
335 instance.Paginate = Mock()
336
337 request = issues_pb2.ListIssuesRequest(query='', project_names=['proj'])
338 response = self.CallWrapped(self.issues_svcr.ListIssues, mc, request)
339
340 self.assertEqual(len(response.issues), 0)
341
342 def testListReferencedIssues(self):
343 """We can get the referenced issues that exist."""
344 self.services.project.TestAddProject(
345 'other-proj', project_id=788, owner_ids=[111])
346 other_issue = fake.MakeTestIssue(
347 788, 1, 'sum', 'Fixed', 111, project_name='other-proj', issue_id=78801)
348 self.services.issue.TestAddIssue(other_issue)
349 # We ignore project_names or local_ids that don't exist in our DB.
350 request = issues_pb2.ListReferencedIssuesRequest(
351 issue_refs=[
352 common_pb2.IssueRef(project_name='proj', local_id=1),
353 common_pb2.IssueRef(project_name='other-proj', local_id=1),
354 common_pb2.IssueRef(project_name='other-proj', local_id=2),
355 common_pb2.IssueRef(project_name='ghost-proj', local_id=1)
356 ]
357 )
358 mc = monorailcontext.MonorailContext(
359 self.services, cnxn=self.cnxn, requester='owner@example.com')
360 mc.LookupLoggedInUserPerms(self.project)
361
362 response = self.CallWrapped(
363 self.issues_svcr.ListReferencedIssues, mc, request)
364 self.assertEqual(len(response.closed_refs), 1)
365 self.assertEqual(len(response.open_refs), 1)
366 self.assertEqual(
367 issue_objects_pb2.Issue(
368 local_id=1,
369 project_name='other-proj',
370 summary='sum',
371 status_ref=common_pb2.StatusRef(
372 status='Fixed'),
373 owner_ref=common_pb2.UserRef(
374 user_id=111,
375 display_name='owner@example.com'),
376 reporter_ref=common_pb2.UserRef(
377 user_id=111,
378 display_name='owner@example.com')),
379 response.closed_refs[0])
380 self.assertEqual(
381 issue_objects_pb2.Issue(
382 local_id=1,
383 project_name='proj',
384 summary='sum',
385 status_ref=common_pb2.StatusRef(
386 status='New',
387 means_open=True),
388 owner_ref=common_pb2.UserRef(
389 user_id=111,
390 display_name='owner@example.com'),
391 blocked_on_issue_refs=[common_pb2.IssueRef(
392 project_name='proj',
393 local_id=2)],
394 reporter_ref=common_pb2.UserRef(
395 user_id=111,
396 display_name='owner@example.com'),
397 opened_timestamp=self.NOW,
398 component_modified_timestamp=self.NOW,
399 status_modified_timestamp=self.NOW,
400 owner_modified_timestamp=self.NOW),
401 response.open_refs[0])
402
403 def testListReferencedIssues_MissingInput(self):
404 request = issues_pb2.ListReferencedIssuesRequest(
405 issue_refs=[common_pb2.IssueRef(local_id=1)])
406 mc = monorailcontext.MonorailContext(
407 self.services, cnxn=self.cnxn, requester='owner@example.com')
408 with self.assertRaises(exceptions.InputException):
409 self.CallWrapped(self.issues_svcr.ListReferencedIssues, mc, request)
410
411 def testListApplicableFieldDefs_EmptyIssueRefs(self):
412 request = issues_pb2.ListApplicableFieldDefsRequest()
413 mc = monorailcontext.MonorailContext(
414 self.services, cnxn=self.cnxn, requester='owner@example.com')
415 response = self.CallWrapped(
416 self.issues_svcr.ListApplicableFieldDefs, mc, request)
417 self.assertEqual(response, issues_pb2.ListApplicableFieldDefsResponse())
418
419 def testListApplicableFieldDefs_CrossProjectRequest(self):
420 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
421 common_pb2.IssueRef(project_name='proj2', local_id=2)]
422 request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
423 mc = monorailcontext.MonorailContext(
424 self.services, cnxn=self.cnxn, requester='owner@example.com')
425 with self.assertRaises(exceptions.InputException):
426 self.CallWrapped(self.issues_svcr.ListApplicableFieldDefs, mc, request)
427
428 def testListApplicableFieldDefs_MissingProjectName(self):
429 issue_refs = [common_pb2.IssueRef(local_id=1),
430 common_pb2.IssueRef(local_id=2)]
431 request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
432 mc = monorailcontext.MonorailContext(
433 self.services, cnxn=self.cnxn, requester='owner@example.com')
434 with self.assertRaises(exceptions.InputException):
435 self.CallWrapped(self.issues_svcr.ListApplicableFieldDefs, mc, request)
436
437 def testListApplicableFieldDefs_Normal(self):
438 self.issue_1.labels = ['Type-Feedback']
439 self.issue_2.approval_values = [
440 tracker_pb2.ApprovalValue(approval_id=self.fd_3.field_id)]
441 self.fd_1.applicable_type = 'Defect' # not applicable
442 self.fd_2.applicable_type = 'feedback' # applicable
443 self.fd_3.applicable_type = 'ignored' # is APPROVAL_TYPE, applicable
444 self.fd_4.applicable_type = '' # applicable
445 self.fd_5.applicable_type = '' # is APPROVAl_TYPE, not applicable
446 config = tracker_pb2.ProjectIssueConfig(
447 project_id=789,
448 field_defs=[self.fd_1, self.fd_2, self.fd_3, self.fd_4, self.fd_5])
449 self.services.config.StoreConfig(self.cnxn, config)
450 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
451 common_pb2.IssueRef(project_name='proj', local_id=2)]
452 request = issues_pb2.ListApplicableFieldDefsRequest(issue_refs=issue_refs)
453 mc = monorailcontext.MonorailContext(
454 self.services, cnxn=self.cnxn, requester='owner@example.com')
455 response = self.CallWrapped(
456 self.issues_svcr.ListApplicableFieldDefs, mc, request)
457 converted_field_defs = [converters.ConvertFieldDef(fd, [], {}, config, True)
458 for fd in [self.fd_2, self.fd_3, self.fd_4]]
459 self.assertEqual(response, issues_pb2.ListApplicableFieldDefsResponse(
460 field_defs=converted_field_defs))
461
462 def testUpdateIssue_Denied_Edit(self):
463 """We reject requests to update an issue when the user lacks perms."""
464 request = issues_pb2.UpdateIssueRequest()
465 request.issue_ref.project_name = 'proj'
466 request.issue_ref.local_id = 1
467 request.delta.summary.value = 'new summary'
468
469 # Anon user can never update.
470 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
471 mc.LookupLoggedInUserPerms(self.project)
472 with self.assertRaises(permissions.PermissionException):
473 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
474
475 # Signed in user cannot view this issue.
476 mc = monorailcontext.MonorailContext(
477 self.services, cnxn=self.cnxn, requester='approver3@example.com')
478 mc.LookupLoggedInUserPerms(self.project)
479 self.issue_1.labels = ['Restrict-View-CoreTeam']
480 with self.assertRaises(permissions.PermissionException):
481 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
482
483 # Signed in user cannot edit this issue.
484 self.issue_1.labels = ['Restrict-EditIssue-CoreTeam']
485 with self.assertRaises(permissions.PermissionException):
486 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
487
488 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
489 def testUpdateIssue_JustAComment(self, _fake_pasicn):
490 """We check AddIssueComment when the user is only commenting."""
491 request = issues_pb2.UpdateIssueRequest()
492 request.comment_content = 'Foo'
493 request.issue_ref.project_name = 'proj'
494 request.issue_ref.local_id = 1
495 # Note: no delta.
496
497 # Anon user can never update.
498 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
499 mc.LookupLoggedInUserPerms(self.project)
500 with self.assertRaises(permissions.PermissionException):
501 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
502
503 # Signed in user cannot view this issue.
504 mc = monorailcontext.MonorailContext(
505 self.services, cnxn=self.cnxn, requester='approver3@example.com')
506 mc.LookupLoggedInUserPerms(self.project)
507 self.issue_1.labels = ['Restrict-View-CoreTeam']
508 with self.assertRaises(permissions.PermissionException):
509 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
510
511 # Signed in user cannot edit this issue, but they can still comment.
512 self.issue_1.labels = ['Restrict-EditIssue-CoreTeam']
513 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
514
515 # Signed in user cannot post even a text comment.
516 self.issue_1.labels = ['Restrict-AddIssueComment-CoreTeam']
517 with self.assertRaises(permissions.PermissionException):
518 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
519
520 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
521 def testUpdateIssue_Normal(self, fake_pasicn):
522 """We can update an issue."""
523 request = issues_pb2.UpdateIssueRequest()
524 request.issue_ref.project_name = 'proj'
525 request.issue_ref.local_id = 1
526 request.delta.summary.value = 'New summary'
527 request.delta.label_refs_add.extend([
528 common_pb2.LabelRef(label='Hot')])
529 request.comment_content = 'test comment'
530 mc = monorailcontext.MonorailContext(
531 self.services, cnxn=self.cnxn, requester='owner@example.com')
532 mc.LookupLoggedInUserPerms(self.project)
533
534 response = self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
535
536 actual = response.issue
537 # Intended stuff was changed.
538 self.assertEqual(1, len(actual.label_refs))
539 self.assertEqual('Hot', actual.label_refs[0].label)
540 self.assertEqual('New summary', actual.summary)
541
542 # Other stuff didn't change.
543 self.assertEqual('proj', actual.project_name)
544 self.assertEqual(1, actual.local_id)
545 self.assertEqual(1, len(actual.blocked_on_issue_refs))
546 self.assertEqual('proj', actual.blocked_on_issue_refs[0].project_name)
547 self.assertEqual(2, actual.blocked_on_issue_refs[0].local_id)
548
549 # A comment was added.
550 fake_pasicn.assert_called_once()
551 comments = self.services.issue.GetCommentsForIssue(
552 self.cnxn, self.issue_1.issue_id)
553 self.assertEqual(2, len(comments))
554 self.assertEqual('test comment', comments[1].content)
555
556 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
557 def testUpdateIssue_CommentOnly(self, fake_pasicn):
558 """We can update an issue with a comment w/o making any other changes."""
559 request = issues_pb2.UpdateIssueRequest()
560 request.issue_ref.project_name = 'proj'
561 request.issue_ref.local_id = 1
562 request.comment_content = 'test comment'
563 mc = monorailcontext.MonorailContext(
564 self.services, cnxn=self.cnxn, requester='owner@example.com')
565 mc.LookupLoggedInUserPerms(self.project)
566
567 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
568
569 # A comment was added.
570 fake_pasicn.assert_called_once()
571 comments = self.services.issue.GetCommentsForIssue(
572 self.cnxn, self.issue_1.issue_id)
573 self.assertEqual(2, len(comments))
574 self.assertEqual('test comment', comments[1].content)
575 self.assertFalse(comments[1].is_description)
576
577 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
578 def testUpdateIssue_CommentWithAttachments(self, fake_pasicn):
579 """We can update an issue with a comment and attachments."""
580 request = issues_pb2.UpdateIssueRequest()
581 request.issue_ref.project_name = 'proj'
582 request.issue_ref.local_id = 1
583 request.comment_content = 'test comment'
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100584 request.uploads.extend(
585 [
586 issue_objects_pb2.AttachmentUpload(
587 filename='a.txt', content=b'aaaaa')
588 ])
Copybara854996b2021-09-07 19:36:02 +0000589 mc = monorailcontext.MonorailContext(
590 self.services, cnxn=self.cnxn, requester='owner@example.com')
591 mc.LookupLoggedInUserPerms(self.project)
592
593 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
594
595 # A comment with an attachment was added.
596 fake_pasicn.assert_called_once()
597 comments = self.services.issue.GetCommentsForIssue(
598 self.cnxn, self.issue_1.issue_id)
599 self.assertEqual(2, len(comments))
600 self.assertEqual('test comment', comments[1].content)
601 self.assertFalse(comments[1].is_description)
602 self.assertEqual(1, len(comments[1].attachments))
603 self.assertEqual('a.txt', comments[1].attachments[0].filename)
604 self.assertEqual(5, self.project.attachment_bytes_used)
605
606 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
607 def testUpdateIssue_Description(self, fake_pasicn):
608 """We can update an issue's description."""
609 request = issues_pb2.UpdateIssueRequest()
610 request.issue_ref.project_name = 'proj'
611 request.issue_ref.local_id = 1
612 request.comment_content = 'new description'
613 request.is_description = True
614 mc = monorailcontext.MonorailContext(
615 self.services, cnxn=self.cnxn, requester='owner@example.com')
616 mc.LookupLoggedInUserPerms(self.project)
617
618 self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
619
620 # A comment was added.
621 fake_pasicn.assert_called_once()
622 comments = self.services.issue.GetCommentsForIssue(
623 self.cnxn, self.issue_1.issue_id)
624 self.assertEqual(2, len(comments))
625 self.assertEqual('new description', comments[1].content)
626 self.assertTrue(comments[1].is_description)
627
628 @patch('features.send_notifications.PrepareAndSendIssueChangeNotification')
629 def testUpdateIssue_NoOp(self, fake_pasicn):
630 """We gracefully ignore requests that have no delta or comment."""
631 request = issues_pb2.UpdateIssueRequest()
632 request.issue_ref.project_name = 'proj'
633 request.issue_ref.local_id = 1
634 mc = monorailcontext.MonorailContext(
635 self.services, cnxn=self.cnxn, requester='owner@example.com')
636 mc.LookupLoggedInUserPerms(self.project)
637
638 response = self.CallWrapped(self.issues_svcr.UpdateIssue, mc, request)
639
640 actual = response.issue
641 # Other stuff didn't change.
642 self.assertEqual('proj', actual.project_name)
643 self.assertEqual(1, actual.local_id)
644 self.assertEqual('sum', actual.summary)
645 self.assertEqual('New', actual.status_ref.status)
646
647 # No comment was added.
648 fake_pasicn.assert_not_called()
649 comments = self.services.issue.GetCommentsForIssue(
650 self.cnxn, self.issue_1.issue_id)
651 self.assertEqual(1, len(comments))
652
653 def testStarIssue_Denied(self):
654 """We reject requests to star an issue if the user lacks perms."""
655 request = issues_pb2.StarIssueRequest()
656 request.issue_ref.project_name = 'proj'
657 request.issue_ref.local_id = 1
658 request.starred = True
659
660 # Anon user cannot star an issue.
661 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
662 mc.LookupLoggedInUserPerms(self.project)
663 with self.assertRaises(permissions.PermissionException):
664 self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
665
666 # User star an issue that they cannot view.
667 self.issue_1.labels = ['Restrict-View-CoreTeam']
668 mc = monorailcontext.MonorailContext(
669 self.services, cnxn=self.cnxn, requester='approver3@example.com')
670 mc.LookupLoggedInUserPerms(self.project)
671 with self.assertRaises(permissions.PermissionException):
672 self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
673
674 # The issue was not actually starred.
675 self.assertEqual(0, self.issue_1.star_count)
676
677 def testStarIssue_Normal(self):
678 """Users can star and unstar issues."""
679 request = issues_pb2.StarIssueRequest()
680 request.issue_ref.project_name = 'proj'
681 request.issue_ref.local_id = 1
682 request.starred = True
683 mc = monorailcontext.MonorailContext(
684 self.services, cnxn=self.cnxn, requester='approver3@example.com')
685 mc.LookupLoggedInUserPerms(self.project)
686
687 # First, star it.
688 response = self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
689 self.assertEqual(1, response.star_count)
690
691 # Then, unstar it.
692 request.starred = False
693 response = self.CallWrapped(self.issues_svcr.StarIssue, mc, request)
694 self.assertEqual(0, response.star_count)
695
696 def testIsIssueStared_Anon(self):
697 """Anon users can't star issues, so they always get back False."""
698 request = issues_pb2.IsIssueStarredRequest()
699 request.issue_ref.project_name = 'proj'
700 request.issue_ref.local_id = 1
701 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
702 mc.LookupLoggedInUserPerms(self.project)
703
704 response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
705 self.assertFalse(response.is_starred)
706
707 def testIsIssueStared_Denied(self):
708 """Users can't ask about an issue that they cannot currently view."""
709 request = issues_pb2.IsIssueStarredRequest()
710 request.issue_ref.project_name = 'proj'
711 request.issue_ref.local_id = 1
712 mc = monorailcontext.MonorailContext(
713 self.services, cnxn=self.cnxn, requester='approver3@example.com')
714 mc.LookupLoggedInUserPerms(self.project)
715 self.issue_1.labels = ['Restrict-View-CoreTeam']
716
717 with self.assertRaises(permissions.PermissionException):
718 self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
719
720 def testIsIssueStared_Normal(self):
721 """Users can star and unstar issues."""
722 request = issues_pb2.IsIssueStarredRequest()
723 request.issue_ref.project_name = 'proj'
724 request.issue_ref.local_id = 1
725 mc = monorailcontext.MonorailContext(
726 self.services, cnxn=self.cnxn, requester='approver3@example.com')
727 mc.LookupLoggedInUserPerms(self.project)
728
729 # It is not initially starred by this user.
730 response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
731 self.assertFalse(response.is_starred)
732
733 # If we star it, we get response True.
734 self.services.issue_star.SetStar(
735 self.cnxn, self.services, 'fake config', self.issue_1.issue_id,
736 333, True)
737 response = self.CallWrapped(self.issues_svcr.IsIssueStarred, mc, request)
738 self.assertTrue(response.is_starred)
739
740 def testListStarredIssues_Anon(self):
741 """Users can't see their starred issues until they sign in."""
742 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
743 mc.LookupLoggedInUserPerms(self.project)
744
745 response = self.CallWrapped(self.issues_svcr.ListStarredIssues, mc, {})
746 # Assert that response has an empty list
747 self.assertEqual(0, len(response.starred_issue_refs))
748
749 def testListStarredIssues_Normal(self):
750 """User can access which issues they've starred."""
751 mc = monorailcontext.MonorailContext(
752 self.services, cnxn=self.cnxn, requester='approver3@example.com')
753 mc.LookupLoggedInUserPerms(self.project)
754
755 # First, star some issues
756 self.services.issue_star.SetStar(
757 self.cnxn, self.services, 'fake config', self.issue_1.issue_id,
758 333, True)
759 self.services.issue_star.SetStar(
760 self.cnxn, self.services, 'fake config', self.issue_2.issue_id,
761 333, True)
762
763 # Now test that user can retrieve their star in a list
764 response = self.CallWrapped(self.issues_svcr.ListStarredIssues, mc, {})
765 self.assertEqual(2, len(response.starred_issue_refs))
766
767 def testListComments_Normal(self):
768 """We can get comments on an issue."""
769 comment = tracker_pb2.IssueComment(
770 user_id=111, timestamp=self.NOW, content='second',
771 project_id=789, issue_id=self.issue_1.issue_id, sequence=1)
772 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
773 request = issues_pb2.ListCommentsRequest()
774 request.issue_ref.project_name = 'proj'
775 request.issue_ref.local_id = 1
776 mc = monorailcontext.MonorailContext(
777 self.services, cnxn=self.cnxn, requester='owner@example.com')
778 mc.LookupLoggedInUserPerms(self.project)
779
780 response = self.CallWrapped(self.issues_svcr.ListComments, mc, request)
781
782 actual_0 = response.comments[0]
783 actual_1 = response.comments[1]
784 expected_0 = issue_objects_pb2.Comment(
785 project_name='proj', local_id=1, sequence_num=0, is_deleted=False,
786 commenter=common_pb2.UserRef(
787 user_id=111, display_name='owner@example.com'),
788 timestamp=self.NOW, content='sum', is_spam=False,
789 description_num=1, can_delete=True, can_flag=True)
790 expected_1 = issue_objects_pb2.Comment(
791 project_name='proj', local_id=1, sequence_num=1, is_deleted=False,
792 commenter=common_pb2.UserRef(
793 user_id=111, display_name='owner@example.com'),
794 timestamp=self.NOW, content='second', can_delete=True, can_flag=True)
795 self.assertEqual(expected_0, actual_0)
796 self.assertEqual(expected_1, actual_1)
797
798 def testListActivities_Normal(self):
799 """We can get issue activity."""
800 self.services.user.TestAddUser('user@example.com', 444)
801
802 config = tracker_pb2.ProjectIssueConfig(
803 project_id=789,
804 field_defs=[self.fd_1])
805 self.services.config.StoreConfig(self.cnxn, config)
806
807 comment = tracker_pb2.IssueComment(
808 user_id=444, timestamp=self.NOW, content='c1',
809 project_id=789, issue_id=self.issue_1.issue_id, sequence=1)
810 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
811
812 self.services.project.TestAddProject(
813 'proj2', project_id=790, owner_ids=[111], contrib_ids=[222, 333])
814 issue_2 = fake.MakeTestIssue(
815 790, 1, 'sum', 'New', 444, project_name='proj2',
816 opened_timestamp=self.NOW, issue_id=2001)
817 comment_2 = tracker_pb2.IssueComment(
818 user_id=444, timestamp=self.NOW, content='c2',
819 project_id=790, issue_id=issue_2.issue_id, sequence=1)
820 self.services.issue.TestAddComment(comment_2, issue_2.local_id)
821 self.services.issue.TestAddIssue(issue_2)
822
823 issue_3 = fake.MakeTestIssue(
824 790, 2, 'sum', 'New', 111, project_name='proj2',
825 opened_timestamp=self.NOW, issue_id=2002, labels=['Restrict-View-Foo'])
826 comment_3 = tracker_pb2.IssueComment(
827 user_id=444, timestamp=self.NOW, content='c3',
828 project_id=790, issue_id=issue_3.issue_id, sequence=1)
829 self.services.issue.TestAddComment(comment_3, issue_3.local_id)
830 self.services.issue.TestAddIssue(issue_3)
831
832 request = issues_pb2.ListActivitiesRequest()
833 request.user_ref.user_id = 444
834 mc = monorailcontext.MonorailContext(
835 self.services, cnxn=self.cnxn, requester='user@example.com')
836 mc.LookupLoggedInUserPerms(self.project)
837 response = self.CallWrapped(self.issues_svcr.ListActivities, mc, request)
838
839 self.maxDiff = None
840 self.assertEqual([
841 issue_objects_pb2.Comment(
842 project_name='proj',
843 local_id=1,
844 commenter=common_pb2.UserRef(
845 user_id=444, display_name='user@example.com'),
846 timestamp=self.NOW,
847 content='c1',
848 sequence_num=1,
849 can_delete=True,
850 can_flag=True),
851 issue_objects_pb2.Comment(
852 project_name='proj2',
853 local_id=1,
854 commenter=common_pb2.UserRef(
855 user_id=444, display_name='user@example.com'),
856 timestamp=self.NOW,
857 content='sum',
858 description_num=1,
859 can_delete=True,
860 can_flag=True),
861 issue_objects_pb2.Comment(
862 project_name='proj2',
863 local_id=1,
864 commenter=common_pb2.UserRef(
865 user_id=444, display_name='user@example.com'),
866 timestamp=self.NOW,
867 content='c2',
868 sequence_num=1,
869 can_delete=True,
870 can_flag=True)],
871 sorted(
872 response.comments,
873 key=lambda c: (c.project_name, c.local_id, c.sequence_num)))
874 self.assertEqual([
875 issue_objects_pb2.IssueSummary(
876 project_name='proj',
877 local_id=1,
878 summary='sum'),
879 issue_objects_pb2.IssueSummary(
880 project_name='proj2',
881 local_id=1,
882 summary='sum')],
883 sorted(
884 response.issue_summaries,
885 key=lambda issue: (issue.project_name, issue.local_id)))
886
887 def testListActivities_Amendment(self):
888 self.services.user.TestAddUser('user@example.com', 444)
889
890 comment = tracker_pb2.IssueComment(
891 user_id=444,
892 timestamp=self.NOW,
893 amendments=[tracker_bizobj.MakeOwnerAmendment(111, 222)],
894 project_id=789,
895 issue_id=self.issue_1.issue_id,
896 content='',
897 sequence=1)
898 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
899
900 request = issues_pb2.ListActivitiesRequest()
901 request.user_ref.user_id = 444
902 mc = monorailcontext.MonorailContext(
903 self.services, cnxn=self.cnxn, requester='user@example.com')
904 mc.LookupLoggedInUserPerms(self.project)
905 response = self.CallWrapped(self.issues_svcr.ListActivities, mc, request)
906
907 self.assertEqual([
908 issue_objects_pb2.Comment(
909 project_name='proj',
910 local_id=1,
911 commenter=common_pb2.UserRef(
912 user_id=444, display_name='user@example.com'),
913 timestamp=self.NOW,
914 content='',
915 sequence_num=1,
916 amendments=[issue_objects_pb2.Amendment(
917 field_name="Owner",
918 new_or_delta_value="ow...@example.com")],
919 can_delete=True,
920 can_flag=True)],
921 sorted(
922 response.comments,
923 key=lambda c: (c.project_name, c.local_id, c.sequence_num)))
924 self.assertEqual([
925 issue_objects_pb2.IssueSummary(
926 project_name='proj',
927 local_id=1,
928 summary='sum')],
929 sorted(
930 response.issue_summaries,
931 key=lambda issue: (issue.project_name, issue.local_id)))
932
933 @patch('testing.fake.IssueService.SoftDeleteComment')
934 def testDeleteComment_Invalid(self, fake_softdeletecomment):
935 """We reject requests to delete a non-existent comment."""
936 # Note: no comments added to self.issue_1 after the description.
937 request = issues_pb2.DeleteCommentRequest(
938 issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
939 sequence_num=2, delete=True)
940 mc = monorailcontext.MonorailContext(
941 self.services, cnxn=self.cnxn, requester='owner@example.com')
942
943 with self.assertRaises(exceptions.NoSuchCommentException):
944 self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
945
946 fake_softdeletecomment.assert_not_called()
947
948 def testDeleteComment_Normal(self):
949 """An authorized user can delete and undelete a comment."""
950 comment_1 = tracker_pb2.IssueComment(
951 project_id=789, issue_id=self.issue_1.issue_id, content='one')
952 self.services.issue.TestAddComment(comment_1, 1)
953 comment_2 = tracker_pb2.IssueComment(
954 project_id=789, issue_id=self.issue_1.issue_id, content='two',
955 user_id=222)
956 self.services.issue.TestAddComment(comment_2, 1)
957
958 # Delete a comment.
959 request = issues_pb2.DeleteCommentRequest(
960 issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
961 sequence_num=2, delete=True)
962 mc = monorailcontext.MonorailContext(
963 self.services, cnxn=self.cnxn, requester='owner@example.com')
964
965 response = self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
966
967 self.assertTrue(isinstance(response, empty_pb2.Empty))
968 self.assertEqual(111, comment_2.deleted_by)
969
970 # Undelete a comment.
971 request.delete=False
972
973 response = self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
974
975 self.assertTrue(isinstance(response, empty_pb2.Empty))
976 self.assertEqual(None, comment_2.deleted_by)
977
978 @patch('testing.fake.IssueService.SoftDeleteComment')
979 def testDeleteComment_Denied(self, fake_softdeletecomment):
980 """An unauthorized user cannot delete a comment."""
981 comment_1 = tracker_pb2.IssueComment(
982 project_id=789, issue_id=self.issue_1.issue_id, content='one',
983 user_id=222)
984 self.services.issue.TestAddComment(comment_1, 1)
985
986 request = issues_pb2.DeleteCommentRequest(
987 issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
988 sequence_num=1, delete=True)
989 mc = monorailcontext.MonorailContext(
990 self.services, cnxn=self.cnxn, requester='approver3@example.com')
991
992 with self.assertRaises(permissions.PermissionException):
993 self.CallWrapped(self.issues_svcr.DeleteComment, mc, request)
994
995 fake_softdeletecomment.assert_not_called()
996 self.assertIsNone(comment_1.deleted_by)
997
998 def testUpdateApproval_MissingFieldDef(self):
999 """Missing Approval Field Def throwns exception."""
1000 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1001 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1002 approval_delta = issue_objects_pb2.ApprovalDelta(
1003 status=issue_objects_pb2.REVIEW_REQUESTED)
1004 request = issues_pb2.UpdateApprovalRequest(
1005 issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta)
1006
1007 request.issue_ref.project_name = 'proj'
1008 request.issue_ref.local_id = 1
1009 mc = monorailcontext.MonorailContext(
1010 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1011 auth=self.auth)
1012
1013 with self.assertRaises(exceptions.NoSuchFieldDefException):
1014 self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
1015
1016 def testBulkUpdateApprovals_EmptyIssueRefs(self):
1017 mc = monorailcontext.MonorailContext(
1018 self.services, cnxn=self.cnxn, requester='owner@example.com')
1019 request = issues_pb2.BulkUpdateApprovalsRequest(
1020 field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
1021 approval_delta=issue_objects_pb2.ApprovalDelta())
1022 with self.assertRaises(exceptions.InputException):
1023 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1024
1025 def testBulkUpdateApprovals_NoProjectName(self):
1026 mc = monorailcontext.MonorailContext(
1027 self.services, cnxn=self.cnxn, requester='owner@example.com')
1028 issue_refs = [common_pb2.IssueRef(local_id=1),
1029 common_pb2.IssueRef(local_id=2)]
1030 request = issues_pb2.BulkUpdateApprovalsRequest(
1031 issue_refs=issue_refs,
1032 field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
1033 approval_delta=issue_objects_pb2.ApprovalDelta())
1034 with self.assertRaises(exceptions.InputException):
1035 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1036
1037 def testBulkUpdateApprovals_CrossProjectRequest(self):
1038 mc = monorailcontext.MonorailContext(
1039 self.services, cnxn=self.cnxn, requester='owner@example.com')
1040 issue_refs = [common_pb2.IssueRef(project_name='p1', local_id=1),
1041 common_pb2.IssueRef(project_name='p2', local_id=2)]
1042 request = issues_pb2.BulkUpdateApprovalsRequest(
1043 issue_refs=issue_refs,
1044 field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
1045 approval_delta=issue_objects_pb2.ApprovalDelta())
1046 with self.assertRaises(exceptions.InputException):
1047 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1048
1049 def testBulkUpdateApprovals_NoSuchFieldDef(self):
1050 mc = monorailcontext.MonorailContext(
1051 self.services, cnxn=self.cnxn, requester='owner@example.com')
1052 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
1053 common_pb2.IssueRef(project_name='proj', local_id=2)]
1054 request = issues_pb2.BulkUpdateApprovalsRequest(
1055 issue_refs=issue_refs,
1056 field_ref=common_pb2.FieldRef(field_name='LegalApproval'),
1057 approval_delta=issue_objects_pb2.ApprovalDelta())
1058 with self.assertRaises(exceptions.NoSuchFieldDefException):
1059 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1060
1061 def testBulkUpdateApprovals_AnonDenied(self):
1062 """Anon user cannot make any updates"""
1063 config = tracker_pb2.ProjectIssueConfig(
1064 project_id=789,
1065 field_defs=[self.fd_3])
1066 self.services.config.StoreConfig(self.cnxn, config)
1067 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1068 approval_delta = issue_objects_pb2.ApprovalDelta()
1069 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
1070 common_pb2.IssueRef(project_name='proj', local_id=2)]
1071 request = issues_pb2.BulkUpdateApprovalsRequest(
1072 issue_refs=issue_refs, field_ref=field_ref,
1073 approval_delta=approval_delta)
1074
1075 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
1076 with self.assertRaises(permissions.PermissionException):
1077 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1078
1079 def testBulkUpdateApprovals_UserLacksViewPerms(self):
1080 """User who cannot view issue cannot update issue."""
1081 config = tracker_pb2.ProjectIssueConfig(
1082 project_id=789,
1083 field_defs=[self.fd_3])
1084 self.services.config.StoreConfig(self.cnxn, config)
1085 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1086 approval_delta = issue_objects_pb2.ApprovalDelta()
1087 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
1088 common_pb2.IssueRef(project_name='proj', local_id=2)]
1089 request = issues_pb2.BulkUpdateApprovalsRequest(
1090 issue_refs=issue_refs, field_ref=field_ref,
1091 approval_delta=approval_delta)
1092
1093 self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
1094 mc = monorailcontext.MonorailContext(
1095 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
1096 with self.assertRaises(permissions.PermissionException):
1097 self.CallWrapped(self.issues_svcr.BulkUpdateApprovals, mc, request)
1098
1099 @patch('time.time')
1100 @patch('businesslogic.work_env.WorkEnv.BulkUpdateIssueApprovals')
1101 @patch('businesslogic.work_env.WorkEnv.GetIssueRefs')
1102 def testBulkUpdateApprovals_Normal(
1103 self, mockGetIssueRefs, mockBulkUpdateIssueApprovals, mockTime):
1104 """Issue approvals that can be updated are updated and returned."""
1105 mockTime.return_value = 12345
1106 mockGetIssueRefs.return_value = {1001: ('proj', 1), 1002: ('proj', 2)}
1107 config = tracker_pb2.ProjectIssueConfig(
1108 project_id=789,
1109 field_defs=[self.fd_3])
1110 self.services.config.StoreConfig(self.cnxn, config)
1111 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1112 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1),
1113 common_pb2.IssueRef(project_name='proj', local_id=2)]
1114 request = issues_pb2.BulkUpdateApprovalsRequest(
1115 issue_refs=issue_refs, field_ref=field_ref,
1116 approval_delta=issue_objects_pb2.ApprovalDelta(
1117 status=issue_objects_pb2.APPROVED),
1118 comment_content='new bulk comment')
1119 mc = monorailcontext.MonorailContext(
1120 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
1121 response = self.CallWrapped(
1122 self.issues_svcr.BulkUpdateApprovals, mc, request)
1123 self.assertEqual(
1124 response,
1125 issues_pb2.BulkUpdateApprovalsResponse(
1126 issue_refs=[common_pb2.IssueRef(project_name='proj', local_id=1),
1127 common_pb2.IssueRef(project_name='proj', local_id=2)]))
1128
1129 approval_delta = tracker_pb2.ApprovalDelta(
1130 status=tracker_pb2.ApprovalStatus.APPROVED,
1131 setter_id=444, set_on=12345)
1132 mockBulkUpdateIssueApprovals.assert_called_once_with(
1133 [1001, 1002], 3, self.project, approval_delta,
1134 'new bulk comment', send_email=False)
1135
1136 @patch('businesslogic.work_env.WorkEnv.BulkUpdateIssueApprovals')
1137 @patch('businesslogic.work_env.WorkEnv.GetIssueRefs')
1138 def testBulkUpdateApprovals_EmptyDelta(
1139 self, mockGetIssueRefs, mockBulkUpdateIssueApprovals):
1140 """Bulk update approval requests don't fail with an empty approval delta."""
1141 mockGetIssueRefs.return_value = {1001: ('proj', 1)}
1142 config = tracker_pb2.ProjectIssueConfig(
1143 project_id=789,
1144 field_defs=[self.fd_3])
1145 self.services.config.StoreConfig(self.cnxn, config)
1146 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1147 issue_refs = [common_pb2.IssueRef(project_name='proj', local_id=1)]
1148 request = issues_pb2.BulkUpdateApprovalsRequest(
1149 issue_refs=issue_refs, field_ref=field_ref,
1150 comment_content='new bulk comment',
1151 send_email=True)
1152 mc = monorailcontext.MonorailContext(
1153 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
1154 self.CallWrapped(
1155 self.issues_svcr.BulkUpdateApprovals, mc, request)
1156
1157 approval_delta = tracker_pb2.ApprovalDelta()
1158 mockBulkUpdateIssueApprovals.assert_called_once_with(
1159 [1001], 3, self.project, approval_delta,
1160 'new bulk comment', send_email=True)
1161
1162
1163 @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
1164 @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
1165 def testUpdateApproval(self, _mockPrepareAndSend, mockUpdateIssueApproval):
1166 """We can update an approval."""
1167
1168 av_3 = tracker_pb2.ApprovalValue(
1169 approval_id=3,
1170 status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW,
1171 approver_ids=[333]
1172 )
1173 self.issue_1.approval_values = [av_3]
1174
1175 config = self.services.config.GetProjectConfig(
1176 self.cnxn, 789)
1177 config.field_defs = [self.fd_1, self.fd_3]
1178
1179 self.services.config.StoreConfig(self.cnxn, config)
1180
1181 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1182 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1183 approval_delta = issue_objects_pb2.ApprovalDelta(
1184 status=issue_objects_pb2.REVIEW_REQUESTED,
1185 approver_refs_add=[
1186 common_pb2.UserRef(user_id=222, display_name='approver2@example.com')
1187 ],
1188 field_vals_add=[
1189 issue_objects_pb2.FieldValue(
1190 field_ref=common_pb2.FieldRef(field_name='FirstField'),
1191 value='string')
1192 ]
1193 )
1194
1195 request = issues_pb2.UpdateApprovalRequest(
1196 issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta,
1197 comment_content='Well, actually'
1198 )
1199 request.issue_ref.project_name = 'proj'
1200 request.issue_ref.local_id = 1
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001201 request.uploads.extend(
1202 [
1203 issue_objects_pb2.AttachmentUpload(
1204 filename='a.txt', content=b'aaaaa')
1205 ])
Copybara854996b2021-09-07 19:36:02 +00001206 request.kept_attachments.extend([1, 2, 3])
1207 request.send_email = True
1208
1209 mc = monorailcontext.MonorailContext(
1210 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1211 auth=self.auth)
1212
1213 mockUpdateIssueApproval.return_value = [
1214 tracker_pb2.ApprovalValue(
1215 approval_id=3,
1216 status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
1217 setter_id=333,
1218 approver_ids=[333, 222]),
1219 'comment_pb',
1220 {}, # Fake issue.
1221 ]
1222
1223 actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
1224
1225 expected = issues_pb2.UpdateApprovalResponse()
1226 expected.approval.CopyFrom(
1227 issue_objects_pb2.Approval(
1228 field_ref=common_pb2.FieldRef(
1229 field_id=3,
1230 field_name='LegalApproval',
1231 type=common_pb2.APPROVAL_TYPE),
1232 approver_refs=[
1233 common_pb2.UserRef(
1234 user_id=333, display_name='approver3@example.com'),
1235 common_pb2.UserRef(
1236 user_id=222, display_name='approver2@example.com')
1237 ],
1238 status=issue_objects_pb2.REVIEW_REQUESTED,
1239 setter_ref=common_pb2.UserRef(
1240 user_id=333, display_name='approver3@example.com'),
1241 phase_ref=issue_objects_pb2.PhaseRef()
1242 )
1243 )
1244
1245 work_env.WorkEnv(mc, self.services).UpdateIssueApproval.\
1246 assert_called_once_with(
1247 self.issue_1.issue_id, 3, ANY, u'Well, actually', False,
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001248 attachments=[(u'a.txt', b'aaaaa', 'text/plain')], send_email=True,
Copybara854996b2021-09-07 19:36:02 +00001249 kept_attachments=[1, 2, 3])
1250 self.assertEqual(expected, actual)
1251
1252 @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
1253 @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
1254 def testUpdateApproval_IsDescription(
1255 self, _mockPrepareAndSend, mockUpdateIssueApproval):
1256 """We can update an approval survey."""
1257
1258 av_3 = tracker_pb2.ApprovalValue(approval_id=3)
1259 self.issue_1.approval_values = [av_3]
1260
1261 config = self.services.config.GetProjectConfig(self.cnxn, 789)
1262 config.field_defs = [self.fd_3]
1263 self.services.config.StoreConfig(self.cnxn, config)
1264
1265 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1266 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1267 approval_delta = issue_objects_pb2.ApprovalDelta()
1268
1269 request = issues_pb2.UpdateApprovalRequest(
1270 issue_ref=issue_ref, field_ref=field_ref, approval_delta=approval_delta,
1271 comment_content='Better response.', is_description=True)
1272
1273 mc = monorailcontext.MonorailContext(
1274 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1275 auth=self.auth)
1276
1277 mockUpdateIssueApproval.return_value = [
1278 tracker_pb2.ApprovalValue(approval_id=3),
1279 'comment_pb',
1280 {}, # Fake issue.
1281 ]
1282
1283 actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
1284
1285 expected = issues_pb2.UpdateApprovalResponse()
1286 expected.approval.CopyFrom(
1287 issue_objects_pb2.Approval(
1288 field_ref=common_pb2.FieldRef(
1289 field_id=3,
1290 field_name='LegalApproval',
1291 type=common_pb2.APPROVAL_TYPE),
1292 phase_ref=issue_objects_pb2.PhaseRef()
1293 )
1294 )
1295
1296 work_env.WorkEnv(mc, self.services
1297 ).UpdateIssueApproval.assert_called_once_with(
1298 self.issue_1.issue_id, 3,
1299 tracker_pb2.ApprovalDelta(),
1300 u'Better response.', True, attachments=[], send_email=False,
1301 kept_attachments=[])
1302 self.assertEqual(expected, actual)
1303
1304 @patch('businesslogic.work_env.WorkEnv.UpdateIssueApproval')
1305 @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
1306 def testUpdateApproval_EmptyDelta(
1307 self, _mockPrepareAndSend, mockUpdateIssueApproval):
1308 self.issue_1.approval_values = [tracker_pb2.ApprovalValue(approval_id=3)]
1309
1310 config = self.services.config.GetProjectConfig(self.cnxn, 789)
1311 config.field_defs = [self.fd_3]
1312 self.services.config.StoreConfig(self.cnxn, config)
1313
1314 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1315 field_ref = common_pb2.FieldRef(field_name='LegalApproval')
1316
1317 request = issues_pb2.UpdateApprovalRequest(
1318 issue_ref=issue_ref, field_ref=field_ref,
1319 comment_content='Better response.', is_description=True)
1320
1321 mc = monorailcontext.MonorailContext(
1322 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1323 auth=self.auth)
1324
1325 mockUpdateIssueApproval.return_value = [
1326 tracker_pb2.ApprovalValue(approval_id=3),
1327 'comment_pb',
1328 {}, # Fake issue.
1329 ]
1330
1331 actual = self.CallWrapped(self.issues_svcr.UpdateApproval, mc, request)
1332
1333 approval_value = issue_objects_pb2.Approval(
1334 field_ref=common_pb2.FieldRef(
1335 field_id=3,
1336 field_name='LegalApproval',
1337 type=common_pb2.APPROVAL_TYPE),
1338 phase_ref=issue_objects_pb2.PhaseRef()
1339 )
1340 expected = issues_pb2.UpdateApprovalResponse(approval=approval_value)
1341 self.assertEqual(expected, actual)
1342
1343 mockUpdateIssueApproval.assert_called_once_with(
1344 self.issue_1.issue_id, 3,
1345 tracker_pb2.ApprovalDelta(),
1346 u'Better response.', True, attachments=[], send_email=False,
1347 kept_attachments=[])
1348
1349 @patch('businesslogic.work_env.WorkEnv.ConvertIssueApprovalsTemplate')
1350 def testConvertIssueApprovalsTemplate(self, mockWorkEnvConvertApprovals):
1351 mc = monorailcontext.MonorailContext(
1352 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1353 auth=self.auth)
1354 request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
1355 issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1),
1356 template_name='template_name', comment_content='CHICKEN',
1357 send_email=True)
1358 response = self.CallWrapped(
1359 self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
1360 config = self.services.config.GetProjectConfig(self.cnxn, 789)
1361 mockWorkEnvConvertApprovals.assert_called_once_with(
1362 config, self.issue_1, 'template_name', request.comment_content,
1363 send_email=request.send_email)
1364 self.assertEqual(
1365 response.issue,
1366 issue_objects_pb2.Issue(
1367 project_name='proj',
1368 local_id=1,
1369 summary='sum',
1370 owner_ref=common_pb2.UserRef(
1371 user_id=111, display_name='owner@example.com'),
1372 status_ref=common_pb2.StatusRef(status='New', means_open=True),
1373 blocked_on_issue_refs=[
1374 common_pb2.IssueRef(project_name='proj', local_id=2)],
1375 reporter_ref=common_pb2.UserRef(
1376 user_id=111, display_name='owner@example.com'),
1377 opened_timestamp=self.NOW,
1378 component_modified_timestamp=self.NOW,
1379 status_modified_timestamp=self.NOW,
1380 owner_modified_timestamp=self.NOW,
1381 ))
1382
1383 def testConvertIssueApprovalsTemplate_MissingRequiredFields(self):
1384 mc = monorailcontext.MonorailContext(
1385 self.services, cnxn=self.cnxn, requester='approver3@example.com',
1386 auth=self.auth)
1387 request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
1388 issue_ref=common_pb2.IssueRef(project_name='proj', local_id=1))
1389 with self.assertRaises(exceptions.InputException):
1390 self.CallWrapped(
1391 self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
1392
1393 request = issues_pb2.ConvertIssueApprovalsTemplateRequest(
1394 template_name='name')
1395 with self.assertRaises(exceptions.InputException):
1396 self.CallWrapped(
1397 self.issues_svcr.ConvertIssueApprovalsTemplate, mc, request)
1398
1399 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1400 def testSnapshotCounts_RequiredFields(self, mockSnapshotCountsQuery):
1401 """Test that timestamp is required at all times.
1402 And that label_prefix is required when group_by is 'label'.
1403 """
1404 mc = monorailcontext.MonorailContext(
1405 self.services, cnxn=self.cnxn, requester='owner@example.com')
1406
1407 # Test timestamp is required.
1408 request = issues_pb2.IssueSnapshotRequest(project_name='proj')
1409 with self.assertRaises(exceptions.InputException):
1410 self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1411
1412 # Test project_name or hotlist_id is required.
1413 request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109)
1414 with self.assertRaises(exceptions.InputException):
1415 self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1416
1417 # Test label_prefix is required when group_by is 'label'.
1418 request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
1419 project_name='proj', group_by='label')
1420 with self.assertRaises(exceptions.InputException):
1421 self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1422
1423 mockSnapshotCountsQuery.assert_not_called()
1424
1425 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1426 def testSnapshotCounts_Basic(self, mockSnapshotCountsQuery):
1427 """Tests the happy path case."""
1428 request = issues_pb2.IssueSnapshotRequest(
1429 timestamp=1531334109, project_name='proj')
1430 mc = monorailcontext.MonorailContext(
1431 self.services, cnxn=self.cnxn, requester='owner@example.com')
1432 mockSnapshotCountsQuery.return_value = ({'total': 123}, [], True)
1433
1434 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1435
1436 self.assertEqual(123, response.snapshot_count[0].count)
1437 self.assertEqual(0, len(response.unsupported_field))
1438 self.assertTrue(response.search_limit_reached)
1439 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1440 '', query=None, canned_query=None, label_prefix='', hotlist=None)
1441
1442 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1443 @patch('search.searchpipeline.ReplaceKeywordsWithUserIDs')
1444 @patch('features.savedqueries_helpers.SavedQueryIDToCond')
1445 def testSnapshotCounts_ReplacesKeywords(self, mockSavedQueryIDToCond,
1446 mockReplaceKeywordsWithUserIDs,
1447 mockSnapshotCountsQuery):
1448 """Tests that canned query is unpacked and keywords in query and canned
1449 query are replaced with user IDs."""
1450 request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
1451 project_name='proj', query='owner:me', canned_query=3)
1452 mc = monorailcontext.MonorailContext(
1453 self.services, cnxn=self.cnxn, requester='owner@example.com')
1454 mockSavedQueryIDToCond.return_value = 'cc:me'
1455 mockReplaceKeywordsWithUserIDs.side_effect = [
1456 ('cc:2345', []), ('owner:1234', [])]
1457 mockSnapshotCountsQuery.return_value = ({'total': 789}, [], False)
1458
1459 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1460
1461 self.assertEqual(789, response.snapshot_count[0].count)
1462 self.assertEqual(0, len(response.unsupported_field))
1463 self.assertFalse(response.search_limit_reached)
1464 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1465 '', query='owner:1234', canned_query='cc:2345', label_prefix='',
1466 hotlist=None)
1467
1468 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1469 def testSnapshotCounts_GroupByLabel(self, mockSnapshotCountsQuery):
1470 """Tests grouping by label with label_prefix and a query.
1471 But no canned_query.
1472 """
1473 request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
1474 project_name='proj', group_by='label', label_prefix='Type',
1475 query='rutabaga:rutabaga')
1476 mc = monorailcontext.MonorailContext(
1477 self.services, cnxn=self.cnxn, requester='owner@example.com')
1478 mockSnapshotCountsQuery.return_value = (
1479 {'label1': 123, 'label2': 987},
1480 ['rutabaga'],
1481 True)
1482
1483 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1484
1485 self.assertEqual(2, len(response.snapshot_count))
1486 self.assertEqual('label1', response.snapshot_count[0].dimension)
1487 self.assertEqual(123, response.snapshot_count[0].count)
1488 self.assertEqual('label2', response.snapshot_count[1].dimension)
1489 self.assertEqual(987, response.snapshot_count[1].count)
1490 self.assertEqual(1, len(response.unsupported_field))
1491 self.assertEqual('rutabaga', response.unsupported_field[0])
1492 self.assertTrue(response.search_limit_reached)
1493 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1494 'label', label_prefix='Type', query='rutabaga:rutabaga',
1495 canned_query=None, hotlist=None)
1496
1497 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1498 def testSnapshotCounts_GroupByComponent(self, mockSnapshotCountsQuery):
1499 """Tests grouping by component with a query and a canned_query."""
1500 request = issues_pb2.IssueSnapshotRequest(timestamp=1531334109,
1501 project_name='proj', group_by='component',
1502 query='rutabaga:rutabaga', canned_query=2)
1503 mc = monorailcontext.MonorailContext(
1504 self.services, cnxn=self.cnxn, requester='owner@example.com')
1505 mockSnapshotCountsQuery.return_value = (
1506 {'component1': 123, 'component2': 987},
1507 ['rutabaga'],
1508 True)
1509
1510 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1511
1512 self.assertEqual(2, len(response.snapshot_count))
1513 self.assertEqual('component1', response.snapshot_count[0].dimension)
1514 self.assertEqual(123, response.snapshot_count[0].count)
1515 self.assertEqual('component2', response.snapshot_count[1].dimension)
1516 self.assertEqual(987, response.snapshot_count[1].count)
1517 self.assertEqual(1, len(response.unsupported_field))
1518 self.assertEqual('rutabaga', response.unsupported_field[0])
1519 self.assertTrue(response.search_limit_reached)
1520 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1521 'component', label_prefix='', query='rutabaga:rutabaga',
1522 canned_query='is:open', hotlist=None)
1523
1524 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1525 def testSnapshotCounts_GroupByOpen(self, mockSnapshotCountsQuery):
1526 """Tests grouping by open with a query."""
1527 request = issues_pb2.IssueSnapshotRequest(
1528 timestamp=1531334109, project_name='proj', group_by='open')
1529 mc = monorailcontext.MonorailContext(
1530 self.services, cnxn=self.cnxn, requester='owner@example.com')
1531 mockSnapshotCountsQuery.return_value = (
1532 {'Opened': 100, 'Closed': 23}, [], True)
1533
1534 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1535
1536 self.assertEqual(2, len(response.snapshot_count))
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001537 self.assertEqual('Closed', response.snapshot_count[0].dimension)
1538 self.assertEqual(23, response.snapshot_count[0].count)
1539 self.assertEqual('Opened', response.snapshot_count[1].dimension)
1540 self.assertEqual(100, response.snapshot_count[1].count)
Copybara854996b2021-09-07 19:36:02 +00001541 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1542 'open', label_prefix='', query=None, canned_query=None, hotlist=None)
1543
1544 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1545 def testSnapshotCounts_GroupByStatus(self, mockSnapshotCountsQuery):
1546 """Tests grouping by status with a query."""
1547 request = issues_pb2.IssueSnapshotRequest(
1548 timestamp=1531334109, project_name='proj', group_by='status')
1549 mc = monorailcontext.MonorailContext(
1550 self.services, cnxn=self.cnxn, requester='owner@example.com')
1551 mockSnapshotCountsQuery.return_value = (
1552 {'Accepted': 100, 'Fixed': 23}, [], True)
1553
1554 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1555
1556 self.assertEqual(2, len(response.snapshot_count))
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001557 self.assertEqual('Accepted', response.snapshot_count[0].dimension)
1558 self.assertEqual(100, response.snapshot_count[0].count)
1559 self.assertEqual('Fixed', response.snapshot_count[1].dimension)
1560 self.assertEqual(23, response.snapshot_count[1].count)
Copybara854996b2021-09-07 19:36:02 +00001561 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1562 'status', label_prefix='', query=None, canned_query=None, hotlist=None)
1563
1564 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1565 def testSnapshotCounts_GroupByOwner(self, mockSnapshotCountsQuery):
1566 """Tests grouping by status with a query."""
1567 request = issues_pb2.IssueSnapshotRequest(
1568 timestamp=1531334109, project_name='proj', group_by='owner')
1569 mc = monorailcontext.MonorailContext(
1570 self.services, cnxn=self.cnxn, requester='owner@example.com')
1571 mockSnapshotCountsQuery.return_value = ({111: 100}, [], True)
1572
1573 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1574
1575 self.assertEqual(1, len(response.snapshot_count))
1576 self.assertEqual('owner@example.com', response.snapshot_count[0].dimension)
1577 self.assertEqual(100, response.snapshot_count[0].count)
1578 mockSnapshotCountsQuery.assert_called_once_with(self.project, 1531334109,
1579 'owner', label_prefix='', query=None, canned_query=None, hotlist=None)
1580
1581 @patch('businesslogic.work_env.WorkEnv.GetHotlist')
1582 @patch('businesslogic.work_env.WorkEnv.SnapshotCountsQuery')
1583 def testSnapshotCounts_WithHotlist(self, mockSnapshotCountsQuery,
1584 mockGetHotlist):
1585 """Tests grouping by status with a hotlist."""
1586 request = issues_pb2.IssueSnapshotRequest(
1587 timestamp=1531334109, hotlist_id=19191)
1588 mc = monorailcontext.MonorailContext(
1589 self.services, cnxn=self.cnxn, requester='owner@example.com')
1590 mockSnapshotCountsQuery.return_value = ({'total': 123}, [], True)
1591 fake_hotlist = fake.Hotlist('hotlist_rutabaga', 19191)
1592 mockGetHotlist.return_value = fake_hotlist
1593
1594 response = self.CallWrapped(self.issues_svcr.IssueSnapshot, mc, request)
1595
1596 self.assertEqual(1, len(response.snapshot_count))
1597 self.assertEqual('total', response.snapshot_count[0].dimension)
1598 self.assertEqual(123, response.snapshot_count[0].count)
1599 mockSnapshotCountsQuery.assert_called_once_with(None, 1531334109,
1600 '', label_prefix='', query=None, canned_query=None,
1601 hotlist=fake_hotlist)
1602
1603 def AddField(self, name, field_type_str):
1604 kwargs = {
1605 'cnxn': self.cnxn,
1606 'project_id': self.project.project_id,
1607 'field_name': name,
1608 'field_type_str': field_type_str}
1609 kwargs.update(
1610 {
1611 arg: None for arg in (
1612 'applic_type', 'applic_pred', 'is_required', 'is_niche',
1613 'is_multivalued', 'min_value', 'max_value', 'regex',
1614 'needs_member', 'needs_perm', 'grants_perm', 'notify_on',
1615 'date_action_str', 'docstring')
1616 })
1617 kwargs.update({arg: [] for arg in ('admin_ids', 'editor_ids')})
1618
1619 return self.services.config.CreateFieldDef(**kwargs)
1620
1621 @patch('testing.fake.FeaturesService.GetFilterRules')
1622 def testPresubmitIssue_NoDerivedFields(self, mockGetFilterRules):
1623 """When no rules match, we respond with just owner availability."""
1624 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1625 issue_delta = issue_objects_pb2.IssueDelta(
1626 owner_ref=common_pb2.UserRef(user_id=111),
1627 label_refs_add=[common_pb2.LabelRef(label='foo')])
1628
1629 mockGetFilterRules.return_value = [
1630 filterrules_helpers.MakeRule('label:bar', add_labels=['baz'])]
1631
1632 request = issues_pb2.PresubmitIssueRequest(
1633 issue_ref=issue_ref, issue_delta=issue_delta)
1634 mc = monorailcontext.MonorailContext(
1635 self.services, cnxn=self.cnxn, requester='owner@example.com')
1636 mc.LookupLoggedInUserPerms(self.project)
1637 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1638
1639 self.assertEqual(
1640 issues_pb2.PresubmitIssueResponse(
1641 owner_availability="User never visited",
1642 owner_availability_state="never"),
1643 response)
1644
1645 @patch('testing.fake.FeaturesService.GetFilterRules')
1646 def testPresubmitIssue_IncompleteOwnerEmail(self, mockGetFilterRules):
1647 """User is in the process of typing in the proposed owner."""
1648 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1649 issue_delta = issue_objects_pb2.IssueDelta(
1650 owner_ref=common_pb2.UserRef(display_name='owner@examp'))
1651
1652 mockGetFilterRules.return_value = []
1653 request = issues_pb2.PresubmitIssueRequest(
1654 issue_ref=issue_ref, issue_delta=issue_delta)
1655 mc = monorailcontext.MonorailContext(
1656 self.services, cnxn=self.cnxn, requester='owner@example.com')
1657 mc.LookupLoggedInUserPerms(self.project)
1658 actual = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1659
1660 self.assertEqual(
1661 issues_pb2.PresubmitIssueResponse(),
1662 actual)
1663
1664 @patch('testing.fake.FeaturesService.GetFilterRules')
1665 def testPresubmitIssue_NewIssue(self, mockGetFilterRules):
1666 """Proposed owner has a vacation message set."""
1667 self.user_1.vacation_message = 'In Galapagos Islands'
1668 issue_ref = common_pb2.IssueRef(project_name='proj')
1669 issue_delta = issue_objects_pb2.IssueDelta(
1670 owner_ref=common_pb2.UserRef(user_id=111),
1671 label_refs_add=[common_pb2.LabelRef(label='foo')])
1672
1673 mockGetFilterRules.return_value = []
1674
1675 request = issues_pb2.PresubmitIssueRequest(
1676 issue_ref=issue_ref, issue_delta=issue_delta)
1677 mc = monorailcontext.MonorailContext(
1678 self.services, cnxn=self.cnxn, requester='owner@example.com')
1679 mc.LookupLoggedInUserPerms(self.project)
1680 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1681
1682 self.assertEqual(
1683 issues_pb2.PresubmitIssueResponse(
1684 owner_availability='In Galapagos Islands',
1685 owner_availability_state='none'),
1686 response)
1687
1688 @patch('testing.fake.FeaturesService.GetFilterRules')
1689 def testPresubmitIssue_OwnerVacation(self, mockGetFilterRules):
1690 """Proposed owner has a vacation message set."""
1691 self.user_1.vacation_message = 'In Galapagos Islands'
1692 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1693 issue_delta = issue_objects_pb2.IssueDelta(
1694 owner_ref=common_pb2.UserRef(user_id=111),
1695 label_refs_add=[common_pb2.LabelRef(label='foo')])
1696
1697 mockGetFilterRules.return_value = []
1698
1699 request = issues_pb2.PresubmitIssueRequest(
1700 issue_ref=issue_ref, issue_delta=issue_delta)
1701 mc = monorailcontext.MonorailContext(
1702 self.services, cnxn=self.cnxn, requester='owner@example.com')
1703 mc.LookupLoggedInUserPerms(self.project)
1704 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1705
1706 self.assertEqual(
1707 issues_pb2.PresubmitIssueResponse(
1708 owner_availability='In Galapagos Islands',
1709 owner_availability_state='none'),
1710 response)
1711
1712 @patch('testing.fake.FeaturesService.GetFilterRules')
1713 def testPresubmitIssue_OwnerIsAvailable(self, mockGetFilterRules):
1714 """Proposed owner not on vacation and has visited recently."""
1715 self.user_1.last_visit_timestamp = int(time.time())
1716 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1717 issue_delta = issue_objects_pb2.IssueDelta(
1718 owner_ref=common_pb2.UserRef(user_id=111),
1719 label_refs_add=[common_pb2.LabelRef(label='foo')])
1720
1721 mockGetFilterRules.return_value = []
1722
1723 request = issues_pb2.PresubmitIssueRequest(
1724 issue_ref=issue_ref, issue_delta=issue_delta)
1725 mc = monorailcontext.MonorailContext(
1726 self.services, cnxn=self.cnxn, requester='owner@example.com')
1727 mc.LookupLoggedInUserPerms(self.project)
1728 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1729
1730 self.assertEqual(
1731 issues_pb2.PresubmitIssueResponse(
1732 owner_availability='',
1733 owner_availability_state=''),
1734 response)
1735
1736 @patch('testing.fake.FeaturesService.GetFilterRules')
1737 def testPresubmitIssue_DerivedLabels(self, mockGetFilterRules):
1738 """Test that we can match label rules and return derived labels."""
1739 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1740 issue_delta = issue_objects_pb2.IssueDelta(
1741 owner_ref=common_pb2.UserRef(user_id=111),
1742 label_refs_add=[common_pb2.LabelRef(label='foo')])
1743
1744 mockGetFilterRules.return_value = [
1745 filterrules_helpers.MakeRule('label:foo', add_labels=['bar', 'baz'])]
1746
1747 request = issues_pb2.PresubmitIssueRequest(
1748 issue_ref=issue_ref, issue_delta=issue_delta)
1749 mc = monorailcontext.MonorailContext(
1750 self.services, cnxn=self.cnxn, requester='owner@example.com')
1751 mc.LookupLoggedInUserPerms(self.project)
1752 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1753
1754 self.assertEqual(
1755 [common_pb2.ValueAndWhy(
1756 value='bar',
1757 why='Added by rule: IF label:foo THEN ADD LABEL'),
1758 common_pb2.ValueAndWhy(
1759 value='baz',
1760 why='Added by rule: IF label:foo THEN ADD LABEL')],
1761 [vnw for vnw in response.derived_labels])
1762
1763 @patch('testing.fake.FeaturesService.GetFilterRules')
1764 def testPresubmitIssue_DerivedOwner(self, mockGetFilterRules):
1765 """Test that we can match component rules and return derived owners."""
1766 self.services.config.CreateComponentDef(
1767 self.cnxn, self.project.project_id, 'Foo', 'Foo Docstring', False,
1768 [], [], 0, 111, [])
1769 self.issue_1.owner_id = 0
1770 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1771 issue_delta = issue_objects_pb2.IssueDelta(
1772 comp_refs_add=[common_pb2.ComponentRef(path='Foo')])
1773
1774 mockGetFilterRules.return_value = [
1775 filterrules_helpers.MakeRule('component:Foo', default_owner_id=222)]
1776
1777 request = issues_pb2.PresubmitIssueRequest(
1778 issue_ref=issue_ref, issue_delta=issue_delta)
1779 mc = monorailcontext.MonorailContext(
1780 self.services, cnxn=self.cnxn, requester='owner@example.com')
1781 mc.LookupLoggedInUserPerms(self.project)
1782 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1783
1784 self.assertEqual(
1785 [common_pb2.ValueAndWhy(
1786 value='approver2@example.com',
1787 why='Added by rule: IF component:Foo THEN SET DEFAULT OWNER')],
1788 [vnw for vnw in response.derived_owners])
1789
1790 @patch('testing.fake.FeaturesService.GetFilterRules')
1791 def testPresubmitIssue_DerivedCCs(self, mockGetFilterRules):
1792 """Test that we can match field rules and return derived cc emails."""
1793 field_id = self.AddField('Foo', 'ENUM_TYPE')
1794 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1795 issue_delta = issue_objects_pb2.IssueDelta(
1796 owner_ref=common_pb2.UserRef(user_id=111),
1797 field_vals_add=[issue_objects_pb2.FieldValue(
1798 value='Bar', field_ref=common_pb2.FieldRef(field_id=field_id))])
1799
1800 mockGetFilterRules.return_value = [
1801 filterrules_helpers.MakeRule('Foo=Bar', add_cc_ids=[222, 333])]
1802
1803 request = issues_pb2.PresubmitIssueRequest(
1804 issue_ref=issue_ref, issue_delta=issue_delta)
1805 mc = monorailcontext.MonorailContext(
1806 self.services, cnxn=self.cnxn, requester='owner@example.com')
1807 mc.LookupLoggedInUserPerms(self.project)
1808 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1809
1810 self.assertEqual(
1811 [common_pb2.ValueAndWhy(
1812 value='approver2@example.com',
1813 why='Added by rule: IF Foo=Bar THEN ADD CC'),
1814 common_pb2.ValueAndWhy(
1815 value='approver3@example.com',
1816 why='Added by rule: IF Foo=Bar THEN ADD CC')],
1817 [vnw for vnw in response.derived_ccs])
1818
1819 @patch('testing.fake.FeaturesService.GetFilterRules')
1820 def testPresubmitIssue_DerivedCCsNonMember(self, mockGetFilterRules):
1821 """Test that we can return obscured cc emails to non-members."""
1822 field_id = self.AddField('Foo', 'ENUM_TYPE')
1823 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1824 issue_delta = issue_objects_pb2.IssueDelta(
1825 owner_ref=common_pb2.UserRef(user_id=111),
1826 field_vals_add=[issue_objects_pb2.FieldValue(
1827 value='Bar', field_ref=common_pb2.FieldRef(field_id=field_id))])
1828
1829 mockGetFilterRules.return_value = [
1830 filterrules_helpers.MakeRule('Foo=Bar', add_cc_ids=[222, 333])]
1831
1832 request = issues_pb2.PresubmitIssueRequest(
1833 issue_ref=issue_ref, issue_delta=issue_delta)
1834 mc = monorailcontext.MonorailContext(
1835 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
1836 mc.LookupLoggedInUserPerms(self.project)
1837 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1838
1839 self.assertEqual(
1840 [
1841 common_pb2.ValueAndWhy(
1842 value='appro...@example.com',
1843 why='Added by rule: IF Foo=Bar THEN ADD CC'),
1844 common_pb2.ValueAndWhy(
1845 value='appro...@example.com',
1846 why='Added by rule: IF Foo=Bar THEN ADD CC')
1847 ], [vnw for vnw in response.derived_ccs])
1848
1849 @patch('testing.fake.FeaturesService.GetFilterRules')
1850 def testPresubmitIssue_Warnings(self, mockGetFilterRules):
1851 """Test that we can match owner rules and return warnings."""
1852 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1853 issue_delta = issue_objects_pb2.IssueDelta(
1854 owner_ref=common_pb2.UserRef(user_id=111))
1855
1856 mockGetFilterRules.return_value = [
1857 filterrules_helpers.MakeRule(
1858 'owner:owner@example.com', warning='Owner is too busy')]
1859
1860 request = issues_pb2.PresubmitIssueRequest(
1861 issue_ref=issue_ref, issue_delta=issue_delta)
1862 mc = monorailcontext.MonorailContext(
1863 self.services, cnxn=self.cnxn, requester='owner@example.com')
1864 mc.LookupLoggedInUserPerms(self.project)
1865 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1866
1867 self.assertEqual(
1868 [common_pb2.ValueAndWhy(
1869 value='Owner is too busy',
1870 why='Added by rule: IF owner:owner@example.com THEN ADD WARNING')],
1871 [vnw for vnw in response.warnings])
1872
1873 @patch('testing.fake.FeaturesService.GetFilterRules')
1874 def testPresubmitIssue_Errors(self, mockGetFilterRules):
1875 """Test that we can match owner rules and return errors."""
1876 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1877 issue_delta = issue_objects_pb2.IssueDelta(
1878 owner_ref=common_pb2.UserRef(user_id=222),
1879 cc_refs_add=[
1880 common_pb2.UserRef(user_id=111),
1881 common_pb2.UserRef(user_id=333)])
1882
1883 mockGetFilterRules.return_value = [
1884 filterrules_helpers.MakeRule(
1885 'cc:owner@example.com', error='Owner is not to be disturbed')]
1886
1887 request = issues_pb2.PresubmitIssueRequest(
1888 issue_ref=issue_ref, issue_delta=issue_delta)
1889 mc = monorailcontext.MonorailContext(
1890 self.services, cnxn=self.cnxn, requester='owner@example.com')
1891 mc.LookupLoggedInUserPerms(self.project)
1892 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1893
1894 self.assertEqual(
1895 [common_pb2.ValueAndWhy(
1896 value='Owner is not to be disturbed',
1897 why='Added by rule: IF cc:owner@example.com THEN ADD ERROR')],
1898 [vnw for vnw in response.errors])
1899
1900 @patch('testing.fake.FeaturesService.GetFilterRules')
1901 def testPresubmitIssue_Errors_ExistingOwner(self, mockGetFilterRules):
1902 """Test that we apply the rules to the issue + delta, not only delta."""
1903 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
1904 issue_delta = issue_objects_pb2.IssueDelta()
1905
1906 mockGetFilterRules.return_value = [
1907 filterrules_helpers.MakeRule(
1908 'owner:owner@example.com', error='Owner is not to be disturbed')]
1909
1910 request = issues_pb2.PresubmitIssueRequest(
1911 issue_ref=issue_ref, issue_delta=issue_delta)
1912 mc = monorailcontext.MonorailContext(
1913 self.services, cnxn=self.cnxn, requester='owner@example.com')
1914 mc.LookupLoggedInUserPerms(self.project)
1915 response = self.CallWrapped(self.issues_svcr.PresubmitIssue, mc, request)
1916
1917 self.assertEqual(
1918 [common_pb2.ValueAndWhy(
1919 value='Owner is not to be disturbed',
1920 why='Added by rule: IF owner:owner@example.com THEN ADD ERROR')],
1921 [vnw for vnw in response.errors])
1922
1923 def testRerankBlockedOnIssues_SplitBelow(self):
1924 issues = []
1925 for idx in range(3, 6):
1926 issues.append(fake.MakeTestIssue(
1927 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
1928 self.services.issue.TestAddIssue(issues[-1])
1929 self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
1930 self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
1931
1932 request = issues_pb2.RerankBlockedOnIssuesRequest(
1933 issue_ref=common_pb2.IssueRef(
1934 project_name='proj',
1935 local_id=1),
1936 moved_ref=common_pb2.IssueRef(
1937 project_name='proj',
1938 local_id=2),
1939 target_ref=common_pb2.IssueRef(
1940 project_name='proj',
1941 local_id=4),
1942 split_above=False)
1943 mc = monorailcontext.MonorailContext(
1944 self.services, cnxn=self.cnxn, requester='owner@example.com')
1945 response = self.CallWrapped(
1946 self.issues_svcr.RerankBlockedOnIssues, mc, request)
1947
1948 self.assertEqual(
1949 [3, 4, 2, 5],
1950 [blocked_on_ref.local_id
1951 for blocked_on_ref in response.blocked_on_issue_refs])
1952
1953 def testRerankBlockedOnIssues_SplitAbove(self):
1954 self.project.committer_ids.append(222)
1955 issues = []
1956 for idx in range(3, 6):
1957 issues.append(fake.MakeTestIssue(
1958 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
1959 self.services.issue.TestAddIssue(issues[-1])
1960 self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
1961 self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
1962
1963 request = issues_pb2.RerankBlockedOnIssuesRequest(
1964 issue_ref=common_pb2.IssueRef(
1965 project_name='proj',
1966 local_id=1),
1967 moved_ref=common_pb2.IssueRef(
1968 project_name='proj',
1969 local_id=2),
1970 target_ref=common_pb2.IssueRef(
1971 project_name='proj',
1972 local_id=4),
1973 split_above=True)
1974 mc = monorailcontext.MonorailContext(
1975 self.services, cnxn=self.cnxn, requester='approver2@example.com')
1976 response = self.CallWrapped(
1977 self.issues_svcr.RerankBlockedOnIssues, mc, request)
1978
1979 self.assertEqual(
1980 [3, 2, 4, 5],
1981 [blocked_on_ref.local_id
1982 for blocked_on_ref in response.blocked_on_issue_refs])
1983
1984 def testRerankBlockedOnIssues_CantEditIssue(self):
1985 self.project.committer_ids.append(222)
1986 issues = []
1987 for idx in range(3, 6):
1988 issues.append(fake.MakeTestIssue(
1989 789, idx, 'sum', 'New', 111, project_name='proj', issue_id=1000+idx))
1990 self.services.issue.TestAddIssue(issues[-1])
1991 self.issue_1.blocked_on_iids.append(issues[-1].issue_id)
1992 self.issue_1.blocked_on_ranks.append(self.issue_1.blocked_on_ranks[-1]-1)
1993
1994 self.issue_1.labels = ['Restrict-EditIssue-Foo']
1995
1996 request = issues_pb2.RerankBlockedOnIssuesRequest(
1997 issue_ref=common_pb2.IssueRef(
1998 project_name='proj',
1999 local_id=1),
2000 moved_ref=common_pb2.IssueRef(
2001 project_name='proj',
2002 local_id=2),
2003 target_ref=common_pb2.IssueRef(
2004 project_name='proj',
2005 local_id=4),
2006 split_above=True)
2007 mc = monorailcontext.MonorailContext(
2008 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2009 with self.assertRaises(permissions.PermissionException):
2010 self.CallWrapped(self.issues_svcr.RerankBlockedOnIssues, mc, request)
2011
2012 def testRerankBlockedOnIssues_ComplexPermissions(self):
2013 """We can rerank blocked on issues, regardless of perms on other issues.
2014
2015 If Issue 1 is blocked on Issue 3 and Issue 4, we should be able to reorder
2016 them as long as we have permission to edit Issue 1, even if we don't have
2017 permission to view or edit Issues 3 or 4.
2018 """
2019 # Issue 3 is in proj2, which we don't have access to.
2020 project_2 = self.services.project.TestAddProject(
2021 'proj2', project_id=790, owner_ids=[222], contrib_ids=[333])
2022 project_2.access = project_pb2.ProjectAccess.MEMBERS_ONLY
2023 issue_3 = fake.MakeTestIssue(
2024 790, 3, 'sum', 'New', 111, project_name='proj2', issue_id=1003)
2025
2026 # Issue 4 requires a permission we don't have in order to edit it.
2027 issue_4 = fake.MakeTestIssue(
2028 789, 4, 'sum', 'New', 111, project_name='proj', issue_id=1004)
2029 issue_4.labels = ['Restrict-EditIssue-Foo']
2030
2031 self.services.issue.TestAddIssue(issue_3)
2032 self.services.issue.TestAddIssue(issue_4)
2033
2034 self.issue_1.blocked_on_iids = [1003, 1004]
2035 self.issue_1.blocked_on_ranks = [2, 1]
2036
2037 request = issues_pb2.RerankBlockedOnIssuesRequest(
2038 issue_ref=common_pb2.IssueRef(
2039 project_name='proj',
2040 local_id=1),
2041 moved_ref=common_pb2.IssueRef(
2042 project_name='proj2',
2043 local_id=3),
2044 target_ref=common_pb2.IssueRef(
2045 project_name='proj',
2046 local_id=4),
2047 split_above=False)
2048 mc = monorailcontext.MonorailContext(
2049 self.services, cnxn=self.cnxn, requester='owner@example.com')
2050 response = self.CallWrapped(
2051 self.issues_svcr.RerankBlockedOnIssues, mc, request)
2052
2053 self.assertEqual(
2054 [4, 3],
2055 [blocked_on_ref.local_id
2056 for blocked_on_ref in response.blocked_on_issue_refs])
2057
2058 def testDeleteIssue_Delete(self):
2059 """We can delete an issue."""
2060 issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
2061 self.assertFalse(issue.deleted)
2062
2063 request = issues_pb2.DeleteIssueRequest(
2064 issue_ref=common_pb2.IssueRef(
2065 project_name='proj',
2066 local_id=1),
2067 delete=True)
2068 mc = monorailcontext.MonorailContext(
2069 self.services, cnxn=self.cnxn, requester='owner@example.com')
2070 self.CallWrapped(self.issues_svcr.DeleteIssue, mc, request)
2071
2072 issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
2073 self.assertTrue(issue.deleted)
2074
2075 def testDeleteIssue_Undelete(self):
2076 """We can undelete an issue."""
2077 self.services.issue.SoftDeleteIssue(
2078 self.cnxn, self.project.project_id, 1, True, self.services.user)
2079 issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
2080 self.assertTrue(issue.deleted)
2081
2082 request = issues_pb2.DeleteIssueRequest(
2083 issue_ref=common_pb2.IssueRef(
2084 project_name='proj',
2085 local_id=1),
2086 delete=False)
2087 mc = monorailcontext.MonorailContext(
2088 self.services, cnxn=self.cnxn, requester='owner@example.com')
2089 self.CallWrapped(self.issues_svcr.DeleteIssue, mc, request)
2090
2091 issue = self.services.issue.GetIssue(self.cnxn, self.issue_1.issue_id)
2092 self.assertFalse(issue.deleted)
2093
2094 def testDeleteIssueComment_Delete(self):
2095 """We can delete an issue comment."""
2096 comment = tracker_pb2.IssueComment(
2097 project_id=self.project.project_id,
2098 issue_id=self.issue_1.issue_id,
2099 user_id=111,
2100 content='Foo',
2101 timestamp=12345)
2102 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
2103
2104 request = issues_pb2.DeleteIssueCommentRequest(
2105 issue_ref=common_pb2.IssueRef(
2106 project_name='proj',
2107 local_id=1),
2108 sequence_num=1,
2109 delete=True)
2110 mc = monorailcontext.MonorailContext(
2111 self.services, cnxn=self.cnxn, requester='owner@example.com')
2112 self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
2113
2114 comment = self.services.issue.GetComment(self.cnxn, comment.id)
2115 self.assertEqual(111, comment.deleted_by)
2116
2117 def testDeleteIssueComment_Undelete(self):
2118 """We can undelete an issue comment."""
2119 comment = tracker_pb2.IssueComment(
2120 project_id=self.project.project_id,
2121 issue_id=self.issue_1.issue_id,
2122 user_id=111,
2123 content='Foo',
2124 timestamp=12345,
2125 deleted_by=111)
2126 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
2127
2128 request = issues_pb2.DeleteIssueCommentRequest(
2129 issue_ref=common_pb2.IssueRef(
2130 project_name='proj',
2131 local_id=1),
2132 sequence_num=1,
2133 delete=False)
2134 mc = monorailcontext.MonorailContext(
2135 self.services, cnxn=self.cnxn, requester='owner@example.com')
2136 self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
2137
2138 comment = self.services.issue.GetComment(self.cnxn, comment.id)
2139 self.assertIsNone(comment.deleted_by)
2140
2141 def testDeleteIssueComment_InvalidSequenceNum(self):
2142 """We can handle invalid sequence numbers."""
2143 request = issues_pb2.DeleteIssueCommentRequest(
2144 issue_ref=common_pb2.IssueRef(
2145 project_name='proj',
2146 local_id=1),
2147 sequence_num=1,
2148 delete=True)
2149 mc = monorailcontext.MonorailContext(
2150 self.services, cnxn=self.cnxn, requester='owner@example.com')
2151
2152 with self.assertRaises(exceptions.InputException):
2153 self.CallWrapped(self.issues_svcr.DeleteIssueComment, mc, request)
2154
2155 def testDeleteAttachment_Delete(self):
2156 """We can delete an issue comment attachment."""
2157 comment = tracker_pb2.IssueComment(
2158 project_id=self.project.project_id,
2159 issue_id=self.issue_1.issue_id,
2160 user_id=111,
2161 content='Foo',
2162 timestamp=12345)
2163 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
2164 attachment = tracker_pb2.Attachment()
2165 self.services.issue.TestAddAttachment(attachment, comment.id, 1)
2166
2167 request = issues_pb2.DeleteAttachmentRequest(
2168 issue_ref=common_pb2.IssueRef(
2169 project_name='proj',
2170 local_id=1),
2171 sequence_num=1,
2172 attachment_id=attachment.attachment_id,
2173 delete=True)
2174 mc = monorailcontext.MonorailContext(
2175 self.services, cnxn=self.cnxn, requester='owner@example.com')
2176 self.CallWrapped(
2177 self.issues_svcr.DeleteAttachment, mc, request)
2178
2179 self.assertTrue(attachment.deleted)
2180
2181 def testDeleteAttachment_Undelete(self):
2182 """We can undelete an issue comment attachment."""
2183 comment = tracker_pb2.IssueComment(
2184 project_id=self.project.project_id,
2185 issue_id=self.issue_1.issue_id,
2186 user_id=111,
2187 content='Foo',
2188 timestamp=12345,
2189 deleted_by=111)
2190 self.services.issue.TestAddComment(comment, self.issue_1.local_id)
2191 attachment = tracker_pb2.Attachment(deleted=True)
2192 self.services.issue.TestAddAttachment(attachment, comment.id, 1)
2193
2194 request = issues_pb2.DeleteAttachmentRequest(
2195 issue_ref=common_pb2.IssueRef(
2196 project_name='proj',
2197 local_id=1),
2198 sequence_num=1,
2199 attachment_id=attachment.attachment_id,
2200 delete=False)
2201 mc = monorailcontext.MonorailContext(
2202 self.services, cnxn=self.cnxn, requester='owner@example.com')
2203 self.CallWrapped(
2204 self.issues_svcr.DeleteAttachment, mc, request)
2205
2206 self.assertFalse(attachment.deleted)
2207
2208 def testDeleteAttachment_InvalidSequenceNum(self):
2209 """We can handle invalid sequence numbers."""
2210 request = issues_pb2.DeleteAttachmentRequest(
2211 issue_ref=common_pb2.IssueRef(
2212 project_name='proj',
2213 local_id=1),
2214 sequence_num=1,
2215 attachment_id=1234,
2216 delete=True)
2217 mc = monorailcontext.MonorailContext(
2218 self.services, cnxn=self.cnxn, requester='owner@example.com')
2219
2220 with self.assertRaises(exceptions.InputException):
2221 self.CallWrapped(
2222 self.issues_svcr.DeleteAttachment, mc, request)
2223
2224 def testFlagIssues_Normal(self):
2225 """Test that an user can flag an issue as spam."""
2226 self.services.user.TestAddUser('user@example.com', 999)
2227
2228 request = issues_pb2.FlagIssuesRequest(
2229 issue_refs=[
2230 common_pb2.IssueRef(
2231 project_name='proj',
2232 local_id=1),
2233 common_pb2.IssueRef(
2234 project_name='proj',
2235 local_id=2)],
2236 flag=True)
2237 mc = monorailcontext.MonorailContext(
2238 self.services, cnxn=self.cnxn, requester='user@example.com')
2239 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2240
2241 issue_id = self.issue_1.issue_id
2242 self.assertEqual(
2243 [999], self.services.spam.reports_by_issue_id[issue_id])
2244 self.assertNotIn(
2245 999, self.services.spam.manual_verdicts_by_issue_id[issue_id])
2246
2247 issue_id2 = self.issue_2.issue_id
2248 self.assertEqual(
2249 [999], self.services.spam.reports_by_issue_id[issue_id2])
2250 self.assertNotIn(
2251 999, self.services.spam.manual_verdicts_by_issue_id[issue_id2])
2252
2253 def testFlagIssues_Unflag(self):
2254 """Test that we can un-flag an issue as spam."""
2255 self.services.spam.FlagIssues(
2256 self.cnxn, self.services.issue, [self.issue_1], 111, True)
2257 self.services.spam.RecordManualIssueVerdicts(
2258 self.cnxn, self.services.issue, [self.issue_1], 111, True)
2259
2260 request = issues_pb2.FlagIssuesRequest(
2261 issue_refs=[
2262 common_pb2.IssueRef(
2263 project_name='proj',
2264 local_id=1)],
2265 flag=False)
2266 mc = monorailcontext.MonorailContext(
2267 self.services, cnxn=self.cnxn, requester='owner@example.com')
2268 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2269
2270 issue_id = self.issue_1.issue_id
2271 self.assertEqual([], self.services.spam.reports_by_issue_id[issue_id])
2272 self.assertFalse(
2273 self.services.spam.manual_verdicts_by_issue_id[issue_id][111])
2274
2275 def testFlagIssues_OwnerAutoVerdict(self):
2276 """Test that an owner can flag an issue as spam and it is a verdict."""
2277 request = issues_pb2.FlagIssuesRequest(
2278 issue_refs=[
2279 common_pb2.IssueRef(
2280 project_name='proj',
2281 local_id=1)],
2282 flag=True)
2283 mc = monorailcontext.MonorailContext(
2284 self.services, cnxn=self.cnxn, requester='owner@example.com')
2285 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2286
2287 issue_id = self.issue_1.issue_id
2288 self.assertEqual(
2289 [111], self.services.spam.reports_by_issue_id[issue_id])
2290 self.assertTrue(
2291 self.services.spam.manual_verdicts_by_issue_id[issue_id][111])
2292
2293 def testFlagIssues_CommitterAutoVerdict(self):
2294 """Test that an owner can flag an issue as spam and it is a verdict."""
2295 self.services.user.TestAddUser('committer@example.com', 999)
2296 self.services.project.TestAddProjectMembers(
2297 [999], self.project, fake.COMMITTER_ROLE)
2298
2299 request = issues_pb2.FlagIssuesRequest(
2300 issue_refs=[
2301 common_pb2.IssueRef(
2302 project_name='proj',
2303 local_id=1)],
2304 flag=True)
2305 mc = monorailcontext.MonorailContext(
2306 self.services, cnxn=self.cnxn, requester='committer@example.com')
2307 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2308
2309 issue_id = self.issue_1.issue_id
2310 self.assertEqual(
2311 [999], self.services.spam.reports_by_issue_id[issue_id])
2312 self.assertTrue(
2313 self.services.spam.manual_verdicts_by_issue_id[issue_id][999])
2314
2315 def testFlagIssues_ContributorAutoVerdict(self):
2316 """Test that an owner can flag an issue as spam and it is a verdict."""
2317 request = issues_pb2.FlagIssuesRequest(
2318 issue_refs=[
2319 common_pb2.IssueRef(
2320 project_name='proj',
2321 local_id=1)],
2322 flag=True)
2323 mc = monorailcontext.MonorailContext(
2324 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2325 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2326
2327 issue_id = self.issue_1.issue_id
2328 self.assertEqual(
2329 [222], self.services.spam.reports_by_issue_id[issue_id])
2330 self.assertTrue(
2331 self.services.spam.manual_verdicts_by_issue_id[issue_id][222])
2332
2333 def testFlagIssues_NotAllowed(self):
2334 """Test that anon users cannot flag issues as spam."""
2335 request = issues_pb2.FlagIssuesRequest(
2336 issue_refs=[
2337 common_pb2.IssueRef(
2338 project_name='proj',
2339 local_id=1)],
2340 flag=True)
2341 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
2342 with self.assertRaises(permissions.PermissionException):
2343 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2344
2345 self.assertEqual(
2346 [], self.services.spam.reports_by_issue_id[self.issue_1.issue_id])
2347 self.assertEqual({}, self.services.spam.manual_verdicts_by_issue_id)
2348
2349 def testFlagIssues_CrossProjectNotAllowed(self):
2350 """Test that cross-project requests are rejected."""
2351 request = issues_pb2.FlagIssuesRequest(
2352 issue_refs=[
2353 common_pb2.IssueRef(
2354 project_name='proj',
2355 local_id=1),
2356 common_pb2.IssueRef(
2357 project_name='proj2',
2358 local_id=2)],
2359 flag=True)
2360 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
2361 with self.assertRaises(exceptions.InputException):
2362 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2363
2364 self.assertEqual(
2365 [], self.services.spam.reports_by_issue_id[self.issue_1.issue_id])
2366 self.assertEqual({}, self.services.spam.manual_verdicts_by_issue_id)
2367
2368 def testFlagIssues_MissingIssueRefs(self):
2369 request = issues_pb2.FlagIssuesRequest(flag=True)
2370 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
2371 with self.assertRaises(exceptions.InputException):
2372 self.CallWrapped(self.issues_svcr.FlagIssues, mc, request)
2373
2374 def testFlagComment_InvalidSequenceNumber(self):
2375 """Test that we reject requests with invalid sequence numbers."""
2376 request = issues_pb2.FlagCommentRequest(
2377 issue_ref=common_pb2.IssueRef(
2378 project_name='proj',
2379 local_id=1),
2380 sequence_num=1,
2381 flag=True)
2382 mc = monorailcontext.MonorailContext(
2383 self.services, cnxn=self.cnxn, requester='user@example.com')
2384 with self.assertRaises(exceptions.InputException):
2385 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2386
2387 def testFlagComment_Normal(self):
2388 """Test that an user can flag a comment as spam."""
2389 self.services.user.TestAddUser('user@example.com', 999)
2390 comment = tracker_pb2.IssueComment(
2391 project_id=789, content='soon to be deleted', user_id=111,
2392 issue_id=self.issue_1.issue_id)
2393 self.services.issue.TestAddComment(comment, 1)
2394
2395 request = issues_pb2.FlagCommentRequest(
2396 issue_ref=common_pb2.IssueRef(
2397 project_name='proj',
2398 local_id=1),
2399 sequence_num=1,
2400 flag=True)
2401 mc = monorailcontext.MonorailContext(
2402 self.services, cnxn=self.cnxn, requester='user@example.com')
2403 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2404
2405 comment_reports = self.services.spam.comment_reports_by_issue_id
2406 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2407 self.assertEqual([999], comment_reports[self.issue_1.issue_id][comment.id])
2408 self.assertNotIn(999, manual_verdicts[comment.id])
2409
2410 def testFlagComment_Unflag(self):
2411 """Test that we can un-flag a comment as spam."""
2412 comment = tracker_pb2.IssueComment(
2413 project_id=789, content='soon to be deleted', user_id=999,
2414 issue_id=self.issue_1.issue_id)
2415 self.services.issue.TestAddComment(comment, 1)
2416
2417 self.services.spam.FlagComment(
2418 self.cnxn, self.issue_1, comment.id, 999, 111, True)
2419 self.services.spam.RecordManualCommentVerdict(
2420 self.cnxn, self.services.issue, self.services.user, comment.id, 111,
2421 True)
2422
2423 request = issues_pb2.FlagCommentRequest(
2424 issue_ref=common_pb2.IssueRef(
2425 project_name='proj',
2426 local_id=1),
2427 sequence_num=1,
2428 flag=False)
2429 mc = monorailcontext.MonorailContext(
2430 self.services, cnxn=self.cnxn, requester='owner@example.com')
2431 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2432
2433 comment_reports = self.services.spam.comment_reports_by_issue_id
2434 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2435 self.assertEqual([], comment_reports[self.issue_1.issue_id][comment.id])
2436 self.assertFalse(manual_verdicts[comment.id][111])
2437
2438 def testFlagComment_OwnerAutoVerdict(self):
2439 """Test that an owner can flag a comment as spam and it is a verdict."""
2440 comment = tracker_pb2.IssueComment(
2441 project_id=789, content='soon to be deleted', user_id=999,
2442 issue_id=self.issue_1.issue_id)
2443 self.services.issue.TestAddComment(comment, 1)
2444
2445 request = issues_pb2.FlagCommentRequest(
2446 issue_ref=common_pb2.IssueRef(
2447 project_name='proj',
2448 local_id=1),
2449 sequence_num=1,
2450 flag=True)
2451 mc = monorailcontext.MonorailContext(
2452 self.services, cnxn=self.cnxn, requester='owner@example.com')
2453 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2454
2455 comment_reports = self.services.spam.comment_reports_by_issue_id
2456 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2457 self.assertEqual([111], comment_reports[self.issue_1.issue_id][comment.id])
2458 self.assertTrue(manual_verdicts[comment.id][111])
2459
2460 def testFlagComment_CommitterAutoVerdict(self):
2461 """Test that an owner can flag an issue as spam and it is a verdict."""
2462 self.services.user.TestAddUser('committer@example.com', 999)
2463 self.services.project.TestAddProjectMembers(
2464 [999], self.project, fake.COMMITTER_ROLE)
2465
2466 comment = tracker_pb2.IssueComment(
2467 project_id=789, content='soon to be deleted', user_id=999,
2468 issue_id=self.issue_1.issue_id)
2469 self.services.issue.TestAddComment(comment, 1)
2470
2471 request = issues_pb2.FlagCommentRequest(
2472 issue_ref=common_pb2.IssueRef(
2473 project_name='proj',
2474 local_id=1),
2475 sequence_num=1,
2476 flag=True)
2477 mc = monorailcontext.MonorailContext(
2478 self.services, cnxn=self.cnxn, requester='committer@example.com')
2479 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2480
2481 comment_reports = self.services.spam.comment_reports_by_issue_id
2482 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2483 self.assertEqual([999], comment_reports[self.issue_1.issue_id][comment.id])
2484 self.assertTrue(manual_verdicts[comment.id][999])
2485
2486 def testFlagComment_ContributorAutoVerdict(self):
2487 """Test that an owner can flag an issue as spam and it is a verdict."""
2488 comment = tracker_pb2.IssueComment(
2489 project_id=789, content='soon to be deleted', user_id=999,
2490 issue_id=self.issue_1.issue_id)
2491 self.services.issue.TestAddComment(comment, 1)
2492
2493 request = issues_pb2.FlagCommentRequest(
2494 issue_ref=common_pb2.IssueRef(
2495 project_name='proj',
2496 local_id=1),
2497 sequence_num=1,
2498 flag=True)
2499 mc = monorailcontext.MonorailContext(
2500 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2501 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2502
2503 comment_reports = self.services.spam.comment_reports_by_issue_id
2504 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2505 self.assertEqual([222], comment_reports[self.issue_1.issue_id][comment.id])
2506 self.assertTrue(manual_verdicts[comment.id][222])
2507
2508 def testFlagComment_NotAllowed(self):
2509 """Test that anon users cannot flag issues as spam."""
2510 comment = tracker_pb2.IssueComment(
2511 project_id=789, content='soon to be deleted', user_id=999,
2512 issue_id=self.issue_1.issue_id)
2513 self.services.issue.TestAddComment(comment, 1)
2514
2515 request = issues_pb2.FlagCommentRequest(
2516 issue_ref=common_pb2.IssueRef(
2517 project_name='proj',
2518 local_id=1),
2519 sequence_num=1,
2520 flag=True)
2521 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
2522
2523 with self.assertRaises(permissions.PermissionException):
2524 self.CallWrapped(self.issues_svcr.FlagComment, mc, request)
2525
2526 comment_reports = self.services.spam.comment_reports_by_issue_id
2527 manual_verdicts = self.services.spam.manual_verdicts_by_comment_id
2528 self.assertNotIn(comment.id, comment_reports[self.issue_1.issue_id])
2529 self.assertEqual({}, manual_verdicts[comment.id])
2530
2531 def testListIssuePermissions_Normal(self):
2532 issue_1 = fake.MakeTestIssue(
2533 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
2534 self.services.issue.TestAddIssue(issue_1)
2535
2536 request = issues_pb2.ListIssuePermissionsRequest(
2537 issue_ref=common_pb2.IssueRef(
2538 project_name='proj',
2539 local_id=1))
2540 mc = monorailcontext.MonorailContext(
2541 self.services, cnxn=self.cnxn, requester='user@example.com')
2542
2543 response = self.CallWrapped(
2544 self.issues_svcr.ListIssuePermissions, mc, request)
2545 self.assertEqual(
2546 issues_pb2.ListIssuePermissionsResponse(
2547 permissions=[
2548 'addissuecomment',
2549 'createissue',
2550 'deleteown',
2551 'flagspam',
2552 'setstar',
2553 'view']),
2554 response)
2555
2556 def testListIssuePermissions_DeletedIssue(self):
2557 issue_1 = fake.MakeTestIssue(
2558 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
2559 issue_1.deleted = True
2560 self.services.issue.TestAddIssue(issue_1)
2561
2562 request = issues_pb2.ListIssuePermissionsRequest(
2563 issue_ref=common_pb2.IssueRef(
2564 project_name='proj',
2565 local_id=1))
2566 mc = monorailcontext.MonorailContext(
2567 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2568
2569 response = self.CallWrapped(
2570 self.issues_svcr.ListIssuePermissions, mc, request)
2571 self.assertEqual(
2572 issues_pb2.ListIssuePermissionsResponse(permissions=['view']),
2573 response)
2574
2575 def testListIssuePermissions_CanViewDeletedIssue(self):
2576 issue_1 = fake.MakeTestIssue(
2577 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
2578 issue_1.deleted = True
2579 self.services.issue.TestAddIssue(issue_1)
2580
2581 request = issues_pb2.ListIssuePermissionsRequest(
2582 issue_ref=common_pb2.IssueRef(
2583 project_name='proj',
2584 local_id=1))
2585 mc = monorailcontext.MonorailContext(
2586 self.services, cnxn=self.cnxn, requester='owner@example.com')
2587
2588 response = self.CallWrapped(
2589 self.issues_svcr.ListIssuePermissions, mc, request)
2590 self.assertEqual(
2591 issues_pb2.ListIssuePermissionsResponse(permissions=[
2592 'deleteissue',
2593 'view']),
2594 response)
2595
2596 def testListIssuePermissions_IssueRestrictions(self):
2597 issue_1 = fake.MakeTestIssue(
2598 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
2599 issue_1.labels = ['Restrict-SetStar-CustomPerm']
2600 self.services.issue.TestAddIssue(issue_1)
2601
2602 request = issues_pb2.ListIssuePermissionsRequest(
2603 issue_ref=common_pb2.IssueRef(
2604 project_name='proj',
2605 local_id=1))
2606 mc = monorailcontext.MonorailContext(
2607 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2608
2609 response = self.CallWrapped(
2610 self.issues_svcr.ListIssuePermissions, mc, request)
2611 self.assertEqual(
2612 issues_pb2.ListIssuePermissionsResponse(
2613 permissions=[
2614 'addissuecomment',
2615 'createissue',
2616 'deleteown',
2617 'flagspam',
2618 'verdictspam',
2619 'view']),
2620 response)
2621
2622 def testListIssuePermissions_IssueGrantedPerms(self):
2623 self.services.config.CreateFieldDef(
2624 self.cnxn, 789, 'Field Name', 'USER_TYPE', None, None, None, None, None,
2625 None, None, None, None, None, 'CustomPerm', None, None, 'Docstring', [],
2626 [])
2627 issue_1 = fake.MakeTestIssue(
2628 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=1001)
2629 issue_1.labels = ['Restrict-SetStar-CustomPerm']
2630 issue_1.field_values = [tracker_pb2.FieldValue(user_id=222, field_id=123)]
2631 self.services.issue.TestAddIssue(issue_1)
2632
2633 request = issues_pb2.ListIssuePermissionsRequest(
2634 issue_ref=common_pb2.IssueRef(
2635 project_name='proj',
2636 local_id=1))
2637 mc = monorailcontext.MonorailContext(
2638 self.services, cnxn=self.cnxn, requester='approver2@example.com')
2639
2640 response = self.CallWrapped(
2641 self.issues_svcr.ListIssuePermissions, mc, request)
2642 self.assertEqual(
2643 issues_pb2.ListIssuePermissionsResponse(
2644 permissions=[
2645 'addissuecomment',
2646 'createissue',
2647 'customperm',
2648 'deleteown',
2649 'flagspam',
2650 'setstar',
2651 'verdictspam',
2652 'view']),
2653 response)
2654
2655 @patch('services.tracker_fulltext.IndexIssues')
2656 @patch('services.tracker_fulltext.UnindexIssues')
2657 def testMoveIssue_Normal(self, _mock_index, _mock_unindex):
2658 issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
2659 self.services.issue.TestAddIssue(issue)
2660 self.project.owner_ids = [111]
2661 target_project = self.services.project.TestAddProject(
2662 'dest', project_id=988, committer_ids=[111])
2663
2664 request = issues_pb2.MoveIssueRequest(
2665 issue_ref=common_pb2.IssueRef(
2666 project_name='proj',
2667 local_id=1),
2668 target_project_name='dest')
2669 mc = monorailcontext.MonorailContext(
2670 self.services, cnxn=self.cnxn, requester='owner@example.com')
2671 response = self.CallWrapped(
2672 self.issues_svcr.MoveIssue, mc, request)
2673
2674 self.assertEqual(
2675 issues_pb2.MoveIssueResponse(
2676 new_issue_ref=common_pb2.IssueRef(
2677 project_name='dest',
2678 local_id=1)),
2679 response)
2680
2681 moved_issue = self.services.issue.GetIssueByLocalID(self.cnxn,
2682 target_project.project_id, 1)
2683 self.assertEqual(target_project.project_id, moved_issue.project_id)
2684 self.assertEqual(issue.summary, moved_issue.summary)
2685 self.assertEqual(moved_issue.reporter_id, 111)
2686
2687 @patch('services.tracker_fulltext.IndexIssues')
2688 def testCopyIssue_Normal(self, _mock_index):
2689 issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
2690 self.services.issue.TestAddIssue(issue)
2691 self.project.owner_ids = [111]
2692
2693 request = issues_pb2.CopyIssueRequest(
2694 issue_ref=common_pb2.IssueRef(
2695 project_name='proj',
2696 local_id=1),
2697 target_project_name='proj')
2698 mc = monorailcontext.MonorailContext(
2699 self.services, cnxn=self.cnxn, requester='owner@example.com')
2700 response = self.CallWrapped(
2701 self.issues_svcr.CopyIssue, mc, request)
2702
2703 self.assertEqual(
2704 issues_pb2.CopyIssueResponse(
2705 new_issue_ref=common_pb2.IssueRef(
2706 project_name='proj',
2707 local_id=3)),
2708 response)
2709
2710 copied_issue = self.services.issue.GetIssueByLocalID(self.cnxn,
2711 self.project.project_id, 3)
2712 self.assertEqual(self.project.project_id, copied_issue.project_id)
2713 self.assertEqual(issue.summary, copied_issue.summary)
2714 self.assertEqual(copied_issue.reporter_id, 111)