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