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