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