Author: russellm
Date: 2009-02-27 07:14:59 -0600 (Fri, 27 Feb 2009)
New Revision: 9911

Modified:
   django/trunk/django/test/client.py
   django/trunk/django/test/testcases.py
   django/trunk/docs/topics/testing.txt
   django/trunk/tests/modeltests/test_client/models.py
   django/trunk/tests/regressiontests/test_client_regress/models.py
   django/trunk/tests/regressiontests/test_client_regress/urls.py
   django/trunk/tests/regressiontests/test_client_regress/views.py
Log:
Fixed #4476 -- Added a ``follow`` option to the test client request methods. 
This implements browser-like behavior for the test client, following redirect 
chains when a 30X response is received. Thanks to Marc Fargas and Keith Bussell 
for their work on this.

Modified: django/trunk/django/test/client.py
===================================================================
--- django/trunk/django/test/client.py  2009-02-27 05:14:11 UTC (rev 9910)
+++ django/trunk/django/test/client.py  2009-02-27 13:14:59 UTC (rev 9911)
@@ -1,5 +1,5 @@
 import urllib
-from urlparse import urlparse, urlunparse
+from urlparse import urlparse, urlunparse, urlsplit
 import sys
 import os
 try:
@@ -12,7 +12,7 @@
 from django.core.handlers.base import BaseHandler
 from django.core.handlers.wsgi import WSGIRequest
 from django.core.signals import got_request_exception
-from django.http import SimpleCookie, HttpRequest
+from django.http import SimpleCookie, HttpRequest, QueryDict
 from django.template import TemplateDoesNotExist
 from django.test import signals
 from django.utils.functional import curry
@@ -261,7 +261,7 @@
 
         return response
 
-    def get(self, path, data={}, **extra):
+    def get(self, path, data={}, follow=False, **extra):
         """
         Requests a response from the server using GET.
         """
@@ -275,9 +275,13 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
-    def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
+    def post(self, path, data={}, content_type=MULTIPART_CONTENT,
+             follow=False, **extra):
         """
         Requests a response from the server using POST.
         """
@@ -297,9 +301,12 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
-    def head(self, path, data={}, **extra):
+    def head(self, path, data={}, follow=False, **extra):
         """
         Request a response from the server using HEAD.
         """
@@ -313,9 +320,12 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
-    def options(self, path, data={}, **extra):
+    def options(self, path, data={}, follow=False, **extra):
         """
         Request a response from the server using OPTIONS.
         """
@@ -328,9 +338,13 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
-    def put(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
+    def put(self, path, data={}, content_type=MULTIPART_CONTENT,
+            follow=False, **extra):
         """
         Send a resource to the server using PUT.
         """
@@ -350,9 +364,12 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
-    def delete(self, path, data={}, **extra):
+    def delete(self, path, data={}, follow=False, **extra):
         """
         Send a DELETE request to the server.
         """
@@ -365,7 +382,10 @@
         }
         r.update(extra)
 
-        return self.request(**r)
+        response = self.request(**r)
+        if follow:
+            response = self._handle_redirects(response)
+        return response
 
     def login(self, **credentials):
         """
@@ -416,3 +436,27 @@
         session = __import__(settings.SESSION_ENGINE, {}, {}, 
['']).SessionStore()
         
session.delete(session_key=self.cookies[settings.SESSION_COOKIE_NAME].value)
         self.cookies = SimpleCookie()
+
+    def _handle_redirects(self, response):
+        "Follows any redirects by requesting responses from the server using 
GET."
+
+        response.redirect_chain = []
+        while response.status_code in (301, 302, 303, 307):
+            url = response['Location']
+            scheme, netloc, path, query, fragment = urlsplit(url)
+
+            redirect_chain = response.redirect_chain
+            redirect_chain.append((url, response.status_code))
+
+            # The test client doesn't handle external links,
+            # but since the situation is simulated in test_client,
+            # we fake things here by ignoring the netloc portion of the
+            # redirected URL.
+            response = self.get(path, QueryDict(query), follow=False)
+            response.redirect_chain = redirect_chain
+
+            # Prevent loops
+            if response.redirect_chain[-1] in response.redirect_chain[0:-1]:
+                break
+        return response
+

Modified: django/trunk/django/test/testcases.py
===================================================================
--- django/trunk/django/test/testcases.py       2009-02-27 05:14:11 UTC (rev 
9910)
+++ django/trunk/django/test/testcases.py       2009-02-27 13:14:59 UTC (rev 
9911)
@@ -43,7 +43,7 @@
     transaction.savepoint_commit = nop
     transaction.savepoint_rollback = nop
     transaction.enter_transaction_management = nop
-    transaction.leave_transaction_management = nop        
+    transaction.leave_transaction_management = nop
 
 def restore_transaction_methods():
     transaction.commit = real_commit
@@ -198,7 +198,7 @@
         # Rollback, in case of database errors. Otherwise they'd have
         # side effects on other tests.
         transaction.rollback_unless_managed()
-        
+
 class TransactionTestCase(unittest.TestCase):
     def _pre_setup(self):
         """Performs any pre-test setup. This includes:
@@ -242,7 +242,7 @@
             import sys
             result.addError(self, sys.exc_info())
             return
-        super(TransactionTestCase, self).__call__(result)        
+        super(TransactionTestCase, self).__call__(result)
         try:
             self._post_teardown()
         except (KeyboardInterrupt, SystemExit):
@@ -263,7 +263,7 @@
     def _fixture_teardown(self):
         pass
 
-    def _urlconf_teardown(self):        
+    def _urlconf_teardown(self):
         if hasattr(self, '_old_root_urlconf'):
             settings.ROOT_URLCONF = self._old_root_urlconf
             clear_url_caches()
@@ -276,25 +276,48 @@
         Note that assertRedirects won't work for external links since it uses
         TestClient to do a request.
         """
-        self.assertEqual(response.status_code, status_code,
-            ("Response didn't redirect as expected: Response code was %d"
-             " (expected %d)" % (response.status_code, status_code)))
-        url = response['Location']
-        scheme, netloc, path, query, fragment = urlsplit(url)
+        if hasattr(response, 'redirect_chain'):
+            # The request was a followed redirect
+            self.assertTrue(len(response.redirect_chain) > 0,
+                ("Response didn't redirect as expected: Response code was %d"
+                " (expected %d)" % (response.status_code, status_code)))
+
+            self.assertEqual(response.redirect_chain[0][1], status_code,
+                ("Initial response didn't redirect as expected: Response code 
was %d"
+                 " (expected %d)" % (response.redirect_chain[0][1], 
status_code)))
+
+            url, status_code = response.redirect_chain[-1]
+
+            self.assertEqual(response.status_code, target_status_code,
+                ("Response didn't redirect as expected: Final Response code 
was %d"
+                " (expected %d)" % (response.status_code, target_status_code)))
+
+        else:
+            # Not a followed redirect
+            self.assertEqual(response.status_code, status_code,
+                ("Response didn't redirect as expected: Response code was %d"
+                 " (expected %d)" % (response.status_code, status_code)))
+
+            url = response['Location']
+            scheme, netloc, path, query, fragment = urlsplit(url)
+
+            redirect_response = response.client.get(path, QueryDict(query))
+
+            # Get the redirection page, using the same client that was used
+            # to obtain the original response.
+            self.assertEqual(redirect_response.status_code, target_status_code,
+                ("Couldn't retrieve redirection page '%s': response code was 
%d"
+                 " (expected %d)") %
+                     (path, redirect_response.status_code, target_status_code))
+
         e_scheme, e_netloc, e_path, e_query, e_fragment = 
urlsplit(expected_url)
         if not (e_scheme or e_netloc):
             expected_url = urlunsplit(('http', host or 'testserver', e_path,
-                    e_query, e_fragment))
+                e_query, e_fragment))
+
         self.assertEqual(url, expected_url,
             "Response redirected to '%s', expected '%s'" % (url, expected_url))
 
-        # Get the redirection page, using the same client that was used
-        # to obtain the original response.
-        redirect_response = response.client.get(path, QueryDict(query))
-        self.assertEqual(redirect_response.status_code, target_status_code,
-            ("Couldn't retrieve redirection page '%s': response code was %d"
-             " (expected %d)") %
-                 (path, redirect_response.status_code, target_status_code))
 
     def assertContains(self, response, text, count=None, status_code=200):
         """
@@ -401,15 +424,15 @@
 class TestCase(TransactionTestCase):
     """
     Does basically the same as TransactionTestCase, but surrounds every test
-    with a transaction, monkey-patches the real transaction management 
routines to 
-    do nothing, and rollsback the test transaction at the end of the test. You 
have 
+    with a transaction, monkey-patches the real transaction management 
routines to
+    do nothing, and rollsback the test transaction at the end of the test. You 
have
     to use TransactionTestCase, if you need transaction management inside a 
test.
     """
 
     def _fixture_setup(self):
         if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
             return super(TestCase, self)._fixture_setup()
-        
+
         transaction.enter_transaction_management()
         transaction.managed(True)
         disable_transaction_methods()
@@ -426,7 +449,7 @@
     def _fixture_teardown(self):
         if not settings.DATABASE_SUPPORTS_TRANSACTIONS:
             return super(TestCase, self)._fixture_teardown()
-                
+
         restore_transaction_methods()
         transaction.rollback()
         transaction.leave_transaction_management()

Modified: django/trunk/docs/topics/testing.txt
===================================================================
--- django/trunk/docs/topics/testing.txt        2009-02-27 05:14:11 UTC (rev 
9910)
+++ django/trunk/docs/topics/testing.txt        2009-02-27 13:14:59 UTC (rev 
9911)
@@ -478,8 +478,9 @@
     Once you have a ``Client`` instance, you can call any of the following
     methods:
 
-    .. method:: Client.get(path, data={})
+    .. method:: Client.get(path, data={}, follow=False)
 
+
         Makes a GET request on the provided ``path`` and returns a ``Response``
         object, which is documented below.
 
@@ -505,8 +506,19 @@
         If you provide URL both an encoded GET data and a data argument,
         the data argument will take precedence.
 
-    .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT)
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
 
+        If you had an url ``/redirect_me/`` that redirected to ``/next/``, that
+        redirected to ``/final/``, this is what you'd see::
+
+            >>> response = c.get('/redirect_me/')
+            >>> response.redirect_chain
+            [(u'http://testserver/next/', 302), (u'http://testserver/final/', 
302)]
+
+    .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, 
follow=False)
+
         Makes a POST request on the provided ``path`` and returns a
         ``Response`` object, which is documented below.
 
@@ -556,7 +568,7 @@
         Note that you should manually close the file after it has been provided
         to ``post()``.
 
-        .. versionadded:: development
+        .. versionchanged:: 1.1
 
         If the URL you request with a POST contains encoded parameters, these
         parameters will be made available in the request.GET data. For example,
@@ -568,36 +580,56 @@
         to retrieve the username and password, and could interrogate 
request.GET
         to determine if the user was a visitor.
 
-    .. method:: Client.head(path, data={})
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
 
+    .. method:: Client.head(path, data={}, follow=False)
+
         .. versionadded:: development
 
         Makes a HEAD request on the provided ``path`` and returns a 
``Response``
         object. Useful for testing RESTful interfaces. Acts just like
         :meth:`Client.get` except it does not return a message body.
 
-    .. method:: Client.options(path, data={})
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
 
+    .. method:: Client.options(path, data={}, follow=False)
+
         .. versionadded:: development
 
         Makes an OPTIONS request on the provided ``path`` and returns a
         ``Response`` object. Useful for testing RESTful interfaces.
 
-    .. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT)
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
 
+    .. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT, 
follow=False)
+
         .. versionadded:: development
 
         Makes an PUT request on the provided ``path`` and returns a
         ``Response`` object. Useful for testing RESTful interfaces. Acts just
         like :meth:`Client.post` except with the PUT request method.
 
-    .. method:: Client.delete(path)
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
 
+    .. method:: Client.delete(path, follow=False)
+
         .. versionadded:: development
 
         Makes an DELETE request on the provided ``path`` and returns a
         ``Response`` object. Useful for testing RESTful interfaces.
 
+        If you set ``follow`` to ``True`` the client will follow any redirects
+        and a ``redirect_chain`` attribute will be set in the response object
+        containing tuples of the intermediate urls and status codes.
+
     .. method:: Client.login(**credentials)
 
         .. versionadded:: 1.0
@@ -789,48 +821,48 @@
 
 .. class:: TransactionTestCase()
 
-Django ``TestCase`` classes make use of database transaction facilities, if 
-available, to speed up the process of resetting the database to a known state 
-at the beginning of each test. A consequence of this, however, is that the 
-effects of transaction commit and rollback cannot be tested by a Django 
-``TestCase`` class. If your test requires testing of such transactional 
+Django ``TestCase`` classes make use of database transaction facilities, if
+available, to speed up the process of resetting the database to a known state
+at the beginning of each test. A consequence of this, however, is that the
+effects of transaction commit and rollback cannot be tested by a Django
+``TestCase`` class. If your test requires testing of such transactional
 behavior, you should use a Django ``TransactionTestCase``.
 
-``TransactionTestCase`` and ``TestCase`` are identical except for the manner 
-in which the database is reset to a known state and the ability for test code 
-to test the effects of commit and rollback. A ``TranscationTestCase`` resets 
-the database before the test runs by truncating all tables and reloading 
-initial data. A ``TransactionTestCase`` may call commit and rollback and 
-observe the effects of these calls on the database.  
+``TransactionTestCase`` and ``TestCase`` are identical except for the manner
+in which the database is reset to a known state and the ability for test code
+to test the effects of commit and rollback. A ``TranscationTestCase`` resets
+the database before the test runs by truncating all tables and reloading
+initial data. A ``TransactionTestCase`` may call commit and rollback and
+observe the effects of these calls on the database.
 
-A ``TestCase``, on the other hand, does not truncate tables and reload initial 
-data at the beginning of a test. Instead, it encloses the test code in a 
-database transaction that is rolled back at the end of the test.  It also 
-prevents the code under test from issuing any commit or rollback operations 
-on the database, to ensure that the rollback at the end of the test restores 
-the database to its initial state. In order to guarantee that all ``TestCase`` 
-code starts with a clean database, the Django test runner runs all 
``TestCase`` 
-tests first, before any other tests (e.g. doctests) that may alter the 
+A ``TestCase``, on the other hand, does not truncate tables and reload initial
+data at the beginning of a test. Instead, it encloses the test code in a
+database transaction that is rolled back at the end of the test.  It also
+prevents the code under test from issuing any commit or rollback operations
+on the database, to ensure that the rollback at the end of the test restores
+the database to its initial state. In order to guarantee that all ``TestCase``
+code starts with a clean database, the Django test runner runs all ``TestCase``
+tests first, before any other tests (e.g. doctests) that may alter the
 database without restoring it to its original state.
 
-When running on a database that does not support rollback (e.g. MySQL with the 
-MyISAM storage engine), ``TestCase`` falls back to initializing the database 
+When running on a database that does not support rollback (e.g. MySQL with the
+MyISAM storage engine), ``TestCase`` falls back to initializing the database
 by truncating tables and reloading initial data.
 
 
 .. note::
-    The ``TestCase`` use of rollback to un-do the effects of the test code 
-    may reveal previously-undetected errors in test code.  For example, 
-    test code that assumes primary keys values will be assigned starting at 
-    one may find that assumption no longer holds true when rollbacks instead 
-    of table truncation are being used to reset the database.  Similarly, 
-    the reordering of tests so that all ``TestCase`` classes run first may 
-    reveal unexpected dependencies on test case ordering.  In such cases a 
+    The ``TestCase`` use of rollback to un-do the effects of the test code
+    may reveal previously-undetected errors in test code.  For example,
+    test code that assumes primary keys values will be assigned starting at
+    one may find that assumption no longer holds true when rollbacks instead
+    of table truncation are being used to reset the database.  Similarly,
+    the reordering of tests so that all ``TestCase`` classes run first may
+    reveal unexpected dependencies on test case ordering.  In such cases a
     quick fix is to switch the ``TestCase`` to a ``TransactionTestCase``.
     A better long-term fix, that allows the test to take advantage of the
     speed benefit of ``TestCase``, is to fix the underlying test problem.
-              
 
+
 Default test client
 ~~~~~~~~~~~~~~~~~~~
 
@@ -1028,9 +1060,15 @@
 .. method:: assertRedirects(response, expected_url, status_code=302, 
target_status_code=200)
 
     Asserts that the response return a ``status_code`` redirect status, it
-    redirected to ``expected_url`` (including any GET data), and the subsequent
+    redirected to ``expected_url`` (including any GET data), and the final
     page was received with ``target_status_code``.
 
+    .. versionadded:: 1.1
+
+    If your request used the ``follow`` argument, the ``expected_url`` and
+    ``target_status_code`` will be the url and status code for the final
+    point of the redirect chain.
+
 E-mail services
 ---------------
 

Modified: django/trunk/tests/modeltests/test_client/models.py
===================================================================
--- django/trunk/tests/modeltests/test_client/models.py 2009-02-27 05:14:11 UTC 
(rev 9910)
+++ django/trunk/tests/modeltests/test_client/models.py 2009-02-27 13:14:59 UTC 
(rev 9911)
@@ -70,13 +70,13 @@
         self.assertEqual(response.context['data'], '37')
         self.assertEqual(response.template.name, 'POST Template')
         self.failUnless('Data received' in response.content)
-    
+
     def test_response_headers(self):
         "Check the value of HTTP headers returned in a response"
         response = self.client.get("/test_client/header_view/")
-        
+
         self.assertEquals(response['X-DJANGO-TEST'], 'Slartibartfast')
-        
+
     def test_raw_post(self):
         "POST raw data (with a content type) to a view"
         test_doc = """<?xml version="1.0" 
encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm 
Gladwell</author></book></library>"""
@@ -132,6 +132,12 @@
         # the attempt to get the redirection location returned 301 when 
retrieved
         self.assertRedirects(response, 
'http://testserver/test_client/permanent_redirect_view/', 
target_status_code=301)
 
+    def test_follow_redirect(self):
+        "A URL that redirects can be followed to termination."
+        response = self.client.get('/test_client/double_redirect_view/', 
follow=True)
+        self.assertRedirects(response, 
'http://testserver/test_client/get_view/', status_code=302, 
target_status_code=200)
+        self.assertEquals(len(response.redirect_chain), 2)
+
     def test_notfound_response(self):
         "GET a URL that responds as '404:Not Found'"
         response = self.client.get('/test_client/bad_view/')

Modified: django/trunk/tests/regressiontests/test_client_regress/models.py
===================================================================
--- django/trunk/tests/regressiontests/test_client_regress/models.py    
2009-02-27 05:14:11 UTC (rev 9910)
+++ django/trunk/tests/regressiontests/test_client_regress/models.py    
2009-02-27 13:14:59 UTC (rev 9911)
@@ -148,6 +148,107 @@
         except AssertionError, e:
             self.assertEquals(str(e), "Couldn't retrieve redirection page 
'/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
 
+    def test_redirect_chain(self):
+        "You can follow a redirect chain of multiple redirects"
+        response = 
self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True)
+        self.assertRedirects(response, 
'/test_client_regress/no_template_view/',
+            status_code=301, target_status_code=200)
+
+        self.assertEquals(len(response.redirect_chain), 1)
+        self.assertEquals(response.redirect_chain[0], 
('http://testserver/test_client_regress/no_template_view/', 301))
+
+    def test_multiple_redirect_chain(self):
+        "You can follow a redirect chain of multiple redirects"
+        response = self.client.get('/test_client_regress/redirects/', {}, 
follow=True)
+        self.assertRedirects(response, 
'/test_client_regress/no_template_view/',
+            status_code=301, target_status_code=200)
+
+        self.assertEquals(len(response.redirect_chain), 3)
+        self.assertEquals(response.redirect_chain[0], 
('http://testserver/test_client_regress/redirects/further/', 301))
+        self.assertEquals(response.redirect_chain[1], 
('http://testserver/test_client_regress/redirects/further/more/', 301))
+        self.assertEquals(response.redirect_chain[2], 
('http://testserver/test_client_regress/no_template_view/', 301))
+
+    def test_redirect_chain_to_non_existent(self):
+        "You can follow a chain to a non-existent view"
+        response = 
self.client.get('/test_client_regress/redirect_to_non_existent_view2/', {}, 
follow=True)
+        self.assertRedirects(response, 
'/test_client_regress/non_existent_view/',
+            status_code=301, target_status_code=404)
+
+    def test_redirect_chain_to_self(self):
+        "Redirections to self are caught and escaped"
+        response = self.client.get('/test_client_regress/redirect_to_self/', 
{}, follow=True)
+        # The chain of redirects stops once the cycle is detected.
+        self.assertRedirects(response, 
'/test_client_regress/redirect_to_self/',
+            status_code=301, target_status_code=301)
+        self.assertEquals(len(response.redirect_chain), 2)
+
+    def test_circular_redirect(self):
+        "Circular redirect chains are caught and escaped"
+        response = 
self.client.get('/test_client_regress/circular_redirect_1/', {}, follow=True)
+        # The chain of redirects will get back to the starting point, but stop 
there.
+        self.assertRedirects(response, 
'/test_client_regress/circular_redirect_2/',
+            status_code=301, target_status_code=301)
+        self.assertEquals(len(response.redirect_chain), 4)
+
+    def test_redirect_chain_post(self):
+        "A redirect chain will be followed from an initial POST post"
+        response = self.client.post('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/no_template_view/', 301, 200)
+        self.assertEquals(len(response.redirect_chain), 3)
+
+    def test_redirect_chain_head(self):
+        "A redirect chain will be followed from an initial HEAD request"
+        response = self.client.head('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/no_template_view/', 301, 200)
+        self.assertEquals(len(response.redirect_chain), 3)
+
+    def test_redirect_chain_options(self):
+        "A redirect chain will be followed from an initial OPTIONS request"
+        response = self.client.options('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/no_template_view/', 301, 200)
+        self.assertEquals(len(response.redirect_chain), 3)
+
+    def test_redirect_chain_put(self):
+        "A redirect chain will be followed from an initial PUT request"
+        response = self.client.put('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/no_template_view/', 301, 200)
+        self.assertEquals(len(response.redirect_chain), 3)
+
+    def test_redirect_chain_delete(self):
+        "A redirect chain will be followed from an initial DELETE request"
+        response = self.client.delete('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/no_template_view/', 301, 200)
+        self.assertEquals(len(response.redirect_chain), 3)
+
+    def test_redirect_chain_on_non_redirect_page(self):
+        "An assertion is raised if the original page couldn't be retrieved as 
expected"
+        # This page will redirect with code 301, not 302
+        response = self.client.get('/test_client/get_view/', follow=True)
+        try:
+            self.assertRedirects(response, '/test_client/get_view/')
+        except AssertionError, e:
+            self.assertEquals(str(e), "Response didn't redirect as expected: 
Response code was 200 (expected 302)")
+
+    def test_redirect_on_non_redirect_page(self):
+        "An assertion is raised if the original page couldn't be retrieved as 
expected"
+        # This page will redirect with code 301, not 302
+        response = self.client.get('/test_client/get_view/')
+        try:
+            self.assertRedirects(response, '/test_client/get_view/')
+        except AssertionError, e:
+            self.assertEquals(str(e), "Response didn't redirect as expected: 
Response code was 200 (expected 302)")
+
+
 class AssertFormErrorTests(TestCase):
     def test_unknown_form(self):
         "An assertion is raised if the form name is unknown"

Modified: django/trunk/tests/regressiontests/test_client_regress/urls.py
===================================================================
--- django/trunk/tests/regressiontests/test_client_regress/urls.py      
2009-02-27 05:14:11 UTC (rev 9910)
+++ django/trunk/tests/regressiontests/test_client_regress/urls.py      
2009-02-27 13:14:59 UTC (rev 9911)
@@ -1,4 +1,5 @@
 from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
 import views
 
 urlpatterns = patterns('',
@@ -8,6 +9,15 @@
     (r'^request_data/$', views.request_data),
     url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, 
name='arg_view'),
     (r'^login_protected_redirect_view/$', views.login_protected_redirect_view),
+    (r'^redirects/$', redirect_to, {'url': 
'/test_client_regress/redirects/further/'}),
+    (r'^redirects/further/$', redirect_to, {'url': 
'/test_client_regress/redirects/further/more/'}),
+    (r'^redirects/further/more/$', redirect_to, {'url': 
'/test_client_regress/no_template_view/'}),
+    (r'^redirect_to_non_existent_view/$', redirect_to, {'url': 
'/test_client_regress/non_existent_view/'}),
+    (r'^redirect_to_non_existent_view2/$', redirect_to, {'url': 
'/test_client_regress/redirect_to_non_existent_view/'}),
+    (r'^redirect_to_self/$', redirect_to, {'url': 
'/test_client_regress/redirect_to_self/'}),
+    (r'^circular_redirect_1/$', redirect_to, {'url': 
'/test_client_regress/circular_redirect_2/'}),
+    (r'^circular_redirect_2/$', redirect_to, {'url': 
'/test_client_regress/circular_redirect_3/'}),
+    (r'^circular_redirect_3/$', redirect_to, {'url': 
'/test_client_regress/circular_redirect_1/'}),
     (r'^set_session/$', views.set_session_view),
     (r'^check_session/$', views.check_session_view),
     (r'^request_methods/$', views.request_methods_view),

Modified: django/trunk/tests/regressiontests/test_client_regress/views.py
===================================================================
--- django/trunk/tests/regressiontests/test_client_regress/views.py     
2009-02-27 05:14:11 UTC (rev 9910)
+++ django/trunk/tests/regressiontests/test_client_regress/views.py     
2009-02-27 13:14:59 UTC (rev 9911)
@@ -58,4 +58,4 @@
 
 def request_methods_view(request):
     "A view that responds with the request method"
-    return HttpResponse('request method: %s' % request.method)
\ No newline at end of file
+    return HttpResponse('request method: %s' % request.method)


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to