diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 4aea85b..dd98dcb 100644 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -128,6 +128,11 @@ class JenkinsException(Exception): pass +class NotFoundException(JenkinsException): + '''A special exception to call out the case of receiving a 404.''' + pass + + def auth_headers(username, password): '''Simple implementation of HTTP Basic Authentication. @@ -169,13 +174,14 @@ class Jenkins(object): def maybe_add_crumb(self, req): # We don't know yet whether we need a crumb if self.crumb is None: - response = self.jenkins_open(Request( - self.server + CRUMB_URL), add_crumb=False) - if response: - self.crumb = json.loads(response.decode('utf-8')) - else: + try: + response = self.jenkins_open(Request( + self.server + CRUMB_URL), add_crumb=False) + except NotFoundException: # Don't need crumbs self.crumb = False + else: + self.crumb = json.loads(response.decode('utf-8')) if self.crumb: req.add_header(self.crumb['crumbRequestField'], self.crumb['crumb']) @@ -209,17 +215,19 @@ class Jenkins(object): :param name: Job name, ``str`` :returns: Name of job or None ''' - response = self.jenkins_open( - Request(self.server + JOB_NAME % self._get_encoded_params(locals()))) - if response: + try: + response = self.jenkins_open( + Request(self.server + JOB_NAME % + self._get_encoded_params(locals()))) + except NotFoundException: + return None + else: actual = json.loads(response)['name'] if actual != name: raise JenkinsException( 'Jenkins returned an unexpected job name %s ' '(expected: %s)' % (actual, name)) return actual - else: - return None def debug_job_info(self, job_name): '''Print out job info in more readable format.''' @@ -250,6 +258,8 @@ class Jenkins(object): 'Possibly authentication failed [%s]: %s' % ( e.code, e.msg) ) + elif e.code == 404: + raise NotFoundException('Requested item could not be found') # right now I'm getting 302 infinites on a successful delete def get_build_info(self, name, number, depth=0): @@ -307,9 +317,14 @@ class Jenkins(object): ''' # Jenkins seems to always return a 404 when using this REST endpoint # https://issues.jenkins-ci.org/browse/JENKINS-21311 - self.jenkins_open( - Request(self.server + CANCEL_QUEUE % locals(), '', - headers={'Referer': self.server})) + try: + self.jenkins_open( + Request(self.server + CANCEL_QUEUE % locals(), '', + headers={'Referer': self.server})) + except NotFoundException: + # Exception is expected; cancel_queue() is a best-effort + # mechanism, so ignore it + pass def get_info(self): """Get information on this Master. diff --git a/tests/test_jenkins.py b/tests/test_jenkins.py index afe6f53..e7aec31 100644 --- a/tests/test_jenkins.py +++ b/tests/test_jenkins.py @@ -88,7 +88,7 @@ class JenkinsTest(unittest.TestCase): @patch('jenkins.urlopen') def test_maybe_add_crumb(self, jenkins_mock): - jenkins_mock.return_value = get_mock_urlopen_return_value() + jenkins_mock.side_effect = jenkins.NotFoundException() j = jenkins.Jenkins('http://example.com/', 'test', 'test') request = jenkins.Request('http://example.com/job/TestJob') @@ -162,11 +162,29 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.call_args[0][0].get_full_url(), 'http://example.com/job/TestJob') + @patch('jenkins.urlopen') + def test_jenkins_open__404(self, jenkins_mock): + jenkins_mock.side_effect = jenkins.HTTPError( + 'http://example.com/job/TestJob', + code=404, + msg="basic auth failed", + hdrs=[], + fp=None) + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + request = jenkins.Request('http://example.com/job/TestJob') + + with self.assertRaises(jenkins.NotFoundException) as context_manager: + j.jenkins_open(request, add_crumb=False) + self.assertEqual( + str(context_manager.exception), + 'Requested item could not be found') + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + 'http://example.com/job/TestJob') + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_assert_job_exists__job_missing(self, jenkins_mock): - jenkins_mock.side_effect = [ - None, - ] + jenkins_mock.side_effect = jenkins.NotFoundException() j = jenkins.Jenkins('http://example.com/', 'test', 'test') with self.assertRaises(jenkins.JenkinsException) as context_manager: @@ -191,7 +209,7 @@ class JenkinsTest(unittest.TestCase): Foo """ jenkins_mock.side_effect = [ - None, + jenkins.NotFoundException(), None, json.dumps({'name': 'Test Job'}), ] @@ -233,9 +251,9 @@ class JenkinsTest(unittest.TestCase): Foo """ jenkins_mock.side_effect = [ + jenkins.NotFoundException(), None, - None, - None, + jenkins.NotFoundException(), ] j = jenkins.Jenkins('http://example.com/', 'test', 'test') @@ -317,7 +335,7 @@ class JenkinsTest(unittest.TestCase): @patch.object(jenkins.Jenkins, 'jenkins_open') def test_build_job__job_doesnt_exist(self, jenkins_mock): - jenkins_mock.side_effect = [None] + jenkins_mock.side_effect = jenkins.NotFoundException() j = jenkins.Jenkins('http://example.com/', 'test', 'test') with self.assertRaises(jenkins.JenkinsException) as context_manager: @@ -833,8 +851,7 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.side_effect = [ json.dumps({'name': 'TestJob'}), None, - None, - None, + jenkins.NotFoundException(), ] j = jenkins.Jenkins('http://example.com/', 'test', 'test') @@ -870,8 +887,7 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.side_effect = [ json.dumps({'name': 'TestJob'}), None, - None, - None, + jenkins.NotFoundException(), ] j = jenkins.Jenkins('http://example.com/', 'test', 'test') @@ -889,8 +905,7 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.side_effect = [ json.dumps({'name': 'Test Job'}), None, - None, - None, + jenkins.NotFoundException(), ] j = jenkins.Jenkins('http://example.com/', 'test', 'test') @@ -899,7 +914,6 @@ class JenkinsTest(unittest.TestCase): self.assertEqual( jenkins_mock.call_args_list[1][0][0].get_full_url(), 'http://example.com/job/Test%20Job/doDelete') - self.assertFalse(j.job_exists('Test Job')) @patch.object(jenkins.Jenkins, 'jenkins_open') def test_delete_job__delete_failed(self, jenkins_mock): @@ -967,7 +981,7 @@ class JenkinsTest(unittest.TestCase): @patch.object(jenkins.Jenkins, 'jenkins_open') def test_get_job_name__None(self, jenkins_mock): - jenkins_mock.return_value = None + jenkins_mock.side_effect = jenkins.NotFoundException() j = jenkins.Jenkins('http://example.com/', 'test', 'test') job_name = j.get_job_name(u'TestJob') @@ -1005,6 +1019,19 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.call_args[0][0].get_full_url(), u'http://example.com/queue/cancelItem?id=52') + @patch.object(jenkins.Jenkins, 'jenkins_open', + side_effect=jenkins.NotFoundException('not found')) + def test_cancel_queue__notfound(self, jenkins_mock): + job_name_to_return = {u'name': 'TestJob'} + jenkins_mock.return_value = json.dumps(job_name_to_return) + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + + j.cancel_queue(52) + + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + u'http://example.com/queue/cancelItem?id=52') + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_get_node_info(self, jenkins_mock): node_info = {