Browse Source

Fixed #23606 -- Implemented Client and RequestFactory trace() methods.

Thanks KevinEtienne for the suggestion.
Rigel Di Scala 10 years ago
parent
commit
28634394f5

+ 13 - 0
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.

+ 4 - 0
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.

+ 2 - 2
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

+ 14 - 0
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>`

+ 52 - 3
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)

+ 1 - 0
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),

+ 29 - 1
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