Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django SECURE_SSL_REDIRECT breaks unit tests that use the in-built client

I am working on a project that already has hundreds of unit tests, many of which use either the built in django Client, or the django rest framework APIClient for making requests and testing responses.

Having recently implemented the necessaries to make SSL work locally, and setting the SECURE_SSL_REDIRECT to True (trying to make our dockerised dev and test environments as close to production as possible), I have come a cropper to find that so many unit tests fail, due to the (API)Clients requesting, by default, always using http, not https.

Many (most) requests look like this:

response = self.client.get(some_url)

I am aware that I could use:

response = self.client.get(some_url, secure=True)

But this does mean changing a lot of unit tests. The same is true for using follow=True, but had the added disadvantage that this could produce some other undesired behaviour.

I cannot see a way of setting the use of secure requests as a default behaviour in the Django Client. I could make my own SecureClient (and SecureAPIClient), but I would then have to make sure that I make a new base TestCase (possibly multiple) to inherit from, and change this everywhere for all the tests - still a lot of work.

It is possible of course to monkey patch the Client, but I am reluctant to to do this as, again, it could have undesired effects that are hard to debug later.

TLDR; Is there a simple (ideally supported) way, to make all unit test requests via the django test's Client, to use SSL by default?

like image 391
David Downes Avatar asked Apr 03 '18 09:04

David Downes


People also ask

What is RequestFactory in Django?

The request factory The RequestFactory shares the same API as the test client. However, instead of behaving like a browser, the RequestFactory provides a way to generate a request instance that can be used as the first argument to any view.

What test framework does Django use?

The preferred way to write tests in Django is using the unittest module built-in to the Python standard library. This is covered in detail in the Writing and running tests document. You can also use any other Python test framework; Django provides an API and tools for that kind of integration.

What is Django test client?

The test client is a Python class that acts as a dummy web browser, allowing you to test your views and interact with your Django-powered application programmatically.


2 Answers

  • Option 1. Disable the SECURE_SSL_REDIRECT when needed:

    from django.test import override_settings
    
    class FooTest(TestCase):
    
        def setUp(self):
            settings_manager = override_settings(SECURE_SSL_REDIRECT=False)
            settings_manager.enable()
            self.addCleanup(settings_manager.disable)
    
  • Option 2: Wrap get and post methods of the APIClient:

    class ApiTestCase(TestCase):
        def setUp(self):
            self.client = APIClient()
    
        def get(self, *args, **kwargs):
            return self.client.get(secure=True, *args, **kwargs)
    
        def post(self, *args, **kwargs):
            return self.client.post(secure=True, *args, **kwargs)
    
  • Option 3: just maintain a separate settings file for the test environment with the SECURE_SSL_REDIRECT not being set.

I've tried all three options and recommend going with the (3). Here is why:

  1. django.test.client is just an imitation of a real client. It's there to unit test your views. It doesn't produce real WSGI requests, so test environment won't match production environment anyway.

  2. SECURE_SSL_REDIRECT is a SecurityMiddleware setting. Don't unit test third-party code.

  3. Making sure that your code always uses SSL is a good goal. However, this is an integration testing problem. The right tool for this job is Selenium + LiveServerTestCase.

like image 87
Max Malysh Avatar answered Oct 16 '22 17:10

Max Malysh


If you're using pytest and pytest-django, you can also make a fixture that modifies the test client:

import functools
import pytest

@pytest.fixture
def client(client):
    client.get = functools.partial(client.get, secure=True)
    client.post = functools.partial(client.post, secure=True)
    return client
like image 24
Tom Carrick Avatar answered Oct 16 '22 19:10

Tom Carrick