| # Copyright 2016 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Tests for the issue admin pages.""" |
| from __future__ import print_function |
| from __future__ import division |
| from __future__ import absolute_import |
| |
| try: |
| from mox3 import mox |
| except ImportError: |
| import mox |
| import six |
| import unittest |
| |
| import settings |
| from mock import Mock, patch |
| |
| from framework import permissions |
| from framework import urls |
| from mrproto import tracker_pb2 |
| from services import service_manager |
| from services import template_svc |
| from testing import fake |
| from testing import testing_helpers |
| from tracker import issueadmin |
| from tracker import tracker_bizobj |
| from tracker import tracker_constants |
| |
| |
| class TestBase(unittest.TestCase): |
| |
| def setUpServlet(self, servlet_factory): |
| # pylint: disable=attribute-defined-outside-init |
| self.services = service_manager.Services( |
| project=fake.ProjectService(), |
| config=fake.ConfigService(), |
| user=fake.UserService(), |
| issue=fake.IssueService(), |
| template=Mock(spec=template_svc.TemplateService), |
| features=fake.FeaturesService()) |
| self.servlet = servlet_factory(services=self.services) |
| self.project = self.services.project.TestAddProject( |
| 'proj', project_id=789, contrib_ids=[333]) |
| self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) |
| self.services.config.StoreConfig(None, self.config) |
| self.cnxn = fake.MonorailConnection() |
| self.mr = testing_helpers.MakeMonorailRequest( |
| path='/p/proj/admin', project=self.project) |
| # Default to admin perms given that most tests assume the user can edit. |
| self.mr.perms = permissions.ADMIN_PERMISSIONSET |
| self.mox = mox.Mox() |
| self.test_template = tracker_bizobj.MakeIssueTemplate( |
| 'Test Template', 'sum', 'New', 111, 'content', [], [], [], []) |
| self.test_template.template_id = 12345 |
| self.test_templates = testing_helpers.DefaultTemplates() |
| self.test_templates.append(self.test_template) |
| self.services.template.GetProjectTemplates\ |
| .return_value = self.test_templates |
| self.services.template.GetTemplateSetForProject\ |
| .return_value = [(12345, 'Test template', 0)] |
| |
| settings.config_freeze_project_ids = {} |
| |
| def tearDown(self): |
| self.mox.UnsetStubs() |
| self.mox.ResetAll() |
| |
| def _mockGetUser(self): |
| self.mox.StubOutWithMock(self.services.user, 'GetUser') |
| user = self.services.user.TestAddUser('user@invalid', 100) |
| self.services.user.GetUser( |
| mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(user) |
| |
| |
| class IssueAdminBaseTest(TestBase): |
| |
| def setUp(self): |
| super(IssueAdminBaseTest, self).setUpServlet(issueadmin.IssueAdminBase) |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| |
| six.assertCountEqual( |
| self, [ |
| 'admin_tab_mode', 'config', 'open_text', 'closed_text', |
| 'labels_text', 'can_edit_project' |
| ], list(page_data.keys())) |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| |
| |
| class AdminStatusesTest(TestBase): |
| |
| def setUp(self): |
| super(AdminStatusesTest, self).setUpServlet(issueadmin.AdminStatuses) |
| self.servlet.mr = self.mr |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_MissingInput(self, mock_pc): |
| post_data = fake.PostData() |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) |
| mock_pc.assert_called_once() |
| self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES), |
| len(self.config.well_known_statuses)) |
| self.assertEqual(tracker_constants.DEFAULT_STATUSES_OFFER_MERGE, |
| self.config.statuses_offer_merge) |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_EmptyInput(self, mock_pc): |
| post_data = fake.PostData( |
| predefinedopen=[''], predefinedclosed=[''], statuses_offer_merge=['']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) |
| mock_pc.assert_called_once() |
| self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES), |
| len(self.config.well_known_statuses)) |
| self.assertEqual(tracker_constants.DEFAULT_STATUSES_OFFER_MERGE, |
| self.config.statuses_offer_merge) |
| |
| def testProcessSubtabForm_Normal(self): |
| post_data = fake.PostData( |
| predefinedopen=['New = newly reported'], |
| predefinedclosed=['Fixed\nDuplicate'], |
| statuses_offer_merge=['Duplicate']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertEqual(urls.ADMIN_STATUSES, next_url) |
| self.assertEqual(3, len(self.config.well_known_statuses)) |
| self.assertEqual('New', self.config.well_known_statuses[0].status) |
| self.assertTrue(self.config.well_known_statuses[0].means_open) |
| self.assertEqual('Fixed', self.config.well_known_statuses[1].status) |
| self.assertFalse(self.config.well_known_statuses[1].means_open) |
| self.assertEqual('Duplicate', self.config.well_known_statuses[2].status) |
| self.assertFalse(self.config.well_known_statuses[2].means_open) |
| self.assertEqual(['Duplicate'], self.config.statuses_offer_merge) |
| |
| |
| class AdminLabelsTest(TestBase): |
| |
| def setUp(self): |
| super(AdminLabelsTest, self).setUpServlet(issueadmin.AdminLabels) |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| |
| six.assertCountEqual( |
| self, [ |
| 'admin_tab_mode', 'config', 'field_defs', 'open_text', |
| 'closed_text', 'labels_text', 'can_edit_project' |
| ], list(page_data.keys())) |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| self.assertEqual([], page_data['field_defs']) |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_MissingInput(self, mock_pc): |
| post_data = fake.PostData() |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) |
| mock_pc.assert_called_once() |
| self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS), |
| len(self.config.well_known_labels)) |
| self.assertEqual(tracker_constants.DEFAULT_EXCL_LABEL_PREFIXES, |
| self.config.exclusive_label_prefixes) |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_EmptyInput(self, mock_pc): |
| post_data = fake.PostData( |
| predefinedlabels=[''], excl_prefixes=['']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) # Because PleaseCorrect() was called. |
| mock_pc.assert_called_once() |
| self.assertEqual(len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS), |
| len(self.config.well_known_labels)) |
| self.assertEqual(tracker_constants.DEFAULT_EXCL_LABEL_PREFIXES, |
| self.config.exclusive_label_prefixes) |
| |
| def testProcessSubtabForm_Normal(self): |
| post_data = fake.PostData( |
| predefinedlabels=['Pri-0 = Burning issue\nPri-4 = It can wait'], |
| excl_prefixes=['pri']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertEqual(urls.ADMIN_LABELS, next_url) |
| self.assertEqual(2, len(self.config.well_known_labels)) |
| self.assertEqual('Pri-0', self.config.well_known_labels[0].label) |
| self.assertEqual('Pri-4', self.config.well_known_labels[1].label) |
| self.assertEqual(['pri'], self.config.exclusive_label_prefixes) |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_Duplicates(self, mock_pc): |
| post_data = fake.PostData( |
| predefinedlabels=['Pri-0\nPri-4\npri-0'], |
| excl_prefixes=['pri']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) |
| mock_pc.assert_called_once() |
| self.assertEqual( |
| 'Duplicate label: pri-0', |
| self.mr.errors.label_defs) |
| |
| @patch('framework.servlet.Servlet.PleaseCorrect') |
| def testProcessSubtabForm_Conflict(self, mock_pc): |
| post_data = fake.PostData( |
| predefinedlabels=['Multi-Part-One\nPri-4\npri-0'], |
| excl_prefixes=['pri']) |
| self.config.field_defs = [ |
| tracker_pb2.FieldDef( |
| field_name='Multi-Part', |
| field_type=tracker_pb2.FieldTypes.ENUM_TYPE)] |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertIsNone(next_url) |
| mock_pc.assert_called_once() |
| self.assertEqual( |
| 'Label "Multi-Part-One" should be defined in enum "multi-part"', |
| self.mr.errors.label_defs) |
| |
| |
| class AdminTemplatesTest(TestBase): |
| |
| def setUp(self): |
| super(AdminTemplatesTest, self).setUpServlet(issueadmin.AdminTemplates) |
| self.mr.auth.user_id = 333 |
| self.mr.auth.effective_ids = {333} |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| |
| def testProcessSubtabForm_NoEditProjectPerm(self): |
| """If user lacks perms, raise an exception.""" |
| post_data = fake.PostData( |
| default_template_for_developers=['Test Template'], |
| default_template_for_users=['Test Template']) |
| self.mr.perms = permissions.EMPTY_PERMISSIONSET |
| self.assertRaises( |
| permissions.PermissionException, |
| self.servlet.ProcessSubtabForm, post_data, self.mr) |
| self.assertEqual(0, self.config.default_template_for_developers) |
| self.assertEqual(0, self.config.default_template_for_users) |
| |
| def testProcessSubtabForm_Normal(self): |
| """If user has perms, set default templates.""" |
| post_data = fake.PostData( |
| default_template_for_developers=['Test Template'], |
| default_template_for_users=['Test Template']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertEqual(urls.ADMIN_TEMPLATES, next_url) |
| self.assertEqual(12345, self.config.default_template_for_developers) |
| self.assertEqual(12345, self.config.default_template_for_users) |
| |
| def testParseDefaultTemplateSelections_NotSpecified(self): |
| post_data = fake.PostData() |
| for_devs, for_users = self.servlet._ParseDefaultTemplateSelections( |
| post_data, self.test_templates) |
| self.assertEqual(None, for_devs) |
| self.assertEqual(None, for_users) |
| |
| def testParseDefaultTemplateSelections_TemplateNotFoundIsIgnored(self): |
| post_data = fake.PostData( |
| default_template_for_developers=['Bad value'], |
| default_template_for_users=['Bad value']) |
| for_devs, for_users = self.servlet._ParseDefaultTemplateSelections( |
| post_data, self.test_templates) |
| self.assertEqual(None, for_devs) |
| self.assertEqual(None, for_users) |
| |
| def testParseDefaultTemplateSelections_Normal(self): |
| post_data = fake.PostData( |
| default_template_for_developers=['Test Template'], |
| default_template_for_users=['Test Template']) |
| for_devs, for_users = self.servlet._ParseDefaultTemplateSelections( |
| post_data, self.test_templates) |
| self.assertEqual(12345, for_devs) |
| self.assertEqual(12345, for_users) |
| |
| |
| class AdminComponentsTest(TestBase): |
| |
| def setUp(self): |
| super(AdminComponentsTest, self).setUpServlet(issueadmin.AdminComponents) |
| self.cd_clean = tracker_bizobj.MakeComponentDef( |
| 1, self.project.project_id, 'BackEnd', 'doc', False, [], [111], 100000, |
| 122, 10000000, 133) |
| self.cd_with_subcomp = tracker_bizobj.MakeComponentDef( |
| 2, self.project.project_id, 'FrontEnd', 'doc', False, [], [111], |
| 100000, 122, 10000000, 133) |
| self.subcd = tracker_bizobj.MakeComponentDef( |
| 3, self.project.project_id, 'FrontEnd>Worker', 'doc', False, [], [111], |
| 100000, 122, 10000000, 133) |
| self.cd_with_template = tracker_bizobj.MakeComponentDef( |
| 4, self.project.project_id, 'Middle', 'doc', False, [], [111], |
| 100000, 122, 10000000, 133) |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| six.assertCountEqual( |
| self, [ |
| 'admin_tab_mode', 'failed_templ', 'component_defs', 'failed_perm', |
| 'config', 'failed_subcomp', 'open_text', 'closed_text', |
| 'labels_text', 'can_edit_project' |
| ], list(page_data.keys())) |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| self.assertEqual([], page_data['component_defs']) |
| |
| def testProcessFormData_NoErrors(self): |
| self.config.component_defs = [ |
| self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template] |
| self.services.template.TemplatesWithComponent.return_value = [] |
| post_data = { |
| 'delete_components' : '%s,%s,%s' % ( |
| self.cd_clean.path, self.cd_with_subcomp.path, self.subcd.path)} |
| url = self.servlet.ProcessFormData(self.mr, post_data) |
| self.assertTrue( |
| url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted=' |
| 'FrontEnd%3EWorker%2CFrontEnd%2CBackEnd&failed_perm=&' |
| 'failed_subcomp=&failed_templ=&ts=')) |
| |
| def testProcessFormData_SubCompError(self): |
| self.config.component_defs = [ |
| self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template] |
| self.services.template.TemplatesWithComponent.return_value = [] |
| post_data = { |
| 'delete_components' : '%s,%s' % ( |
| self.cd_clean.path, self.cd_with_subcomp.path)} |
| url = self.servlet.ProcessFormData(self.mr, post_data) |
| self.assertTrue( |
| url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted=' |
| 'BackEnd&failed_perm=&failed_subcomp=FrontEnd&' |
| 'failed_templ=&ts=')) |
| |
| def testProcessFormData_TemplateError(self): |
| self.config.component_defs = [ |
| self.cd_clean, self.cd_with_subcomp, self.subcd, self.cd_with_template] |
| |
| def mockTemplatesWithComponent(_cnxn, component_id): |
| if component_id == 4: |
| return 'template' |
| self.services.template.TemplatesWithComponent\ |
| .side_effect = mockTemplatesWithComponent |
| |
| post_data = { |
| 'delete_components' : '%s,%s,%s,%s' % ( |
| self.cd_clean.path, self.cd_with_subcomp.path, self.subcd.path, |
| self.cd_with_template.path)} |
| url = self.servlet.ProcessFormData(self.mr, post_data) |
| self.assertTrue( |
| url.startswith('http://127.0.0.1/p/proj/adminComponents?deleted=' |
| 'FrontEnd%3EWorker%2CFrontEnd%2CBackEnd&failed_perm=&' |
| 'failed_subcomp=&failed_templ=Middle&ts=')) |
| |
| |
| class AdminViewsTest(TestBase): |
| |
| def setUp(self): |
| super(AdminViewsTest, self).setUpServlet(issueadmin.AdminViews) |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| |
| six.assertCountEqual( |
| self, [ |
| 'canned_queries', 'admin_tab_mode', 'config', 'issue_notify', |
| 'new_query_indexes', 'max_queries', 'open_text', 'closed_text', |
| 'labels_text', 'can_edit_project' |
| ], list(page_data.keys())) |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| |
| def testProcessSubtabForm(self): |
| post_data = fake.PostData( |
| default_col_spec=['id pri mstone owner status summary'], |
| default_sort_spec=['mstone pri'], |
| default_x_attr=['owner'], default_y_attr=['mstone']) |
| next_url = self.servlet.ProcessSubtabForm(post_data, self.mr) |
| self.assertEqual(urls.ADMIN_VIEWS, next_url) |
| self.assertEqual( |
| 'id pri mstone owner status summary', self.config.default_col_spec) |
| self.assertEqual('mstone pri', self.config.default_sort_spec) |
| self.assertEqual('owner', self.config.default_x_attr) |
| self.assertEqual('mstone', self.config.default_y_attr) |
| |
| |
| class AdminViewsFunctionsTest(unittest.TestCase): |
| |
| def testParseListPreferences(self): |
| # If no input, col_spec will be default column spec. |
| # For other fiels empty strings should be returned. |
| (col_spec, sort_spec, x_attr, y_attr, member_default_query, |
| ) = issueadmin._ParseListPreferences({}) |
| self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, col_spec) |
| self.assertEqual('', sort_spec) |
| self.assertEqual('', x_attr) |
| self.assertEqual('', y_attr) |
| self.assertEqual('', member_default_query) |
| |
| # Test how hyphens in input are treated. |
| spec = 'label1-sub1 label2 label3-sub3' |
| (col_spec, sort_spec, x_attr, y_attr, member_default_query, |
| ) = issueadmin._ParseListPreferences( |
| fake.PostData(default_col_spec=[spec], |
| default_sort_spec=[spec], |
| default_x_attr=[spec], |
| default_y_attr=[spec]), |
| ) |
| |
| # Hyphens (and anything following) should be stripped from each term. |
| self.assertEqual('label1-sub1 label2 label3-sub3', col_spec) |
| |
| # The sort spec should be as given (except with whitespace condensed). |
| self.assertEqual(' '.join(spec.split()), sort_spec) |
| |
| # Only the first term (up to the first hyphen) should be used for x- or |
| # y-attr. |
| self.assertEqual('label1-sub1', x_attr) |
| self.assertEqual('label1-sub1', y_attr) |
| |
| # Test that multibyte strings are not mangled. |
| spec = ( |
| b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9 ' |
| b'\xe5\x9c\xb0\xe3\x81\xa6-\xe5\xbd\x93-\xe3\x81\xbe\xe3\x81\x99') |
| spec = spec.decode('utf-8') |
| (col_spec, sort_spec, x_attr, y_attr, member_default_query, |
| ) = issueadmin._ParseListPreferences( |
| fake.PostData(default_col_spec=[spec], |
| default_sort_spec=[spec], |
| default_x_attr=[spec], |
| default_y_attr=[spec], |
| member_default_query=[spec]), |
| ) |
| self.assertEqual(spec, col_spec) |
| self.assertEqual(' '.join(spec.split()), sort_spec) |
| self.assertEqual( |
| b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9'.decode('utf-8'), x_attr) |
| self.assertEqual( |
| b'\xe7\xaa\xbf\xe8\x8b\xa5-\xe7\xb9\xb9'.decode('utf-8'), y_attr) |
| self.assertEqual(spec, member_default_query) |
| |
| |
| class AdminRulesTest(TestBase): |
| |
| def setUp(self): |
| super(AdminRulesTest, self).setUpServlet(issueadmin.AdminRules) |
| |
| def testGatherPageData(self): |
| self._mockGetUser() |
| self.mox.ReplayAll() |
| page_data = self.servlet.GatherPageData(self.mr) |
| self.mox.VerifyAll() |
| |
| six.assertCountEqual( |
| self, [ |
| 'admin_tab_mode', 'config', 'rules', 'new_rule_indexes', |
| 'max_rules', 'open_text', 'closed_text', 'labels_text', |
| 'can_edit_project' |
| ], list(page_data.keys())) |
| config_view = page_data['config'] |
| self.assertEqual(789, config_view.project_id) |
| self.assertEqual([], page_data['rules']) |
| |
| def testProcessSubtabForm(self): |
| pass # TODO(jrobbins): write this test |