From f420d6de01de4558a8f285deaedae58cbc4cb743 Mon Sep 17 00:00:00 2001 From: Dennis Dmitriev Date: Mon, 4 Jun 2018 21:02:20 +0300 Subject: [PATCH] Check for 'Location' header in the response If the keep_alive is not available because of using reverse proxy, the header 'Content-Lenght' may not be available, causing the exception: Error communicating with server[...]: empty response However, if 'Location' header is present, the response is valid. Add unit tests: - build_job must pass even if no 'content-lenght' in response header - build_job must fail if no 'location' in response header Change-Id: I4da6dd19f9d8302a76652a3686a9377f9a2503a6 Closes-Bug:#1775047 --- jenkins/__init__.py | 7 +++++++ tests/helper.py | 5 +++-- tests/jobs/test_build.py | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 5cb99ea..8e8dd9d 100755 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -513,6 +513,7 @@ class Jenkins(object): headers = response.headers if (headers.get('content-length') is None and headers.get('transfer-encoding') is None and + headers.get('location') is None and (response.content is None or len(response.content) <= 0)): # response body should only exist if one of these is provided raise EmptyResponseException( @@ -1274,6 +1275,12 @@ class Jenkins(object): ''' response = self.jenkins_request(requests.Request( 'POST', self.build_job_url(name, parameters, token))) + + if 'Location' not in response.headers: + raise EmptyResponseException( + "Header 'Location' not found in " + "response from server[%s]" % self.server) + location = response.headers['Location'] # location is a queue item, eg. "http://jenkins/queue/item/25/" if location.endswith('/'): diff --git a/tests/helper.py b/tests/helper.py index 882ca83..f6f57cc 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -78,14 +78,15 @@ class NullServer(socketserver.TCPServer): *args, **kwargs) -def build_response_mock(status_code, json_body=None, headers=None, **kwargs): +def build_response_mock(status_code, json_body=None, headers=None, + add_content_length=True, **kwargs): real_response = requests.Response() real_response.status_code = status_code text = None if json_body is not None: text = json.dumps(json_body) - if headers is not {}: + if add_content_length and headers is not {}: real_response.headers['content-length'] = len(text) if headers is not None: diff --git a/tests/jobs/test_build.py b/tests/jobs/test_build.py index 7fac8e4..611925a 100644 --- a/tests/jobs/test_build.py +++ b/tests/jobs/test_build.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from mock import patch +import jenkins from six.moves.urllib.parse import quote from tests.helper import build_response_mock from tests.jobs.base import JenkinsJobsTestBase @@ -19,6 +20,49 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase): self.make_url(quote(u'job/Test Jøb/build'.encode('utf8')))) self.assertEqual(queue_id, 25) + @patch('jenkins.requests.Session.send', autospec=True) + def test_assert_no_location(self, session_send_mock): + session_send_mock.return_value = build_response_mock(302, {}) + + with self.assertRaises(jenkins.EmptyResponseException) as context_mgr: + self.j.build_job(u'Test Job') + + self.assertEqual( + str(context_mgr.exception), + "Header 'Location' not found in response from server[{0}]".format( + self.make_url(''))) + + @patch.object(jenkins.Jenkins, 'maybe_add_crumb') + @patch('jenkins.requests.Session.send', autospec=True) + def test_simple_no_content_lenght(self, session_send_mock, + maybe_add_crumb_mock): + maybe_add_crumb_mock.return_value = None + session_send_mock.return_value = build_response_mock( + 201, None, add_content_length=False, + headers={'Location': self.make_url('/queue/item/25/')}) + + queue_id = self.j.build_job(u'Test Job') + + self.assertEqual(session_send_mock.call_args[0][1].url, + self.make_url('job/Test%20Job/build')) + self.assertEqual(queue_id, 25) + + @patch.object(jenkins.Jenkins, 'maybe_add_crumb') + @patch('jenkins.requests.Session.send', autospec=True) + def test_assert_no_content_lenght_no_location(self, session_send_mock, + maybe_add_crumb_mock): + maybe_add_crumb_mock.return_value = None + session_send_mock.return_value = build_response_mock( + 201, None, add_content_length=False) + + with self.assertRaises(jenkins.EmptyResponseException) as context_mgr: + self.j.build_job(u'Test Job') + + self.assertEqual( + str(context_mgr.exception), + 'Error communicating with server[{0}]: empty response'.format( + self.make_url(''))) + @patch('jenkins.requests.Session.send', autospec=True) def test_in_folder(self, session_send_mock): session_send_mock.return_value = build_response_mock(