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