blob: 607996a0838e824a1de0322bf16eca5a86516c87 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2018 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Unit tests for Template editing/viewing servlet."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import mox
12import logging
13import unittest
14import settings
15
16from mock import Mock
17
18import ezt
19
20from framework import permissions
21from services import service_manager
22from services import template_svc
23from testing import fake
24from testing import testing_helpers
25from tracker import templatedetail
26from tracker import tracker_bizobj
27from proto import tracker_pb2
28
29
30class TemplateDetailTest(unittest.TestCase):
31 """Tests for the TemplateDetail servlet."""
32
33 def setUp(self):
34 self.cnxn = 'fake cnxn'
35 mock_template_service = Mock(spec=template_svc.TemplateService)
36 self.services = service_manager.Services(project=fake.ProjectService(),
37 config=fake.ConfigService(),
38 template=mock_template_service,
39 usergroup=fake.UserGroupService(),
40 user=fake.UserService())
41 self.servlet = templatedetail.TemplateDetail('req', 'res',
42 services=self.services)
43
44 self.services.user.TestAddUser('gatsby@example.com', 111)
45 self.services.user.TestAddUser('sport@example.com', 222)
46 self.services.user.TestAddUser('gatsby@example.com', 111)
47 self.services.user.TestAddUser('daisy@example.com', 333)
48
49 self.project = self.services.project.TestAddProject('proj')
50 self.services.project.TestAddProjectMembers(
51 [333], self.project, 'CONTRIBUTOR_ROLE')
52
53 self.template = self.test_template = tracker_bizobj.MakeIssueTemplate(
54 'TestTemplate', 'sum', 'New', 111, 'content', ['label1', 'label2'],
55 [], [222], [], summary_must_be_edited=True,
56 owner_defaults_to_member=True, component_required=False,
57 members_only=False)
58 self.template.template_id = 12345
59 self.services.template.GetTemplateByName = Mock(
60 return_value=self.template)
61
62 self.mr = testing_helpers.MakeMonorailRequest(project=self.project)
63 self.mr.template_name = 'TestTemplate'
64
65 self.mox = mox.Mox()
66
67 self.fd_1 = tracker_bizobj.MakeFieldDef(
68 1, 789, 'UXReview', tracker_pb2.FieldTypes.STR_TYPE, None,
69 '', False, False, False, None, None, '', False, '', '',
70 tracker_pb2.NotifyTriggers.NEVER, 'no_action',
71 'Approval for UX review', False, approval_id=2)
72 self.fd_2 = tracker_bizobj.MakeFieldDef(
73 2, 789, 'UXReview', tracker_pb2.FieldTypes.STR_TYPE, None,
74 '', False, False, False, None, None, '', False, '', '',
75 tracker_pb2.NotifyTriggers.NEVER, 'no_action',
76 'Approval for UX review', False)
77 self.fd_3 = tracker_bizobj.MakeFieldDef(
78 3, 789, 'TestApproval', tracker_pb2.FieldTypes.APPROVAL_TYPE, None,
79 '', False, False, False, None, None, '', False, '', '',
80 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'Approval for Test',
81 False)
82 self.fd_4 = tracker_bizobj.MakeFieldDef(
83 4, 789, 'SecurityApproval', tracker_pb2.FieldTypes.APPROVAL_TYPE, None,
84 '', False, False, False, None, None, '', False, '', '',
85 tracker_pb2.NotifyTriggers.NEVER, 'no_action', 'Approval for Security',
86 False)
87 self.fd_5 = tracker_bizobj.MakeFieldDef(
88 5, 789, 'GateTarget', tracker_pb2.FieldTypes.INT_TYPE, None,
89 '', False, False, False, None, None, '', False, '', '',
90 tracker_pb2.NotifyTriggers.NEVER, 'no_action',
91 'milestone target', False, is_phase_field=True)
92 self.fd_6 = tracker_bizobj.MakeFieldDef(
93 6, 789, 'Choices', tracker_pb2.FieldTypes.ENUM_TYPE, None,
94 '', False, False, False, None, None, '', False, '', '',
95 tracker_pb2.NotifyTriggers.NEVER, 'no_action',
96 'milestone target', False, is_phase_field=True)
97 self.fd_7 = tracker_bizobj.MakeFieldDef(
98 7,
99 789,
100 'RestrictedField',
101 tracker_pb2.FieldTypes.INT_TYPE,
102 None,
103 '',
104 False,
105 False,
106 False,
107 None,
108 None,
109 '',
110 False,
111 '',
112 '',
113 tracker_pb2.NotifyTriggers.NEVER,
114 'no_action',
115 'RestrictedField',
116 False,
117 is_restricted_field=True)
118 self.fd_8 = tracker_bizobj.MakeFieldDef(
119 8,
120 789,
121 'RestrictedEnumField',
122 tracker_pb2.FieldTypes.ENUM_TYPE,
123 None,
124 '',
125 False,
126 False,
127 False,
128 None,
129 None,
130 '',
131 False,
132 '',
133 '',
134 tracker_pb2.NotifyTriggers.NEVER,
135 'no_action',
136 'RestrictedEnumField',
137 False,
138 is_restricted_field=True)
139 self.fd_9 = tracker_bizobj.MakeFieldDef(
140 9,
141 789,
142 'RestrictedField_2',
143 tracker_pb2.FieldTypes.INT_TYPE,
144 None,
145 '',
146 False,
147 False,
148 False,
149 None,
150 None,
151 '',
152 False,
153 '',
154 '',
155 tracker_pb2.NotifyTriggers.NEVER,
156 'no_action',
157 'RestrictedField_2',
158 False,
159 is_restricted_field=True)
160 self.fd_10 = tracker_bizobj.MakeFieldDef(
161 10,
162 789,
163 'RestrictedEnumField_2',
164 tracker_pb2.FieldTypes.ENUM_TYPE,
165 None,
166 '',
167 False,
168 False,
169 False,
170 None,
171 None,
172 '',
173 False,
174 '',
175 '',
176 tracker_pb2.NotifyTriggers.NEVER,
177 'no_action',
178 'RestrictedEnumField_2',
179 False,
180 is_restricted_field=True)
181
182 self.ad_3 = tracker_pb2.ApprovalDef(approval_id=3)
183 self.ad_4 = tracker_pb2.ApprovalDef(approval_id=4)
184
185 self.cd_1 = tracker_bizobj.MakeComponentDef(
186 1, 789, 'BackEnd', 'doc', False, [111], [], 100000, 222)
187 self.template.component_ids.append(1)
188
189 self.canary_phase = tracker_pb2.Phase(
190 name='Canary', phase_id=1, rank=1)
191 self.av_3 = tracker_pb2.ApprovalValue(approval_id=3, phase_id=1)
192 self.stable_phase = tracker_pb2.Phase(
193 name='Stable', phase_id=2, rank=3)
194 self.av_4 = tracker_pb2.ApprovalValue(approval_id=4, phase_id=2)
195 self.template.phases.extend([self.stable_phase, self.canary_phase])
196 self.template.approval_values.extend([self.av_3, self.av_4])
197
198 self.config = self.services.config.GetProjectConfig(
199 'fake cnxn', self.project.project_id)
200 self.templates = testing_helpers.DefaultTemplates()
201 self.template.labels.extend(
202 ['GateTarget-Should-Not', 'GateTarget-Be-Masked',
203 'Choices-Wrapped', 'Choices-Burritod'])
204 self.templates.append(self.template)
205 self.services.template.GetProjectTemplates = Mock(
206 return_value=self.templates)
207 self.services.template.FindTemplateByName = Mock(return_value=self.template)
208 self.config.component_defs.append(self.cd_1)
209 self.config.field_defs.extend(
210 [
211 self.fd_1, self.fd_2, self.fd_3, self.fd_4, self.fd_5, self.fd_6,
212 self.fd_7, self.fd_8, self.fd_9, self.fd_10
213 ])
214 self.config.approval_defs.extend([self.ad_3, self.ad_4])
215 self.services.config.StoreConfig(None, self.config)
216
217 def tearDown(self):
218 self.mox.UnsetStubs()
219 self.mox.ResetAll()
220
221 def testAssertBasePermission_Anyone(self):
222 self.mr.auth.effective_ids = {222}
223 self.servlet.AssertBasePermission(self.mr)
224
225 self.mr.auth.effective_ids = {333}
226 self.servlet.AssertBasePermission(self.mr)
227
228 self.mr.perms = permissions.OWNER_ACTIVE_PERMISSIONSET
229 self.servlet.AssertBasePermission(self.mr)
230
231 self.mr.perms = permissions.READ_ONLY_PERMISSIONSET
232 self.servlet.AssertBasePermission(self.mr)
233
234 def testAssertBasePermision_MembersOnly(self):
235 self.template.members_only = True
236 self.mr.auth.effective_ids = {222}
237 self.servlet.AssertBasePermission(self.mr)
238
239 self.mr.auth.effective_ids = {333}
240 self.servlet.AssertBasePermission(self.mr)
241
242 self.mr.auth.effective_ids = {444}
243 self.assertRaises(
244 permissions.PermissionException,
245 self.servlet.AssertBasePermission, self.mr)
246
247 self.mr.perms = permissions.READ_ONLY_PERMISSIONSET
248 self.assertRaises(
249 permissions.PermissionException,
250 self.servlet.AssertBasePermission, self.mr)
251
252 def testGatherPageData(self):
253 self.mr.perms = permissions.PermissionSet([])
254 self.mr.auth.effective_ids = {222} # template admin
255 page_data = self.servlet.GatherPageData(self.mr)
256 self.assertEqual(self.servlet.PROCESS_TAB_TEMPLATES,
257 page_data['admin_tab_mode'])
258 self.assertTrue(page_data['allow_edit'])
259 self.assertEqual(page_data['uneditable_fields'], ezt.boolean(True))
260 self.assertFalse(page_data['new_template_form'])
261 self.assertFalse(page_data['initial_members_only'])
262 self.assertEqual(page_data['template_name'], 'TestTemplate')
263 self.assertEqual(page_data['initial_summary'], 'sum')
264 self.assertTrue(page_data['initial_must_edit_summary'])
265 self.assertEqual(page_data['initial_content'], 'content')
266 self.assertEqual(page_data['initial_status'], 'New')
267 self.assertEqual(page_data['initial_owner'], 'gatsby@example.com')
268 self.assertTrue(page_data['initial_owner_defaults_to_member'])
269 self.assertEqual(page_data['initial_components'], 'BackEnd')
270 self.assertFalse(page_data['initial_component_required'])
271 self.assertItemsEqual(
272 page_data['labels'],
273 ['label1', 'label2', 'GateTarget-Should-Not', 'GateTarget-Be-Masked'])
274 self.assertEqual(page_data['initial_admins'], 'sport@example.com')
275 self.assertTrue(page_data['initial_add_approvals'])
276 self.assertEqual(len(page_data['initial_phases']), 6)
277 phases = [phase for phase in page_data['initial_phases'] if phase.name]
278 self.assertEqual(len(phases), 2)
279 self.assertEqual(len(page_data['approvals']), 2)
280 self.assertItemsEqual(page_data['prechecked_approvals'],
281 ['3_phase_0', '4_phase_1'])
282 self.assertTrue(page_data['fields'][3].is_editable) #nonRestrictedField
283 self.assertIsNone(page_data['fields'][4].is_editable) #restrictedField
284
285 def testProcessFormData_Reject(self):
286 self.mr.auth.effective_ids = {222}
287 post_data = fake.PostData(
288 name=['TestTemplate'],
289 members_only=['on'],
290 summary=['TLDR'],
291 summary_must_be_edited=['on'],
292 content=['HEY WHY'],
293 status=['Accepted'],
294 owner=['someone@world.com'],
295 label=['label-One', 'label-Two'],
296 custom_1=['NO'],
297 custom_2=['MOOD'],
298 components=['hey, hey2,he3'],
299 component_required=['on'],
300 owner_defaults_to_member=['no'],
301 add_approvals = ['on'],
302 phase_0=['Canary'],
303 phase_1=['Stable-Exp'],
304 phase_2=['Stable'],
305 phase_3=[''],
306 phase_4=[''],
307 phase_5=[''],
308 approval_3=['phase_0'],
309 approval_4=['phase_2']
310 )
311
312 self.mox.StubOutWithMock(self.servlet, 'PleaseCorrect')
313 self.servlet.PleaseCorrect(
314 self.mr,
315 initial_members_only=ezt.boolean(True),
316 template_name='TestTemplate',
317 initial_summary='TLDR',
318 initial_must_edit_summary=ezt.boolean(True),
319 initial_content='HEY WHY',
320 initial_status='Accepted',
321 initial_owner='someone@world.com',
322 initial_owner_defaults_to_member=ezt.boolean(False),
323 initial_components='hey, hey2, he3',
324 initial_component_required=ezt.boolean(True),
325 initial_admins='',
326 labels=['label-One', 'label-Two'],
327 fields=mox.IgnoreArg(),
328 initial_add_approvals=ezt.boolean(True),
329 initial_phases=[tracker_pb2.Phase(name=name) for
330 name in ['Canary', 'Stable-Exp', 'Stable', '', '', '']],
331 approvals=mox.IgnoreArg(),
332 prechecked_approvals=['3_phase_0', '4_phase_2'],
333 required_approval_ids=[]
334 )
335 self.mox.ReplayAll()
336 url = self.servlet.ProcessFormData(self.mr, post_data)
337 self.mox.VerifyAll()
338 self.assertEqual('Owner not found.', self.mr.errors.owner)
339 self.assertEqual('Unknown component he3', self.mr.errors.components)
340 self.assertIsNone(url)
341 self.assertEqual('Defined gates must have assigned approvals.',
342 self.mr.errors.phase_approvals)
343
344 def testProcessFormData_RejectRestrictedFields(self):
345 """Template admins cannot set restricted fields by default."""
346 self.mr.perms = permissions.PermissionSet([])
347 self.mr.auth.effective_ids = {222} # template admin
348 post_data_add_fv = fake.PostData(
349 name=['TestTemplate'],
350 members_only=['on'],
351 summary=['TLDR'],
352 summary_must_be_edited=[''],
353 content=['HEY WHY'],
354 status=['Accepted'],
355 owner=['daisy@example.com'],
356 label=['label-One', 'label-Two'],
357 custom_1=['NO'],
358 custom_2=['MOOD'],
359 custom_7=['37'],
360 components=['BackEnd'],
361 component_required=['on'],
362 owner_defaults_to_member=['on'],
363 add_approvals=['no'],
364 phase_0=[''],
365 phase_1=[''],
366 phase_2=[''],
367 phase_3=[''],
368 phase_4=[''],
369 phase_5=['OOPs'],
370 approval_3=['phase_0'],
371 approval_4=['phase_2'])
372 post_data_label_edits_enum = fake.PostData(
373 name=['TestTemplate'],
374 members_only=['on'],
375 summary=['TLDR'],
376 summary_must_be_edited=[''],
377 content=['HEY WHY'],
378 status=['Accepted'],
379 owner=['daisy@example.com'],
380 label=['label-One', 'label-Two', 'RestrictedEnumField-7'],
381 components=['BackEnd'],
382 component_required=['on'],
383 owner_defaults_to_member=['on'],
384 add_approvals=['no'],
385 phase_0=[''],
386 phase_1=[''],
387 phase_2=[''],
388 phase_3=[''],
389 phase_4=[''],
390 phase_5=['OOPs'],
391 approval_3=['phase_0'],
392 approval_4=['phase_2'])
393
394 self.assertRaises(
395 AssertionError, self.servlet.ProcessFormData, self.mr, post_data_add_fv)
396 self.assertRaises(
397 AssertionError, self.servlet.ProcessFormData, self.mr,
398 post_data_label_edits_enum)
399
400 def testProcessFormData_Accept(self):
401 self.fd_7.editor_ids = [222]
402 self.fd_8.editor_ids = [222]
403 self.mr.perms = permissions.PermissionSet([])
404 self.mr.auth.effective_ids = {222} # template admin
405 temp_restricted_fv = tracker_bizobj.MakeFieldValue(
406 9, 3737, None, None, None, None, False)
407 self.template.field_values.append(temp_restricted_fv)
408 self.template.labels.append('RestrictedEnumField_2-b')
409 post_data = fake.PostData(
410 name=['TestTemplate'],
411 members_only=['on'],
412 summary=['TLDR'],
413 summary_must_be_edited=[''],
414 content=['HEY WHY'],
415 status=['Accepted'],
416 owner=['daisy@example.com'],
417 label=['label-One', 'label-Two', 'RestrictedEnumField-7'],
418 custom_1=['NO'],
419 custom_2=['MOOD'],
420 custom_7=['37'],
421 components=['BackEnd'],
422 component_required=['on'],
423 owner_defaults_to_member=['on'],
424 add_approvals=['no'],
425 phase_0=[''],
426 phase_1=[''],
427 phase_2=[''],
428 phase_3=[''],
429 phase_4=[''],
430 phase_5=['OOPs'],
431 approval_3=['phase_0'],
432 approval_4=['phase_2'])
433 url = self.servlet.ProcessFormData(self.mr, post_data)
434
435 self.assertTrue('/templates/detail?saved=1&template=TestTemplate&' in url)
436
437 self.services.template.UpdateIssueTemplateDef.assert_called_once_with(
438 self.mr.cnxn,
439 47925,
440 12345,
441 status='Accepted',
442 component_required=True,
443 phases=[],
444 approval_values=[],
445 name='TestTemplate',
446 field_values=[
447 tracker_pb2.FieldValue(field_id=1, str_value='NO', derived=False),
448 tracker_pb2.FieldValue(field_id=2, str_value='MOOD', derived=False),
449 tracker_pb2.FieldValue(field_id=7, int_value=37, derived=False),
450 tracker_pb2.FieldValue(field_id=9, int_value=3737, derived=False)
451 ],
452 labels=[
453 'label-One', 'label-Two', 'RestrictedEnumField-7',
454 'RestrictedEnumField_2-b'
455 ],
456 owner_defaults_to_member=True,
457 admin_ids=[],
458 content='HEY WHY',
459 component_ids=[1],
460 summary_must_be_edited=False,
461 summary='TLDR',
462 members_only=True,
463 owner_id=333)
464
465 def testProcessFormData_AcceptPhases(self):
466 self.mr.auth.effective_ids = {222}
467 post_data = fake.PostData(
468 name=['TestTemplate'],
469 members_only=['on'],
470 summary=['TLDR'],
471 summary_must_be_edited=[''],
472 content=['HEY WHY'],
473 status=['Accepted'],
474 owner=['daisy@example.com'],
475 label=['label-One', 'label-Two'],
476 custom_1=['NO'],
477 custom_2=['MOOD'],
478 components=['BackEnd'],
479 component_required=['on'],
480 owner_defaults_to_member=['on'],
481 add_approvals = ['on'],
482 phase_0=['Canary'],
483 phase_1=['Stable'],
484 phase_2=[''],
485 phase_3=[''],
486 phase_4=[''],
487 phase_5=[''],
488 approval_3=['phase_0'],
489 approval_4=['phase_1']
490 )
491 url = self.servlet.ProcessFormData(self.mr, post_data)
492
493 self.assertTrue('/templates/detail?saved=1&template=TestTemplate&' in url)
494
495 self.services.template.UpdateIssueTemplateDef.assert_called_once_with(
496 self.mr.cnxn, 47925, 12345, status='Accepted', component_required=True,
497 phases=[
498 tracker_pb2.Phase(name='Canary', rank=0, phase_id=0),
499 tracker_pb2.Phase(name='Stable', rank=1, phase_id=1)],
500 approval_values=[tracker_pb2.ApprovalValue(approval_id=3, phase_id=0),
501 tracker_pb2.ApprovalValue(approval_id=4, phase_id=1)],
502 name='TestTemplate', field_values=[
503 tracker_pb2.FieldValue(field_id=1, str_value='NO', derived=False),
504 tracker_pb2.FieldValue(
505 field_id=2, str_value='MOOD', derived=False)],
506 labels=['label-One', 'label-Two'], owner_defaults_to_member=True,
507 admin_ids=[], content='HEY WHY', component_ids=[1],
508 summary_must_be_edited=False, summary='TLDR', members_only=True,
509 owner_id=333)
510
511 def testProcessFormData_Delete(self):
512 post_data = fake.PostData(
513 deletetemplate=['Submit'],
514 name=['TestTemplate'],
515 members_only=['on'],
516 )
517 url = self.servlet.ProcessFormData(self.mr, post_data)
518
519 self.assertTrue('/p/None/adminTemplates?deleted=1' in url)
520 self.services.template.DeleteIssueTemplateDef\
521 .assert_called_once_with(self.mr.cnxn, 47925, 12345)