Fixed #21341 -- Eased https requests with the test client

All request methods of ``django.test.client.Client`` receive a ``secure``
argument that defaults to ``False`` indicating whether or not to make the
request through https.
Thanks Aymeric Augustin for the review.
This commit is contained in:
Unai Zalakain 2013-10-28 15:00:54 +01:00 committed by Claude Paroz
parent 19256f300e
commit 99b681e227
5 changed files with 89 additions and 45 deletions

View File

@ -269,60 +269,68 @@ class RequestFactory(object):
path = path.encode('utf-8').decode('iso-8859-1') path = path.encode('utf-8').decode('iso-8859-1')
return path return path
def get(self, path, data={}, **extra): def get(self, path, data={}, secure=False, **extra):
"Construct a GET request." "Construct a GET request."
r = { r = {
'QUERY_STRING': urlencode(data, doseq=True), 'QUERY_STRING': urlencode(data, doseq=True),
} }
r.update(extra) r.update(extra)
return self.generic('GET', path, **r) return self.generic('GET', path, secure=secure, **r)
def post(self, path, data={}, content_type=MULTIPART_CONTENT, def post(self, path, data={}, content_type=MULTIPART_CONTENT,
**extra): secure=False, **extra):
"Construct a POST request." "Construct a POST request."
post_data = self._encode_data(data, content_type) post_data = self._encode_data(data, content_type)
return self.generic('POST', path, post_data, content_type, **extra) return self.generic('POST', path, post_data, content_type,
secure=secure, **extra)
def head(self, path, data={}, **extra): def head(self, path, data={}, secure=False, **extra):
"Construct a HEAD request." "Construct a HEAD request."
r = { r = {
'QUERY_STRING': urlencode(data, doseq=True), 'QUERY_STRING': urlencode(data, doseq=True),
} }
r.update(extra) r.update(extra)
return self.generic('HEAD', path, **r) return self.generic('HEAD', path, secure=secure, **r)
def options(self, path, data='', content_type='application/octet-stream', def options(self, path, data='', content_type='application/octet-stream',
**extra): secure=False, **extra):
"Construct an OPTIONS request." "Construct an OPTIONS request."
return self.generic('OPTIONS', path, data, content_type, **extra) return self.generic('OPTIONS', path, data, content_type,
secure=secure, **extra)
def put(self, path, data='', content_type='application/octet-stream', def put(self, path, data='', content_type='application/octet-stream',
**extra): secure=False, **extra):
"Construct a PUT request." "Construct a PUT request."
return self.generic('PUT', path, data, content_type, **extra) return self.generic('PUT', path, data, content_type,
secure=secure, **extra)
def patch(self, path, data='', content_type='application/octet-stream', def patch(self, path, data='', content_type='application/octet-stream',
**extra): secure=False, **extra):
"Construct a PATCH request." "Construct a PATCH request."
return self.generic('PATCH', path, data, content_type, **extra) return self.generic('PATCH', path, data, content_type,
secure=secure, **extra)
def delete(self, path, data='', content_type='application/octet-stream', def delete(self, path, data='', content_type='application/octet-stream',
**extra): secure=False, **extra):
"Construct a DELETE request." "Construct a DELETE request."
return self.generic('DELETE', path, data, content_type, **extra) return self.generic('DELETE', path, data, content_type,
secure=secure, **extra)
def generic(self, method, path, def generic(self, method, path, data='',
data='', content_type='application/octet-stream', **extra): content_type='application/octet-stream', secure=False,
**extra):
"""Constructs an arbitrary HTTP request.""" """Constructs an arbitrary HTTP request."""
parsed = urlparse(path) parsed = urlparse(path)
data = force_bytes(data, settings.DEFAULT_CHARSET) data = force_bytes(data, settings.DEFAULT_CHARSET)
r = { r = {
'PATH_INFO': self._get_path(parsed), 'PATH_INFO': self._get_path(parsed),
'REQUEST_METHOD': str(method), 'REQUEST_METHOD': str(method),
'SERVER_PORT': str('443') if secure else str('80'),
'wsgi.url_scheme': str('https') if secure else str('http'),
} }
if data: if data:
r.update({ r.update({
@ -445,72 +453,82 @@ class Client(RequestFactory):
signals.template_rendered.disconnect(dispatch_uid=signal_uid) signals.template_rendered.disconnect(dispatch_uid=signal_uid)
got_request_exception.disconnect(dispatch_uid="request-exception") got_request_exception.disconnect(dispatch_uid="request-exception")
def get(self, path, data={}, follow=False, **extra): def get(self, path, data={}, follow=False, secure=False, **extra):
""" """
Requests a response from the server using GET. Requests a response from the server using GET.
""" """
response = super(Client, self).get(path, data=data, **extra) response = super(Client, self).get(path, data=data, secure=secure,
**extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def post(self, path, data={}, content_type=MULTIPART_CONTENT, def post(self, path, data={}, content_type=MULTIPART_CONTENT,
follow=False, **extra): follow=False, secure=False, **extra):
""" """
Requests a response from the server using POST. Requests a response from the server using POST.
""" """
response = super(Client, self).post(path, data=data, content_type=content_type, **extra) response = super(Client, self).post(path, data=data,
content_type=content_type,
secure=secure, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def head(self, path, data={}, follow=False, **extra): def head(self, path, data={}, follow=False, secure=False, **extra):
""" """
Request a response from the server using HEAD. Request a response from the server using HEAD.
""" """
response = super(Client, self).head(path, data=data, **extra) response = super(Client, self).head(path, data=data, secure=secure,
**extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def options(self, path, data='', content_type='application/octet-stream', def options(self, path, data='', content_type='application/octet-stream',
follow=False, **extra): follow=False, secure=False, **extra):
""" """
Request a response from the server using OPTIONS. Request a response from the server using OPTIONS.
""" """
response = super(Client, self).options(path, data=data, content_type=content_type, **extra) response = super(Client, self).options(path, data=data,
content_type=content_type,
secure=secure, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def put(self, path, data='', content_type='application/octet-stream', def put(self, path, data='', content_type='application/octet-stream',
follow=False, **extra): follow=False, secure=False, **extra):
""" """
Send a resource to the server using PUT. Send a resource to the server using PUT.
""" """
response = super(Client, self).put(path, data=data, content_type=content_type, **extra) response = super(Client, self).put(path, data=data,
content_type=content_type,
secure=secure, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def patch(self, path, data='', content_type='application/octet-stream', def patch(self, path, data='', content_type='application/octet-stream',
follow=False, **extra): follow=False, secure=False, **extra):
""" """
Send a resource to the server using PATCH. Send a resource to the server using PATCH.
""" """
response = super(Client, self).patch( response = super(Client, self).patch(path, data=data,
path, data=data, content_type=content_type, **extra) content_type=content_type,
secure=secure, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def delete(self, path, data='', content_type='application/octet-stream', def delete(self, path, data='', content_type='application/octet-stream',
follow=False, **extra): follow=False, secure=False, **extra):
""" """
Send a DELETE request to the server. Send a DELETE request to the server.
""" """
response = super(Client, self).delete( response = super(Client, self).delete(path, data=data,
path, data=data, content_type=content_type, **extra) content_type=content_type,
secure=secure, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response

View File

@ -444,6 +444,10 @@ Tests
client can't fetch externals URLs, this allows you to use ``assertRedirects`` client can't fetch externals URLs, this allows you to use ``assertRedirects``
with redirects that aren't part of your Django app. with redirects that aren't part of your Django app.
* The ``secure`` argument was added to all the request methods of
:class:`~django.test.Client`. If ``True``, the request will be made
through HTTPS.
Backwards incompatible changes in 1.7 Backwards incompatible changes in 1.7
===================================== =====================================

View File

@ -431,8 +431,11 @@ Use the ``django.test.Client`` class to make requests.
Once you have a ``Client`` instance, you can call any of the following Once you have a ``Client`` instance, you can call any of the following
methods: methods:
.. method:: Client.get(path, data={}, follow=False, **extra) .. method:: Client.get(path, data={}, follow=False, secure=False, **extra)
.. versionadded:: 1.7
The ``secure`` argument was added.
Makes a GET request on the provided ``path`` and returns a ``Response`` Makes a GET request on the provided ``path`` and returns a ``Response``
object, which is documented below. object, which is documented below.
@ -488,7 +491,10 @@ Use the ``django.test.Client`` class to make requests.
>>> response.redirect_chain >>> response.redirect_chain
[(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
.. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) If you set ``secure`` to ``True`` the client will emulate an HTTPS
request.
.. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)
Makes a POST request on the provided ``path`` and returns a Makes a POST request on the provided ``path`` and returns a
``Response`` object, which is documented below. ``Response`` object, which is documented below.
@ -562,14 +568,17 @@ Use the ``django.test.Client`` class to make requests.
and a ``redirect_chain`` attribute will be set in the response object and a ``redirect_chain`` attribute will be set in the response object
containing tuples of the intermediate urls and status codes. containing tuples of the intermediate urls and status codes.
.. method:: Client.head(path, data={}, follow=False, **extra) If you set ``secure`` to ``True`` the client will emulate an HTTPS
request.
.. method:: Client.head(path, data={}, follow=False, secure=False, **extra)
Makes a HEAD request on the provided ``path`` and returns a Makes a HEAD request on the provided ``path`` and returns a
``Response`` object. This method works just like :meth:`Client.get`, ``Response`` object. This method works just like :meth:`Client.get`,
including the ``follow`` and ``extra`` arguments, except it does not including the ``follow``, ``secure`` and ``extra`` arguments, except
return a message body. it does not return a message body.
.. method:: Client.options(path, data='', content_type='application/octet-stream', follow=False, **extra) .. method:: Client.options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
Makes an OPTIONS request on the provided ``path`` and returns a Makes an OPTIONS request on the provided ``path`` and returns a
``Response`` object. Useful for testing RESTful interfaces. ``Response`` object. Useful for testing RESTful interfaces.
@ -577,10 +586,10 @@ Use the ``django.test.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``. a ``Content-Type`` header is set to ``content_type``.
The ``follow`` and ``extra`` arguments act the same as for The ``follow``, ``secure`` and ``extra`` arguments act the same as for
:meth:`Client.get`. :meth:`Client.get`.
.. method:: Client.put(path, data='', content_type='application/octet-stream', follow=False, **extra) .. method:: Client.put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
Makes a PUT request on the provided ``path`` and returns a Makes a PUT request on the provided ``path`` and returns a
``Response`` object. Useful for testing RESTful interfaces. ``Response`` object. Useful for testing RESTful interfaces.
@ -588,18 +597,18 @@ Use the ``django.test.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``. a ``Content-Type`` header is set to ``content_type``.
The ``follow`` and ``extra`` arguments act the same as for The ``follow``, ``secure`` and ``extra`` arguments act the same as for
:meth:`Client.get`. :meth:`Client.get`.
.. method:: Client.patch(path, data='', content_type='application/octet-stream', follow=False, **extra) .. method:: Client.patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
Makes a PATCH request on the provided ``path`` and returns a Makes a PATCH request on the provided ``path`` and returns a
``Response`` object. Useful for testing RESTful interfaces. ``Response`` object. Useful for testing RESTful interfaces.
The ``follow`` and ``extra`` arguments act the same as for The ``follow``, ``secure`` and ``extra`` arguments act the same as for
:meth:`Client.get`. :meth:`Client.get`.
.. method:: Client.delete(path, data='', content_type='application/octet-stream', follow=False, **extra) .. method:: Client.delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)
Makes an DELETE request on the provided ``path`` and returns a Makes an DELETE request on the provided ``path`` and returns a
``Response`` object. Useful for testing RESTful interfaces. ``Response`` object. Useful for testing RESTful interfaces.
@ -607,7 +616,7 @@ Use the ``django.test.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``. a ``Content-Type`` header is set to ``content_type``.
The ``follow`` and ``extra`` arguments act the same as for The ``follow``, ``secure`` and ``extra`` arguments act the same as for
:meth:`Client.get`. :meth:`Client.get`.
.. method:: Client.login(**credentials) .. method:: Client.login(**credentials)

View File

@ -93,6 +93,18 @@ class ClientTest(TestCase):
self.assertEqual(response.templates[0].name, "Book template") self.assertEqual(response.templates[0].name, "Book template")
self.assertEqual(response.content, b"Blink - Malcolm Gladwell") self.assertEqual(response.content, b"Blink - Malcolm Gladwell")
def test_insecure(self):
"GET a URL through http"
response = self.client.get('/test_client/secure_view/', secure=False)
self.assertFalse(response.test_was_secure_request)
self.assertEqual(response.test_server_port, '80')
def test_secure(self):
"GET a URL through https"
response = self.client.get('/test_client/secure_view/', secure=True)
self.assertTrue(response.test_was_secure_request)
self.assertEqual(response.test_server_port, '443')
def test_redirect(self): def test_redirect(self):
"GET a URL that redirects elsewhere" "GET a URL that redirects elsewhere"
response = self.client.get('/test_client/redirect_view/') response = self.client.get('/test_client/redirect_view/')

View File

@ -68,6 +68,7 @@ def view_with_secure(request):
"A view that indicates if the request was secure" "A view that indicates if the request was secure"
response = HttpResponse() response = HttpResponse()
response.test_was_secure_request = request.is_secure() response.test_was_secure_request = request.is_secure()
response.test_server_port = request.META.get('SERVER_PORT', 80)
return response return response
def double_redirect_view(request): def double_redirect_view(request):