I need to create a blog post app in which I am stuck in testing all test cases. I need a user which can remain logged in throughout the testing phase so that I can use the same logged in user in blog and post and update_post apis but I am unable to do so
def test_login(client, user_data):
# with unittest.mock.patch('django.contrib.auth.authenticate') as mock_authenticate:
# mock_authenticate.return_value = authenticate(
# request=client.post(reverse('login'), user_data, format='json'),
# backend='django.contrib.auth.backends.ModelBackend'
# )
with unittest.mock.patch('django.contrib.auth.authenticate') as mock_authenticate:
user = CustomUser.objects.create_user(**user_data)
mock_authenticate.return_value = user
response = client.post(reverse('login'), user_data, format="json")
assert response.sstatus_code == 200
this is my login api and I want this user to be logged in throughout all apis without using SESSIONS
app/tests.py
from rest_framework.test import APITestCase
from rest_framework import status
class YourTest(APITestCase):
def setUp(self):
self.user = create_user() # Create your own user here.
self.client.force_authenticate(user=self.user)
def test_update_post(self):
# test your apis here.
# Below lines are just example.
response = self.client.patch("posts/3", {"name": "jw"})
assert response.status_code == status.HTTP_200_OK
assert response.data["name"] == "jw"
You can use force_authenticate method to get auth on specific user.
Related
I need to test a simple Django project where users in a certain group have some permissions. Now in tests I know how to create a mock user and I have already tested that users not in the group can't access the data trow API, but now I have to test that users in group can access to data. How I can create and add the mock user the group?
Thanks in advance for your help.
Below my code:
import pytest
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.urls import reverse
from mixer.backend.django import mixer
from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK
from rest_framework.test import APIClient
#pytest.fixture()
def superheroes(db):
return [
mixer.blend('superhero.SuperHero')
]
def get_client(user=None):
res = APIClient()
if user is not None:
res.force_login(user)
return res
# code to test users without permissions (it works!)
def test_anon_user_forbidden():
path = reverse('superheroes-list')
client = get_client()
response = client.get(path)
assert response.status_code == HTTP_403_FORBIDDEN
# code to test users with permission (so users that are in the group)
def test_avengers_user_ok(superheroes):
path = reverse('superheroes-list')
user = mixer.blend(get_user_model())
# add the user to specific group, but how?
client = get_client(user)
response = client.get(path)
assert response.status_code == HTTP_200_OK
I am trying to use pytest to unit test my Flask app. I have the following test case for an endpoint that requires information from flask_login's current_user:
def test_approval_logic():
with app.test_client() as test_client:
app_url_put = '/requests/process/2222'
with app.app_context():
user = User.query.filter_by(uid='xxxxxxx').first()
with app.test_request_context():
login_user(user)
user.authenticated = True
db.session.add(user)
data = dict(
state='EXAMPLE_STATE_NAME',
action='approve'
)
resp = test_client.put(app_url_put, data=data)
assert resp.status_code == 200
Inside the test_request_context, I am able to set current_user correctly. However, this test fails because in the requests view where the PUT is handled, there is no logged in user and 500 error results. The error message is, AttributeError: 'AnonymousUserMixin' object has no attribute 'email'. Can someone explain why current_user goes away and how I can set it correctly?
My guess is that no session cookie is passed in your PUT request.
Here is an example of how I log a user during my tests (I personally user unittest instead of pytest, so I reduced the code to the strict minimum, but let me know if you want a complete example with unittest)
from whereyourappisdefined import application
from models import User
from flask_login import login_user
# Specific route to log an user during tests
#application.route('/auto_login/<user_id>')
def auto_login(user_id):
user = User.query.filter(User.id == user_id).first()
login_user(user)
return "ok"
def yourtest():
application.config['TESTING'] = True # see my side note
test_client = application.test_client()
response = test_client.get(f"/auto_login/1")
app_url_put = '/requests/process/2222'
data = dict(
state='EXAMPLE_STATE_NAME',
action='approve'
)
r = test_client.put(app_url_put, data=data)
In the documentation we can read:
https://werkzeug.palletsprojects.com/en/2.0.x/test/#werkzeug.test.Client
The use_cookies parameter indicates whether cookies should be stored
and sent for subsequent requests. This is True by default but passing
False will disable this behavior.
So during the first request GET /auto_login/1 the application will receive a session cookie and keep it for further HTTP requests.
Side note:
Enable TESTING in your application (https://flask.palletsprojects.com/en/2.0.x/testing/)
During setup, the TESTING config flag is activated. What this does is
disable error catching during request handling so that you get better
error reports when performing test requests against the application.
Using test client to dispatch a request
The current session is not bound to test_client, so the request uses a new session.
Set the session cookie on the client, so Flask can load the same session for the request:
from flask import session
def set_session_cookie(client):
val = app.session_interface.get_signing_serializer(app).dumps(dict(session))
client.set_cookie('localhost', app.session_cookie_name, val)
Usage:
# with app.test_client() as test_client: # Change these
# with app.app_context(): #
# with app.test_request_context(): #
with app.test_request_context(), app.test_client() as test_client: # to this
login_user(user)
user.authenticated = True
db.session.add(user)
data = dict(
state='EXAMPLE_STATE_NAME',
action='approve'
)
set_session_cookie(test_client) # Add this
resp = test_client.put(app_url_put, data=data)
About the compatibility of with app.test_request_context()
i. with app.test_client()
with app.test_client() preserves the context of requests (Flask doc: Keeping the Context Around), so you would get this error when exiting an inner with app.test_request_context():
AssertionError: Popped wrong request context. (<RequestContext 'http://localhost/requests/process/2222' [PUT] of app> instead of <RequestContext 'http://localhost/' [GET] of app>)
Instead, enter app.test_request_context() before app.test_client() as shown above.
ii. with app.app_context()
with app.test_request_context() already pushes an app context, so with app.app_context() is unnecessary.
Using test request context without dispatching a request
From https://flask.palletsprojects.com/en/2.0.x/api/#flask.Flask.test_request_context:
This is mostly useful during testing, where you may want to run a function that uses request data without dispatching a full request.
Usage:
data = dict(
state='EXAMPLE_STATE_NAME',
action='approve'
)
with app.test_request_context(data=data): # Pass data here
login_user(user)
user.authenticated = True
db.session.add(user)
requests_process(2222) # Call function for '/requests/process/2222' directly
Here's how I do it on my sites:
user = User.query.filter_by(user_id='xxxxxxx').one_or_none()
if user:
user.authenticated = True
db.session.add(user)
db.session.commit()
login_user(user)
else:
# here I redirect to an unauthorized page, as the user wasn't found
I don't know the order is the issue or just the absence of db.session.commit(), but I think you need to have done both in order for your put request to work.
Note, also, that I am using a one_or_none() because there shouldn't be a possibility of multiple users with the same user_id, just a True or False depending on whether a user was found or not.
I am learning testing in Django, and have a view which I want to test. This view should only be accessed by staff users. Suppose the view is:
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
if the request is coming from staff users, it should redirect to repositories otherwise I should get something like permission denied. I am starting with something like in tests.py.
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
# User send a request to access repositories
response = staff_users(request)
self.assertIsNone(response)
The problem is here I am not associating my request object with any users, and I also got to know about from django.contrib.admin.views.decorators import staff_member_required but not sure how to use them here. Could anyone tell me how should I test my view should only be accessed by staff users?
All you need to do is decorate your view which you want to protect as shown below:
#staff_member_required
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
If you want a custom logic for testing instead of using django decorator then you can write your own decorator as well.
def staff_users_only(function):
def wrap(request, *args, **kwargs):
profile = request.session['user_profile']
if profile is True: #then its a staff member
return function(request, *args, **kwargs)
else:
return HttpResponseRedirect('/')
wrap.__doc__=function.__doc__
wrap.__name__=function.__name__
return wrap
and use it as:
#staff_users_only
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
Edit
Association of sessions on request object for testing can be done as:
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
#create a session which will hold the user profile that will be used in by our custom decorator
request.session = {} #Session middleware is not available in UnitTest hence create a blank dictionary for testing purpose
request.session['user_profile'] = self.user.is_staff #assuming its django user.
# User send a request to access repositories
response = staff_users(request)
#Check the response type for appropriate action
self.assertIsNone(response)
Edit 2
Also it would be a far better idea to use django Client library for testing:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'abc', 'password': '1234'})
>>> response.status_code
200
>>> response = c.get('/user/protected-page/')
>>> response.content
b'<!DOCTYPE html...
I have a Django-rest-framework viewset/router to define an API endpoint. The viewset is defined as such:
class DocumentViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
model = Document
And the router is defined as
router = DefaultRouter()
router.register(r'documents', viewsets.DocumentViewSet)
with url pattern url(r'^api/', include(router.urls))
I can hit this endpoint in the browser/through curl just fine by getting the right access token and using it for authorization. However, it's not clear how to write tests against this endpoint.
Here is what I've tried:
class DocumentAPITests(APITestCase):
def test_get_all_documents(self):
user = User.objects.create_user('test', 'test#test.com', 'test')
client = APIClient()
client.credentials(username="test", password="test")
response = client.get("/api/documents/")
self.assertEqual(response.status_code, 200)
This fails with an HTTP 401 response from the client.get() call. What is the right way to test an API endpoint in DRF using django-oauth-toolkit for oauth2 authentication?
When you are writing tests, you should aim to extract anything you are not testing from the test itself, typically putting any setup code in the setUp method of the test. In the case of API tests with OAuth, this usually includes the test user, OAuth application, and the active access token.
For django-oauth-toolkit, and other Django applications, I would always recommend looking at the tests to see how they do it. This allows you to avoid making unneeded API calls, especially for multi-part processes like OAuth, and only create the few model objects that are required.
def setUp(self):
self.test_user = UserModel.objects.create_user("test_user", "test#user.com", "123456")
self.application = Application(
name="Test Application",
redirect_uris="http://localhost",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.application.save()
def test_revoke_access_token(self):
from datetime import datetime
from django.utils import timezone
tok = AccessToken.objects.create(
user=self.test_user, token='1234567890',
application=self.application, scope='read write',
expires=timezone.now() + datetime.timedelta(days=1)
)
From there you just need to authenticate using the token that has been generated. You can do this by injecting the Authorization header, or you can use the force_authenticate method provided by Django REST Framework.
I have used the same library for OAuth2,
This worked for me
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.models import get_access_token_model,
get_application_model
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APITestCase
Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()
class Test_mytest(APITestCase):
def setUp(self):
oauth2_settings._SCOPES = ["read", "write", "scope1", "scope2", "resource1"]
self.test_user = UserModel.objects.create_user("test_user", "test#example.com", "123456")
self.application = Application.objects.create(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.org",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.access_token = AccessToken.objects.create(
user=self.test_user,
scope="read write",
expires=timezone.now() + timezone.timedelta(seconds=300),
token="secret-access-token-key",
application=self.application
)
# read or write as per your choice
self.access_token.scope = "read"
self.access_token.save()
# correct token and correct scope
self.auth = "Bearer {0}".format(self.access_token.token)
def test_success_response(self):
url = reverse('my_url',)
# Obtaining the POST response for the input data
response = self.client.get(url, HTTP_AUTHORIZATION=self.auth)
# checking wether the response is success
self.assertEqual(response.status_code, status.HTTP_200_OK)
Now everything will work as expected.
Hope this helps. Thanks
from oauth2_provider.models import (
get_access_token_model,
get_application_model,
get_id_token_model,
get_refresh_token_model,
)
class TestOauth(APITestCase):
def setUp(self):
""" create and register user """
self.test_user = User.create..
def test_oauth_application_and_tokens_add(self):
print(self.test_user, self.test_user.id)
"""Applications"""
Application = get_application_model()
app = Application()
app.name = "test"
app.client_type = "confidential"
app.authorization_grant_type = "password"
app.user_id = self.test_user.id
app.save()
# client_id:
print("Application Client ID: ", app.client_id)
# client_secret:
print("Application Client SECRET: ", app.client_secret)
"""Access Token"""
AccessToken = get_access_token_model()
token = AccessToken()
token.user_id = self.test_user.id
token.scope = "read write"
token.expires = timezone.now() + timezone.timedelta(seconds=300)
token.token = "secret-access-token-key"
token.application = app
token.save()
# token
print("Access Token: ", token)
self.auth = "Bearer {0}".format(token.token)
""" ID Token """
IDToken = get_id_token_model()
idt = IDToken()
idt.user_id = self.test_user.id
idt.application = app
idt.expires = timezone.now() + timezone.timedelta(days=10)
idt.scope = "read write"
idt.save()
# id token - returns jti token - successfull
print("ID Token: ", idt)
""" Refresh Token """
RefreshToken = get_refresh_token_model()
refr = RefreshToken()
refr.user_id = self.test_user.id
refr.application = app
refr.token = "statictoken" # The token is issued statically.
refr.access_token = (
token # The access token must not have been used before.
)
refr.revoked = timezone.now() + timezone.timedelta(days=10)
refr.save()
# refresh token
print("Refresh Token: ", refr)
I would like to write unit tests for my Django app that is using python-social-auth.
It all works great when running Django and using a browser, thanks python-social-auth!
However, I can't seem to write unit tests because I can't create an authenticated client to test with.
Has anyone done so successfully?
How did you get an authenticated client()?
I have tried this (the login returns false and does not work):
self.c = Client()
self.u = User.objects.create(username="testuser", password="password", is_staff=True, is_active=True, is_superuser=True)
self.u.save()
self.auth = UserSocialAuth(user=self.u, provider="Facebook")
self.auth.save()
self.c.login(username=self.u.username, password=self.u.password)
Got it:
My mistake was thinking that it mattered how that Client got authenticated, for unit testing the views/endpoints oauth really doesn't need to come into play at all.
this worked for me:
self.user = User.objects.create(username='testuser', password='12345', is_active=True, is_staff=True, is_superuser=True)
self.user.set_password('hello')
self.user.save()
self.user = authenticate(username='testuser', password='hello')
login = self.c.login(username='testuser', password='hello')
self.assertTrue(login)
I have found a workaround to the issue by using the django.test.Client.force_login() method instead. With it, you need to fetch a user from the database, whose data is probably stored in a fixture, and specify the authentication backend in the second argument.
Here's the code I've used:
from random import sample
class SubscribeTestCase(TestCase):
fixtures = (
"auth.User.json", "social_django.UserSocialAuth.json",
"<myapp>.CustomProfileUser.json", "<myapp>.SubscriptionPlan.json"
)
def test_user_logged_in(self):
users = User.objects.all()
user = sample(list(users), 1)[0]
# This isn't actually used inside this method
social_user = user.social_auth.get(provider="auth0")
self.client.force_login(
user, "django.contrib.auth.backends.ModelBackend"
)
response = self.client.get(
reverse("<myappnamespace>:subscribe")
)
print(response.content)
# Looking for a way to fetch the user after a
# response was returned? Seems a little hard, see below
I am not sure how you can access a user in a Django unit test scenario after having received a Response object, which as the documentation observes is not the same as the usual HttpResponse used in production environments. I have done a quick research and it does look like developers aren't intended to do that. In my case I didn't need that so I didn't dig deeper.
If one checks Python Social Auth - Django source code, one will see a file in social-app-django/tests/test_views.py that contains an example to test authenticating the user with Facebook.
from unittest import mock
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractBaseUser
from django.test import TestCase, override_settings
from django.urls import reverse
from social_django.models import UserSocialAuth
from social_django.views import get_session_timeout
#override_settings(SOCIAL_AUTH_FACEBOOK_KEY='1',
SOCIAL_AUTH_FACEBOOK_SECRET='2')
class TestViews(TestCase):
def setUp(self):
session = self.client.session
session['facebook_state'] = '1'
session.save()
def test_begin_view(self):
response = self.client.get(reverse('social:begin', kwargs={'backend': 'facebook'}))
self.assertEqual(response.status_code, 302)
url = reverse('social:begin', kwargs={'backend': 'blabla'})
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
#mock.patch('social_core.backends.base.BaseAuth.request')
def test_complete(self, mock_request):
url = reverse('social:complete', kwargs={'backend': 'facebook'})
url += '?code=2&state=1'
mock_request.return_value.json.return_value = {'access_token': '123'}
with mock.patch('django.contrib.sessions.backends.base.SessionBase'
'.set_expiry', side_effect=[OverflowError, None]):
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/accounts/profile/')
To use with another social backend is relatively straightforward; simply substitute facebook with the one one is using.
Also, note that the example doesn't consider partial pipelines but the code can be adjusted to consider them too.
Note:
Agree that their docs could be improved.