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