blob: b18f70bd63ba2da5953f78713f6fcedda5c03537 [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 the issueentry servlet."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020010try:
11 from mox3 import mox
12except ImportError:
13 import mox
Copybara854996b2021-09-07 19:36:02 +000014import time
15import unittest
16
17import ezt
18
19from google.appengine.ext import testbed
20from mock import Mock, patch
Copybara854996b2021-09-07 19:36:02 +000021
22from framework import framework_bizobj
23from framework import framework_views
24from framework import permissions
25from services import service_manager
26from services import template_svc
27from testing import fake
28from testing import testing_helpers
29from tracker import issueentry
30from tracker import tracker_bizobj
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010031from mrproto import tracker_pb2
32from mrproto import user_pb2
Copybara854996b2021-09-07 19:36:02 +000033
34
35class IssueEntryTest(unittest.TestCase):
36 def setUp(self):
37 self.testbed = testbed.Testbed()
38 self.testbed.activate()
39 self.testbed.init_memcache_stub()
40 self.testbed.init_datastore_v3_stub()
41 # Load queue.yaml.
42
43 self.services = service_manager.Services(
44 config=fake.ConfigService(),
45 issue=fake.IssueService(),
46 user=fake.UserService(),
47 usergroup=fake.UserGroupService(),
48 project=fake.ProjectService(),
49 template=Mock(spec=template_svc.TemplateService),
50 features=fake.FeaturesService())
51 self.project = self.services.project.TestAddProject('proj', project_id=987)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010052 self.servlet = issueentry.IssueEntry(services=self.services)
Copybara854996b2021-09-07 19:36:02 +000053 self.user = self.services.user.TestAddUser('to_pass_tests', 0)
54 self.services.features.TestAddHotlist(
55 name='dontcare', summary='', owner_ids=[0])
56 self.template = testing_helpers.DefaultTemplates()[1]
57 self.services.template.GetTemplateByName = Mock(return_value=self.template)
58 self.services.template.GetTemplateSetForProject = Mock(
59 return_value=[(1, 'name', False)])
60
61 # Set-up for testing hotlist parsing.
62 # Scenario:
63 # Users: U1, U2, and U3
64 # Hotlists:
65 # H1: owned by U1 (private)
66 # H2: owned by U2, can be edited by U1 (private)
67 # H2: owned by U3, can be edited by U1 and U2 (public)
68 self.cnxn = fake.MonorailConnection()
69 self.U1 = self.services.user.TestAddUser('U1', 111)
70 self.U2 = self.services.user.TestAddUser('U2', 222)
71 self.U3 = self.services.user.TestAddUser('U3', 333)
72
73 self.H1 = self.services.features.TestAddHotlist(
74 name='H1', summary='', owner_ids=[111], is_private=True)
75 self.H2 = self.services.features.TestAddHotlist(
76 name='H2', summary='', owner_ids=[222], editor_ids=[111],
77 is_private=True)
78 self.H2_U3 = self.services.features.TestAddHotlist(
79 name='H2', summary='', owner_ids=[333], editor_ids=[111, 222],
80 is_private=False)
81
82 self.mox = mox.Mox()
83
84 def tearDown(self):
85 self.testbed.deactivate()
86 self.mox.UnsetStubs()
87 self.mox.ResetAll()
88
89 def testAssertBasePermission(self):
90 """Permit users with CREATE_ISSUE."""
91 mr = testing_helpers.MakeMonorailRequest(
92 path='/p/proj/issues/entry', services=self.services,
93 perms=permissions.EMPTY_PERMISSIONSET)
94 self.assertRaises(permissions.PermissionException,
95 self.servlet.AssertBasePermission, mr)
96 mr = testing_helpers.MakeMonorailRequest(
97 path='/p/proj/issues/entry', services=self.services,
98 perms=permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET)
99 self.servlet.AssertBasePermission(mr)
100
101 def testDiscardUnusedTemplateLabelPrefixes(self):
102 labels = ['pre-val', 'other-value', 'oneword', 'x', '-y', '-w-z', '', '-']
103 self.assertEqual(labels,
104 issueentry._DiscardUnusedTemplateLabelPrefixes(labels))
105
106 labels = ['prefix-value', 'other-?', 'third-', '', '-', '-?']
107 self.assertEqual(['prefix-value', 'third-', '', '-'],
108 issueentry._DiscardUnusedTemplateLabelPrefixes(labels))
109
110 def testGatherPageData(self):
111 user = self.services.user.TestAddUser('user@invalid', 100)
112 mr = testing_helpers.MakeMonorailRequest(
113 path='/p/proj/issues/entry', services=self.services)
114 mr.perms = permissions.PermissionSet(
115 [permissions.CREATE_ISSUE, permissions.EDIT_ISSUE])
116 mr.auth.user_view = framework_views.MakeUserView(
117 'cnxn', self.services.user, 100)
118 mr.auth.effective_ids = {100}
119 mr.template_name = 'rutabaga'
120
121 self.mox.StubOutWithMock(self.services.user, 'GetUser')
122 self.services.user.GetUser(
123 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
124 self.mox.ReplayAll()
125 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
126 config.field_defs = [
127 tracker_bizobj.MakeFieldDef(
128 22, mr.project_id, 'NotEnum', tracker_pb2.FieldTypes.STR_TYPE, None,
129 '', False, False, False, None, None, '', False, '', '',
130 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
131 tracker_bizobj.MakeFieldDef(
132 23, mr.project_id, 'Choices', tracker_pb2.FieldTypes.ENUM_TYPE,
133 None, '', False, False, False, None, None, '', False, '', '',
134 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
135 tracker_bizobj.MakeFieldDef(
136 24,
137 mr.project_id,
138 'RestrictedField',
139 tracker_pb2.FieldTypes.STR_TYPE,
140 None,
141 '',
142 False,
143 False,
144 False,
145 None,
146 None,
147 '',
148 False,
149 '',
150 '',
151 tracker_pb2.NotifyTriggers.NEVER,
152 'no_action',
153 'doc',
154 False,
155 is_restricted_field=True)
156 ]
157 self.services.config.StoreConfig(mr.cnxn, config)
158 template = tracker_pb2.TemplateDef(
159 labels=['NotEnum-Not-Masked', 'Choices-Masked'])
160 self.services.template.GetTemplateByName.return_value = template
161
162 page_data = self.servlet.GatherPageData(mr)
163 self.mox.VerifyAll()
164 self.assertEqual(page_data['initial_owner'], 'user@invalid')
165 self.assertEqual(page_data['initial_status'], 'New')
166 self.assertTrue(page_data['clear_summary_on_click'])
167 self.assertTrue(page_data['must_edit_summary'])
168 self.assertEqual(page_data['labels'], ['NotEnum-Not-Masked'])
169 self.assertEqual(page_data['offer_templates'], ezt.boolean(False))
170 self.assertEqual(page_data['fields'][0].is_editable, ezt.boolean(True))
171 self.assertEqual(page_data['fields'][1].is_editable, ezt.boolean(True))
172 self.assertEqual(page_data['fields'][2].is_editable, ezt.boolean(False))
173 self.assertEqual(page_data['uneditable_fields'], ezt.boolean(True))
174
175 def testGatherPageData_Approvals(self):
176 user = self.services.user.TestAddUser('user@invalid', 100)
177 mr = testing_helpers.MakeMonorailRequest(
178 path='/p/proj/issues/entry', services=self.services)
179 mr.auth.user_view = framework_views.MakeUserView(
180 'cnxn', self.services.user, 100)
181 mr.template_name = 'rutabaga'
182
183 self.mox.StubOutWithMock(self.services.user, 'GetUser')
184 self.services.user.GetUser(
185 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
186 self.mox.ReplayAll()
187 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
188 config.field_defs = [
189 tracker_bizobj.MakeFieldDef(
190 24, mr.project_id, 'UXReview',
191 tracker_pb2.FieldTypes.APPROVAL_TYPE, None, '', False, False,
192 False, None, None, '', False, '', '',
193 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False)]
194 self.services.config.StoreConfig(mr.cnxn, config)
195 template = tracker_pb2.TemplateDef()
196 template.phases = [tracker_pb2.Phase(
197 phase_id=1, rank=4, name='Stable')]
198 template.approval_values = [tracker_pb2.ApprovalValue(
199 approval_id=24, phase_id=1,
200 status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)]
201 self.services.template.GetTemplateByName.return_value = template
202
203 page_data = self.servlet.GatherPageData(mr)
204 self.mox.VerifyAll()
205 self.assertEqual(page_data['approvals'][0].field_name, 'UXReview')
206 self.assertEqual(page_data['initial_phases'][0],
207 tracker_pb2.Phase(phase_id=1, name='Stable', rank=4))
208 self.assertEqual(page_data['prechecked_approvals'], ['24_phase_0'])
209 self.assertEqual(page_data['required_approval_ids'], [24])
210
211 # phase fields row shown when config contains phase fields.
212 config.field_defs.append(tracker_bizobj.MakeFieldDef(
213 26, mr.project_id, 'GateTarget',
214 tracker_pb2.FieldTypes.INT_TYPE, None, '', False, False, False,
215 None, None, '', False, '', '', tracker_pb2.NotifyTriggers.NEVER,
216 'no_action', 'doc', False, is_phase_field=True))
217 self.services.config.StoreConfig(mr.cnxn, config)
218 page_data = self.servlet.GatherPageData(mr)
219 self.assertEqual(page_data['issue_phase_names'], ['stable'])
220
221 # approval subfields in config hidden when chosen template does not contain
222 # its parent approval
223 template = tracker_pb2.TemplateDef()
224 self.services.template.GetTemplateByName.return_value = template
225 page_data = self.servlet.GatherPageData(mr)
226 self.assertEqual(page_data['approvals'], [])
227 # phase fields row hidden when template has no phases
228 self.assertEqual(page_data['issue_phase_names'], [])
229
230 # TODO(jojwang): monorail:6305, remove this test when Edit perms
231 # for field values are implemented.
232 def testGatherPageData_FLTSpecialFields(self):
233 user = self.services.user.TestAddUser('user@invalid', 100)
234 mr = testing_helpers.MakeMonorailRequest(
235 path='/p/proj/issues/entry', services=self.services)
236 mr.auth.user_view = framework_views.MakeUserView(
237 'cnxn', self.services.user, 100)
238 mr.template_name = 'rutabaga'
239
240 self.mox.StubOutWithMock(self.services.user, 'GetUser')
241 self.services.user.GetUser(
242 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
243 self.mox.ReplayAll()
244 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
245 config.field_defs = [
246 tracker_bizobj.MakeFieldDef(
247 25, mr.project_id, 'nOtice',
248 tracker_pb2.FieldTypes.STR_TYPE, None, '', False, False,
249 False, None, None, '', False, '', '',
250 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
251 tracker_bizobj.MakeFieldDef(
252 24, mr.project_id, 'M-Target',
253 tracker_pb2.FieldTypes.STR_TYPE, None, '', False, False,
254 False, None, None, '', False, '', '',
255 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
256 tracker_bizobj.MakeFieldDef(
257 25, mr.project_id, 'whitepaper',
258 tracker_pb2.FieldTypes.STR_TYPE, None, '', False, False,
259 False, None, None, '', False, '', '',
260 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
261 tracker_bizobj.MakeFieldDef(
262 25, mr.project_id, 'm-approved',
263 tracker_pb2.FieldTypes.STR_TYPE, None, '', False, False,
264 False, None, None, '', False, '', '',
265 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'doc', False),
266 ]
267
268 self.services.config.StoreConfig(mr.cnxn, config)
269 template = tracker_pb2.TemplateDef()
270 self.services.template.GetTemplateByName.return_value = template
271
272 page_data = self.servlet.GatherPageData(mr)
273 self.mox.VerifyAll()
274 self.assertEqual(page_data['fields'][0].field_name, 'M-Target')
275 self.assertEqual(len(page_data['fields']), 1)
276
277 def testGatherPageData_DefaultOwnerAvailability(self):
278 user = self.services.user.TestAddUser('user@invalid', 100)
279 mr = testing_helpers.MakeMonorailRequest(
280 path='/p/proj/issues/entry', services=self.services)
281 mr.auth.user_view = framework_views.MakeUserView(
282 'cnxn', self.services.user, 100)
283 mr.template_name = 'rutabaga'
284
285 self.mox.StubOutWithMock(self.services.user, 'GetUser')
286 self.services.user.GetUser(
287 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
288 self.mox.ReplayAll()
289
290 page_data = self.servlet.GatherPageData(mr)
291 self.mox.VerifyAll()
292 self.assertEqual(page_data['initial_owner'], 'user@invalid')
293 self.assertEqual(page_data['owner_avail_state'], 'never')
294 self.assertEqual(
295 page_data['owner_avail_message_short'],
296 'User never visited')
297
298 user.last_visit_timestamp = int(time.time())
299 mr.auth.user_view = framework_views.MakeUserView(
300 'cnxn', self.services.user, 100)
301 page_data = self.servlet.GatherPageData(mr)
302 self.mox.VerifyAll()
303 self.assertEqual(page_data['initial_owner'], 'user@invalid')
304 self.assertEqual(page_data['owner_avail_state'], None)
305 self.assertEqual(page_data['owner_avail_message_short'], '')
306
307 def testGatherPageData_TemplateAllowsKeepingSummary(self):
308 mr = testing_helpers.MakeMonorailRequest(
309 path='/p/proj/issues/entry', services=self.services)
310 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
311 mr.template_name = 'rutabaga'
312 user = self.services.user.TestAddUser('user@invalid', 100)
313
314 self.mox.StubOutWithMock(self.services.user, 'GetUser')
315 self.services.user.GetUser(
316 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
317 self.mox.ReplayAll()
318 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
319 self.services.config.StoreConfig(mr.cnxn, config)
320 self.template.summary_must_be_edited = False
321
322 page_data = self.servlet.GatherPageData(mr)
323 self.mox.VerifyAll()
324 self.assertEqual(page_data['initial_owner'], 'user@invalid')
325 self.assertEqual(page_data['initial_status'], 'New')
326 self.assertFalse(page_data['clear_summary_on_click'])
327 self.assertFalse(page_data['must_edit_summary'])
328
329 def testGatherPageData_DeepLinkSetsSummary(self):
330 mr = testing_helpers.MakeMonorailRequest(
331 path='/p/proj/issues/entry?summary=foo', services=self.services)
332 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
333 user = self.services.user.TestAddUser('user@invalid', 100)
334 mr.template_name = 'rutabaga'
335
336 self.mox.StubOutWithMock(self.services.user, 'GetUser')
337 self.services.user.GetUser(
338 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
339 self.mox.ReplayAll()
340
341 page_data = self.servlet.GatherPageData(mr)
342 self.mox.VerifyAll()
343 self.assertEqual(page_data['initial_owner'], 'user@invalid')
344 self.assertEqual(page_data['initial_status'], 'New')
345 self.assertFalse(page_data['clear_summary_on_click'])
346 self.assertTrue(page_data['must_edit_summary'])
347
348 @patch('framework.framework_bizobj.UserIsInProject')
349 def testGatherPageData_MembersOnlyTemplatesExcluded(self,
350 mockUserIsInProject):
351 """Templates with members_only=True are excluded from results
352 when the user is not a member of the project."""
353 mr = testing_helpers.MakeMonorailRequest(
354 path='/p/proj/issues/entry', services=self.services)
355 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
356 user = self.services.user.TestAddUser('user@invalid', 100)
357 mr.template_name = 'rutabaga'
358 self.services.template.GetTemplateSetForProject = Mock(
359 return_value=[(1, 'one', False), (2, 'two', True)])
360 mockUserIsInProject.return_value = False
361
362 self.mox.StubOutWithMock(self.services.user, 'GetUser')
363 self.services.user.GetUser(
364 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
365 self.mox.ReplayAll()
366
367 page_data = self.servlet.GatherPageData(mr)
368 self.mox.VerifyAll()
369 self.assertEqual(page_data['config'].template_names, ['one'])
370
371 @patch('framework.framework_bizobj.UserIsInProject')
372 def testGatherPageData_DefaultTemplatesMember(self, mockUserIsInProject):
373 """If no template is specified, the default one is used based on
374 whether the user is a project member."""
375 mr = testing_helpers.MakeMonorailRequest(
376 path='/p/proj/issues/entry', services=self.services)
377 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
378 user = self.services.user.TestAddUser('user@invalid', 100)
379 self.services.template.GetTemplateSetForProject = Mock(
380 return_value=[(1, 'one', False), (2, 'two', True)])
381 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
382 config.default_template_for_users = 456
383 config.default_template_for_developers = 789
384 self.services.config.StoreConfig(mr.cnxn, config)
385
386 mockUserIsInProject.return_value = True
387 self.services.template.GetTemplateById = Mock(return_value=self.template)
388 self.mox.StubOutWithMock(self.services.user, 'GetUser')
389 self.services.user.GetUser(
390 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
391
392 self.mox.ReplayAll()
393 self.servlet.GatherPageData(mr)
394 self.mox.VerifyAll()
395
396 call_args = self.services.template.GetTemplateById.call_args[0]
397 self.assertEqual(call_args[1], 789)
398
399 @patch('framework.framework_bizobj.UserIsInProject')
400 def testGatherPageData_DefaultTemplatesNonMember(self, mockUserIsInProject):
401 """If no template is specified, the default one is used based on
402 whether the user is not a project member."""
403 mr = testing_helpers.MakeMonorailRequest(
404 path='/p/proj/issues/entry', services=self.services)
405 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
406 user = self.services.user.TestAddUser('user@invalid', 100)
407 self.services.template.GetTemplateSetForProject = Mock(
408 return_value=[(1, 'one', False), (2, 'two', True)])
409 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
410 config.default_template_for_users = 456
411 config.default_template_for_developers = 789
412 self.services.config.StoreConfig(mr.cnxn, config)
413
414 mockUserIsInProject.return_value = False
415 self.services.template.GetTemplateById = Mock(return_value=self.template)
416 self.mox.StubOutWithMock(self.services.user, 'GetUser')
417 self.services.user.GetUser(
418 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
419
420 self.mox.ReplayAll()
421 self.servlet.GatherPageData(mr)
422 self.mox.VerifyAll()
423
424 call_args = self.services.template.GetTemplateById.call_args[0]
425 self.assertEqual(call_args[1], 456)
426
427 def testGatherPageData_MissingDefaultTemplates(self):
428 """If the default templates were deleted, pick the first template."""
429 mr = testing_helpers.MakeMonorailRequest(
430 path='/p/proj/issues/entry', services=self.services)
431 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
432 user = self.services.user.TestAddUser('user@invalid', 100)
433 self.services.template.GetTemplateSetForProject = Mock(
434 return_value=[(1, 'one', False), (2, 'two', True)])
435
436 self.services.template.GetTemplateById.return_value = None
437 self.services.template.GetProjectTemplates.return_value = [
438 tracker_pb2.TemplateDef(members_only=True),
439 tracker_pb2.TemplateDef(members_only=False)]
440 self.mox.StubOutWithMock(self.services.user, 'GetUser')
441 self.services.user.GetUser(
442 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
443
444 self.mox.ReplayAll()
445 page_data = self.servlet.GatherPageData(mr)
446 self.mox.VerifyAll()
447
448 self.assertTrue(self.services.template.GetProjectTemplates.called)
449 self.assertTrue(page_data['config'].template_view.members_only)
450
451 def testGatherPageData_IncorrectTemplate(self):
452 """The handler shouldn't error out if passed a non-existent template."""
453 mr = testing_helpers.MakeMonorailRequest(
454 path='/p/proj/issues/entry', services=self.services)
455 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
456 mr.template_name = 'rutabaga'
457
458 user = self.services.user.TestAddUser('user@invalid', 100)
459 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
460 config.default_template_for_users = 456
461 config.default_template_for_developers = 789
462 self.services.config.StoreConfig(mr.cnxn, config)
463
464 self.services.template.GetTemplateSetForProject.return_value = [
465 (1, 'one', False), (2, 'two', True)]
466 self.services.template.GetTemplateByName.return_value = None
467 self.services.template.GetTemplateById.return_value = \
468 tracker_pb2.TemplateDef(template_id=123, labels=['yo'])
469 self.services.template.GetProjectTemplates.return_value = [
470 tracker_pb2.TemplateDef(labels=['no']),
471 tracker_pb2.TemplateDef(labels=['maybe'])]
472 self.mox.StubOutWithMock(self.services.user, 'GetUser')
473 self.services.user.GetUser(
474 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
475
476 self.mox.ReplayAll()
477 page_data = self.servlet.GatherPageData(mr)
478 self.mox.VerifyAll()
479
480 self.assertTrue(self.services.template.GetTemplateByName.called)
481 self.assertTrue(self.services.template.GetTemplateById.called)
482 self.assertFalse(self.services.template.GetProjectTemplates.called)
483 self.assertEqual(page_data['config'].template_view.label0, 'yo')
484
485 def testGatherPageData_RestrictNewIssues(self):
486 """Users with this pref set default to reporting issues with R-V-G."""
487 self.mox.ReplayAll()
488 mr = testing_helpers.MakeMonorailRequest(
489 path='/p/proj/issues/entry', services=self.services)
490 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
491 user = self.services.user.TestAddUser('user@invalid', 100)
492 self.services.user.GetUser = Mock(return_value=user)
493 self.services.template.GetTemplateById = Mock(return_value=self.template)
494
495 mr.auth.user_id = 100
496 page_data = self.servlet.GatherPageData(mr)
497 self.assertNotIn('Restrict-View-Google', page_data['labels'])
498
499 pref = user_pb2.UserPrefValue(name='restrict_new_issues', value='true')
500 self.services.user.SetUserPrefs(self.cnxn, 100, [pref])
501 page_data = self.servlet.GatherPageData(mr)
502 self.assertIn('Restrict-View-Google', page_data['labels'])
503
504 def testGatherHelpData_Anon(self):
505 mr = testing_helpers.MakeMonorailRequest(
506 path='/p/proj/issues/entry', project=self.project)
507 mr.auth.user_pb = user_pb2.User()
508 mr.auth.user_id = 0
509
510 help_data = self.servlet.GatherHelpData(mr, {})
511 self.assertEqual(
512 {'account_cue': None,
513 'cue': None,
514 'is_privileged_domain_user': None},
515 help_data)
516
517 def testGatherHelpData_NewUser(self):
518 mr = testing_helpers.MakeMonorailRequest(
519 path='/p/proj/issues/entry', project=self.project)
520 mr.auth.user_pb = user_pb2.User(user_id=111)
521 mr.auth.user_id = 111
522
523 help_data = self.servlet.GatherHelpData(mr, {})
524 self.assertEqual(
525 {'account_cue': None,
526 'cue': 'privacy_click_through',
527 'is_privileged_domain_user': None},
528 help_data)
529
530 def testGatherHelpData_AlreadyClickedThroughPrivacy(self):
531 mr = testing_helpers.MakeMonorailRequest(
532 path='/p/proj/issues/entry', project=self.project)
533 mr.auth.user_pb = user_pb2.User(user_id=111)
534 mr.auth.user_id = 111
535 self.services.user.SetUserPrefs(
536 self.cnxn, 111,
537 [user_pb2.UserPrefValue(name='privacy_click_through', value='true')])
538
539 help_data = self.servlet.GatherHelpData(mr, {})
540 self.assertEqual(
541 {'account_cue': None,
542 'cue': 'code_of_conduct',
543 'is_privileged_domain_user': None},
544 help_data)
545
546 def testGatherHelpData_DismissedEverything(self):
547 mr = testing_helpers.MakeMonorailRequest(
548 path='/p/proj/issues/entry', project=self.project)
549 mr.auth.user_pb = user_pb2.User(user_id=111)
550 mr.auth.user_id = 111
551 self.services.user.SetUserPrefs(
552 self.cnxn, 111,
553 [user_pb2.UserPrefValue(name='privacy_click_through', value='true'),
554 user_pb2.UserPrefValue(name='code_of_conduct', value='true')])
555
556 help_data = self.servlet.GatherHelpData(mr, {})
557 self.assertEqual(
558 {'account_cue': None,
559 'cue': None,
560 'is_privileged_domain_user': None},
561 help_data)
562
563 @patch('framework.cloud_tasks_helpers.create_task')
564 def testProcessFormData_RedirectToEnteredIssue(self, _create_task_mock):
565 mr = testing_helpers.MakeMonorailRequest(
566 path='/p/proj/issues/entry', project=self.project)
567 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
568 mr.template_name = 'rutabaga'
569 mr.auth.effective_ids = set([100])
570 post_data = fake.PostData(
571 template_name=['rutabaga'],
572 summary=['fake summary'],
573 comment=['fake comment'],
574 status=['New'])
575
576 self.mox.ReplayAll()
577 url = self.servlet.ProcessFormData(mr, post_data)
578
579 self.mox.VerifyAll()
580 self.assertTrue('/p/proj/issues/detail?id=' in url)
581
582 @patch('framework.cloud_tasks_helpers.create_task')
583 def testProcessFormData_AcceptWithFields(self, _create_task_mock):
584 """We can create new issues with custom fields (restricted or not)."""
585 mr = testing_helpers.MakeMonorailRequest(
586 path='/p/proj/issues/entry', project=self.project)
587 mr.auth.user_view = framework_views.StuffUserView(100, 'admin@test', True)
588 mr.template_name = 'rutabaga'
589 mr.auth.effective_ids = set([100])
590 config = self.services.config.GetProjectConfig(
591 mr.cnxn, self.project.project_id)
592 config.field_defs = [
593 tracker_bizobj.MakeFieldDef(
594 1, 789, 'NonRestrictedField', tracker_pb2.FieldTypes.INT_TYPE, None,
595 '', False, False, False, None, None, '', False, '', '',
596 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'NonRestrictedField',
597 False),
598 tracker_bizobj.MakeFieldDef(
599 2,
600 789,
601 'RestrictedField',
602 tracker_pb2.FieldTypes.INT_TYPE,
603 None,
604 '',
605 False,
606 False,
607 False,
608 None,
609 None,
610 '',
611 False,
612 '',
613 '',
614 tracker_pb2.NotifyTriggers.NEVER,
615 'no_action',
616 'RestrictedField',
617 False,
618 is_restricted_field=True),
619 tracker_bizobj.MakeFieldDef(
620 3,
621 789,
622 'RestrictedEnumField',
623 tracker_pb2.FieldTypes.ENUM_TYPE,
624 None,
625 '',
626 False,
627 False,
628 False,
629 None,
630 None,
631 '',
632 False,
633 '',
634 '',
635 tracker_pb2.NotifyTriggers.NEVER,
636 'no_action',
637 'RestrictedEnumField',
638 False,
639 is_restricted_field=True)
640 ]
641 self.services.config.StoreConfig(mr.cnxn, config)
642 post_data = fake.PostData(
643 template_name=['rutabaga'],
644 summary=['fake summary'],
645 comment=['fake comment'],
646 custom_1=['3'],
647 custom_2=['7'],
648 label=['RestrictedEnumField-7'],
649 status=['New'])
650
651 self.mox.ReplayAll()
652 url = self.servlet.ProcessFormData(mr, post_data)
653
654 self.mox.VerifyAll()
655 self.assertTrue('/p/proj/issues/detail?id=' in url)
656 field_values = self.services.issue.issues_by_project[987][1].field_values
657 self.assertEqual(
658 self.services.issue.issues_by_project[987][1].labels,
659 ['RestrictedEnumField-7'])
660 self.assertEqual(field_values[0].int_value, 3)
661 self.assertEqual(field_values[1].int_value, 7)
662
663 @patch('framework.cloud_tasks_helpers.create_task')
664 def testProcessFormData_AcceptEnforceTemplateRestrictedDefaultValues(
665 self, _create_task_mock):
666 """The template applies default vals on fields that the user cannot edit."""
667 mr = testing_helpers.MakeMonorailRequest(
668 path='/p/proj/issues/entry', project=self.project)
669 mr.auth.user_view = framework_views.StuffUserView(100, 'admin@test', True)
670 mr.template_name = 'rutabaga'
671 mr.auth.effective_ids = set([100])
672 mr.perms = permissions.PermissionSet([])
673 config = self.services.config.GetProjectConfig(
674 mr.cnxn, self.project.project_id)
675 config.field_defs = [
676 tracker_bizobj.MakeFieldDef(
677 1, 789, 'NonRestrictedField', tracker_pb2.FieldTypes.INT_TYPE, None,
678 '', False, False, False, None, None, '', False, '', '',
679 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'NonRestrictedField',
680 False),
681 tracker_bizobj.MakeFieldDef(
682 2,
683 789,
684 'RestrictedField',
685 tracker_pb2.FieldTypes.INT_TYPE,
686 None,
687 '',
688 False,
689 False,
690 False,
691 None,
692 None,
693 '',
694 False,
695 '',
696 '',
697 tracker_pb2.NotifyTriggers.NEVER,
698 'no_action',
699 'RestrictedField',
700 False,
701 is_restricted_field=True),
702 tracker_bizobj.MakeFieldDef(
703 3,
704 789,
705 'RestrictedEnumField',
706 tracker_pb2.FieldTypes.ENUM_TYPE,
707 None,
708 '',
709 False,
710 False,
711 False,
712 None,
713 None,
714 '',
715 False,
716 '',
717 '',
718 tracker_pb2.NotifyTriggers.NEVER,
719 'no_action',
720 'RestrictedEnumField',
721 False,
722 is_restricted_field=True)
723 ]
724 self.services.config.StoreConfig(mr.cnxn, config)
725 post_data = fake.PostData(
726 template_name=['rutabaga'],
727 summary=['fake summary'],
728 comment=['fake comment'],
729 custom_1=['3'],
730 label=['Hey'],
731 status=['New'])
732
733 temp_restricted_fv = tracker_bizobj.MakeFieldValue(
734 2, 3737, None, None, None, None, False)
735 self.template.field_values.append(temp_restricted_fv)
736 self.template.labels.append('RestrictedEnumField-b')
737
738 self.mox.ReplayAll()
739 url = self.servlet.ProcessFormData(mr, post_data)
740
741 self.mox.VerifyAll()
742 self.assertTrue('/p/proj/issues/detail?id=' in url)
743 field_values = self.services.issue.issues_by_project[987][1].field_values
744 self.assertEqual(
745 self.services.issue.issues_by_project[987][1].labels,
746 ['Hey', 'RestrictedEnumField-b'])
747 self.assertEqual(field_values[0].int_value, 3)
748 self.assertEqual(field_values[1].int_value, 3737)
749
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100750 def testProcessFormData_RejectNewLabels(self):
751 """We raise an AssertionError when new labels are added."""
752 mr = testing_helpers.MakeMonorailRequest(path='/p/proj/issues/entry')
753 mr.perms = permissions.USER_PERMISSIONSET
754 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
755 post_data = fake.PostData(
756 template_name=['rutabaga'],
757 summary=['Nya nya I modified the summary'],
758 comment=[self.template.content],
759 status=['New'],
760 label=['freeze_new_label'])
761
762 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
763 self.servlet.PleaseCorrect(
764 mr,
765 component_required=None,
766 fields=[],
767 initial_blocked_on='',
768 initial_blocking='',
769 initial_cc='',
770 initial_comment=self.template.content,
771 initial_components='',
772 initial_owner='',
773 initial_status='New',
774 initial_summary='Nya nya I modified the summary',
775 initial_hotlists='',
776 labels=['freeze_new_label'],
777 template_name='rutabaga')
778 self.mox.ReplayAll()
779 url = self.servlet.ProcessFormData(mr, post_data)
780 self.mox.VerifyAll()
781 self.assertEqual(
782 (
783 "The creation of new labels is blocked for the Chromium project"
784 " in Monorail. To continue with editing your issue, please"
785 " remove: freeze_new_label label(s)."),
786 mr.errors.labels,
787 )
788 self.assertIsNone(url)
789
Copybara854996b2021-09-07 19:36:02 +0000790 def testProcessFormData_RejectRestrictedFields(self):
791 """We raise an AssertionError when restricted fields are set w/o perms."""
792 mr = testing_helpers.MakeMonorailRequest(
793 path='/p/proj/issues/entry', project=self.project)
794 mr.auth.user_view = framework_views.StuffUserView(
795 100, 'non-admin@test', True)
796 mr.template_name = 'rutabaga'
797 mr.auth.effective_ids = set([100])
798 mr.perms = permissions.PermissionSet([])
799 config = self.services.config.GetProjectConfig(
800 mr.cnxn, self.project.project_id)
801 config.field_defs = [
802 tracker_bizobj.MakeFieldDef(
803 1, 789, 'NonRestrictedField', tracker_pb2.FieldTypes.INT_TYPE, None,
804 '', False, False, False, None, None, '', False, '', '',
805 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'NonRestrictedField',
806 False),
807 tracker_bizobj.MakeFieldDef(
808 2,
809 789,
810 'RestrictedField',
811 tracker_pb2.FieldTypes.INT_TYPE,
812 None,
813 '',
814 False,
815 False,
816 False,
817 None,
818 None,
819 '',
820 False,
821 '',
822 '',
823 tracker_pb2.NotifyTriggers.NEVER,
824 'no_action',
825 'RestrictedField',
826 False,
827 is_restricted_field=True),
828 tracker_bizobj.MakeFieldDef(
829 3,
830 789,
831 'RestrictedEnumField',
832 tracker_pb2.FieldTypes.ENUM_TYPE,
833 None,
834 '',
835 False,
836 False,
837 False,
838 None,
839 None,
840 '',
841 False,
842 '',
843 '',
844 tracker_pb2.NotifyTriggers.NEVER,
845 'no_action',
846 'RestrictedEnumField',
847 False,
848 is_restricted_field=True)
849 ]
850 self.services.config.StoreConfig(mr.cnxn, config)
851 post_data_add_fv = fake.PostData(
852 template_name=['rutabaga'],
853 summary=['fake summary'],
854 comment=['fake comment'],
855 custom_1=['3'],
856 custom_2=['7'],
857 status=['New'])
858 post_data_label_edits_enum = fake.PostData(
859 template_name=['rutabaga'],
860 summary=['fake summary'],
861 comment=['fake comment'],
862 label=['RestrictedEnumField-7'],
863 status=['New'])
864
865 self.assertRaises(
866 AssertionError, self.servlet.ProcessFormData, mr, post_data_add_fv)
867 self.assertRaises(
868 AssertionError, self.servlet.ProcessFormData, mr,
869 post_data_label_edits_enum)
870
871 def testProcessFormData_RejectPlacedholderSummary(self):
872 mr = testing_helpers.MakeMonorailRequest(
873 path='/p/proj/issues/entry')
874 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
875 mr.perms = permissions.USER_PERMISSIONSET
876 mr.template_name = 'rutabaga'
877 post_data = fake.PostData(
878 template_name=['rutabaga'],
879 summary=[issueentry.PLACEHOLDER_SUMMARY],
880 comment=['fake comment'],
881 status=['New'])
882
883 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
884 self.servlet.PleaseCorrect(
885 mr, component_required=None, fields=[], initial_blocked_on='',
886 initial_blocking='', initial_cc='', initial_comment='fake comment',
887 initial_components='', initial_owner='', initial_status='New',
888 initial_summary='Enter one-line summary', initial_hotlists='',
889 labels=[], template_name='rutabaga')
890 self.mox.ReplayAll()
891
892 url = self.servlet.ProcessFormData(mr, post_data)
893 self.mox.VerifyAll()
894 self.assertEqual('Summary is required', mr.errors.summary)
895 self.assertIsNone(url)
896
897 def testProcessFormData_RejectUnmodifiedTemplate(self):
898 mr = testing_helpers.MakeMonorailRequest(
899 path='/p/proj/issues/entry')
900 mr.perms = permissions.USER_PERMISSIONSET
901 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
902 post_data = fake.PostData(
903 template_name=['rutabaga'],
904 summary=['Nya nya I modified the summary'],
905 comment=[self.template.content],
906 status=['New'])
907
908 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
909 self.servlet.PleaseCorrect(
910 mr, component_required=None, fields=[], initial_blocked_on='',
911 initial_blocking='', initial_cc='',
912 initial_comment=self.template.content, initial_components='',
913 initial_owner='', initial_status='New',
914 initial_summary='Nya nya I modified the summary', initial_hotlists='',
915 labels=[], template_name='rutabaga')
916 self.mox.ReplayAll()
917
918 url = self.servlet.ProcessFormData(mr, post_data)
919 self.mox.VerifyAll()
920 self.assertEqual('Template must be filled out.', mr.errors.comment)
921 self.assertIsNone(url)
922
923 def testProcessFormData_RejectNonexistentHotlist(self):
924 mr = testing_helpers.MakeMonorailRequest(
925 path='/p/proj/issues/entry', user_info={'user_id': 111})
926 entered_hotlists = 'H3'
927 post_data = fake.PostData(hotlists=[entered_hotlists],
928 template_name=['rutabaga'])
929 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
930 self.servlet.PleaseCorrect(
931 mr, component_required=None, fields=[], initial_blocked_on='',
932 initial_blocking='', initial_cc='', initial_comment='',
933 initial_components='', initial_owner='', initial_status='',
934 initial_summary='', initial_hotlists=entered_hotlists, labels=[],
935 template_name='rutabaga')
936 self.mox.ReplayAll()
937 url = self.servlet.ProcessFormData(mr, post_data)
938 self.mox.VerifyAll()
939 self.assertEqual('You have no hotlist(s) named: H3', mr.errors.hotlists)
940 self.assertIsNone(url)
941
942 def testProcessFormData_RejectNonexistentHotlistOwner(self):
943 mr = testing_helpers.MakeMonorailRequest(
944 path='/p/proj/issues/entry', user_info={'user_id': 111})
945 entered_hotlists = 'abc:H1'
946 post_data = fake.PostData(hotlists=[entered_hotlists],
947 template_name=['rutabaga'])
948 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
949 self.servlet.PleaseCorrect(
950 mr, component_required=None, fields=[], initial_blocked_on='',
951 initial_blocking='', initial_cc='', initial_comment='',
952 initial_components='', initial_owner='', initial_status='',
953 initial_summary='', initial_hotlists=entered_hotlists, labels=[],
954 template_name='rutabaga')
955 self.mox.ReplayAll()
956 url = self.servlet.ProcessFormData(mr, post_data)
957 self.mox.VerifyAll()
958 self.assertEqual('You have no hotlist(s) owned by: abc', mr.errors.hotlists)
959 self.assertIsNone(url)
960
961 def testProcessFormData_RejectInvalidHotlistName(self):
962 mr = testing_helpers.MakeMonorailRequest(
963 path='/p/proj/issues/entry', user_info={'user_id': 111})
964 entered_hotlists = 'U1:H2'
965 post_data = fake.PostData(hotlists=[entered_hotlists],
966 template_name=['rutabaga'])
967 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
968 self.servlet.PleaseCorrect(
969 mr, component_required=None, fields=[], initial_blocked_on='',
970 initial_blocking='', initial_cc='', initial_comment='',
971 initial_components='', initial_owner='', initial_status='',
972 initial_summary='', initial_hotlists=entered_hotlists, labels=[],
973 template_name='rutabaga')
974 self.mox.ReplayAll()
975 url = self.servlet.ProcessFormData(mr, post_data)
976 self.mox.VerifyAll()
977 self.assertEqual('Not in your hotlist(s): U1:H2', mr.errors.hotlists)
978 self.assertIsNone(url)
979
980 def testProcessFormData_RejectDeprecatedComponent(self):
981 mr = testing_helpers.MakeMonorailRequest(
982 path='/p/proj/issues/entry',
983 user_info={'user_id': 111},
984 project=self.project)
985 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
986 config.component_defs = [
987 tracker_bizobj.MakeComponentDef(
988 1, mr.project_id, 'active', '', False, [], [], 0, 0),
989 tracker_bizobj.MakeComponentDef(
990 2, mr.project_id, 'notactive', '', True, [], [], 0, 0),
991 ]
992 self.services.config.StoreConfig(mr.cnxn, config)
993 print(config)
994 post_data = fake.PostData(
995 template_name=['rutabaga'],
996 summary=['fake summary'],
997 comment=['fake comment'],
998 components=['notactive'])
999
1000 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
1001 self.servlet.PleaseCorrect(
1002 mr,
1003 component_required=None,
1004 fields=[],
1005 initial_blocked_on='',
1006 initial_blocking='',
1007 initial_cc='',
1008 initial_comment='fake comment',
1009 initial_components='notactive',
1010 initial_owner='',
1011 initial_status='',
1012 initial_summary='fake summary',
1013 initial_hotlists='',
1014 labels=[],
1015 template_name='rutabaga')
1016 self.mox.ReplayAll()
1017 url = self.servlet.ProcessFormData(mr, post_data)
1018 self.mox.VerifyAll()
1019 self.assertEqual(mr.errors.components, 'Undefined or deprecated component')
1020 self.assertIsNone(url)
1021
1022 @patch('framework.cloud_tasks_helpers.create_task')
1023 def testProcessFormData_TemplateNameMissing(self, _create_task_mock):
1024 """POST doesn't fail if no template_name is passed."""
1025 mr = testing_helpers.MakeMonorailRequest(
1026 path='/p/proj/issues/entry', project=self.project)
1027 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
1028 mr.auth.effective_ids = set([100])
1029
1030 self.services.template.GetTemplateById.return_value = None
1031 self.services.template.GetProjectTemplates.return_value = [
1032 tracker_pb2.TemplateDef(members_only=True, content=''),
1033 tracker_pb2.TemplateDef(members_only=False, content='')]
1034 post_data = fake.PostData(
1035 summary=['fake summary'],
1036 comment=['fake comment'],
1037 status=['New'])
1038
1039 self.mox.ReplayAll()
1040 url = self.servlet.ProcessFormData(mr, post_data)
1041
1042 self.mox.VerifyAll()
1043 self.assertTrue('/p/proj/issues/detail?id=' in url)
1044
1045 @patch('framework.cloud_tasks_helpers.create_task')
1046 def testProcessFormData_AcceptsFederatedReferences(self, _create_task_mock):
1047 """ProcessFormData accepts federated references."""
1048 mr = testing_helpers.MakeMonorailRequest(
1049 path='/p/proj/issues/entry', project=self.project)
1050 mr.auth.user_view = framework_views.StuffUserView(100, 'user@invalid', True)
1051 mr.auth.effective_ids = set([100])
1052
1053 post_data = fake.PostData(
1054 summary=['fake summary'],
1055 comment=['fake comment'],
1056 status=['New'],
1057 template_name='rutabaga',
1058 blocking=['b/123, b/987'],
1059 blockedon=['b/456, b/654'])
1060
1061 self.mox.ReplayAll()
1062 self.servlet.ProcessFormData(mr, post_data)
1063
1064 self.mox.VerifyAll()
1065 self.assertIsNone(mr.errors.blockedon)
1066 self.assertIsNone(mr.errors.blocking)
1067
1068 def testAttachDefaultApprovers(self):
1069 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1070 config.approval_defs = [
1071 tracker_pb2.ApprovalDef(
1072 approval_id=23, approver_ids=[222], survey='Question?'),
1073 tracker_pb2.ApprovalDef(
1074 approval_id=24, approver_ids=[111], survey='Question?')]
1075 approval_values = [tracker_pb2.ApprovalValue(
1076 approval_id=24, phase_id=1,
1077 status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)]
1078 issueentry._AttachDefaultApprovers(config, approval_values)
1079 self.assertEqual(approval_values[0].approver_ids, [111])
1080
1081 # TODO(aneeshm): add a test for the ambiguous hotlist name case; it works
1082 # correctly when tested locally, but for some reason doesn't in the test
1083 # environment. Probably a result of some quirk in fake.py?