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