Monday, May 25, 2015

Re: I think I might have found a bug in a django test client - please help

El 25/05/15 12:16, R Bazhenov escribió:

Good day everyone.


I would like to ask you if this incorrect behaviour is something known.

First, I am using Django 1.8.2, Python 3.4.3, Python Tools for Visual Studio 2.2RC, VS2013, Windows 8.1, msysgit 1.9.5.

A made a unit test which was supposed to fail, and it did fail from the visual studio test runner, but it was successfully passed when I ran it from the command line using manage.py test command.


I run tests in command as follows: manage.py test --settings=monodemo.settings.dev_russ

This assert does not fail in Django test runner when my Form View (CBV) has a success_url='dashboard:dashboard':

self.assertEqual(self.client.session[SESSION_KEY], str(user.pk))


But it does fail in VS and it should because the return status code was 400 in any case. In visual studio unit tests, it was raising KeyError:'_auth_user_id'.

Also, I realised that I forgot to add 'dashboard' project into installed_apps but it was not affecting the result of the test.


My dependencies:

Django==1.8.2

psycopg2==2.6 (taken from here http://www.stickpeople.com/projects/python/win-psycopg/ and then manually installed into the virtual environment; direct download link: http://www.stickpeople.com/projects/python/win-psycopg/2.6.0/psycopg2-2.6.0.win32-py3.4-pg9.4.1-release.exe )

Unipath==1.1


Please advise if this is something to worry or if I have improperly configured something.

Thank you all in advance!

Ruslan (Russ) Bazhenov


I have attached pretty much all the related code down there. (Tried to exclude what was not related, though)


#monodemo/registration/tests/base.py

import django

from django.test import TestCase

from django.test.utils import setup_test_environment

from django.contrib.auth.models import User

class AbstractTestCase(TestCase):

    """Utility base class for all tests for this app as it contains helper functions"""

 

    @classmethod

    def setUpClass(cls):

        """One time Django setup sequence.

        It is needed for correct testing outside of django test runner

        For example in the UnitTest test runner or the one used in VS"""

        setup_test_environment()

        django.setup()

        super().setUpClass()

 

    def prepare_user(self, username):

        """Creates, saves and returns test user"""

 

        # An interesting observation

        # By default a user iscreated ACTIVE

        return User.objects.create_user(username=username,

                                        email='test@example.com',

                                        password='glassfish100500'

                                        )

 

#monodemo/registration/tests/test_views.py

from django.core.urlresolvers import reverse

from django.contrib.auth import SESSION_KEY

 

from registration.forms import UserLoginForm

from .base import AbstractTestCase

 

class LoginViewTests(AbstractTestCase):

    """Tests for the Registration app views."""

 

    def test_can_login(self):

        """Verifies that a user can authenticate"""

        user = super().prepare_user('Sucker')

        email = 'test@example.com'

        password = 'glassfish100500'

        response = self.client.post(reverse('home_page'), data={'email':email,

                                                                'password':password,

                                                                'remember_me':True

                                                                })

        self.assertEqual(self.client.session[SESSION_KEY], str(user.pk))

 

#monodemo/registration/views.py

from django.views.generic import FormView

from django.contrib.auth import login

 

from registration.forms import UserLoginForm

 

 

class LoginView(FormView):

    """User authentication view"""

    template_name = 'registration/registration_login.html'

    form_class = UserLoginForm

    success_url = 'dashboard:dashboard'

 

    def form_valid(self, form):

        """Logs a verified user in after form is validated"""

        user = form.get_user()

        login(self.request, user)

        return super().form_valid(form)

 

 

#monodemo/registration/forms.py

from django import forms

from django.contrib.auth.models import User

from django.contrib.auth import authenticate

 

 

class UserLoginForm(forms.Form):

    """Verifies login details"""

    email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text',

                                                           'placeholder': 'Email',

                                                           }))

    password = forms.CharField(max_length=128,

                               widget=forms.PasswordInput(attrs={'class': 'text',

                                                                 'placeholder': 'Password',

                                                                 }))

    remember_me = forms.BooleanField(required=False,

                                     label='remember me',

                                     widget=forms.CheckboxInput(attrs={'class': 'checkbox',}))

 

    def clean(self):

        """User verification. Other data does not require validation

        because it will be handled by authenticate()

        """

        cleaned_data = super(UserLoginForm, self).clean()

        email = cleaned_data.get("email")

        password = cleaned_data.get("password")

        try:

            user = User.objects.get(email=email)

        except (User.DoesNotExist, User.MultipleObjectsReturned):

            raise forms.ValidationError("The email address or password were incorrect")

        user = authenticate(username=user.username, password=password)

        if user is None:

            raise forms.ValidationError("The email address or password were incorrect")

        if user.is_active != True:

            raise forms.ValidationError("The email address or password were incorrect")

        # Dirty hack or fancy Python?

        def get_user():

            return user

        self.get_user = get_user

 

#monodemo/monodemo/urls.py

from django.conf.urls import include, url

from django.contrib import admin

 

from registration.views import LoginView

 

urlpatterns = [

    url(r'^$', LoginView.as_view(), name='home_page'),

    url(r'^auth/', include('registration.urls', namespace='registration')),

    url(r'^dashboard/', include('dashboard.urls', namespace='dashboard')),

    url(r'^admin/', include(admin.site.urls)),

]

 

#monodemo/registration/urls.py

from django.conf.urls import include, url

 

urlpatterns = [

    ]

 

#monodemo/dashboard/urls.py

from django.conf.urls import url

 

from .views import LoginView

 

urlpatterns = [

    url(r'^$', LoginView.as_view(), name='dashboard'),

]

 

#monodemo/dashboard/views.py

from django.views.generic import TemplateView

 

class LoginView(TemplateView):

    template_name = 'dashboard/base_dashboard.html'

 

#monodemo/monodemo/settings/base.py

import json

 

from django.core.exceptions import ImproperlyConfigured

from unipath import Path

 

BASE_DIR = Path(__file__).ancestor(3)

MEDIA_ROOT = BASE_DIR.child("media")

STATIC_ROOT = BASE_DIR.child("static")

STATIC_URL = "/static/"

STATICFILES_DIRS = (

    BASE_DIR.child("assets"),

    )

 

 

# Quick-start development settings - unsuitable for production

# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/

 

# JSON-based secrets module

with open(BASE_DIR.child("monodemo", "settings", "secrets.json")) as f:

    secrets = json.loads(f.read())

 

def get_secret(setting, secrets=secrets):

    """Get the secret variable or return explicit exception."""

    try:

        return secrets[setting]

    except KeyError:

        error_msg = "Set the {0} environment variable".format(setting)

        raise ImproperlyConfigured(error_msg)

 

# SECURITY WARNING: keep the secret key used in production secret!

SECRET_KEY = get_secret("SECRET_KEY")

 

# SECURITY WARNING: don't run with debug turned on in production!

DEBUG = False

 

ALLOWED_HOSTS = []

 

 

# Application definition

 

INSTALLED_APPS = (

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'registration'

)

 

MIDDLEWARE_CLASSES = (

    'django.contrib.sessions.middleware.SessionMiddleware',

    'django.middleware.common.CommonMiddleware',

    'django.middleware.csrf.CsrfViewMiddleware',

    'django.contrib.auth.middleware.AuthenticationMiddleware',

    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',

    'django.contrib.messages.middleware.MessageMiddleware',

    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    'django.middleware.security.SecurityMiddleware',

)

 

ROOT_URLCONF = 'monodemo.urls'

 

TEMPLATES = [

    {

        'BACKEND': 'django.template.backends.django.DjangoTemplates',

        'DIRS': (

            BASE_DIR.child("templates"),

            ),

        'APP_DIRS': True,

        'OPTIONS': {

            'context_processors': [

                'django.contrib.auth.context_processors.auth',

                'django.template.context_processors.request',

                'django.template.context_processors.debug',

                'django.template.context_processors.i18n',

                'django.template.context_processors.media',

                'django.template.context_processors.static',

                'django.template.context_processors.tz',

                'django.contrib.messages.context_processors.messages',

            ],

        },

    },

]

 

WSGI_APPLICATION = 'monodemo.wsgi.application'

 

 

# Database

# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

 

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.postgresql_psycopg2',

        'NAME': get_secret("NAME"),

        'USER': get_secret("USER"),

        'PASSWORD': get_secret("PASSWORD"),

    }

}

 

 

# Internationalization

# https://docs.djangoproject.com/en/1.8/topics/i18n/

 

LANGUAGE_CODE = 'en-GB'

 

TIME_ZONE = 'UTC'

 

USE_I18N = True

 

USE_L10N = True

 

USE_TZ = True

 

#monodemo/monodemo/settings/local.py

from .base import *

 

DEBUG = True

 

#monodemo/monodemo/settings/dev_russ.py

from .local import *


I'm a newbie but I read that tests files names should begin with test or you need to specify --patern when calling manage.py test otherwise.

No comments:

Post a Comment