1

I'm trying to write an integration test for a Django application to test if the password was changed from a rest API. However, after calling the password change API, testing for the new password doesn't work.

What I'm doing here is: I change the password to johnjohn through a rest call, then try to check if the password is actually changed

My unittest file

import json
import urllib
import urllib2

import requests
from django.contrib.auth.models import User

from django.test import TestCase
from adaptors.redis_class import DjangoRedis


class PasswordChange(TestCase):
    def setUp(self):
        User.objects.create_user(username='john', email='john@john.com', password='john')
        # class for redis access  
        r = DjangoRedis()
        r.set("email:john@john.com", '0' * 40, ex=3600)
        r.set('0' * 40, "email:john@john.com", ex=3600)
    def test_change_password(self):
        # Fake url I set through redis
        url = 'http://127.0.0.1:8000/reset/' + '0' * 40 + '/'
        r = requests.get(url)
        csrf_token = r.content.split('csrf_token')[1].split('"')[2]
        payload = {'csrfmiddlewaretoken': csrf_token, 'payload': json.dumps({'password': 'johnjohn'})}
        headers = {'Cookie': 'csrftoken=' + csrf_token}
        data = urllib.urlencode(payload)
        req = urllib2.Request(url, data, headers)
        response = urllib2.urlopen(req)
        json_res = json.loads(response.read())

        # This doesn't fail
        self.assertEqual(json_res['password_changed'], True)
        u = User.objects.get(username='john')

        # This fails
        self.assertEqual(u.check_password('johnjohn'),True)

My view

import hashlib
import random

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from django.http import JsonResponse
from django.shortcuts import render
from django.views.generic import View

from adaptors.redis_class import DjangoRedis
from constants import PASSWORD_RESET
from tasks import send_email
from util.rest import get_payload
class ResetPage(View):
    def post(self, requests, reset_token):
        r = DjangoRedis()
        token = r.get(reset_token)
        if token is None:
            return JsonResponse({"token": False})
        payload = get_payload(requests.POST)
        try:
            email = token.split(':')[1].lower()
            u = User.objects.get(email=email)
            u.set_password(payload['password'])
            u.save()
            r.delete(reset_token)
            r.delete('email:' + email)
            return JsonResponse({'password_changed': True})
        except ValueError:
            return JsonResponse({"password_changed": False, 'error': "can't get your email from the database (E01)"})
        except ObjectDoesNotExist:
            return JsonResponse({"password_changed": False, 'error': "User doesn't exist (E02)"})

    def get(self, requests, reset_token):
        r = DjangoRedis()
        if not r.get(reset_token):
            raise Http404
        return render(requests, "login/reset.html", {'token': reset_token})

The unittest file is called rest_pass_login_tests.py in a unittests directory.

I run it by using the following command.

./manage.py test unittests/ --pattern="rest_pass_login_tests.py"

When I test normally through the browser, the password change works fine. However, when I try to check if the password was changed or not through a unittest, I get an invalid password.

What am I doing wrong?

Ahmed
  • 2,825
  • 1
  • 25
  • 39

3 Answers3

1

I was using dev server to run unittests. When I changed my password, I was actually posting to the dev server instead of the test one.

Here is my new unittest

import json

from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase

from adaptors.redis_class import DjangoRedis


class PasswordChange(TestCase):
    def setUp(self):
        User.objects.create_user(username='john', email='john@john.com', password='john')
        r = DjangoRedis()
        r.set("email:john@john.com", '0' * 40, ex=3600)
        r.set('0' * 40, "email:john@john.com", ex=3600)

    def test_change_password(self):
        c = Client()
        payload = {'payload': json.dumps({'password': 'johnjohn'})}

        url = '/reset/' + '0' * 40 + '/'
        res = c.post(url, payload)
        res = res.json()
        self.assertEqual(res['password_changed'], True)
        u = User.objects.get(username='john')
        self.assertEqual(u.check_password('johnjohn'), True)

If you're going to use selenium IDE, make sure you inherent from StaticLiveServerTestCase instead of unittest. Follow this example. Also, don't forget to change this variable self.base_url to self.live_server_url

This inside my setUp method

self.base_url = self.live_server_url

Thanks to fips, koterpillar for helping me on the IRC channel, and aks for commenting under me.

Community
  • 1
  • 1
Ahmed
  • 2,825
  • 1
  • 25
  • 39
0

You are not making an http post as your view expects.

Here is how to make a post using urllib2: Making a POST call instead of GET using urllib2

But, since you already use requests module why not simply do:

url = 'http://127.0.0.1:8000/reset/' + '0' * 40 + '/'
payload = {'csrfmiddlewaretoken': csrf_token, 'payload': json.dumps({'password': 'johnjohn'})}
headers = {'Cookie': 'csrftoken=' + csrf_token}
r = requests.post(url, data=payload, headers=headers)
json_res = json.loads(r.content)

And maybe try debugging/printing the value of json_res to check if there's anything else unexpected.

Also note, this is an integration test as you are testing through http, so your urls.py, views.py and even settings.py are being tested as part of it.

Community
  • 1
  • 1
fips
  • 4,319
  • 5
  • 26
  • 42
  • ```[04/May/2016 03:18:48] "GET /reset/0000000000000000000000000000000000000000/ HTTP/1.1" 200 3058 [04/May/2016 03:18:48] "POST /reset/0000000000000000000000000000000000000000/ HTTP/1.1" 200 26 ``` No, I'm doing a correct post. However, I will test your code – Ahmed May 04 '16 at 03:19
  • ```{u'password_changed': True}```. I have a CBV, and two methods, one for get, and one for post. I'm 100% sure it hits the post one – Ahmed May 04 '16 at 03:22
  • I tried to do E2E test via selenium, and I got the same issue. I removed the UI part and started to do only integration test – Ahmed May 04 '16 at 03:24
  • can you update the question with the code missing `...` from ResetPage.post? – fips May 04 '16 at 03:26
  • The code works fine when I test it through a browser, however, doesn't work in a unittest. – Ahmed May 04 '16 at 03:32
  • maybe something weird about `get_payload`. where is it defined? can you print it's contents? – fips May 04 '16 at 03:47
  • No, it works fine in the browser, the issue is with the unittest. Here is my get_payload http://codepad.org/6WFqbSaZ – Ahmed May 04 '16 at 03:49
  • Exactly, since it works from the browser probably the input you send from the test is different. Did you try printing `payload['password']` before calling save? – fips May 04 '16 at 03:57
  • got the answer from someone on IRC channel. – Ahmed May 04 '16 at 04:01
  • my http requests are going through my dev server, and I should have a running test server instead. – Ahmed May 04 '16 at 04:01
  • awesome. yeah that makes total sense! django's `django.test.Client` https://docs.djangoproject.com/en/1.9/topics/testing/tools/. Django rest framework also has a similar one, that's the one we use for rest endpoints.. and use requests/selenium for e2e tests. – fips May 04 '16 at 04:05
  • Thanks a lot for helping me :) – Ahmed May 04 '16 at 04:09
0

You need to refresh from the database

self.user.refresh_from_db()

import urllib2
import requests
from django.contrib.auth.models import User

from django.test import TestCase
from adaptors.redis_class import DjangoRedis


class PasswordChange(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='john', email='john@john.com', password='john') #Modified
        # class for redis access  
        r = DjangoRedis()
        r.set("email:john@john.com", '0' * 40, ex=3600)
        r.set('0' * 40, "email:john@john.com", ex=3600)
    def test_change_password(self):
        # Fake url I set through redis
        url = 'http://127.0.0.1:8000/reset/' + '0' * 40 + '/'
        r = requests.get(url)
        csrf_token = r.content.split('csrf_token')[1].split('"')[2]
        payload = {'csrfmiddlewaretoken': csrf_token, 'payload': json.dumps({'password': 'johnjohn'})}
        headers = {'Cookie': 'csrftoken=' + csrf_token}
        data = urllib.urlencode(payload)
        req = urllib2.Request(url, data, headers)
        response = urllib2.urlopen(req)
        json_res = json.loads(response.read())

        # This doesn't fail
        self.assertEqual(json_res['password_changed'], True)
        self.user.refresh_from_db() #modified

        # This fails
        self.assertEqual(self.user.check_password('johnjohn'),True)```