NoiK
NoiK

Reputation: 182

Django rest framework: custom headers are different in client than unittests

I have a Django Rest API and then, separately, a client that consume this API.

I pass a custom header using requests in the client:

r = requests.get(url, params=params, headers={'license': '12345'})

Then in API, in my custom permission, I get the header like:

app_license = request.META['HTTP_LICENSE']

I know django rewrites the custom headers for security reasons, so it works fine.

My problem is when I write the unittests in Django rest API:

response = self.client.get(self.url, params=params, headers={'license': '12345'})

Then it raise:

KeyError: 'HTTP_LICENSE'

But if I change the code like this, the test passes without problem but the consumer doesn't work:

request.META['headers']['license']

I can check if there is 'headers' key or not but I don't want to change the code only to pass the unittest, it must be some way to write a unittest that matches reality, right?

I tried to use:

from django.test import TestCase

And:

from rest_framework.test import APITestCase

Both with same result. There is any solution? Thank you!

Upvotes: 4

Views: 1820

Answers (1)

cwallenpoole
cwallenpoole

Reputation: 82048

TL;DR — if you want something to show up in META, make it a kwarg. The client.get method is a different signature from requests.get.


This is because the kwargs of self.client.get map directly to HTTP attributes.

Take a look at the source. You'll notice the stack is:

  1. get
  2. generic
  3. request
  4. _base_environ

In that process, the kwargs are carried to _base_environ with very few alterations. They're then merged into a dictionary that includes all of the basic headers you'd expect:

    # This is a minimal valid WSGI environ dictionary, plus:
    # - HTTP_COOKIE: for cookie support,
    # - REMOTE_ADDR: often useful, see #8551.
    # See http://www.python.org/dev/peps/pep-3333/#environ-variables
    environ = {
        'HTTP_COOKIE': self.cookies.output(header='', sep='; '),
        'PATH_INFO': '/',
        'REMOTE_ADDR': '127.0.0.1',
        'REQUEST_METHOD': 'GET',
        'SCRIPT_NAME': '',
        'SERVER_NAME': 'testserver',
        'SERVER_PORT': '80',
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'wsgi.version': (1, 0),
        'wsgi.url_scheme': 'http',
        'wsgi.input': FakePayload(b''),
        'wsgi.errors': self.errors,
        'wsgi.multiprocess': True,
        'wsgi.multithread': False,
        'wsgi.run_once': False,
    }
    environ.update(self.defaults)
    environ.update(request)
    return environ

The above is then passed into a WSGIRequest object in the request method. That class then takes the provided environ dict and assigns it to the META property:

class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        # If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
        # trailing slash), operate as if '/' was requested.
        path_info = get_path_info(environ) or '/'

        ##############################
        ##                          ##
        ## This is the line you're  ##
        ##       looking for.       ##
        ##                          ##
        ##############################
        self.environ = environ
        self.path_info = path_info

Upvotes: 5

Related Questions