blob: c19a21d34922a70d48f26e0b6a1858171ad2f1da [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Tests for the issue admin pages."""
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
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010014import six
Copybara854996b2021-09-07 19:36:02 +000015import unittest
16
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010017import settings
Copybara854996b2021-09-07 19:36:02 +000018from mock import Mock, patch
19
20from framework import permissions
21from framework import urls
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010022from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000023from services import service_manager
24from services import template_svc
25from testing import fake
26from testing import testing_helpers
27from tracker import issueadmin
28from tracker import tracker_bizobj
29from tracker import tracker_constants
30
31
32class TestBase(unittest.TestCase):
33
34 def setUpServlet(self, servlet_factory):
35 # pylint: disable=attribute-defined-outside-init
36 self.services = service_manager.Services(
37 project=fake.ProjectService(),
38 config=fake.ConfigService(),
39 user=fake.UserService(),
40 issue=fake.IssueService(),
41 template=Mock(spec=template_svc.TemplateService),
42 features=fake.FeaturesService())
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010043 self.servlet = servlet_factory(services=self.services)
Copybara854996b2021-09-07 19:36:02 +000044 self.project = self.services.project.TestAddProject(
45 'proj', project_id=789, contrib_ids=[333])
46 self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
47 self.services.config.StoreConfig(None, self.config)
48 self.cnxn = fake.MonorailConnection()
49 self.mr = testing_helpers.MakeMonorailRequest(
50 path='/p/proj/admin', project=self.project)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010051 # Default to admin perms given that most tests assume the user can edit.
52 self.mr.perms = permissions.ADMIN_PERMISSIONSET
Copybara854996b2021-09-07 19:36:02 +000053 self.mox = mox.Mox()
54 self.test_template = tracker_bizobj.MakeIssueTemplate(
55 'Test Template', 'sum', 'New', 111, 'content', [], [], [], [])
56 self.test_template.template_id = 12345
57 self.test_templates = testing_helpers.DefaultTemplates()
58 self.test_templates.append(self.test_template)
59 self.services.template.GetProjectTemplates\
60 .return_value = self.test_templates
61 self.services.template.GetTemplateSetForProject\
62 .return_value = [(12345, 'Test template', 0)]
63
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010064 settings.config_freeze_project_ids = {}
65
Copybara854996b2021-09-07 19:36:02 +000066 def tearDown(self):
67 self.mox.UnsetStubs()
68 self.mox.ResetAll()
69
70 def _mockGetUser(self):
71 self.mox.StubOutWithMock(self.services.user, 'GetUser')
72 user = self.services.user.TestAddUser('user@invalid', 100)
73 self.services.user.GetUser(
74 mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user)
75
76
77class IssueAdminBaseTest(TestBase):
78
79 def setUp(self):
80 super(IssueAdminBaseTest, self).setUpServlet(issueadmin.IssueAdminBase)
81
82 def testGatherPageData(self):
83 self._mockGetUser()
84 self.mox.ReplayAll()
85 page_data = self.servlet.GatherPageData(self.mr)
86 self.mox.VerifyAll()
87
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010088 six.assertCountEqual(
89 self, [
90 'admin_tab_mode', 'config', 'open_text', 'closed_text',
91 'labels_text', 'can_edit_project'
92 ], list(page_data.keys()))
Copybara854996b2021-09-07 19:36:02 +000093 config_view = page_data['config']
94 self.assertEqual(789, config_view.project_id)
95
96
97class AdminStatusesTest(TestBase):
98
99 def setUp(self):
100 super(AdminStatusesTest, self).setUpServlet(issueadmin.AdminStatuses)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100101 self.servlet.mr = self.mr
Copybara854996b2021-09-07 19:36:02 +0000102
103 @patch('framework.servlet.Servlet.PleaseCorrect')
104 def testProcessSubtabForm_MissingInput(self, mock_pc):
105 post_data = fake.PostData()
106 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
107 self.assertIsNone(next_url)
108 mock_pc.assert_called_once()
109 self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES),
110 len(self.config.well_known_statuses))
111 self.assertEqual(tracker_constants.DEFAULT_STATUSES_OFFER_MERGE,
112 self.config.statuses_offer_merge)
113
114 @patch('framework.servlet.Servlet.PleaseCorrect')
115 def testProcessSubtabForm_EmptyInput(self, mock_pc):
116 post_data = fake.PostData(
117 predefinedopen=[''], predefinedclosed=[''], statuses_offer_merge=[''])
118 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
119 self.assertIsNone(next_url)
120 mock_pc.assert_called_once()
121 self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES),
122 len(self.config.well_known_statuses))
123 self.assertEqual(tracker_constants.DEFAULT_STATUSES_OFFER_MERGE,
124 self.config.statuses_offer_merge)
125
126 def testProcessSubtabForm_Normal(self):
127 post_data = fake.PostData(
128 predefinedopen=['New = newly reported'],
129 predefinedclosed=['Fixed\nDuplicate'],
130 statuses_offer_merge=['Duplicate'])
131 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
132 self.assertEqual(urls.ADMIN_STATUSES, next_url)
133 self.assertEqual(3, len(self.config.well_known_statuses))
134 self.assertEqual('New', self.config.well_known_statuses[0].status)
135 self.assertTrue(self.config.well_known_statuses[0].means_open)
136 self.assertEqual('Fixed', self.config.well_known_statuses[1].status)
137 self.assertFalse(self.config.well_known_statuses[1].means_open)
138 self.assertEqual('Duplicate', self.config.well_known_statuses[2].status)
139 self.assertFalse(self.config.well_known_statuses[2].means_open)
140 self.assertEqual(['Duplicate'], self.config.statuses_offer_merge)
141
142
143class AdminLabelsTest(TestBase):
144
145 def setUp(self):
146 super(AdminLabelsTest, self).setUpServlet(issueadmin.AdminLabels)
147
148 def testGatherPageData(self):
149 self._mockGetUser()
150 self.mox.ReplayAll()
151 page_data = self.servlet.GatherPageData(self.mr)
152 self.mox.VerifyAll()
153
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100154 six.assertCountEqual(
155 self, [
156 'admin_tab_mode', 'config', 'field_defs', 'open_text',
157 'closed_text', 'labels_text', 'can_edit_project'
158 ], list(page_data.keys()))
Copybara854996b2021-09-07 19:36:02 +0000159 config_view = page_data['config']
160 self.assertEqual(789, config_view.project_id)
161 self.assertEqual([], page_data['field_defs'])
162
163 @patch('framework.servlet.Servlet.PleaseCorrect')
164 def testProcessSubtabForm_MissingInput(self, mock_pc):
165 post_data = fake.PostData()
166 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
167 self.assertIsNone(next_url)
168 mock_pc.assert_called_once()
169 self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS),
170 len(self.config.well_known_labels))
171 self.assertEqual(tracker_constants.DEFAULT_EXCL_LABEL_PREFIXES,
172 self.config.exclusive_label_prefixes)
173
174 @patch('framework.servlet.Servlet.PleaseCorrect')
175 def testProcessSubtabForm_EmptyInput(self, mock_pc):
176 post_data = fake.PostData(
177 predefinedlabels=[''], excl_prefixes=[''])
178 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
179 self.assertIsNone(next_url) # Because PleaseCorrect() was called.
180 mock_pc.assert_called_once()
181 self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS),
182 len(self.config.well_known_labels))
183 self.assertEqual(tracker_constants.DEFAULT_EXCL_LABEL_PREFIXES,
184 self.config.exclusive_label_prefixes)
185
186 def testProcessSubtabForm_Normal(self):
187 post_data = fake.PostData(
188 predefinedlabels=['Pri-0 = Burning issue\nPri-4 = It can wait'],
189 excl_prefixes=['pri'])
190 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
191 self.assertEqual(urls.ADMIN_LABELS, next_url)
192 self.assertEqual(2, len(self.config.well_known_labels))
193 self.assertEqual('Pri-0', self.config.well_known_labels[0].label)
194 self.assertEqual('Pri-4', self.config.well_known_labels[1].label)
195 self.assertEqual(['pri'], self.config.exclusive_label_prefixes)
196
197 @patch('framework.servlet.Servlet.PleaseCorrect')
198 def testProcessSubtabForm_Duplicates(self, mock_pc):
199 post_data = fake.PostData(
200 predefinedlabels=['Pri-0\nPri-4\npri-0'],
201 excl_prefixes=['pri'])
202 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
203 self.assertIsNone(next_url)
204 mock_pc.assert_called_once()
205 self.assertEqual(
206 'Duplicate label: pri-0',
207 self.mr.errors.label_defs)
208
209 @patch('framework.servlet.Servlet.PleaseCorrect')
210 def testProcessSubtabForm_Conflict(self, mock_pc):
211 post_data = fake.PostData(
212 predefinedlabels=['Multi-Part-One\nPri-4\npri-0'],
213 excl_prefixes=['pri'])
214 self.config.field_defs = [
215 tracker_pb2.FieldDef(
216 field_name='Multi-Part',
217 field_type=tracker_pb2.FieldTypes.ENUM_TYPE)]
218 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
219 self.assertIsNone(next_url)
220 mock_pc.assert_called_once()
221 self.assertEqual(
222 'Label "Multi-Part-One" should be defined in enum "multi-part"',
223 self.mr.errors.label_defs)
224
225
226class AdminTemplatesTest(TestBase):
227
228 def setUp(self):
229 super(AdminTemplatesTest, self).setUpServlet(issueadmin.AdminTemplates)
230 self.mr.auth.user_id = 333
231 self.mr.auth.effective_ids = {333}
232
233 def testGatherPageData(self):
234 self._mockGetUser()
235 self.mox.ReplayAll()
236 page_data = self.servlet.GatherPageData(self.mr)
237 self.mox.VerifyAll()
238
239 config_view = page_data['config']
240 self.assertEqual(789, config_view.project_id)
241
242 def testProcessSubtabForm_NoEditProjectPerm(self):
243 """If user lacks perms, raise an exception."""
244 post_data = fake.PostData(
245 default_template_for_developers=['Test Template'],
246 default_template_for_users=['Test Template'])
247 self.mr.perms = permissions.EMPTY_PERMISSIONSET
248 self.assertRaises(
249 permissions.PermissionException,
250 self.servlet.ProcessSubtabForm, post_data, self.mr)
251 self.assertEqual(0, self.config.default_template_for_developers)
252 self.assertEqual(0, self.config.default_template_for_users)
253
254 def testProcessSubtabForm_Normal(self):
255 """If user has perms, set default templates."""
256 post_data = fake.PostData(
257 default_template_for_developers=['Test Template'],
258 default_template_for_users=['Test Template'])
259 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
260 self.assertEqual(urls.ADMIN_TEMPLATES, next_url)
261 self.assertEqual(12345, self.config.default_template_for_developers)
262 self.assertEqual(12345, self.config.default_template_for_users)
263
264 def testParseDefaultTemplateSelections_NotSpecified(self):
265 post_data = fake.PostData()
266 for_devs, for_users = self.servlet._ParseDefaultTemplateSelections(
267 post_data, self.test_templates)
268 self.assertEqual(None, for_devs)
269 self.assertEqual(None, for_users)
270
271 def testParseDefaultTemplateSelections_TemplateNotFoundIsIgnored(self):
272 post_data = fake.PostData(
273 default_template_for_developers=['Bad value'],
274 default_template_for_users=['Bad value'])
275 for_devs, for_users = self.servlet._ParseDefaultTemplateSelections(
276 post_data, self.test_templates)
277 self.assertEqual(None, for_devs)
278 self.assertEqual(None, for_users)
279
280 def testParseDefaultTemplateSelections_Normal(self):
281 post_data = fake.PostData(
282 default_template_for_developers=['Test Template'],
283 default_template_for_users=['Test Template'])
284 for_devs, for_users = self.servlet._ParseDefaultTemplateSelections(
285 post_data, self.test_templates)
286 self.assertEqual(12345, for_devs)
287 self.assertEqual(12345, for_users)
288
289
290class AdminComponentsTest(TestBase):
291
292 def setUp(self):
293 super(AdminComponentsTest, self).setUpServlet(issueadmin.AdminComponents)
294 self.cd_clean = tracker_bizobj.MakeComponentDef(
295 1, self.project.project_id, 'BackEnd', 'doc', False, [], [111], 100000,
296 122, 10000000, 133)
297 self.cd_with_subcomp = tracker_bizobj.MakeComponentDef(
298 2, self.project.project_id, 'FrontEnd', 'doc', False, [], [111],
299 100000, 122, 10000000, 133)
300 self.subcd = tracker_bizobj.MakeComponentDef(
301 3, self.project.project_id, 'FrontEnd>Worker', 'doc', False, [], [111],
302 100000, 122, 10000000, 133)
303 self.cd_with_template = tracker_bizobj.MakeComponentDef(
304 4, self.project.project_id, 'Middle', 'doc', False, [], [111],
305 100000, 122, 10000000, 133)
306
307 def testGatherPageData(self):
308 self._mockGetUser()
309 self.mox.ReplayAll()
310 page_data = self.servlet.GatherPageData(self.mr)
311 self.mox.VerifyAll()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100312 six.assertCountEqual(
313 self, [
314 'admin_tab_mode', 'failed_templ', 'component_defs', 'failed_perm',
315 'config', 'failed_subcomp', 'open_text', 'closed_text',
316 'labels_text', 'can_edit_project'
317 ], list(page_data.keys()))
Copybara854996b2021-09-07 19:36:02 +0000318 config_view = page_data['config']
319 self.assertEqual(789, config_view.project_id)
320 self.assertEqual([], page_data['component_defs'])
321
322 def testProcessFormData_NoErrors(self):
323 self.config.component_defs = [
324 self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template]
325 self.services.template.TemplatesWithComponent.return_value = []
326 post_data = {
327 'delete_components' : '%s,%s,%s' % (
328 self.cd_clean.path, self.cd_with_subcomp.path, self.subcd.path)}
329 url = self.servlet.ProcessFormData(self.mr, post_data)
330 self.assertTrue(
331 url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted='
332 'FrontEnd%3EWorker%2CFrontEnd%2CBackEnd&failed_perm=&'
333 'failed_subcomp=&failed_templ=&ts='))
334
335 def testProcessFormData_SubCompError(self):
336 self.config.component_defs = [
337 self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template]
338 self.services.template.TemplatesWithComponent.return_value = []
339 post_data = {
340 'delete_components' : '%s,%s' % (
341 self.cd_clean.path, self.cd_with_subcomp.path)}
342 url = self.servlet.ProcessFormData(self.mr, post_data)
343 self.assertTrue(
344 url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted='
345 'BackEnd&failed_perm=&failed_subcomp=FrontEnd&'
346 'failed_templ=&ts='))
347
348 def testProcessFormData_TemplateError(self):
349 self.config.component_defs = [
350 self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template]
351
352 def mockTemplatesWithComponent(_cnxn, component_id):
353 if component_id == 4:
354 return 'template'
355 self.services.template.TemplatesWithComponent\
356 .side_effect = mockTemplatesWithComponent
357
358 post_data = {
359 'delete_components' : '%s,%s,%s,%s' % (
360 self.cd_clean.path, self.cd_with_subcomp.path, self.subcd.path,
361 self.cd_with_template.path)}
362 url = self.servlet.ProcessFormData(self.mr, post_data)
363 self.assertTrue(
364 url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted='
365 'FrontEnd%3EWorker%2CFrontEnd%2CBackEnd&failed_perm=&'
366 'failed_subcomp=&failed_templ=Middle&ts='))
367
368
369class AdminViewsTest(TestBase):
370
371 def setUp(self):
372 super(AdminViewsTest, self).setUpServlet(issueadmin.AdminViews)
373
374 def testGatherPageData(self):
375 self._mockGetUser()
376 self.mox.ReplayAll()
377 page_data = self.servlet.GatherPageData(self.mr)
378 self.mox.VerifyAll()
379
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100380 six.assertCountEqual(
381 self, [
382 'canned_queries', 'admin_tab_mode', 'config', 'issue_notify',
383 'new_query_indexes', 'max_queries', 'open_text', 'closed_text',
384 'labels_text', 'can_edit_project'
385 ], list(page_data.keys()))
Copybara854996b2021-09-07 19:36:02 +0000386 config_view = page_data['config']
387 self.assertEqual(789, config_view.project_id)
388
389 def testProcessSubtabForm(self):
390 post_data = fake.PostData(
391 default_col_spec=['id pri mstone owner status summary'],
392 default_sort_spec=['mstone pri'],
393 default_x_attr=['owner'], default_y_attr=['mstone'])
394 next_url = self.servlet.ProcessSubtabForm(post_data, self.mr)
395 self.assertEqual(urls.ADMIN_VIEWS, next_url)
396 self.assertEqual(
397 'id pri mstone owner status summary', self.config.default_col_spec)
398 self.assertEqual('mstone pri', self.config.default_sort_spec)
399 self.assertEqual('owner', self.config.default_x_attr)
400 self.assertEqual('mstone', self.config.default_y_attr)
401
402
403class AdminViewsFunctionsTest(unittest.TestCase):
404
405 def testParseListPreferences(self):
406 # If no input, col_spec will be default column spec.
407 # For other fiels empty strings should be returned.
408 (col_spec, sort_spec, x_attr, y_attr, member_default_query,
409 ) = issueadmin._ParseListPreferences({})
410 self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, col_spec)
411 self.assertEqual('', sort_spec)
412 self.assertEqual('', x_attr)
413 self.assertEqual('', y_attr)
414 self.assertEqual('', member_default_query)
415
416 # Test how hyphens in input are treated.
417 spec = 'label1-sub1 label2 label3-sub3'
418 (col_spec, sort_spec, x_attr, y_attr, member_default_query,
419 ) = issueadmin._ParseListPreferences(
420 fake.PostData(default_col_spec=[spec],
421 default_sort_spec=[spec],
422 default_x_attr=[spec],
423 default_y_attr=[spec]),
424 )
425
426 # Hyphens (and anything following) should be stripped from each term.
427 self.assertEqual('label1-sub1 label2 label3-sub3', col_spec)
428
429 # The sort spec should be as given (except with whitespace condensed).
430 self.assertEqual(' '.join(spec.split()), sort_spec)
431
432 # Only the first term (up to the first hyphen) should be used for x- or
433 # y-attr.
434 self.assertEqual('label1-sub1', x_attr)
435 self.assertEqual('label1-sub1', y_attr)
436
437 # Test that multibyte strings are not mangled.
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100438 spec = (
439 b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9 '
440 b'\xe5\x9c\xb0\xe3\x81\xa6-\xe5\xbd\x93-\xe3\x81\xbe\xe3\x81\x99')
Copybara854996b2021-09-07 19:36:02 +0000441 spec = spec.decode('utf-8')
442 (col_spec, sort_spec, x_attr, y_attr, member_default_query,
443 ) = issueadmin._ParseListPreferences(
444 fake.PostData(default_col_spec=[spec],
445 default_sort_spec=[spec],
446 default_x_attr=[spec],
447 default_y_attr=[spec],
448 member_default_query=[spec]),
449 )
450 self.assertEqual(spec, col_spec)
451 self.assertEqual(' '.join(spec.split()), sort_spec)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100452 self.assertEqual(
453 b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9'.decode('utf-8'), x_attr)
454 self.assertEqual(
455 b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9'.decode('utf-8'), y_attr)
Copybara854996b2021-09-07 19:36:02 +0000456 self.assertEqual(spec, member_default_query)
457
458
459class AdminRulesTest(TestBase):
460
461 def setUp(self):
462 super(AdminRulesTest, self).setUpServlet(issueadmin.AdminRules)
463
464 def testGatherPageData(self):
465 self._mockGetUser()
466 self.mox.ReplayAll()
467 page_data = self.servlet.GatherPageData(self.mr)
468 self.mox.VerifyAll()
469
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100470 six.assertCountEqual(
471 self, [
472 'admin_tab_mode', 'config', 'rules', 'new_rule_indexes',
473 'max_rules', 'open_text', 'closed_text', 'labels_text',
474 'can_edit_project'
475 ], list(page_data.keys()))
Copybara854996b2021-09-07 19:36:02 +0000476 config_view = page_data['config']
477 self.assertEqual(789, config_view.project_id)
478 self.assertEqual([], page_data['rules'])
479
480 def testProcessSubtabForm(self):
481 pass # TODO(jrobbins): write this test