From b1bf8c8a4ba04049dc19217bf0e876488a4fae3c Mon Sep 17 00:00:00 2001
From: Jaap Roes <jroes@leukeleu.nl>
Date: Thu, 23 Sep 2021 12:18:15 +0200
Subject: [PATCH] Fixed #33132 -- Fixed test client handling of querystring
 only redirects.

Regression in 1e5aa8e1c79252cc810af21294a6e945d11d37b3.
---
 django/test/client.py      |  5 ++++-
 tests/test_client/tests.py |  7 +++++++
 tests/test_client/urls.py  |  1 +
 tests/test_client/views.py | 15 +++++++++++++++
 4 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/django/test/client.py b/django/test/client.py
index dd735dd833..c65e07ce7e 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -835,8 +835,11 @@ class Client(ClientMixin, RequestFactory):
             if url.port:
                 extra['SERVER_PORT'] = str(url.port)
 
+            path = url.path
+            # RFC 2616: bare domains without path are treated as the root.
+            if not path and url.netloc:
+                path = '/'
             # Prepend the request path to handle relative path redirects
-            path = url.path or '/'
             if not path.startswith('/'):
                 path = urljoin(response.request['PATH_INFO'], path)
 
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index 3799dada91..e8ac4bfc00 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -291,6 +291,13 @@ class ClientTest(TestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.request['PATH_INFO'], '/accounts/login/')
 
+    def test_redirect_to_querystring_only(self):
+        """A URL that consists of a querystring only can be followed"""
+        response = self.client.post('/post_then_get_view/', follow=True)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.request['PATH_INFO'], '/post_then_get_view/')
+        self.assertEqual(response.content, b'The value of success is true.')
+
     def test_follow_307_and_308_redirect(self):
         """
         A 307 or 308 redirect preserves the request method after the redirect.
diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py
index e2275797af..51f1fdbb72 100644
--- a/tests/test_client/urls.py
+++ b/tests/test_client/urls.py
@@ -8,6 +8,7 @@ urlpatterns = [
     path('upload_view/', views.upload_view, name='upload_view'),
     path('get_view/', views.get_view, name='get_view'),
     path('post_view/', views.post_view),
+    path('post_then_get_view/', views.post_then_get_view),
     path('put_view/', views.put_view),
     path('trace_view/', views.trace_view),
     path('header_view/', views.view_with_header),
diff --git a/tests/test_client/views.py b/tests/test_client/views.py
index 692581e892..fa2bac2943 100644
--- a/tests/test_client/views.py
+++ b/tests/test_client/views.py
@@ -85,6 +85,21 @@ def post_view(request):
     return HttpResponse(t.render(c))
 
 
+def post_then_get_view(request):
+    """
+    A view that expects a POST request, returns a redirect response
+    to itself providing only a ?success=true querystring,
+    the value of this querystring is then rendered upon GET.
+    """
+    if request.method == 'POST':
+        return HttpResponseRedirect('?success=true')
+
+    t = Template('The value of success is {{ value }}.', name='GET Template')
+    c = Context({'value': request.GET.get('success', 'false')})
+
+    return HttpResponse(t.render(c))
+
+
 def json_view(request):
     """
     A view that expects a request with the header 'application/json' and JSON