16

I'm working on a Django web application which (amongst other things) needs to handle transaction status info sent using a POST request.

In addition to the HTTP security supported by the payment gateway, my view checks request.META['HTTP_REFERER'] against an entry in settings.py to try to prevent funny business:

if request.META.get('HTTP_REFERER', '') != settings.PAYMENT_URL and not settings.DEBUG:
    return HttpResponseForbidden('Incorrect source URL for updating payment status')

Now I'd like to work out how to test this behaviour.

I can generate a failure easily enough; HTTP_REFERER is (predictably) None with a normal page load:

def test_transaction_status_succeeds(self):
    response = self.client.post(reverse('transaction_status'), { ... })
    self.assertEqual(response.status_code, 403)

How, though, can I fake a successful submission? I've tried setting HTTP_REFERER in extra, e.g. self.client.post(..., extra={'HTTP_REFERER': 'http://foo/bar'}), but this isn't working; the view is apparently still seeing a blank header.

Does the test client even support custom headers? Is there a work-around if not? I'm using Django 1.1, and would prefer not to upgrade just yet if at all possible.

supervacuo
  • 8,420
  • 2
  • 37
  • 59
  • This was not your problem, but for others who are having the difficulty I had: Django was not recognizing the headers I was sending because I didn't properly transform their names as documented [here](https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.client.Client.get), as [described the CGI specification](http://tools.ietf.org/html/draft-robinson-www-interface-00#page-8). For example, `X-CSRFToken` would be `HTTP_X_CSRFTOKEN`. After transforming them I could simply use them as kwargs, as in supervacuo's answer below. – Jeremy Mar 31 '14 at 19:58

2 Answers2

27

Almost right. It's actually:

def transaction_status_suceeds(self):
    response = self.client.post(reverse('transaction_status'), {}, HTTP_REFERER='http://foo/bar')

I'd missed a ** (scatter operator / keyword argument unpacking operator / whatever) when reading the source of test/client.py; extra ends up being a dictionary of extra keyword arguments to the function itself.

Community
  • 1
  • 1
supervacuo
  • 8,420
  • 2
  • 37
  • 59
  • I got a "__init__() got an unexpected keyword argument 'HTTP_REFERER'" when trying to use this. Maybe it changed in newer versions of django? – Steven Rogers Apr 14 '15 at 21:21
  • @StevenRogers, it sounds like you might have done `c = Client(HTTP_REFERER='http://foo/bar')` instead (the only thing I can think of that will call `__init__`). I was suggesting adding that as a kwarg to `post()` & `get()`; I just tested this on Django 1.7.3 and it seems to work fine… – supervacuo Apr 16 '15 at 09:48
5

You can pass HTTP headers to the constructor of Client:

from django.test import Client
from django.urls import reverse

client = Client(
    HTTP_USER_AGENT='Mozilla/5.0',
    HTTP_REFERER='http://www.google.com',
)
response1 = client.get(reverse('foo'))
response2 = client.get(reverse('bar'))

This way you don't need to pass headers every time you make a request.

Max Malysh
  • 21,876
  • 16
  • 91
  • 102
  • It is worth a mention that the `HTTP_HEADER` will get updated after a redirect that results from `client.get` or `client.post`. This wasn't obvious to me at first. – Daniel Holmes Jul 31 '19 at 09:42
  • hello Max Malysh, where exactly should I place your HTTP headers? On .htaccess file , or in the php code ? – Just Me Jan 26 '21 at 08:14