blob: c7bd1ca0a867d881916cf6d254256ed360c66037 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Unittests for monorail.tracker.issuebulkedit."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import mock
11import os
12import unittest
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010013import flask
Copybara854996b2021-09-07 19:36:02 +000014
15from google.appengine.api import memcache
16from google.appengine.ext import testbed
17
18from framework import exceptions
19from framework import permissions
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010020from mrproto import project_pb2
21from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000022from services import service_manager
23from services import tracker_fulltext
24from testing import fake
25from testing import testing_helpers
26from tracker import issuebulkedit
27from tracker import tracker_bizobj
28from tracker import tracker_constants
29
30
31class Response(object):
32
33 def __init__(self):
34 self.status = None
35
36
37class IssueBulkEditTest(unittest.TestCase):
38
39 def setUp(self):
40 self.services = service_manager.Services(
41 features=fake.FeaturesService(),
42 project=fake.ProjectService(),
43 config=fake.ConfigService(),
44 issue=fake.IssueService(),
45 issue_star=fake.IssueStarService(),
46 user=fake.UserService(),
47 usergroup=fake.UserGroupService())
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010048 self.servlet = issuebulkedit.IssueBulkEdit(services=self.services)
Copybara854996b2021-09-07 19:36:02 +000049 self.mr = testing_helpers.MakeMonorailRequest(
50 perms=permissions.OWNER_ACTIVE_PERMISSIONSET)
51 self.project = self.services.project.TestAddProject(
52 name='proj', project_id=789, owner_ids=[111])
53 self.cnxn = 'fake connection'
54 self.config = self.services.config.GetProjectConfig(
55 self.cnxn, self.project.project_id)
56 self.services.config.StoreConfig(self.cnxn, self.config)
57 self.owner = self.services.user.TestAddUser('owner@example.com', 111)
58
59 self.testbed = testbed.Testbed()
60 self.testbed.activate()
61 self.testbed.init_memcache_stub()
62 self.testbed.init_datastore_v3_stub()
63
64 self.mocked_methods = {}
65
66 def tearDown(self):
67 """Restore mocked objects of other modules."""
68 self.testbed.deactivate()
69 for obj, items in self.mocked_methods.items():
70 for member, previous_value in items.items():
71 setattr(obj, member, previous_value)
72
73 def testAssertBasePermission(self):
74 """Permit users with EDIT_ISSUE and ADD_ISSUE_COMMENT permissions."""
75 mr = testing_helpers.MakeMonorailRequest(
76 perms=permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET)
77 self.assertRaises(permissions.PermissionException,
78 self.servlet.AssertBasePermission, mr)
79
80 self.servlet.AssertBasePermission(self.mr)
81
82 def testGatherPageData(self):
83 """Test GPD works in a normal no-corner-cases case."""
84 created_issue_1 = fake.MakeTestIssue(
85 789, 1, 'issue summary', 'New', 0, reporter_id=111)
86 self.services.issue.TestAddIssue(created_issue_1)
87 local_id_1 = created_issue_1.local_id
88 mr = testing_helpers.MakeMonorailRequest(
89 project=self.project)
90 mr.local_id_list = [local_id_1]
91
92 page_data = self.servlet.GatherPageData(mr)
93 self.assertEqual(1, page_data['num_issues'])
94
95 def testGatherPageData_CustomFieldEdition(self):
96 """Test GPD works in a normal no-corner-cases case."""
97 created_issue_1 = fake.MakeTestIssue(
98 789, 1, 'issue summary', 'New', 0, reporter_id=111)
99 self.services.issue.TestAddIssue(created_issue_1)
100 local_id_1 = created_issue_1.local_id
101 mr = testing_helpers.MakeMonorailRequest(
102 project=self.project, perms=permissions.PermissionSet([]))
103 mr.local_id_list = [local_id_1]
104 mr.auth.effective_ids = {222}
105
106 fd_not_restricted = tracker_bizobj.MakeFieldDef(
107 123,
108 789,
109 'CPU',
110 tracker_pb2.FieldTypes.INT_TYPE,
111 None,
112 '',
113 False,
114 False,
115 False,
116 None,
117 None,
118 '',
119 False,
120 '',
121 '',
122 tracker_pb2.NotifyTriggers.NEVER,
123 'no_action',
124 'doc',
125 False,
126 is_restricted_field=False)
127 self.config.field_defs.append(fd_not_restricted)
128
129 fd_restricted = tracker_bizobj.MakeFieldDef(
130 124,
131 789,
132 'CPU',
133 tracker_pb2.FieldTypes.INT_TYPE,
134 None,
135 '',
136 False,
137 False,
138 False,
139 None,
140 None,
141 '',
142 False,
143 '',
144 '',
145 tracker_pb2.NotifyTriggers.NEVER,
146 'no_action',
147 'doc',
148 False,
149 is_restricted_field=True)
150 self.config.field_defs.append(fd_restricted)
151
152 page_data = self.servlet.GatherPageData(mr)
153 self.assertTrue(page_data['fields'][0].is_editable)
154 self.assertFalse(page_data['fields'][1].is_editable)
155
156 def testGatherPageData_NoIssues(self):
157 """Test GPD when no issues are specified in the mr."""
158 mr = testing_helpers.MakeMonorailRequest(
159 project=self.project)
160 self.assertRaises(exceptions.InputException,
161 self.servlet.GatherPageData, mr)
162
163 def testGatherPageData_FilteredIssues(self):
164 """Test GPD when all specified issues get filtered out."""
165 created_issue_1 = fake.MakeTestIssue(
166 789,
167 1,
168 'issue summary',
169 'New',
170 0,
171 reporter_id=111,
172 labels=['restrict-view-Googler'])
173 self.services.issue.TestAddIssue(created_issue_1)
174 local_id_1 = created_issue_1.local_id
175 mr = testing_helpers.MakeMonorailRequest(
176 project=self.project)
177 mr.local_id_list = [local_id_1]
178
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100179 self.assertRaises(Exception, self.servlet.GatherPageData, mr)
Copybara854996b2021-09-07 19:36:02 +0000180
181 def testGatherPageData_TypeLabels(self):
182 """Test that GPD displays a custom field for appropriate issues."""
183 created_issue_1 = fake.MakeTestIssue(
184 789,
185 1,
186 'issue summary',
187 'New',
188 0,
189 reporter_id=111,
190 labels=['type-customlabels'])
191 self.services.issue.TestAddIssue(created_issue_1)
192 local_id_1 = created_issue_1.local_id
193 mr = testing_helpers.MakeMonorailRequest(
194 project=self.project)
195 mr.local_id_list = [local_id_1]
196
197 fd = tracker_bizobj.MakeFieldDef(
198 123, 789, 'CPU', tracker_pb2.FieldTypes.INT_TYPE, None,
199 '', False, False, False, None, None, '', False, '', '',
200 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False)
201 self.config.field_defs.append(fd)
202
203 page_data = self.servlet.GatherPageData(mr)
204 self.assertEqual(1, len(page_data['fields']))
205
206 @mock.patch('framework.cloud_tasks_helpers.create_task')
207 def testProcessFormData(self, _create_task_mock):
208 """Test that PFD works in a normal no-corner-cases case."""
209 created_issue_1 = fake.MakeTestIssue(
210 789, 1, 'issue summary', 'New', 111, reporter_id=111)
211 self.services.issue.TestAddIssue(created_issue_1)
212 local_id_1 = created_issue_1.local_id
213
214 mr = testing_helpers.MakeMonorailRequest(
215 project=self.project,
216 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
217 user_info={'user_id': 111})
218 mr.local_id_list = [local_id_1]
219
220 post_data = fake.PostData(
221 owner=['owner@example.com'], can=[1],
222 q=[''], colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
223 self._MockMethods()
224 url = self.servlet.ProcessFormData(mr, post_data)
225 self.assertTrue('list?can=1&q=&saved=1' in url)
226
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100227 def testProcessFormData_FreezeLabels(self):
228 """Test that PFD works in a normal no-corner-cases case."""
229 created_issue_1 = fake.MakeTestIssue(
230 789, 1, 'issue summary', 'New', 111, reporter_id=111)
231 self.services.issue.TestAddIssue(created_issue_1)
232 local_id_1 = created_issue_1.local_id
233
234 mr = testing_helpers.MakeMonorailRequest(
235 project=self.project,
236 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
237 user_info={'user_id': 111})
238 mr.local_id_list = [local_id_1]
239
240 post_data = fake.PostData(
241 owner=['owner@example.com'],
242 can=[1],
243 q=[''],
244 colspec=[''],
245 sort=[''],
246 groupby=[''],
247 start=[0],
248 num=[100],
249 label=['freeze_new_label'])
250 self._MockMethods()
251 self.servlet.response = flask.Response()
252 self.servlet.ProcessFormData(mr, post_data)
253 self.assertEqual(
254 (
255 "The creation of new labels is blocked for the Chromium project"
256 " in Monorail. To continue with editing your issue, please"
257 " remove: freeze_new_label label(s)."), mr.errors.labels)
258
Copybara854996b2021-09-07 19:36:02 +0000259 def testProcessFormData_NoIssues(self):
260 """Test PFD when no issues are specified."""
261 mr = testing_helpers.MakeMonorailRequest(
262 project=self.project,
263 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
264 user_info={'user_id': 111})
265 post_data = fake.PostData()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100266 self.servlet.response = flask.Response()
Copybara854996b2021-09-07 19:36:02 +0000267 self.servlet.ProcessFormData(mr, post_data)
268 # 400 == bad request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100269 self.assertEqual(400, self.servlet.response.status_code)
Copybara854996b2021-09-07 19:36:02 +0000270
271 def testProcessFormData_NoUser(self):
272 """Test PFD when the user is not logged in."""
273 mr = testing_helpers.MakeMonorailRequest(
274 project=self.project)
275 mr.local_id_list = [99999]
276 post_data = fake.PostData()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100277 self.servlet.response = flask.Response()
Copybara854996b2021-09-07 19:36:02 +0000278 self.servlet.ProcessFormData(mr, post_data)
279 # 400 == bad request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100280 self.assertEqual(400, self.servlet.response.status_code)
Copybara854996b2021-09-07 19:36:02 +0000281
282 def testProcessFormData_CantComment(self):
283 """Test PFD when the user can't comment on any of the issues."""
284 mr = testing_helpers.MakeMonorailRequest(
285 project=self.project,
286 perms=permissions.EMPTY_PERMISSIONSET,
287 user_info={'user_id': 111})
288 mr.local_id_list = [99999]
289 post_data = fake.PostData()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100290 self.servlet.response = flask.Response()
Copybara854996b2021-09-07 19:36:02 +0000291 self.servlet.ProcessFormData(mr, post_data)
292 # 400 == bad request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100293 self.assertEqual(400, self.servlet.response.status_code)
Copybara854996b2021-09-07 19:36:02 +0000294
295 def testProcessFormData_CantEdit(self):
296 """Test PFD when the user can't edit any issue metadata."""
297 mr = testing_helpers.MakeMonorailRequest(
298 project=self.project,
299 perms=permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET,
300 user_info={'user_id': 111})
301 mr.local_id_list = [99999]
302 post_data = fake.PostData()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100303 self.servlet.response = flask.Response()
Copybara854996b2021-09-07 19:36:02 +0000304 self.servlet.ProcessFormData(mr, post_data)
305 # 400 == bad request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100306 self.assertEqual(400, self.servlet.response.status_code)
Copybara854996b2021-09-07 19:36:02 +0000307
308 def testProcessFormData_CantMove(self):
309 """Test PFD when the user can't move issues."""
310 mr = testing_helpers.MakeMonorailRequest(
311 project=self.project,
312 perms=permissions.COMMITTER_ACTIVE_PERMISSIONSET,
313 user_info={'user_id': 111})
314 mr.local_id_list = [99999]
315 post_data = fake.PostData(move_to=['proj'])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100316 self.servlet.response = flask.Response()
Copybara854996b2021-09-07 19:36:02 +0000317 self.servlet.ProcessFormData(mr, post_data)
318 # 400 == bad request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100319 self.assertEqual(400, self.servlet.response.status_code)
Copybara854996b2021-09-07 19:36:02 +0000320
321 created_issue_1 = fake.MakeTestIssue(
322 789, 1, 'issue summary', 'New', 111, reporter_id=111)
323 self.services.issue.TestAddIssue(created_issue_1)
324 local_id_1 = created_issue_1.local_id
325 mr.perms = permissions.OWNER_ACTIVE_PERMISSIONSET
326 mr.local_id_list = [local_id_1]
327 mr.project_name = 'proj'
328 self._MockMethods()
329 self.servlet.ProcessFormData(mr, post_data)
330 self.assertEqual(
331 'The issues are already in project proj', mr.errors.move_to)
332
333 post_data = fake.PostData(move_to=['notexist'])
334 self.servlet.ProcessFormData(mr, post_data)
335 self.assertEqual('No such project: notexist', mr.errors.move_to)
336
337 def _MockMethods(self):
338 # Mock methods of other modules to avoid unnecessary testing
339 self.mocked_methods[tracker_fulltext] = {
340 'IndexIssues': tracker_fulltext.IndexIssues,
341 'UnindexIssues': tracker_fulltext.UnindexIssues}
342 def DoNothing(*_args, **_kwargs):
343 pass
344 self.servlet.PleaseCorrect = DoNothing
345 tracker_fulltext.IndexIssues = DoNothing
346 tracker_fulltext.UnindexIssues = DoNothing
347
348 def GetFirstAmendment(self, project_id, local_id):
349 issue = self.services.issue.GetIssueByLocalID(
350 self.cnxn, project_id, local_id)
351 issue_id = issue.issue_id
352 comments = self.services.issue.GetCommentsForIssue(self.cnxn, issue_id)
353 last_comment = comments[-1]
354 first_amendment = last_comment.amendments[0]
355 return first_amendment.field, first_amendment.newvalue
356
357 def testProcessFormData_BadUserField(self):
358 """Test PFD when a nonexistent user is added as a field value."""
359 created_issue_1 = fake.MakeTestIssue(
360 789, 1, 'issue summary', 'New', 111, reporter_id=111)
361 self.services.issue.TestAddIssue(created_issue_1)
362 local_id_1 = created_issue_1.local_id
363 mr = testing_helpers.MakeMonorailRequest(
364 project=self.project,
365 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
366 user_info={'user_id': 111})
367 mr.local_id_list = [local_id_1]
368
369 fd = tracker_bizobj.MakeFieldDef(
370 12345, 789, 'PM', tracker_pb2.FieldTypes.USER_TYPE, None,
371 '', False, False, False, None, None, '', False, '', '',
372 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False)
373 self.config.field_defs.append(fd)
374
375 post_data = fake.PostData(
376 custom_12345=['ghost@gmail.com'], owner=['owner@example.com'], can=[1],
377 q=[''], colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
378 self._MockMethods()
379 self.servlet.ProcessFormData(mr, post_data)
380 self.assertEqual('User not found.', mr.errors.custom_fields[0].message)
381
382 @mock.patch('framework.cloud_tasks_helpers.create_task')
383 def testProcessFormData_CustomFields(self, _create_task_mock):
384 """Test PFD processes edits to custom fields."""
385 created_issue_1 = fake.MakeTestIssue(
386 789, 1, 'issue summary', 'New', 111, reporter_id=111)
387 self.services.issue.TestAddIssue(created_issue_1)
388 local_id_1 = created_issue_1.local_id
389 mr = testing_helpers.MakeMonorailRequest(
390 project=self.project,
391 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
392 user_info={'user_id': 111})
393 mr.local_id_list = [local_id_1]
394
395 fd = tracker_bizobj.MakeFieldDef(
396 12345, 789, 'CPU', tracker_pb2.FieldTypes.INT_TYPE, None,
397 '', False, False, False, None, None, '', False, '', '',
398 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False)
399 self.config.field_defs.append(fd)
400
401 post_data = fake.PostData(
402 custom_12345=['10'],
403 owner=['owner@example.com'],
404 can=[1],
405 q=[''],
406 colspec=[''],
407 sort=[''],
408 groupby=[''],
409 start=[0],
410 num=[100])
411 self._MockMethods()
412 self.servlet.ProcessFormData(mr, post_data)
413 self.assertEqual(
414 (tracker_pb2.FieldID.CUSTOM, '10'),
415 self.GetFirstAmendment(789, local_id_1))
416
417 @mock.patch('framework.cloud_tasks_helpers.create_task')
418 def testProcessFormData_RestrictedCustomFieldsAccept(self, _create_task_mock):
419 """We accept edits to restricted fields by editors (or admins)."""
420 created_issue_1 = fake.MakeTestIssue(
421 789, 1, 'issue summary', 'New', 111, reporter_id=111)
422 self.services.issue.TestAddIssue(created_issue_1)
423 local_id_1 = created_issue_1.local_id
424 mr = testing_helpers.MakeMonorailRequest(
425 project=self.project,
426 perms=permissions.PermissionSet(
427 [
428 permissions.EDIT_ISSUE, permissions.ADD_ISSUE_COMMENT,
429 permissions.VIEW
430 ]),
431 user_info={'user_id': 111})
432 mr.local_id_list = [local_id_1]
433
434 fd = tracker_bizobj.MakeFieldDef(
435 12345,
436 789,
437 'CPU',
438 tracker_pb2.FieldTypes.INT_TYPE,
439 None,
440 '',
441 False,
442 False,
443 False,
444 None,
445 None,
446 '',
447 False,
448 '',
449 '',
450 tracker_pb2.NotifyTriggers.NEVER,
451 'no_action',
452 'doc',
453 False,
454 is_restricted_field=True)
455 fd.editor_ids = [111]
456 self.config.field_defs.append(fd)
457
458 post_data = fake.PostData(
459 custom_12345=['10'],
460 owner=['owner@example.com'],
461 can=[1],
462 q=[''],
463 colspec=[''],
464 sort=[''],
465 groupby=[''],
466 start=[0],
467 num=[100])
468 self._MockMethods()
469 self.servlet.ProcessFormData(mr, post_data)
470 self.assertEqual(
471 (tracker_pb2.FieldID.CUSTOM, '10'),
472 self.GetFirstAmendment(789, local_id_1))
473
474 def testProcessFormData_RestrictedCustomFieldsReject(self):
475 """We reject edits to restricted fields by non-editors (and non-admins)."""
476 created_issue_1 = fake.MakeTestIssue(
477 789, 1, 'issue summary', 'New', 111, reporter_id=111)
478 self.services.issue.TestAddIssue(created_issue_1)
479 local_id_1 = created_issue_1.local_id
480 mr = testing_helpers.MakeMonorailRequest(
481 project=self.project,
482 perms=permissions.PermissionSet(
483 [
484 permissions.EDIT_ISSUE, permissions.ADD_ISSUE_COMMENT,
485 permissions.VIEW
486 ]),
487 user_info={'user_id': 111})
488 mr.local_id_list = [local_id_1]
489
490 fd_int = tracker_bizobj.MakeFieldDef(
491 11111,
492 789,
493 'fd_int',
494 tracker_pb2.FieldTypes.INT_TYPE,
495 None,
496 '',
497 False,
498 False,
499 False,
500 None,
501 None,
502 '',
503 False,
504 '',
505 '',
506 tracker_pb2.NotifyTriggers.NEVER,
507 'no_action',
508 'doc',
509 False,
510 is_restricted_field=True)
511 fd_enum = tracker_bizobj.MakeFieldDef(
512 44444,
513 789,
514 'fdEnum',
515 tracker_pb2.FieldTypes.ENUM_TYPE,
516 None,
517 '',
518 False,
519 False,
520 False,
521 None,
522 None,
523 '',
524 False,
525 '',
526 '',
527 tracker_pb2.NotifyTriggers.NEVER,
528 'no_action',
529 'doc',
530 False,
531 is_restricted_field=True)
532 fd_int.admin_ids = [222]
533 fd_enum.editor_ids = [333]
534 self.config.field_defs = [fd_int, fd_enum]
535
536 post_data_add_fv = fake.PostData(
537 custom_11111=['10'],
538 owner=['owner@example.com'],
539 can=[1],
540 q=[''],
541 colspec=[''],
542 sort=[''],
543 groupby=[''],
544 start=[0],
545 num=[100])
546 post_data_rm_fv = fake.PostData(
547 op_custom_11111=['remove'],
548 custom_11111=['10'],
549 owner=['owner@example.com'],
550 can=[1],
551 q=[''],
552 colspec=[''],
553 sort=[''],
554 groupby=[''],
555 start=[0],
556 num=[100])
557 post_data_clear_fd = fake.PostData(
558 op_custom_11111=['clear'],
559 owner=['owner@example.com'],
560 can=[1],
561 q=[''],
562 colspec=[''],
563 sort=[''],
564 groupby=[''],
565 start=[0],
566 num=[100])
567 post_data_label_edits_enum = fake.PostData(
568 label=['fdEnum-a'],
569 owner=['owner@example.com'],
570 can=[1],
571 q=[''],
572 colspec=[''],
573 sort=[''],
574 groupby=[''],
575 start=[0],
576 num=[100])
577 post_data_label_rm_enum = fake.PostData(
578 label=['-fdEnum-b'],
579 owner=['owner@example.com'],
580 can=[1],
581 q=[''],
582 colspec=[''],
583 sort=[''],
584 groupby=[''],
585 start=[0],
586 num=[100])
587
588 self._MockMethods()
589 self.assertRaises(
590 AssertionError, self.servlet.ProcessFormData, mr, post_data_add_fv)
591 self.assertRaises(
592 AssertionError, self.servlet.ProcessFormData, mr, post_data_rm_fv)
593 self.assertRaises(
594 AssertionError, self.servlet.ProcessFormData, mr, post_data_clear_fd)
595 self.assertRaises(
596 AssertionError, self.servlet.ProcessFormData, mr,
597 post_data_label_edits_enum)
598 self.assertRaises(
599 AssertionError, self.servlet.ProcessFormData, mr,
600 post_data_label_rm_enum)
601
602 def testProcessFormData_DuplicateStatus_MergeSameIssue(self):
603 """Test PFD processes null/cleared status values."""
604 created_issue_1 = fake.MakeTestIssue(
605 789, 1, 'issue summary', 'New', 111, reporter_id=111)
606 self.services.issue.TestAddIssue(created_issue_1)
607 local_id_1 = created_issue_1.local_id
608
609 created_issue_2 = fake.MakeTestIssue(
610 789, 1, 'issue summary', 'New', 112, reporter_id=112)
611 self.services.issue.TestAddIssue(created_issue_2)
612 merge_into_local_id_2 = created_issue_2.local_id
613
614 mr = testing_helpers.MakeMonorailRequest(
615 project=self.project,
616 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
617 user_info={'user_id': 111})
618 mr.local_id_list = [local_id_1, merge_into_local_id_2]
619 mr.project_name = 'proj'
620
621 # Add required project_name to merge_into_issue.
622 merge_into_issue = self.services.issue.GetIssueByLocalID(
623 mr.cnxn, self.project.project_id, merge_into_local_id_2)
624 merge_into_issue.project_name = 'proj'
625
626 post_data = fake.PostData(status=['Duplicate'],
627 merge_into=[str(merge_into_local_id_2)], owner=['owner@example.com'],
628 can=[1], q=[''], colspec=[''], sort=[''], groupby=[''], start=[0],
629 num=[100])
630 self._MockMethods()
631 self.servlet.ProcessFormData(mr, post_data)
632 self.assertEqual('Cannot merge issue into itself', mr.errors.merge_into_id)
633
634 def testProcessFormData_DuplicateStatus_MergeMissingIssue(self):
635 """Test PFD processes null/cleared status values."""
636 created_issue_1 = fake.MakeTestIssue(
637 789, 1, 'issue summary', 'New', 111, reporter_id=111)
638 self.services.issue.TestAddIssue(created_issue_1)
639 local_id_1 = created_issue_1.local_id
640 created_issue_2 = fake.MakeTestIssue(
641 789, 1, 'issue summary2', 'New', 112, reporter_id=112)
642 self.services.issue.TestAddIssue(created_issue_2)
643 local_id_2 = created_issue_2.local_id
644 mr = testing_helpers.MakeMonorailRequest(
645 project=self.project,
646 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
647 user_info={'user_id': 111})
648 mr.local_id_list = [local_id_1, local_id_2]
649 mr.project_name = 'proj'
650
651 post_data = fake.PostData(status=['Duplicate'],
652 merge_into=['non existant id'], owner=['owner@example.com'],
653 can=[1], q=[''], colspec=[''], sort=[''], groupby=[''], start=[0],
654 num=[100])
655 self._MockMethods()
656 self.servlet.ProcessFormData(mr, post_data)
657 self.assertEqual('Please enter an issue ID', mr.errors.merge_into_id)
658
659 @mock.patch('framework.cloud_tasks_helpers.create_task')
660 def testProcessFormData_DuplicateStatus_Success(self, _create_task_mock):
661 """Test PFD processes null/cleared status values."""
662 created_issue_1 = fake.MakeTestIssue(
663 789, 1, 'issue summary', 'New', 111, reporter_id=111)
664 self.services.issue.TestAddIssue(created_issue_1)
665 local_id_1 = created_issue_1.local_id
666 created_issue_2 = fake.MakeTestIssue(
667 789, 2, 'issue summary2', 'New', 111, reporter_id=111)
668 self.services.issue.TestAddIssue(created_issue_2)
669 local_id_2 = created_issue_2.local_id
670 created_issue_3 = fake.MakeTestIssue(
671 789, 3, 'issue summary3', 'New', 112, reporter_id=112)
672 self.services.issue.TestAddIssue(created_issue_3)
673 merge_into_local_id_3 = created_issue_3.local_id
674 mr = testing_helpers.MakeMonorailRequest(
675 project=self.project,
676 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
677 user_info={'user_id': 111})
678 mr.local_id_list = [local_id_1, local_id_2]
679 mr.project_name = 'proj'
680
681 post_data = fake.PostData(status=['Duplicate'],
682 merge_into=[str(merge_into_local_id_3)], owner=['owner@example.com'],
683 can=[1], q=[''], colspec=[''], sort=[''], groupby=[''], start=[0],
684 num=[100])
685 self._MockMethods()
686
687 # Add project_name, CCs and starrers to the merge_into_issue.
688 merge_into_issue = self.services.issue.GetIssueByLocalID(
689 mr.cnxn, self.project.project_id, merge_into_local_id_3)
690 merge_into_issue.project_name = 'proj'
691 merge_into_issue.cc_ids = [113, 120]
692 self.services.issue_star.SetStar(
693 mr.cnxn, self.services, None, merge_into_issue.issue_id, 120, True)
694
695 # Add project_name, CCs and starrers to the source issues.
696 # Issue 1
697 issue_1 = self.services.issue.GetIssueByLocalID(
698 mr.cnxn, self.project.project_id, local_id_1)
699 issue_1.project_name = 'proj'
700 issue_1.cc_ids = [113, 114]
701 self.services.issue_star.SetStar(
702 mr.cnxn, self.services, None, issue_1.issue_id, 113, True)
703 # Issue 2
704 issue_2 = self.services.issue.GetIssueByLocalID(
705 mr.cnxn, self.project.project_id, local_id_2)
706 issue_2.project_name = 'proj'
707 issue_2.cc_ids = [113, 115, 118]
708 self.services.issue_star.SetStar(
709 mr.cnxn, self.services, None, issue_2.issue_id, 114, True)
710 self.services.issue_star.SetStar(
711 mr.cnxn, self.services, None, issue_2.issue_id, 115, True)
712
713 self.servlet.ProcessFormData(mr, post_data)
714
715 # Verify both source issues were updated.
716 self.assertEqual(
717 (tracker_pb2.FieldID.STATUS, 'Duplicate'),
718 self.GetFirstAmendment(self.project.project_id, local_id_1))
719 self.assertEqual(
720 (tracker_pb2.FieldID.STATUS, 'Duplicate'),
721 self.GetFirstAmendment(self.project.project_id, local_id_2))
722
723 # Verify that the merge into issue was updated with a comment.
724 comments = self.services.issue.GetCommentsForIssue(
725 self.cnxn, merge_into_issue.issue_id)
726 self.assertEqual(
727 'Issue 1 has been merged into this issue.\n'
728 'Issue 2 has been merged into this issue.', comments[-1].content)
729
730 # Verify CC lists and owner were merged to the merge_into issue.
731 self.assertEqual(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100732 [113, 120, 111, 114, 115, 118], merge_into_issue.cc_ids)
Copybara854996b2021-09-07 19:36:02 +0000733 # Verify new starrers were added to the merge_into issue.
734 self.assertEqual(4,
735 self.services.issue_star.CountItemStars(
736 self.cnxn, merge_into_issue.issue_id))
737 self.assertEqual([120, 113, 114, 115],
738 self.services.issue_star.LookupItemStarrers(
739 self.cnxn, merge_into_issue.issue_id))
740
741 @mock.patch('framework.cloud_tasks_helpers.create_task')
742 def testProcessFormData_ClearStatus(self, _create_task_mock):
743 """Test PFD processes null/cleared status values."""
744 created_issue_1 = fake.MakeTestIssue(
745 789, 1, 'issue summary', 'New', 111, reporter_id=111)
746 self.services.issue.TestAddIssue(created_issue_1)
747 local_id_1 = created_issue_1.local_id
748 mr = testing_helpers.MakeMonorailRequest(
749 project=self.project,
750 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
751 user_info={'user_id': 111})
752 mr.local_id_list = [local_id_1]
753
754 post_data = fake.PostData(
755 op_statusenter=['clear'], owner=['owner@example.com'], can=[1],
756 q=[''], colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
757 self._MockMethods()
758 self.servlet.ProcessFormData(mr, post_data)
759 self.assertEqual(
760 (tracker_pb2.FieldID.STATUS, ''), self.GetFirstAmendment(
761 789, local_id_1))
762
763 def testProcessFormData_InvalidOwner(self):
764 """Test PFD rejects invalid owner emails."""
765 created_issue_1 = fake.MakeTestIssue(
766 789, 1, 'issue summary', 'New', 0, reporter_id=111)
767 self.services.issue.TestAddIssue(created_issue_1)
768 local_id_1 = created_issue_1.local_id
769 mr = testing_helpers.MakeMonorailRequest(
770 project=self.project,
771 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
772 user_info={'user_id': 111})
773 mr.local_id_list = [local_id_1]
774 post_data = fake.PostData(
775 owner=['invalid'])
776 self.servlet.response = Response()
777 self._MockMethods()
778 self.servlet.ProcessFormData(mr, post_data)
779 self.assertTrue(mr.errors.AnyErrors())
780
781 @mock.patch('framework.cloud_tasks_helpers.create_task')
782 def testProcessFormData_MoveTo(self, _create_task_mock):
783 """Test PFD processes move_to values."""
784 created_issue_1 = fake.MakeTestIssue(
785 789, 1, 'issue summary', 'New', 111, reporter_id=111)
786 self.services.issue.TestAddIssue(created_issue_1)
787 local_id_1 = created_issue_1.local_id
788 move_to_project = self.services.project.TestAddProject(
789 name='proj2', project_id=790, owner_ids=[111])
790
791 mr = testing_helpers.MakeMonorailRequest(
792 project=self.project,
793 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
794 user_info={'user_id': 111})
795 mr.project_name = 'proj'
796 mr.local_id_list = [local_id_1]
797
798 self._MockMethods()
799 post_data = fake.PostData(
800 move_to=['proj2'], can=[1], q=[''],
801 colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
802 self.servlet.response = Response()
803 self.servlet.ProcessFormData(mr, post_data)
804
805 issue = self.services.issue.GetIssueByLocalID(
806 self.cnxn, move_to_project.project_id, local_id_1)
807 self.assertIsNotNone(issue)
808
809 def testProcessFormData_InvalidBlockIssues(self):
810 """Test PFD processes invalid blocked_on and blocking values."""
811 created_issue_1 = fake.MakeTestIssue(
812 789, 1, 'issue summary', 'New', 111, reporter_id=111)
813 self.services.issue.TestAddIssue(created_issue_1)
814 local_id_1 = created_issue_1.local_id
815 mr = testing_helpers.MakeMonorailRequest(
816 project=self.project,
817 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
818 user_info={'user_id': 111})
819 mr.project_name = 'proj'
820 mr.local_id_list = [local_id_1]
821
822 self._MockMethods()
823 post_data = fake.PostData(
824 op_blockedonenter=['append'], blocked_on=['12345'],
825 op_blockingenter=['append'], blocking=['54321'],
826 can=[1], q=[''],
827 colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
828 self.servlet.ProcessFormData(mr, post_data)
829
830 self.assertEqual('Invalid issue ID 12345', mr.errors.blocked_on)
831 self.assertEqual('Invalid issue ID 54321', mr.errors.blocking)
832
833 def testProcessFormData_BlockIssuesOnItself(self):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100834 """Test PFD processes same issue blocked_on and blocking values."""
Copybara854996b2021-09-07 19:36:02 +0000835 created_issue_1 = fake.MakeTestIssue(
836 789, 1, 'issue summary', 'New', 111, reporter_id=111)
837 self.services.issue.TestAddIssue(created_issue_1)
838 local_id_1 = created_issue_1.local_id
839 created_issue_2 = fake.MakeTestIssue(
840 789, 1, 'issue summary', 'New', 111, reporter_id=111)
841 self.services.issue.TestAddIssue(created_issue_2)
842 local_id_2 = created_issue_2.local_id
843 mr = testing_helpers.MakeMonorailRequest(
844 project=self.project,
845 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
846 user_info={'user_id': 111})
847 mr.project_name = 'proj'
848 mr.local_id_list = [local_id_1, local_id_2]
849
850 self._MockMethods()
851 post_data = fake.PostData(
852 op_blockedonenter=['append'], blocked_on=[str(local_id_1)],
853 op_blockingenter=['append'], blocking=[str(local_id_2)],
854 can=[1], q=[''],
855 colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
856 self.servlet.ProcessFormData(mr, post_data)
857
858 self.assertEqual('Cannot block an issue on itself.', mr.errors.blocked_on)
859 self.assertEqual('Cannot block an issue on itself.', mr.errors.blocking)
860
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100861 def testProcessFormData_BlockIssuesOnArchivedProject(self):
862 """Test PFD processes blocked_on and blocking issues without permissions."""
863 created_issue_1 = fake.MakeTestIssue(
864 789, 1, 'issue summary', 'New', 111, reporter_id=111)
865 self.services.issue.TestAddIssue(created_issue_1)
866 local_id_1 = created_issue_1.local_id
867 # Add issue to archived project.
868 archived_proj = self.services.project.TestAddProject(
869 name='archived-proj', project_id=789987, owner_ids=[111])
870 archived_proj.state = project_pb2.ProjectState.ARCHIVED
871 archived_iid = 2
872 created_issue_2 = fake.MakeTestIssue(
873 789987, archived_iid, 'issue summary', 'New', 111, reporter_id=111)
874 self.services.issue.TestAddIssue(created_issue_2)
875 mr = testing_helpers.MakeMonorailRequest(
876 project=self.project,
877 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
878 user_info={'user_id': 111})
879 mr.project_name = 'proj'
880 mr.local_id_list = [local_id_1]
881
882 global_id = 'archived-proj:2'
883 self._MockMethods()
884 post_data = fake.PostData(
885 op_blockedonenter=['append'],
886 blocked_on=[global_id],
887 op_blockingenter=['append'],
888 blocking=[global_id],
889 can=[1],
890 q=[''],
891 colspec=[''],
892 sort=[''],
893 groupby=[''],
894 start=[0],
895 num=[100])
896 self.servlet.ProcessFormData(mr, post_data)
897
898 self.assertEqual(
899 'Target issue %s cannot be modified' % archived_iid,
900 mr.errors.blocked_on)
901 self.assertEqual(
902 'Target issue %s cannot be modified' % archived_iid, mr.errors.blocking)
903
Copybara854996b2021-09-07 19:36:02 +0000904 @mock.patch('framework.cloud_tasks_helpers.create_task')
905 def testProcessFormData_NormalBlockIssues(self, _create_task_mock):
906 """Test PFD processes blocked_on and blocking values."""
907 created_issue_1 = fake.MakeTestIssue(
908 789, 1, 'issue summary', 'New', 111, reporter_id=111)
909 self.services.issue.TestAddIssue(created_issue_1)
910 local_id_1 = created_issue_1.local_id
911
912 created_issueid = fake.MakeTestIssue(
913 789, 2, 'blocking', 'New', 111, reporter_id=111)
914 self.services.issue.TestAddIssue(created_issueid)
915 blocking_id = created_issueid.local_id
916
917 created_issueid = fake.MakeTestIssue(
918 789, 3, 'blocked on', 'New', 111, reporter_id=111)
919 self.services.issue.TestAddIssue(created_issueid)
920 blocked_on_id = created_issueid.local_id
921 mr = testing_helpers.MakeMonorailRequest(
922 project=self.project,
923 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
924 user_info={'user_id': 111})
925 mr.project_name = 'proj'
926 mr.local_id_list = [local_id_1]
927
928 self._MockMethods()
929 post_data = fake.PostData(
930 op_blockedonenter=['append'], blocked_on=[str(blocked_on_id)],
931 op_blockingenter=['append'], blocking=[str(blocking_id)],
932 can=[1], q=[''],
933 colspec=[''], sort=[''], groupby=[''], start=[0], num=[100])
934 self.servlet.ProcessFormData(mr, post_data)
935
936 self.assertIsNone(mr.errors.blocked_on)
937 self.assertIsNone(mr.errors.blocking)
938
939 def testProcessFormData_TooLongComment(self):
940 """Test PFD rejects comments that are too long."""
941 created_issue_1 = fake.MakeTestIssue(
942 789, 1, 'issue summary', 'New', 111, reporter_id=111)
943 self.services.issue.TestAddIssue(created_issue_1)
944 local_id_1 = created_issue_1.local_id
945
946 mr = testing_helpers.MakeMonorailRequest(
947 project=self.project,
948 perms=permissions.OWNER_ACTIVE_PERMISSIONSET,
949 user_info={'user_id': 111})
950 mr.local_id_list = [local_id_1]
951
952 post_data = fake.PostData(
953 owner=['owner@example.com'],
954 can=[1],
955 q=[''],
956 colspec=[''],
957 sort=[''],
958 groupby=[''],
959 start=[0],
960 num=[100],
961 comment=[' ' + 'c' * tracker_constants.MAX_COMMENT_CHARS + ' '])
962 self._MockMethods()
963 self.servlet.ProcessFormData(mr, post_data)
964 self.assertTrue(mr.errors.AnyErrors())
965 self.assertEqual('Comment is too long.', mr.errors.comment)