diff --git a/docs/helpers.rst b/docs/helpers.rst index 47e1ad76a..a4eba3f9e 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -121,6 +121,39 @@ Example response = my_view(request) assert response.status_code == 200 +``django_rf_user``, ``django_rf_admin``, ``django_rf_unauth`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Additional instances of RequestFactory, which mimic the behavior of these +two middlewares: + +- `django.contrib.sessions.middleware.SessionMiddleware`_ +- `django.contrib.auth.middleware.AuthenticationMiddleware`_ + +This will add two attributes to the ``request`` object, +``request.user`` and ``request.session``. + +- ``request.session`` is an in-memory `SessionBase`_ object. +- ``request.user`` depends on the fixture being used: + + - ``django_rf_unauth``: an `AnonymousUser`_ + - ``django_rf_user``: a normal user with no additional privileges + - ``django_rf_admin``: an admin user + +:: + + from myapp.views import my_view + + def test_details(django_rf_unauth): + request = django_rf_unauth.get('/customer/details') + response = my_view(request) + assert response.status_code == 200 + +.. _django.contrib.sessions.middleware.SessionMiddleware: https://docs.djangoproject.com/en/2.0/ref/middleware/#django.contrib.sessions.middleware.SessionMiddleware +.. _django.contrib.auth.middleware.AuthenticationMiddleware: https://docs.djangoproject.com/en/2.0/ref/middleware/#django.contrib.auth.middleware.AuthenticationMiddleware +.. _SessionBase: https://docs.djangoproject.com/en/2.0/topics/http/sessions/#django.contrib.sessions.backends.base.SessionBase +.. _AnonymousUser: https://docs.djangoproject.com/en/2.0/ref/contrib/auth/#django.contrib.auth.models.AnonymousUser + ``client`` - ``django.test.Client`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,6 +200,20 @@ Example Using the `admin_client` fixture will cause the test to automatically be marked for database use (no need to specify the ``django_db`` mark). +``django_user_client`` - ``django.test.Client`` logged in as normal user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Same as ``admin_client``, but for a regular user. + +Example +""""""" + +:: + + def test_an_admin_view(django_user_client): + response = user_client.get('/admin/') + assert response.status_code == 403 + ``admin_user`` - a admin user (superuser) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -177,6 +224,11 @@ Using the `admin_user` fixture will cause the test to automatically be marked fo ``django_db`` mark). +``django_user`` - a normal user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Same as ``admin_user``, but for a regular user. + ``django_user_model`` ~~~~~~~~~~~~~~~~~~~~~ diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index cc6a19dca..388149c61 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -16,9 +16,11 @@ from .lazy_django import skip_if_no_django __all__ = ['django_db_setup', 'db', 'transactional_db', 'admin_user', - 'django_user_model', 'django_username_field', - 'client', 'admin_client', 'rf', 'settings', 'live_server', - '_live_server_helper', 'django_assert_num_queries'] + 'django_user_model', 'django_username_field', 'client', + 'admin_client', 'django_user_client', 'rf', 'settings', + 'django_user', 'django_rf_unauth', 'django_rf_user', + 'django_rf_admin', 'live_server', '_live_server_helper', + 'django_assert_num_queries'] @pytest.fixture(scope='session') @@ -229,6 +231,36 @@ def admin_client(db, admin_user): return client +@pytest.fixture() +def django_user(db, django_user_model, django_username_field): + """A Django user. + This uses an existing user with username "user", or creates a new one with + password "password". + """ + UserModel = django_user_model + username_field = django_username_field + + try: + user = UserModel._default_manager.get(**{username_field: 'user'}) + except UserModel.DoesNotExist: + extra_fields = {} + if username_field != 'username': + extra_fields[username_field] = 'user' + user = UserModel._default_manager.create_user( + 'user', 'user@example.com', 'password', **extra_fields) + return user + + +@pytest.fixture() +def django_user_client(db, django_user): + """A Django test client logged in as a normal user.""" + from django.test.client import Client + + client = Client() + client.login(username=django_user.username, password='password') + return client + + @pytest.fixture() def rf(): """RequestFactory instance""" @@ -239,6 +271,76 @@ def rf(): return RequestFactory() +@pytest.fixture +def django_rf_unauth(): + """Anonymous user request factory. + + This does two things to the request object: + + 1. Adds an anonymous user per Django's doc examples on request + factories: + + https://docs.djangoproject.com/en/2.0/topics/testing/advanced/#example + + This simiulates ``django.contrib.auth.middleware.AuthenticationMiddleware``. + + 2. Adds a naive session storage object to request. + + This simiulates ``django.contrib.sessions.middleware.SessionMiddleware``, + and prevents errors on stuff like SessionWizardView in django-formtools. + """ + from django.test.client import RequestFactory + from django.contrib.auth.models import AnonymousUser + from django.contrib.sessions.backends.base import SessionBase + + class UnauthRequestFactory(RequestFactory): + def request(self, **request): + r = super(UnauthRequestFactory, self).request(**request) + r.user = AnonymousUser() + r.session = SessionBase() + return r + + return UnauthRequestFactory() + + +@pytest.fixture +def django_rf_admin(admin_user): + """Admin user request facctory. + + Mimics AuthenticationMiddleware. Also adds a memory-backed session store + to the request object.""" + from django.test.client import RequestFactory + from django.contrib.sessions.backends.base import SessionBase + + class AdminRequestFactory(RequestFactory): + def request(self, **request): + r = super(AdminRequestFactory, self).request(**request) + r.user = admin_user + r.session = SessionBase() + return r + + return AdminRequestFactory() + + +@pytest.fixture +def django_rf_user(django_user): + """Normal user request factory. + + Mimics AuthenticationMiddleware. Also adds a memory-backed session store + to the request object.""" + from django.test.client import RequestFactory + from django.contrib.sessions.backends.base import SessionBase + + class UserRequestFactory(RequestFactory): + def request(self, **request): + r = super(UserRequestFactory, self).request(**request) + r.user = django_user + r.session = SessionBase() + return r + + return UserRequestFactory() + + class SettingsWrapper(object): _to_restore = [] diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1585c6983..da613f9ab 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -22,6 +22,7 @@ from .fixtures import django_db_createdb # noqa from .fixtures import django_db_modify_db_settings # noqa from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa +from .fixtures import django_user # noqa from .fixtures import _live_server_helper # noqa from .fixtures import admin_client # noqa from .fixtures import admin_user # noqa @@ -31,8 +32,12 @@ from .fixtures import django_username_field # noqa from .fixtures import live_server # noqa from .fixtures import rf # noqa +from .fixtures import django_rf_admin # noqa +from .fixtures import django_rf_user # noqa +from .fixtures import django_rf_unauth # noqa from .fixtures import settings # noqa from .fixtures import transactional_db # noqa +from .fixtures import django_user_client # noqa from .pytest_compat import getfixturevalue from .lazy_django import django_settings_is_configured, skip_if_no_django diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 10ceac35c..80baa8a2e 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -11,6 +11,7 @@ import pytest from django.db import connection, transaction +from django.contrib.sessions.backends.base import SessionBase from django.conf import settings as real_settings from django.core import mail from django.test.client import Client, RequestFactory @@ -33,25 +34,70 @@ def test_admin_client(admin_client): assert force_text(resp.content) == 'You are an admin' +@pytest.mark.django_db +def test_user_client(django_user_client): + assert isinstance(django_user_client, Client) + resp = django_user_client.get('/admin-required/') + assert force_text(resp.content) == 'Access denied' + + def test_admin_client_no_db_marker(admin_client): assert isinstance(admin_client, Client) resp = admin_client.get('/admin-required/') assert force_text(resp.content) == 'You are an admin' +def test_user_client_no_db_marker(django_user_client): + assert isinstance(django_user_client, Client) + resp = django_user_client.get('/admin-required/') + assert force_text(resp.content) == 'Access denied' + + @pytest.mark.django_db def test_admin_user(admin_user, django_user_model): assert isinstance(admin_user, django_user_model) +@pytest.mark.django_db +def test_django_user(django_user, django_user_model): + assert isinstance(django_user, django_user_model) + + def test_admin_user_no_db_marker(admin_user, django_user_model): assert isinstance(admin_user, django_user_model) +def test_django_user_no_db_marker(django_user, django_user_model): + assert isinstance(django_user, django_user_model) + + def test_rf(rf): assert isinstance(rf, RequestFactory) +def test_rf_user(django_rf_user, django_user_model): + assert isinstance(django_rf_user, RequestFactory) + request = django_rf_user.get('/admin-required/') + assert isinstance(request.user, django_user_model) + assert isinstance(request.session, SessionBase) + + +def test_rf_admin(django_rf_admin, django_user_model): + assert isinstance(django_rf_admin, RequestFactory) + request = django_rf_admin.get('/admin-required/') + assert isinstance(request.user, django_user_model) + assert isinstance(request.session, SessionBase) + + +def test_rf_unauth(django_rf_unauth, django_user_model): + assert isinstance(django_rf_unauth, RequestFactory) + request = django_rf_unauth.get('/admin-required/') + assert hasattr(request, 'user') + # AnonymousUser is a plain-object (duck-typed) + assert not isinstance(request.user, django_user_model) + assert isinstance(request.session, SessionBase) + + @pytest.mark.django_db def test_django_assert_num_queries_db(django_assert_num_queries): with django_assert_num_queries(3):