summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRigel Di Scala <rigel.discala@propylon.com>2014-10-13 12:10:00 +0100
committerTim Graham <timograham@gmail.com>2014-10-28 10:11:12 -0400
commit28634394f53b9ab27d3419cfde047ee78e491d97 (patch)
tree00a7e45cf79d6b2b468b94d9c183a2c384929627
parent713f23492a52df8dbb4ae1175f5d0a0f3e2c4005 (diff)
Fixed #23606 -- Implemented Client and RequestFactory trace() methods.
Thanks KevinEtienne for the suggestion.
-rw-r--r--django/test/client.py13
-rw-r--r--docs/releases/1.8.txt4
-rw-r--r--docs/topics/testing/advanced.txt4
-rw-r--r--docs/topics/testing/tools.txt14
-rw-r--r--tests/test_client/tests.py55
-rw-r--r--tests/test_client/urls.py1
-rw-r--r--tests/test_client/views.py30
7 files changed, 115 insertions, 6 deletions
diff --git a/django/test/client.py b/django/test/client.py
index b1ff33e3d3..fb8ff75e95 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -306,6 +306,10 @@ class RequestFactory(object):
r.update(extra)
return self.generic('HEAD', path, secure=secure, **r)
+ def trace(self, path, secure=False, **extra):
+ "Construct a TRACE request."
+ return self.generic('TRACE', path, secure=secure, **extra)
+
def options(self, path, data='', content_type='application/octet-stream',
secure=False, **extra):
"Construct an OPTIONS request."
@@ -552,6 +556,15 @@ class Client(RequestFactory):
response = self._handle_redirects(response, **extra)
return response
+ def trace(self, path, data='', follow=False, secure=False, **extra):
+ """
+ Send a TRACE request to the server.
+ """
+ response = super(Client, self).trace(path, data=data, secure=secure, **extra)
+ if follow:
+ response = self._handle_redirects(response, **extra)
+ return response
+
def login(self, **credentials):
"""
Sets the Factory to appear as if it has successfully logged into a site.
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index eef836f6aa..23c6deeef1 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -372,6 +372,10 @@ Requests and Responses
Tests
^^^^^
+* The :class:`RequestFactory.trace() <django.test.RequestFactory>`
+ and :class:`Client.trace() <django.test.Client.trace>` methods were
+ implemented, allowing you to create ``TRACE`` requests in your tests.
+
* The ``count`` argument was added to
:meth:`~django.test.SimpleTestCase.assertTemplateUsed`. This allows you to
assert that a template was rendered a specific number of times.
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index bbd8584328..d817cf4f02 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -21,8 +21,8 @@ restricted subset of the test client API:
* It only has access to the HTTP methods :meth:`~Client.get()`,
:meth:`~Client.post()`, :meth:`~Client.put()`,
- :meth:`~Client.delete()`, :meth:`~Client.head()` and
- :meth:`~Client.options()`.
+ :meth:`~Client.delete()`, :meth:`~Client.head()`,
+ :meth:`~Client.options()`, and :meth:`~Client.trace()`.
* These methods accept all the same arguments *except* for
``follows``. Since this is just a factory for producing
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 43c5fcaf48..439910aa8a 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -316,6 +316,20 @@ Use the ``django.test.Client`` class to make requests.
The ``follow``, ``secure`` and ``extra`` arguments act the same as for
:meth:`Client.get`.
+ .. method:: Client.trace(path, follow=False, secure=False, **extra)
+
+ .. versionadded:: 1.8
+
+ Makes a TRACE request on the provided ``path`` and returns a
+ ``Response`` object. Useful for simulating diagnostic probes.
+
+ Unlike the other request methods, ``data`` is not provided as a keyword
+ parameter in order to comply with :rfc:`2616`, which mandates that
+ TRACE requests should not have an entity-body.
+
+ The ``follow``, ``secure``, and ``extra`` arguments act the same as for
+ :meth:`Client.get`.
+
.. method:: Client.login(**credentials)
If your site uses Django's :doc:`authentication system</topics/auth/index>`
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index 034bb95b41..cf96b89a62 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -23,10 +23,11 @@ rather than the HTML rendered to the end-user.
from __future__ import unicode_literals
from django.core import mail
+from django.http import HttpResponse
from django.test import Client, TestCase, RequestFactory
from django.test import override_settings
-from .views import get_view
+from .views import get_view, post_view, trace_view
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
@@ -79,6 +80,13 @@ class ClientTest(TestCase):
self.assertEqual(response.templates[0].name, 'POST Template')
self.assertContains(response, 'Data received')
+ def test_trace(self):
+ """TRACE a view"""
+ response = self.client.trace('/trace_view/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['method'], 'TRACE')
+ self.assertEqual(response.templates[0].name, 'TRACE Template')
+
def test_response_headers(self):
"Check the value of HTTP headers returned in a response"
response = self.client.get("/header_view/")
@@ -552,13 +560,54 @@ class CustomTestClientTest(TestCase):
self.assertEqual(hasattr(self.client, "i_am_customized"), True)
+_generic_view = lambda request: HttpResponse(status=200)
+
+
@override_settings(ROOT_URLCONF='test_client.urls')
class RequestFactoryTest(TestCase):
+ """Tests for the request factory."""
+
+ # A mapping between names of HTTP/1.1 methods and their test views.
+ http_methods_and_views = (
+ ('get', get_view),
+ ('post', post_view),
+ ('put', _generic_view),
+ ('patch', _generic_view),
+ ('delete', _generic_view),
+ ('head', _generic_view),
+ ('options', _generic_view),
+ ('trace', trace_view),
+ )
+
+ def setUp(self):
+ self.request_factory = RequestFactory()
def test_request_factory(self):
- factory = RequestFactory()
- request = factory.get('/somewhere/')
+ """The request factory implements all the HTTP/1.1 methods."""
+ for method_name, view in self.http_methods_and_views:
+ method = getattr(self.request_factory, method_name)
+ request = method('/somewhere/')
+ response = view(request)
+
+ self.assertEqual(response.status_code, 200)
+
+ def test_get_request_from_factory(self):
+ """
+ The request factory returns a templated response for a GET request.
+ """
+ request = self.request_factory.get('/somewhere/')
response = get_view(request)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'This is a test')
+
+ def test_trace_request_from_factory(self):
+ """The request factory returns an echo response for a TRACE request."""
+ url_path = '/somewhere/'
+ request = self.request_factory.trace(url_path)
+ response = trace_view(request)
+ protocol = request.META["SERVER_PROTOCOL"]
+ echoed_request_line = "TRACE {} {}".format(url_path, protocol)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, echoed_request_line)
diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py
index 4b34bfb113..7073d4590e 100644
--- a/tests/test_client/urls.py
+++ b/tests/test_client/urls.py
@@ -8,6 +8,7 @@ from . import views
urlpatterns = [
url(r'^get_view/$', views.get_view, name='get_view'),
url(r'^post_view/$', views.post_view),
+ url(r'^trace_view/$', views.trace_view),
url(r'^header_view/$', views.view_with_header),
url(r'^raw_post_view/$', views.raw_post_view),
url(r'^redirect_view/$', views.redirect_view),
diff --git a/tests/test_client/views.py b/tests/test_client/views.py
index 8660e93c7f..2b7a4de63c 100644
--- a/tests/test_client/views.py
+++ b/tests/test_client/views.py
@@ -5,7 +5,10 @@ from django.core import mail
from django.forms import fields
from django.forms.forms import Form, ValidationError
from django.forms.formsets import formset_factory, BaseFormSet
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
+from django.http import (
+ HttpResponse, HttpResponseRedirect, HttpResponseNotFound,
+ HttpResponseNotAllowed, HttpResponseBadRequest,
+)
from django.shortcuts import render_to_response
from django.template import Context, Template
from django.utils.decorators import method_decorator
@@ -20,6 +23,31 @@ def get_view(request):
return HttpResponse(t.render(c))
+def trace_view(request):
+ """
+ A simple view that expects a TRACE request and echoes its status line.
+
+ TRACE requests should not have an entity; the view will return a 400 status
+ response if it is present.
+ """
+ if request.method.upper() != "TRACE":
+ return HttpResponseNotAllowed("TRACE")
+ elif request.body:
+ return HttpResponseBadRequest("TRACE requests MUST NOT include an entity")
+ else:
+ protocol = request.META["SERVER_PROTOCOL"]
+ t = Template(
+ '{{ method }} {{ uri }} {{ version }}',
+ name="TRACE Template",
+ )
+ c = Context({
+ 'method': request.method,
+ 'uri': request.path,
+ 'version': protocol,
+ })
+ return HttpResponse(t.render(c))
+
+
def post_view(request):
"""A view that expects a POST, and returns a different template depending
on whether any POST data is available