Skip to content
/ django Public

Commit 2863439

Browse files
Rigel Di Scalatimgraham
authored andcommitted
Fixed #23606 -- Implemented Client and RequestFactory trace() methods.
Thanks KevinEtienne for the suggestion.
1 parent 713f234 commit 2863439

File tree

7 files changed

+115
-6
lines changed

7 files changed

+115
-6
lines changed

django/test/client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ def head(self, path, data=None, secure=False, **extra):
306306
r.update(extra)
307307
return self.generic('HEAD', path, secure=secure, **r)
308308

309+
def trace(self, path, secure=False, **extra):
310+
"Construct a TRACE request."
311+
return self.generic('TRACE', path, secure=secure, **extra)
312+
309313
def options(self, path, data='', content_type='application/octet-stream',
310314
secure=False, **extra):
311315
"Construct an OPTIONS request."
@@ -552,6 +556,15 @@ def delete(self, path, data='', content_type='application/octet-stream',
552556
response = self._handle_redirects(response, **extra)
553557
return response
554558

559+
def trace(self, path, data='', follow=False, secure=False, **extra):
560+
"""
561+
Send a TRACE request to the server.
562+
"""
563+
response = super(Client, self).trace(path, data=data, secure=secure, **extra)
564+
if follow:
565+
response = self._handle_redirects(response, **extra)
566+
return response
567+
555568
def login(self, **credentials):
556569
"""
557570
Sets the Factory to appear as if it has successfully logged into a site.

docs/releases/1.8.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ Requests and Responses
372372
Tests
373373
^^^^^
374374

375+
* The :class:`RequestFactory.trace() <django.test.RequestFactory>`
376+
and :class:`Client.trace() <django.test.Client.trace>` methods were
377+
implemented, allowing you to create ``TRACE`` requests in your tests.
378+
375379
* The ``count`` argument was added to
376380
:meth:`~django.test.SimpleTestCase.assertTemplateUsed`. This allows you to
377381
assert that a template was rendered a specific number of times.

docs/topics/testing/advanced.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ restricted subset of the test client API:
2121

2222
* It only has access to the HTTP methods :meth:`~Client.get()`,
2323
:meth:`~Client.post()`, :meth:`~Client.put()`,
24-
:meth:`~Client.delete()`, :meth:`~Client.head()` and
25-
:meth:`~Client.options()`.
24+
:meth:`~Client.delete()`, :meth:`~Client.head()`,
25+
:meth:`~Client.options()`, and :meth:`~Client.trace()`.
2626

2727
* These methods accept all the same arguments *except* for
2828
``follows``. Since this is just a factory for producing

docs/topics/testing/tools.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,20 @@ Use the ``django.test.Client`` class to make requests.
316316
The ``follow``, ``secure`` and ``extra`` arguments act the same as for
317317
:meth:`Client.get`.
318318

319+
.. method:: Client.trace(path, follow=False, secure=False, **extra)
320+
321+
.. versionadded:: 1.8
322+
323+
Makes a TRACE request on the provided ``path`` and returns a
324+
``Response`` object. Useful for simulating diagnostic probes.
325+
326+
Unlike the other request methods, ``data`` is not provided as a keyword
327+
parameter in order to comply with :rfc:`2616`, which mandates that
328+
TRACE requests should not have an entity-body.
329+
330+
The ``follow``, ``secure``, and ``extra`` arguments act the same as for
331+
:meth:`Client.get`.
332+
319333
.. method:: Client.login(**credentials)
320334

321335
If your site uses Django's :doc:`authentication system</topics/auth/index>`

tests/test_client/tests.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
from __future__ import unicode_literals
2424

2525
from django.core import mail
26+
from django.http import HttpResponse
2627
from django.test import Client, TestCase, RequestFactory
2728
from django.test import override_settings
2829

29-
from .views import get_view
30+
from .views import get_view, post_view, trace_view
3031

3132

3233
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
@@ -79,6 +80,13 @@ def test_post(self):
7980
self.assertEqual(response.templates[0].name, 'POST Template')
8081
self.assertContains(response, 'Data received')
8182

83+
def test_trace(self):
84+
"""TRACE a view"""
85+
response = self.client.trace('/trace_view/')
86+
self.assertEqual(response.status_code, 200)
87+
self.assertEqual(response.context['method'], 'TRACE')
88+
self.assertEqual(response.templates[0].name, 'TRACE Template')
89+
8290
def test_response_headers(self):
8391
"Check the value of HTTP headers returned in a response"
8492
response = self.client.get("/header_view/")
@@ -552,13 +560,54 @@ def test_custom_test_client(self):
552560
self.assertEqual(hasattr(self.client, "i_am_customized"), True)
553561

554562

563+
_generic_view = lambda request: HttpResponse(status=200)
564+
565+
555566
@override_settings(ROOT_URLCONF='test_client.urls')
556567
class RequestFactoryTest(TestCase):
568+
"""Tests for the request factory."""
569+
570+
# A mapping between names of HTTP/1.1 methods and their test views.
571+
http_methods_and_views = (
572+
('get', get_view),
573+
('post', post_view),
574+
('put', _generic_view),
575+
('patch', _generic_view),
576+
('delete', _generic_view),
577+
('head', _generic_view),
578+
('options', _generic_view),
579+
('trace', trace_view),
580+
)
581+
582+
def setUp(self):
583+
self.request_factory = RequestFactory()
557584

558585
def test_request_factory(self):
559-
factory = RequestFactory()
560-
request = factory.get('/somewhere/')
586+
"""The request factory implements all the HTTP/1.1 methods."""
587+
for method_name, view in self.http_methods_and_views:
588+
method = getattr(self.request_factory, method_name)
589+
request = method('/somewhere/')
590+
response = view(request)
591+
592+
self.assertEqual(response.status_code, 200)
593+
594+
def test_get_request_from_factory(self):
595+
"""
596+
The request factory returns a templated response for a GET request.
597+
"""
598+
request = self.request_factory.get('/somewhere/')
561599
response = get_view(request)
562600

563601
self.assertEqual(response.status_code, 200)
564602
self.assertContains(response, 'This is a test')
603+
604+
def test_trace_request_from_factory(self):
605+
"""The request factory returns an echo response for a TRACE request."""
606+
url_path = '/somewhere/'
607+
request = self.request_factory.trace(url_path)
608+
response = trace_view(request)
609+
protocol = request.META["SERVER_PROTOCOL"]
610+
echoed_request_line = "TRACE {} {}".format(url_path, protocol)
611+
612+
self.assertEqual(response.status_code, 200)
613+
self.assertContains(response, echoed_request_line)

tests/test_client/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
urlpatterns = [
99
url(r'^get_view/$', views.get_view, name='get_view'),
1010
url(r'^post_view/$', views.post_view),
11+
url(r'^trace_view/$', views.trace_view),
1112
url(r'^header_view/$', views.view_with_header),
1213
url(r'^raw_post_view/$', views.raw_post_view),
1314
url(r'^redirect_view/$', views.redirect_view),

tests/test_client/views.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from django.forms import fields
66
from django.forms.forms import Form, ValidationError
77
from django.forms.formsets import formset_factory, BaseFormSet
8-
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
8+
from django.http import (
9+
HttpResponse, HttpResponseRedirect, HttpResponseNotFound,
10+
HttpResponseNotAllowed, HttpResponseBadRequest,
11+
)
912
from django.shortcuts import render_to_response
1013
from django.template import Context, Template
1114
from django.utils.decorators import method_decorator
@@ -20,6 +23,31 @@ def get_view(request):
2023
return HttpResponse(t.render(c))
2124

2225

26+
def trace_view(request):
27+
"""
28+
A simple view that expects a TRACE request and echoes its status line.
29+
30+
TRACE requests should not have an entity; the view will return a 400 status
31+
response if it is present.
32+
"""
33+
if request.method.upper() != "TRACE":
34+
return HttpResponseNotAllowed("TRACE")
35+
elif request.body:
36+
return HttpResponseBadRequest("TRACE requests MUST NOT include an entity")
37+
else:
38+
protocol = request.META["SERVER_PROTOCOL"]
39+
t = Template(
40+
'{{ method }} {{ uri }} {{ version }}',
41+
name="TRACE Template",
42+
)
43+
c = Context({
44+
'method': request.method,
45+
'uri': request.path,
46+
'version': protocol,
47+
})
48+
return HttpResponse(t.render(c))
49+
50+
2351
def post_view(request):
2452
"""A view that expects a POST, and returns a different template depending
2553
on whether any POST data is available

0 commit comments

Comments
 (0)