From e539017eef6d5eb60136346f6c4f8eb55ae59cdd Mon Sep 17 00:00:00 2001 From: Amadeusz Kryze Date: Thu, 5 Apr 2018 22:57:34 +0200 Subject: [PATCH] Added folder support in views keeping backward compatibility and added tests for it. Change-Id: I48c231cd1b9a038ede526e7e7a50e65af8aaf13b --- jenkins/__init__.py | 20 ++- tests/test_view.py | 316 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+), 7 deletions(-) diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 7ab31cf..72c6f1e 100755 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -109,11 +109,11 @@ NODE_INFO = 'computer/%(name)s/api/json?depth=%(depth)s' NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' TOGGLE_OFFLINE = 'computer/%(name)s/toggleOffline?offlineMessage=%(msg)s' CONFIG_NODE = 'computer/%(name)s/config.xml' -VIEW_NAME = 'view/%(name)s/api/json?tree=name' +VIEW_NAME = '%(folder_url)sview/%(short_name)s/api/json?tree=name' VIEW_JOBS = 'view/%(name)s/api/json?tree=jobs[url,color,name]' -CREATE_VIEW = 'createView?name=%(name)s' -CONFIG_VIEW = 'view/%(name)s/config.xml' -DELETE_VIEW = 'view/%(name)s/doDelete' +CREATE_VIEW = '%(folder_url)screateView?name=%(short_name)s' +CONFIG_VIEW = '%(folder_url)sview/%(short_name)s/config.xml' +DELETE_VIEW = '%(folder_url)sview/%(short_name)s/doDelete' SCRIPT_TEXT = 'scriptText' PROMOTION_NAME = '%(folder_url)sjob/%(short_name)s/promotion/process/%(name)s/api/json?tree=name' PROMOTION_INFO = '%(folder_url)sjob/%(short_name)s/promotion/api/json?depth=%(depth)s' @@ -1447,6 +1447,7 @@ class Jenkins(object): :returns: list of jobs, ``[{str: str, str: str, str: str, str: str}]`` ''' + folder_url, short_name = self._get_job_folder(name) try: response = self.jenkins_open(requests.Request( 'GET', self._build_url(VIEW_JOBS, locals()) @@ -1476,6 +1477,7 @@ class Jenkins(object): :param name: View name, ``str`` :returns: Name of view or None ''' + folder_url, short_name = self._get_job_folder(name) try: response = self.jenkins_open(requests.Request( 'GET', self._build_url(VIEW_NAME, locals()))) @@ -1483,11 +1485,11 @@ class Jenkins(object): return None else: actual = json.loads(response)['name'] - if actual != name: + if actual != short_name: raise JenkinsException( 'Jenkins returned an unexpected view name %s ' - '(expected: %s)' % (actual, name)) - return actual + '(expected: %s)' % (actual, short_name)) + return name def assert_view_exists(self, name, exception_message='view[%s] does not exist'): @@ -1524,6 +1526,7 @@ class Jenkins(object): :param name: Name of Jenkins view, ``str`` ''' + folder_url, short_name = self._get_job_folder(name) self.jenkins_open(requests.Request( 'POST', self._build_url(DELETE_VIEW, locals()) )) @@ -1536,6 +1539,7 @@ class Jenkins(object): :param name: Name of Jenkins view, ``str`` :param config_xml: config file text, ``str`` ''' + folder_url, short_name = self._get_job_folder(name) if self.view_exists(name): raise JenkinsException('view[%s] already exists' % (name)) @@ -1554,6 +1558,7 @@ class Jenkins(object): :param name: Name of Jenkins view, ``str`` :param config_xml: New XML configuration, ``str`` ''' + folder_url, short_name = self._get_job_folder(name) reconfig_url = self._build_url(CONFIG_VIEW, locals()) self.jenkins_open(requests.Request( 'POST', reconfig_url, @@ -1567,6 +1572,7 @@ class Jenkins(object): :param name: Name of Jenkins view, ``str`` :returns: view configuration (XML format) ''' + folder_url, short_name = self._get_job_folder(name) request = requests.Request('GET', self._build_url(CONFIG_VIEW, locals())) return self.jenkins_open(request) diff --git a/tests/test_view.py b/tests/test_view.py index 183c129..6abab09 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -28,6 +28,36 @@ class JenkinsGetViewNameTest(JenkinsViewsTestBase): self.make_url('view/Test%20View/api/json?tree=name')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_simple(self, jenkins_mock): + # VIEW_NAME will always return just name of view instead + # of dir/view_name and this is specific of jenkins + view_name_to_return = {u'name': 'Test View'} + jenkins_mock.return_value = json.dumps(view_name_to_return) + + view_name = self.j.get_view_name(u'Test Dir/Test View') + + self.assertEqual(view_name, 'Test Dir/Test View') + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Dir/view/Test%20View/api/json?tree=name')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_extended(self, jenkins_mock): + # VIEW_NAME will always return just name of view instead + # of dir/view_name and this is specific of jenkins + view_name_to_return = {u'name': 'Test View'} + jenkins_mock.return_value = json.dumps(view_name_to_return) + + view_name = self.j.get_view_name(u'Test Extended/Test Dir/Test View') + + self.assertEqual(view_name, 'Test Extended/Test Dir/Test View') + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Extended/job/Test%20Dir/view/Test%20View/api/json?tree=name')) + self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_return_none(self, jenkins_mock): jenkins_mock.side_effect = jenkins.NotFoundException() @@ -40,6 +70,30 @@ class JenkinsGetViewNameTest(JenkinsViewsTestBase): self.make_url('view/TestView/api/json?tree=name')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_return_none(self, jenkins_mock): + jenkins_mock.side_effect = jenkins.NotFoundException() + + view_name = self.j.get_view_name(u'TestDir/TestView') + + self.assertEqual(view_name, None) + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/TestDir/view/TestView/api/json?tree=name')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_return_none(self, jenkins_mock): + jenkins_mock.side_effect = jenkins.NotFoundException() + + view_name = self.j.get_view_name(u'TestExtended/TestDir/TestView') + + self.assertEqual(view_name, None) + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/TestExtended/job/TestDir/view/TestView/api/json?tree=name')) + self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_unexpected_view_name(self, jenkins_mock): view_name_to_return = {u'name': 'not the right name'} @@ -56,6 +110,38 @@ class JenkinsGetViewNameTest(JenkinsViewsTestBase): '(expected: {1})'.format(view_name_to_return['name'], 'TestView')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_unexpected_dir_view_name(self, jenkins_mock): + view_name_to_return = {u'name': 'not the right name'} + jenkins_mock.return_value = json.dumps(view_name_to_return) + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.get_view_name(u'TestDir/TestView') + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + str(context_manager.exception), + 'Jenkins returned an unexpected view name {0} ' + '(expected: {1})'.format(view_name_to_return['name'], 'TestView')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_unexpected_extended_dir_view_name(self, jenkins_mock): + view_name_to_return = {u'name': 'not the right name'} + jenkins_mock.return_value = json.dumps(view_name_to_return) + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.get_view_name(u'TestExtended/TestDir/TestView') + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestExtended/job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + str(context_manager.exception), + 'Jenkins returned an unexpected view name {0} ' + '(expected: {1})'.format(view_name_to_return['name'], 'TestView')) + self._check_requests(jenkins_mock.call_args_list) + class JenkinsAssertViewTest(JenkinsViewsTestBase): @@ -78,6 +164,22 @@ class JenkinsAssertViewTest(JenkinsViewsTestBase): self.j.assert_view_exists('ExistingView') self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_view_exists(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'ExistingView'}), + ] + self.j.assert_view_exists('Dir/ExistingView') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_view_exists(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'ExistingView'}), + ] + self.j.assert_view_exists('Extended/Dir/ExistingView') + self._check_requests(jenkins_mock.call_args_list) + class JenkinsGetViewsTest(JenkinsViewsTestBase): @@ -115,6 +217,34 @@ class JenkinsDeleteViewTest(JenkinsViewsTestBase): self.make_url('view/Test%20View/doDelete')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + None, + jenkins.NotFoundException(), + ] + + self.j.delete_view(u'Test Dir/Test View') + + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/Test%20Dir/view/Test%20View/doDelete')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + None, + jenkins.NotFoundException(), + ] + + self.j.delete_view(u'Test Extended/Test Dir/Test View') + + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/Test%20Extended/job/Test%20Dir/view/Test%20View/doDelete')) + self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_failed(self, jenkins_mock): jenkins_mock.side_effect = [ @@ -133,6 +263,42 @@ class JenkinsDeleteViewTest(JenkinsViewsTestBase): 'delete[TestView] failed') self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_failed(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'TestView'}), + json.dumps({'name': 'TestView'}), + json.dumps({'name': 'TestView'}), + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.delete_view(u'TestDir/TestView') + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestDir/view/TestView/doDelete')) + self.assertEqual( + str(context_manager.exception), + 'delete[TestDir/TestView] failed') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_failed(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'TestView'}), + json.dumps({'name': 'TestView'}), + json.dumps({'name': 'TestView'}), + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.delete_view(u'TestExtended/TestDir/TestView') + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestExtended/job/TestDir/view/TestView/doDelete')) + self.assertEqual( + str(context_manager.exception), + 'delete[TestExtended/TestDir/TestView] failed') + self._check_requests(jenkins_mock.call_args_list) + class JenkinsCreateViewTest(JenkinsViewsTestBase): @@ -151,6 +317,36 @@ class JenkinsCreateViewTest(JenkinsViewsTestBase): self.make_url('createView?name=Test%20View')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + jenkins.NotFoundException(), + None, + json.dumps({'name': 'Test View'}), + ] + + self.j.create_view(u'Test Dir/Test View', self.config_xml) + + self.assertEqual( + jenkins_mock.call_args_list[1][0][0].url, + self.make_url('job/Test%20Dir/createView?name=Test%20View')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + jenkins.NotFoundException(), + None, + json.dumps({'name': 'Test View'}), + ] + + self.j.create_view(u'Test Extended/Test Dir/Test View', self.config_xml) + + self.assertEqual( + jenkins_mock.call_args_list[1][0][0].url, + self.make_url('job/Test%20Extended/job/Test%20Dir/createView?name=Test%20View')) + self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_already_exists(self, jenkins_mock): jenkins_mock.side_effect = [ @@ -168,6 +364,40 @@ class JenkinsCreateViewTest(JenkinsViewsTestBase): 'view[TestView] already exists') self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_already_exists(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'TestView'}), + None, + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.create_view(u'TestDir/TestView', self.config_xml) + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + str(context_manager.exception), + 'view[TestDir/TestView] already exists') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_already_exists(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'TestView'}), + None, + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.create_view(u'TestExtended/TestDir/TestView', self.config_xml) + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestExtended/job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + str(context_manager.exception), + 'view[TestExtended/TestDir/TestView] already exists') + self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_failed(self, jenkins_mock): jenkins_mock.side_effect = [ @@ -189,6 +419,48 @@ class JenkinsCreateViewTest(JenkinsViewsTestBase): 'create[TestView] failed') self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_failed(self, jenkins_mock): + jenkins_mock.side_effect = [ + jenkins.NotFoundException(), + None, + jenkins.NotFoundException(), + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.create_view(u'TestDir/TestView', self.config_xml) + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + jenkins_mock.call_args_list[1][0][0].url, + self.make_url('job/TestDir/createView?name=TestView')) + self.assertEqual( + str(context_manager.exception), + 'create[TestDir/TestView] failed') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_failed(self, jenkins_mock): + jenkins_mock.side_effect = [ + jenkins.NotFoundException(), + None, + jenkins.NotFoundException(), + ] + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.create_view(u'TestExtended/TestDir/TestView', self.config_xml) + self.assertEqual( + jenkins_mock.call_args_list[0][0][0].url, + self.make_url('job/TestExtended/job/TestDir/view/TestView/api/json?tree=name')) + self.assertEqual( + jenkins_mock.call_args_list[1][0][0].url, + self.make_url('job/TestExtended/job/TestDir/createView?name=TestView')) + self.assertEqual( + str(context_manager.exception), + 'create[TestExtended/TestDir/TestView] failed') + self._check_requests(jenkins_mock.call_args_list) + class JenkinsReconfigViewTest(JenkinsViewsTestBase): @@ -205,6 +477,32 @@ class JenkinsReconfigViewTest(JenkinsViewsTestBase): self.make_url('view/Test%20View/config.xml')) self._check_requests(jenkins_mock.call_args_list) + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'Test View'}), + None, + ] + + self.j.reconfig_view(u'Test Dir/Test View', self.config_xml) + + self.assertEqual(jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Dir/view/Test%20View/config.xml')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_extended_dir_simple(self, jenkins_mock): + jenkins_mock.side_effect = [ + json.dumps({'name': 'Test View'}), + None, + ] + + self.j.reconfig_view(u'Test Extended/Test Dir/Test View', self.config_xml) + + self.assertEqual(jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Extended/job/Test%20Dir/view/Test%20View/config.xml')) + self._check_requests(jenkins_mock.call_args_list) + class JenkinsGetViewConfigTest(JenkinsViewsTestBase): @@ -216,3 +514,21 @@ class JenkinsGetViewConfigTest(JenkinsViewsTestBase): jenkins_mock.call_args[0][0].url, self.make_url('view/Test%20View/config.xml')) self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_encodes_dir_view_name(self, jenkins_mock): + self.j.get_view_config(u'Test Dir/Test View') + + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Dir/view/Test%20View/config.xml')) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_encodes_extended_dir_view_name(self, jenkins_mock): + self.j.get_view_config(u'Test Extended/Test Dir/Test View') + + self.assertEqual( + jenkins_mock.call_args[0][0].url, + self.make_url('job/Test%20Extended/job/Test%20Dir/view/Test%20View/config.xml')) + self._check_requests(jenkins_mock.call_args_list)