I wanted to introduce unit testing to a Django application. Although I started failing on the first thing I wanted to test. Can you tell me what I am doing wrong?
The view I want to test
#user_passes_test(lambda u: u.has_module_perms('myModule'))
def myView(request):
...someCode...
I wanted to test the user_passes_test bit, I also have more complex tests so I wanted to know if my tests let the right users and only them access the view. I focused on the bit that didn't work and simplified it a bit.
from django.contrib.auth.models import User
from django.test import TestCase
from settings import DJANGO_ROOT
class PermissionsTestCase(TestCase):
fixtures = [DJANGO_ROOT + 'testdata.json']
def setUp(self):
self.user = User.objects.create(username='user', password='pass')
self.user.save()
def test_permissions_overview(self):
url = '/secret/'
#User not logged in (guest)
response = self.client.get(url)
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/secret/')
#Logged in user that should not be able to see this page
response = self.client.get(url)
self.client.login(username='user', password='pass')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/secret/')
#Logged in user that has 'myModule' module permissions
self.user.user_permissions.add('myModule.view_myThing')
self.user.save()
self.assertTrue(self.user.has_module_perms('myModule')) #This one fails
self.client.login(username='user',password='pass')
response = self.client.get(url)
self.assertContains(response, "What to look for") #This one too
And the last bit keeps on failing. The permission doesn't get through. Any ideas?
This won't convert the password to hash
User.objects.create(username='user', password='pass')
the correct way to create user is :
User.objects.create_user(username='user', password='pass')
here is full summary
>>> from django.contrib.auth.models import User
>>> x=User.objects.create(username='user', password='pass')
>>> x.password
'pass'
>>> from django.test import Client
>>> c = Client()
>>> c.login(username='user',password='pass')
False
# But create_user works
>>> y=User.objects.create_user(username='user2', password='pass')
>>> y.password
u'pbkdf2_sha256$12000$oh55gfrnCZn5$hwGrkUZx38siegWHLXSYoNDT2SSP1M5+Whh5PnJZD8I='
>>> c.login(username='user2',password='pass')
True
Permissions are strings.
# this tests to see if a user has 'myModule'
#user_passes_test(lambda u: u.has_module_perms('myModule'))
#gives the user a completely different permission called 'myModule.view_myThing'
self.user.user_permissions.add('myModule.view_myThing')
#tests to see if user has a permission called 'myModule' which is not 'myModule.view_myThing'
self.assertTrue(self.user.has_module_perms('myModule'))
Base on what you have written I cannot infer your intent but you have at least 2 distinct permissions you are attempting to add/check. I would change the permissions you give to the user as follows:
self.user.user_permissions.add('myModule')
Related
I am using Django 3.2
I am trying to test a model that checks if a user is authenticated. The model code looks something like this:
class Foo(Model):
def do_something(user, **kwargs):
if user.is_authenticated():
do_one_thing(user, **kwargs)
else:
do_another_thing(**kwargs)
My test code looks something like this:
from model_bakery import baker
from django.contrib.auth import authenticate, get_user_model
User = get_user_model()
class FooModelTest(TestCase):
def setUp(self):
self.user1 = baker.make(User, username='testuser_1', password='testexample1', email='user1#example.com', is_active = True, is_superuser=False)
baker.make(User, username='testuser_2', password='testexample2', email='admin#example.com', is_active = True, is_superuser=True)
self.unauthorised_user = authenticate(username='testuser_2', password='wrongone')
def test_user(self):
self.assertTrue(self.user1.is_validated) # why is this true? - I haven't "logged in" yet?
self.assertFalse(self.unauthorised.is_validated) # Barfs here since authenticate returned a None
So, How am I to mock an unauthenticated user, for testing purposes? - since it appears that the is_authenticated property is read only?
[[ Update ]]
I've just done a little more research + testing, and it looks like the solution to this would be to use AnnonymousUser as follows:
from django.contrib.auth.models import AnonymousUser
self.unaunthorised_user = AnonymousUser()
I am waiting to see if someone can confirm this (preferably, with a link to documentation)
Hers is a sample permission that I want to unit test.
# permissions.py
from myapp.models import Membership
class IsOrganizationOwner(permissions.BasePermission):
"""
Custom permission to allow only owner of the organization to do a certian task.
"""
def has_object_permission(self, request, view, obj):
try:
membership = Membership.objects.get(user = request.user, organization = obj)
except Membership.DoesNotExist:
return False
return membership.is_admin
and here is how it is applied
# viewsets.py
class OrganizationViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Organizations to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticated, IsOrganizationOwner,)
queryset = Organization.objects.all().order_by('name')
serializer_class = OrganizationSerializer
Now I am very new to testing in django and I don't know how to test this permission. Any help would be appreciated.
Here is one approach:
from django_mock_queries.query import MockSet
from mock import patch, MagicMock
from unittest import TestCase
class TestPermissions(TestCase):
memberships = MockSet()
patch_memberships = patch('myapp.models.Membership.objects', memberships)
def setUp(self):
self.permission = IsOrganizationOwner()
self.memberships.clear()
self.request = MagicMock(user=MagicMock())
self.view = MagicMock()
def create_membership(self, organization, is_admin):
self.request.user.is_admin = is_admin
self.memberships.add(
MagicMock(user=self.request.user, organization=organization)
)
#patch_memberships
def test_permissions_is_organization_owner_returns_false_when_membership_does_not_exist(self):
org = MagicMock()
self.assertFalse(self.permission.has_object_permission(self.request, self.view, org))
#patch_memberships
def test_permissions_is_organization_owner_returns_false_when_membership_is_not_admin(self):
org = MagicMock()
self.create_membership(org, False)
self.assertFalse(self.permission.has_object_permission(self.request, self.view, org))
#patch_memberships
def test_permissions_is_organization_owner_returns_true_when_membership_is_admin(self):
org = MagicMock()
self.create_membership(org, True)
self.assertTrue(self.permission.has_object_permission(self.request, self.view, org))
I used a library that I wrote that mocks django queryset functions to make the tests smaller and more readable. But if you prefer you can use Mock or MagicMock to patch only the things you need.
EDIT: For the sake of completeness let's assume you also wanted to integration test OrganizationViewSet, here's some tests for that:
from django.contrib.auth.models import User
from django.test import TestCase, Client
from model_mommy import mommy
class TestOrganizationViewSet(TestCase):
url = '/organizations/'
def create_user(self, is_admin):
password = 'password'
user = mommy.prepare(User, is_admin=is_admin)
user.set_password(password)
user.save()
return user, password
def get_organizations_as(self, user=None, password=None):
api = Client()
if user:
mommy.make(Membership, user=user, organization=mommy.make(Organization))
api.login(username=user.username, password=password)
return api.get(self.url)
def test_organizations_viewset_returns_200_for_admins(self):
response = self.get_organizations_as(*self.create_user(True))
self.assertEqual(response.status_code, 200)
def test_organizations_viewset_returns_403_for_non_admins(self):
response = self.get_organizations_as(*self.create_user(False))
self.assertEqual(response.status_code, 403)
def test_organizations_viewset_returns_403_for_anonymous(self):
response = self.get_organizations_as()
self.assertEqual(response.status_code, 403)
As others have pointed out, you need those tests too, just not for every possible test case. Unit tests are best for that, as integration tests will write to the database etc. and will make your CI procedures slower - in case that sort of thing is relevant to you.
I was wrangling with this myself, and I think I found a simple solution that tests the behavior of the permissions check in isolation without having to mock everything out. Since this response is coming 4 years after the original answer, Django may have evolved considerably since then.
Testing a permission seems to be as easy as instantiating the permission and testing its has_permission method with contrived objects. For instance, I tested this out with the IsAdminUser permission, and the test passed:
from django.contrib.auth.models import User
from django.test import RequestFactory, TestCase
from rest_framework.permissions import IsAdminUser
class IsAdminUserTest(TestCase):
def test_admin_user_returns_true(self):
admin_user = User.objects.create(username='foo', is_staff=True)
factory = RequestFactory()
request = factory.delete('/')
request.user = admin_user
permission_check = IsAdminUser()
permission = permission_check.has_permission(request, None)
self.assertTrue(permission)
Changing is_staff to False in the User instantiation causes the test to fail as I'd expect.
Updating this with an actual example
I wrote my own custom permission to check if a user is an admin (staff user) and to allow only read-only operations otherwise. Note that, since this is a unit test, it doesn't interface with any endpoint or even seek to mock those out; it just tests the expected behavior of the permissions check.
Here's the permission:
from rest_framework import permissions
class IsAdminUserOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return request.user.is_staff
And here's the full unit test suite:
from django.contrib.auth.models import User
from django.test import RequestFactory, TestCase
from community.permissions import IsAdminUserOrReadOnly
class IsAdminOrReadOnlyTest(TestCase):
def setUp(self):
self.admin_user = User.objects.create(username='foo', is_staff=True)
self.non_admin_user = User.objects.create(username='bar')
self.factory = RequestFactory()
def test_admin_user_returns_true(self):
request = self.factory.delete('/')
request.user = self.admin_user
permission_check = IsAdminUserOrReadOnly()
permission = permission_check.has_permission(request, None)
self.assertTrue(permission)
def test_admin_user_returns_true_on_safe_method(self):
request = self.factory.get('/')
request.user = self.admin_user
permission_check = IsAdminUserOrReadOnly()
permission = permission_check.has_permission(request, None)
self.assertTrue(permission)
def test_non_admin_user_returns_false(self):
request = self.factory.delete('/')
request.user = self.non_admin_user
permission_check = IsAdminUserOrReadOnly()
permission = permission_check.has_permission(request, None)
self.assertFalse(permission)
def test_non_admin_user_returns_true_on_safe_method(self):
request = self.factory.get('/')
request.user = self.non_admin_user
permission_check = IsAdminUserOrReadOnly()
permission = permission_check.has_permission(request, None)
self.assertTrue(permission)
I assume you could use a similar pattern for just about any user attribute you wanted to write a permission against.
When I get to integration/functional testing, only then will I worry about how this permission affects the interfaces of the API.
As a small enhancement/alternative for the approach outlined above, I would use pytest's admin_user fixture provided for django support:
https://pytest-django.readthedocs.io/en/latest/helpers.html#admin-user-an-admin-user-superuser
So a happy path test could look something like this:
def test_returns_200_when_user_is_authenticated(self,
client, admin_user):
client.force_login(admin_user)
response = client.put('/my_url/pk/', data={'some data'},
content_type='application/json'))
assert response.status_code == 200
And you could also test the opposite scenario where the user is not logged in:
def test_returns_403_when_user_is_not_authenticated(
self, client):
response = client.put('/my_url/pk/', data={'some data'},
content_type='application/json'))
assert response.status_code == 403
I'm working on my first project which uses Django REST Framework, and I'm having issues testing the API. I'm getting 403 Forbidden errors instead of the expected 200 or 201. However, the API works as expected.
I have been going through DRF Testing docs, and it all seems straightforward, but I believe my client is not being logged in. Normally in my Django projects I use a mixture of factory boy and django webtest which I've had a lot of happy success with. I'm not finding that same happiness testing the DRF API after a couple days of fiddling around.
I'm not sure if this is a problem relating to something I'm doing wrong with DRF APITestCase/APIClient or a problem with the django test in general.
I'm just pasting the following code and not posting the serializers/viewsets because the API works in the browser, it seems I'm just having issues with the APIClient authentication in the APITestCase.
# settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
# tests.py
from django.test import TestCase
from rest_framework.test import APITestCase, APIClient
from accounts.models import User
from .factories import StaffUserFactory
class MainSetUp(TestCase):
def setUp(self):
self.user = StaffUserFactory
self.api_root = '/api/v0/'
self.client = APIClient()
class APITests(MainSetUp, APITestCase):
def test_create_feedback(self):
"""
Ensure we can create a new account object.
"""
self.client.login(username='staffuser', password='staffpassword')
url = '%sfeedback/' % self.api_root
data = {
'feedback': 'this is test feedback'
}
response = self.client.post(url, data, user=self.user)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data, data)
# factories.py
from factory.django import DjangoModelFactory
from django.contrib.auth import get_user_model
User = get_user_model()
class UserFactory(DjangoModelFactory):
class Meta:
model = User
class StaffUserFactory(UserFactory):
username = 'staffuser'
password = 'staffpassword'
email = 'staff#email.com'
first_name = 'Staff'
last_name = 'User'
is_staff = True
I've never used DjangoModelFactory before, but it appears that you have to call create after setting your user to the StaffUserFactory. http://factoryboy.readthedocs.org/en/latest/_modules/factory/django.html#DjangoModelFactory
class MainSetUp(TestCase):
def setUp(self):
self.user = StaffUserFactory
self.user.create()
self.api_root = '/api/v0/'
self.client = APIClient()
I bet your User's password is not being set properly. You should use set_password. As a start, trying changing your setUp to this:
def setUp(self):
self.user = StaffUserFactory
self.user.set_password('staffpassword')
self.user.save() # You could probably omit this, but set_password does't call it
self.api_root = '/api/v0/'
self.client = APIClient()
If that works, you probably want to override _generate() in your factory to add that step.
Another thing to check would be that SessionAuthentication is in your DEFAULT_AUTHENTICATION_CLASSES setting.
I think you must instantiate the Factory to get real object, like this:
self.user = StaffUserFactory()
Hope that help.
Moreover, you don't need to create a seperate class for staff, just set is_staff=True is enough. Like this:
self.user = UseFactory(is_staff=True)
I'm trying to access the request.user object when testing my app using django's client class.
from django.test import TestCase
from django.test.client import Client
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.client.login( username="foo", password="bar")
def test_one(self):
response = self.client.get("/my_profile/")
self.fail( response.request.user )
This will obviously fail, but it fails because response.request doesn't have a user attribute.
AttributeError: 'dict' object has no attribute 'user'
Is there any way to access the user object from the client instance? I want to do this in order to check if some test user's data is properly rendered. I can try to tackle this by setting up all the necessary info during setUp, but before I do that I wanted to check if I'm not just missing something.
This may seem a roundabout way of doing it but it can be useful.
Use RequestFactory, its pretty straightforward. It imitates the request object.
from django.test import TestCase, RequestFactory
from django.test.client import Client
from django.contrib.auth.models import User
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='foo', email='foo#bar',
password='bar')
def test_one(self):
self.client.login( username="foo", password="bar")
request = self.factory.get("/my_profile/")
request.user = self.user
#rest of your code
def tearDown(self):
self.user.delete()
I hope that was helpful.
Use response.context['user'].
User is automatically available in the template context if you use RequestContext. See auth data in templates doc.
Otherwise i believe you should just query it:
def test_one(self):
response = self.client.get("/my_profile/")
user = User.objects.get(username="foo")
self.fail( user.some_field == False )
I can't figure out why the permission required decorator isn't working. I would like to allow access to a view only for staff members. I have tried
#permission_required('request.user.is_staff',login_url="../admin")
def series_info(request):
...
and also
#permission_required('user.is_staff',login_url="../admin")
def series_info(request):
...
As the superuser, I can access the view, but any users I create as staff can't access it and are redirected to the login url page. I tested the login_required decorator and that works fine.
permission_required() must be passed a permission name, not a Python expression in a string. Try this instead:
from contrib.auth.decorators import user_passes_test
def staff_required(login_url=None):
return user_passes_test(lambda u: u.is_staff, login_url=login_url)
#staff_required(login_url="../admin")
def series_info(request)
...
Thanks. That does work. Do you have an
example of how to use
permission_required? From the
documentation
docs.djangoproject.com/en/1.0/… and
djangobook.com/en/2.0/chapter14 I
thought what I had should work.
Re-read the links you posted; permission_required() will test if a user has been granted a particular permission. It does not test the attributes of the user object.
From http://www.djangobook.com/en/2.0/chapter14/:
def vote(request):
if request.user.is_authenticated() and request.user.has_perm('polls.can_vote'):
# vote here
else:
return HttpResponse("You can't vote in this poll.")
#
#
# # #
###
#
def user_can_vote(user):
return user.is_authenticated() and user.has_perm("polls.can_vote")
#user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
# vote here
#
#
# # #
###
#
from django.contrib.auth.decorators import permission_required
#permission_required('polls.can_vote', login_url="/login/")
def vote(request):
# vote here
This is how I would do it:
from django.contrib.admin.views.decorators import staff_member_required
#staff_member_required
def series_info(request):
...
The documentation says about staff_member_required:
Decorator for views that checks that the user is logged in and is a staff member, displaying the login page if necessary.
Here is an example of behavior I don't understand. I create a user, request and decorate a test function with permission_required checking for 'is_staff'. If the user is superuser, then access is granted to the test function. If the user only has is_staff = True, access is not granted.
from django.http import HttpRequest
from django.contrib.auth.models import User
from django.contrib.auth.decorators import permission_required
#permission_required('is_staff')
def test(dummy='dummy'):
print 'In test'
mb_user = User.objects.create_user('mitch', 'mb#home.com', 'mbpassword')
mb_user.is_staff = True
req = HttpRequest()
req.user = mb_user
test(req) # access to test denied - redirected
req.user.is_staff = False
test(req) # same as when is_staff is True
req.user.is_superuser = True
test(req) # access to test allowed
By the way, if you're using class-based views, you should wrap your decorator in the method_decorator decorator (go figure):
class MyView(DetailView):
...
#method_decorator(permission_required('polls.can_vote', login_url=reverse_lazy('my_login')))
def dispatch(self, request, *args, **kwargs):
.... blah ....
class MyModel(models.Model):
...
def has_perm(self perm, obj=None):
if perm == 'polls.canvote':
return self.can_vote()
This works for me on my 'project' table/model:
#permission_required('myApp.add_project')
def create(request):
# python code etc...
Obviously change the add_project to the add_[whatever your model/table is]. To edit it would be:
#permission_required('myApp.edit_project')
and to delete:
#permission_required('myApp.delete_project')
But I found that the key thing is to make sure your auth tables are set up correctly. This is what caused me problems. Here is a MySQL SQL query I wrote to check permissions if you are using groups. This should work in most dBs:
select usr.id as 'user id',usr.username,grp.id as 'group id',grp.name as 'group name',grpu.id as 'auth_user_groups',grpp.id as 'auth_group_permissions',perm.name,perm.codename
from auth_user usr
left join auth_user_groups grpu on usr.id = grpu.user_id
left join auth_group grp on grpu.group_id = grp.id
left join auth_group_permissions grpp on grp.id = grpp.group_id
left join auth_permission perm on grpp.permission_id = perm.id
order by usr.id;
I found that my permissions were not set up correctly, and also watch out for the django_content_type table which must have rows for each app and table for each of add, edit, delete. So if you have a project table you should see this in django_content_type:
id [generated by dB]
app_label myApp
model project
If you are having trouble another good idea is to enable and use the django admin app. This will show you where your problems are, and by setting up some test permissions, users and groups you can then examine the tables discussed above to see what is being inserted where. This will give you a good idea of how auth permissions works.
I write this to maybe save someone from having to spend a few hours figuring out what I did!