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