Opened 5 years ago

Closed 5 years ago

#31533 closed Bug (invalid)

Test client request's post body can be read multiple times, unlike real requests.

Reported by: Ben Beecher Owned by: nobody
Component: Testing framework Version: 3.0
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I have a DRF based signup flow that looks like this:

    def post(self, request, *args, **kwargs):
        register_serializer = self.get_serializer(data=request.data)
        login_serializer = settings.SERIALIZERS.token_create(data=request.data)

        if register_serializer.is_valid():
            return self.create(request, *args, **kwargs)
        elif login_serializer.is_valid():
            return TokenCreateView.as_view()(request=request._request)
        else:
            register_serializer.is_valid(raise_exception=True)

In short - try to sign a user up, then try to log them in.

with the following test I get a good response:

    def test_register_but_really_login(self):
        response = self.client.post(self.url, self.form_data)
        self.assertEqual(response.status_code, s.HTTP_201_CREATED)
        response = self.client.post(self.url, self.form_data)
        self.assertEqual(response.status_code, s.HTTP_200_OK)

But when run for real I get this response with the same behavior:

10:57:18 backend.1  | 127.0.0.1 - - [03/May/2020 14:57:18] "POST /auth/register/ HTTP/1.1" 500 -
10:57:18 backend.1  | Traceback (most recent call last):
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/contrib/staticfiles/handlers.py", line 68, in __call__
10:57:18 backend.1  |     return self.application(environ, start_response)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/wsgi.py", line 133, in __call__
10:57:18 backend.1  |     response = self.get_response(request)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/base.py", line 75, in get_response
10:57:18 backend.1  |     response = self._middleware_chain(request)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/exception.py", line 36, in inner
10:57:18 backend.1  |     response = response_for_exception(request, exc)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
10:57:18 backend.1  |     response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/exception.py", line 125, in handle_uncaught_exception
10:57:18 backend.1  |     return debug.technical_500_response(request, *exc_info)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django_extensions/management/technical_response.py", line 37, in null_technical_500_response
10:57:18 backend.1  |     six.reraise(exc_type, exc_value, tb)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/six.py", line 702, in reraise
10:57:18 backend.1  |     raise value.with_traceback(tb)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
10:57:18 backend.1  |     response = get_response(request)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
10:57:18 backend.1  |     response = self.process_exception_by_middleware(e, request)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
10:57:18 backend.1  |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
10:57:18 backend.1  |     return view_func(*args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/views/generic/base.py", line 71, in view
10:57:18 backend.1  |     return self.dispatch(request, *args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
10:57:18 backend.1  |     response = self.handle_exception(exc)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
10:57:18 backend.1  |     self.raise_uncaught_exception(exc)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
10:57:18 backend.1  |     raise exc
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
10:57:18 backend.1  |     response = handler(request, *args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/workspace/sampleapp/sampleapp/account/views.py", line 31, in post
10:57:18 backend.1  |     return TokenCreateView.as_view()(request=request._request)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
10:57:18 backend.1  |     return view_func(*args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/views/generic/base.py", line 71, in view
10:57:18 backend.1  |     return self.dispatch(request, *args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
10:57:18 backend.1  |     response = self.handle_exception(exc)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
10:57:18 backend.1  |     self.raise_uncaught_exception(exc)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
10:57:18 backend.1  |     raise exc
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
10:57:18 backend.1  |     response = handler(request, *args, **kwargs)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/djoser/utils.py", line 36, in post
10:57:18 backend.1  |     serializer = self.get_serializer(data=request.data)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 209, in data
10:57:18 backend.1  |     self._load_data_and_files()
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 272, in _load_data_and_files
10:57:18 backend.1  |     self._data, self._files = self._parse()
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 322, in _parse
10:57:18 backend.1  |     stream = self.stream
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 196, in stream
10:57:18 backend.1  |     self._load_stream()
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 302, in _load_stream
10:57:18 backend.1  |     self._stream = io.BytesIO(self.body)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/rest_framework/request.py", line 409, in __getattr__
10:57:18 backend.1  |     return getattr(self._request, attr)
10:57:18 backend.1  |   File "/home/bbeecher/.virtualenvs/sampleapp/lib/python3.8/site-packages/django/http/request.py", line 292, in body
10:57:18 backend.1  |     raise RawPostDataException("You cannot access body after reading from request's data stream")
10:57:18 backend.1  | django.http.request.RawPostDataException: You cannot access body after reading from request's data stream

The error message makes total sense - I am in fact trying to access the request body a second time. But I do the same thing in test successfully.

Change History (2)

comment:1 by Ichlasul Affan, 5 years ago

Hi Ben,

Django's django.test.Client is a browser-like simulator. Each time you run self.client.post}}, {{self.client.get}}, etc., {{{Client will create a new, different request even though the body is the same.

Are you using djoser? If yes, I think that's not how you use djoser properly. You just need to set up djoser in your settings.py and urls.py, then use that URL to create tokens or something else. You don't need to recall djoser's view inside your view using your own request intended for your view, it will be problemmatic in some places.

comment:2 by Mariusz Felisiak, 5 years ago

Resolution: invalid
Status: newclosed
Summary: Test client request's post body can be read multiple times, unlike real requestsTest client request's post body can be read multiple times, unlike real requests.

test.Client.post() makes a new POST request on each call. I don't see any issue in this behavior.

Note: See TracTickets for help on using tickets.
Back to Top