From 4fdd51b73240bf9c8d9472fcc45df699f0714755 Mon Sep 17 00:00:00 2001 From: Unai Zalakain Date: Tue, 29 Oct 2013 18:31:54 +0100 Subject: [PATCH] Fixed #15179 -- middlewares not applied for test client login() Requests made with django.test.Client.login() and logout() respect defaults defined in django.test.Client instantiation and are processed through middleware. Thanks to Loic for the reviews. --- django/test/client.py | 22 +++++++++++---- docs/releases/1.7.txt | 5 ++++ docs/topics/testing/overview.txt | 14 ++++++++++ tests/test_client_regress/tests.py | 44 ++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index c916b7dbe0..bf099e5b37 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -15,7 +15,7 @@ from django.core.handlers.wsgi import WSGIRequest from django.core.signals import (request_started, request_finished, got_request_exception) from django.db import close_old_connections -from django.http import SimpleCookie, HttpRequest, QueryDict +from django.http import SimpleCookie, QueryDict from django.template import TemplateDoesNotExist from django.test import signals from django.utils.functional import curry @@ -83,8 +83,9 @@ def closing_iterator_wrapper(iterable, close): class ClientHandler(BaseHandler): """ A HTTP Handler that can be used for testing purposes. - Uses the WSGI interface to compose requests, but returns - the raw HttpResponse object + Uses the WSGI interface to compose requests, but returns the raw + HttpResponse object with the originating WSGIRequest attached to its + ``request_instance`` attribute. """ def __init__(self, enforce_csrf_checks=True, *args, **kwargs): self.enforce_csrf_checks = enforce_csrf_checks @@ -105,7 +106,13 @@ class ClientHandler(BaseHandler): # required for backwards compatibility with external tests against # admin views. request._dont_enforce_csrf_checks = not self.enforce_csrf_checks + + # Request goes through middleware. response = self.get_response(request) + # Attach the originating request to the response so that it could be + # later retrieved. + response.request_instance = request + # We're emulating a WSGI server; we must call the close method # on completion. if response.streaming: @@ -546,8 +553,9 @@ class Client(RequestFactory): 'django.contrib.sessions' in settings.INSTALLED_APPS): engine = import_module(settings.SESSION_ENGINE) - # Create a fake request to store login details. - request = HttpRequest() + # Create a fake request that goes through request middleware + request = self.request().request_instance + if self.session: request.session = self.session else: @@ -579,7 +587,9 @@ class Client(RequestFactory): Causes the authenticated user to be logged out. """ - request = HttpRequest() + # Create a fake request that goes through request middleware + request = self.request().request_instance + engine = import_module(settings.SESSION_ENGINE) UserModel = get_user_model() if self.session: diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 8428c1853a..749a843a5a 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -496,6 +496,11 @@ Tests :class:`~django.test.Client`. If ``True``, the request will be made through HTTPS. +* Requests made with :meth:`Client.login() ` and + :meth:`Client.logout() ` respect defaults defined + in :class:`~django.test.Client` instantiation and are processed through + middleware. + Backwards incompatible changes in 1.7 ===================================== diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 133931559e..c221702005 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -663,6 +663,13 @@ Use the ``django.test.Client`` class to make requests. :meth:`~django.contrib.auth.models.UserManager.create_user` helper method to create a new user with a correctly hashed password. + .. versionadded:: 1.7 + + Requests made with :meth:`~django.test.Client.login` go through the + request middleware. If you need to control the environment, you can + do so at :class:`~django.test.Client` instantiation or with the + `Client.defaults` attribute. + .. method:: Client.logout() If your site uses Django's :doc:`authentication system`, @@ -673,6 +680,13 @@ Use the ``django.test.Client`` class to make requests. and session data cleared to defaults. Subsequent requests will appear to come from an :class:`~django.contrib.auth.models.AnonymousUser`. + .. versionadded:: 1.7 + + Requests made with :meth:`~django.test.Client.logout` go through the + request middleware. If you need to control the environment, you can + do so at :class:`~django.test.Client` instantiation or with the + `Client.defaults` attribute. + Testing responses ~~~~~~~~~~~~~~~~~ diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 5f907925ba..1bee0b95e7 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import os import itertools +from django.conf import settings from django.core.urlresolvers import reverse from django.template import (TemplateSyntaxError, Context, Template, loader) @@ -761,6 +762,11 @@ class AssertFormsetErrorTests(TestCase): **kwargs) +class ProcessedMiddleware(object): + def process_request(self, request): + request.has_been_processed = True + + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class LoginTests(TestCase): fixtures = ['testdata'] @@ -781,6 +787,24 @@ class LoginTests(TestCase): # default client. self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") + @override_settings( + MIDDLEWARE_CLASSES=list(settings.MIDDLEWARE_CLASSES) + + ['test_client_regress.tests.ProcessedMiddleware']) + def test_request_middleware(self): + "Check that the request middleware is executed on login request" + + def listener(sender, signal, **kwargs): + request = kwargs['request'] + self.assertTrue(hasattr(request, 'has_been_processed')) + + # Unlike other Client request performing methods, login and logout don't + # return the response, therefore we must use signals to get it + user_logged_in.connect(listener) + try: + self.client.login(username='testclient', password='password') + finally: + user_logged_in.disconnect(listener) + @override_settings( PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), @@ -1260,12 +1284,32 @@ class UploadedFileEncodingTest(TestCase): class RequestHeadersTest(TestCase): + fixtures = ['testdata'] + def test_client_headers(self): "A test client can receive custom headers" response = self.client.get("/test_client_regress/check_headers/", HTTP_X_ARG_CHECK='Testing 123') self.assertEqual(response.content, b"HTTP_X_ARG_CHECK: Testing 123") self.assertEqual(response.status_code, 200) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) + def test_client_login_headers(self): + "Test client headers are used in login" + + client = Client(HTTP_HOST='different') + + def listener(sender, signal, **kwargs): + request = kwargs['request'] + self.assertEqual(request.get_host(), 'different') + + # Unlike other Client request performing methods, login and logout don't + # return the response, therefore we must use signals to get it + user_logged_in.connect(listener) + try: + client.login(username='testclient', password='password') + finally: + user_logged_in.disconnect(listener) + def test_client_headers_redirect(self): "Test client headers are preserved through redirects" response = self.client.get("/test_client_regress/check_headers_redirect/", follow=True, HTTP_X_ARG_CHECK='Testing 123')