I am writing some tests for a form on a Django site. I want to test that a logged in user is able to use the form assuming correct input. To that end, I am using a Django request factory. I can't seem to get my test user logged in though. That, or I'm not even making the request correctly. I'm not sure. Relevant code below:
def create_user():
username = 'User '+id_generator()
return User.objects.create(username = username, password = 'password')
def test_can_register_deck(self):
t = create_empty_tourney()
u = create_user()
d = create_deck(u)
rf = RequestFactory()
request = rf.post(reverse('tourney:tourney', args=(t.slug,)),{'deck' : d})
request.user = u
response = self.client.get(reverse('tourney:tourney', args=(t.slug,)), request)
self.assertEqual(response.status_code, 200)
The request has to think the user is logged in, or the rest of the test won't work. This throws no errors, but I'm not even sure my response is utilizing the request correctly. Am I correctly telling the test to "make a post request to the tourney page as this user, and try to submit a deck"?
I think that the RequestFactory and the client are two different ways of testing django views.
The request that is returned from rf.post is meant to be passed directly to a view function. If you were to do that, i think you will find that, the user will be detected as logged in.
I looked at the source, and the documentation, and I don't see where the test client accepts a request. You can log in a user with the test client using:
self.client.login(
username=u.username,
password='password')
Related
In my app I am trying to get the User data using a get request and it works in Postman but the problem occurs when I try to send the data from FrontEnd as we cannot send Body in get Request
URL:
path('user/meta/', UserDetails.as_view()),
Views.py
class UserDetails(APIView):
"""Get basic details of user"""
def get(self, request):
username = request.data["username"]
if User.objects.filter(username = username).exists():
user = User.objects.get(username = username)
print(user)
return Response({
'verified': user.verified,
})
else:
return Response("User doesn't exists", status=status.HTTP_400_BAD_REQUEST)
How should I modify it such that I can get the data from get request?
So, your requesting URL will become /user-info/?username=john
and then, use request.GET
username = request.GET.get("username","default_value")
The good practice is to use query params in such case,
so your url will look like,
/user-info/?username=john
and you modify your code to,
username= self.request.query_params.get('username')
I know this is an old topic and the question is answered, but just to underline that the queried data, you obtained from the user in the API should go through the Serializers (https://www.django-rest-framework.org/tutorial/1-serialization/) to at least somehow protect against unwanted values. This will also avoid you directly querying the database models.
I'm extremely confused as to how to test pages that require a login. I keep getting a 302 instead of a 200 in my response, and in inspecting the response in pdb I am definitely redirecting to a login page. I am using login middleware instead of the decorator, if that's relevant.
class SimplePageLoadsTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('test_user')
self.client.login(username='test_user', password='test_user')
def test_login(self):
self.assertTrue(self.user.is_authenticated())
def test_index(self):
self.client.login(username='test_user', password='test_user')
response = self.client.get(reverse('index'))
self.assertEqual(response.status_code, 200)
The test_login test passes. I wasn't sure whether or not I needed to re-login the user per test (I think not, since I didn't need in test_login, but I've tried it both ways with the same result. I threw a few print statements in my view, and they do not output, so I know I'm not hitting the view at all, like I suspected.
I can provide the view or middleware if they're relevant.
EDIT: I disabled the middleware and replaced it with the #login_required decorator, and had the same problem.
EDIT AGAIN: Just to check, I took out all login checks, and everything worked (as I expected). So I'm nearly positive that the self.client just doesn't know I've logged in.
It doesn't look like you are creating your user with a password. Without providing a password it is considered a user that cannot be logged in. Providing a password to create_user should fix it
self.client = Client()
self.user = User.objects.create_user('test_user', password='test_user')
self.client.login(username='test_user', password='test_user')
I'm developing a Flask application and using Flask-security for user authentication (which in turn uses Flask-login underneath).
I have a route which requires authentication, /user. I'm trying to write a unit test which tests that, for an authenticated user, this returns the appropriate response.
In my unittest I'm creating a user and logging as that user like so:
from unittest import TestCase
from app import app, db
from models import User
from flask_security.utils import login_user
class UserTest(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
db.create_all()
def tearDown(self):
if self._ctx is not None:
self._ctx.pop()
db.session.remove()
db.drop_all()
def test_user_authentication():
# (the test case is within a test request context)
user = User(active=True)
db.session.add(user)
db.session.commit()
login_user(user)
# current_user here is the user
print(current_user)
# current_user within this request is an anonymous user
r = test_client.get('/user')
Within the test current_user returns the correct user. However, the requested view always returns an AnonymousUser as the current_user.
The /user route is defined as:
class CurrentUser(Resource):
def get(self):
return current_user # returns an AnonymousUser
I'm fairly certain I'm just not fully understanding how testing Flask request contexts work. I've read this Flask Request Context documentation a bunch but am still not understanding how to approach this particular unit test.
The problem is different request contexts.
In your normal Flask application, each request creates a new context which will be reused through the whole chain until creating the final response and sending it back to the browser.
When you create and run Flask tests and execute a request (e.g. self.client.post(...)) the context is discarded after receiving the response. Therefore, the current_user is always an AnonymousUser.
To fix this, we have to tell Flask to reuse the same context for the whole test. You can do that by simply wrapping your code with:
with self.client:
You can read more about this topic in the following wonderful article:
https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/
Example
Before:
def test_that_something_works():
response = self.client.post('login', { username: 'James', password: '007' })
# this will fail, because current_user is an AnonymousUser
assertEquals(current_user.username, 'James')
After:
def test_that_something_works():
with self.client:
response = self.client.post('login', { username: 'James', password: '007' })
# success
assertEquals(current_user.username, 'James')
The problem is that the test_client.get() call causes a new request context to be pushed, so the one you pushed in your the setUp() method of your test case is not the one that the /user handler sees.
I think the approach shown in the Logging In and Out and Test Adding Messages sections of the documentation is the best approach for testing logins. The idea is to send the login request through the application, like a regular client would. This will take care of registering the logged in user in the user session of the test client.
I didn't much like the other solution shown, mainly because you have to keep your password in a unit test file (and I'm using Flask-LDAP-Login, so it's nonobvious to add a dummy user, etc.), so I hacked around it:
In the place where I set up my test app, I added:
#app.route('/auto_login')
def auto_login():
user = ( models.User
.query
.filter_by(username="Test User")
.first() )
login_user(user, remember=True)
return "ok"
However, I am making quite a lot of changes to the test instance of the flask app, like using a different DB, where I construct it, so adding a route doesn't make the code noticeably messier. Obv this route doesn't exist in the real app.
Then I do:
def login(self):
response = self.app.test_client.get("/auto_login")
Anything done after that with test_client should be logged in.
From the docs: https://flask-login.readthedocs.io/en/latest/
It can be convenient to globally turn off authentication when unit testing. To enable this, if the application configuration variable LOGIN_DISABLED is set to True, this decorator will be ignored.
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.
Maybe a stupid question here:
Is Requests(A python HTTP lib) support Django 1.4 ?
I use Requests follow the Official Quick Start like below:
requests.get('http://127.0.0.1:8000/getAllTracks', auth=('myUser', 'myPass'))
but i never get authentication right.(Of course i've checked the url, username, password again and again.)
The above url 'http://127.0.0.1:8000/getAllTracks' matches an url pattern of the url.py of a Django project, and that url pattern's callback is the 'getAllTracks' view of a Django app.
If i comment out the authentication code of the 'getAllTracks' view, then the above code works OK, but if i add those authentication code back for the view, then the above code never get authenticated right.
The authentication code of the view is actually very simple, exactly like below (The second line):
def getAllTracks(request):
if request.user.is_authenticated():
tracks = Tracks.objects.all()
if tracks:
# Do sth. here
Which means if i delete the above second line(with some indents adjustments of course), then the requests.get() operation do the right thing for me, but if not(keep the second line), then it never get it right.
Any help would be appreciated.
In Django authentication works in following way:
There is a SessionMiddleware and AuthenticationMiddleware. The process_request() of both these classes is called before any view is called.
SessionMiddleware uses cookies at a lower level. It checks for a cookie named sessionid and try to associate this cookie with a user.
AuthenticationMiddleware checks if this cookie is associated with an user then sets request.user as that corresponding user. If the cookie sessionid is not found or can't be associated with any user, then request.user is set to an instance of AnonymousUser().
Since Http is a stateless protocol, django maintains session for a particular user using these two middlewares and using cookies at a lower level.
Coming to the code, so that requests can work with django.
You must first call the view where you authenticate and login the user. The response from this view will contain sessionid in cookies.
You should use this cookie and send it in the next request so that django can authenticate this particular user and so that your request.user.is_authenticated() passes.
from django.contrib.auth import authenticate, login
def login_user(request):
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user:
login(request, user)
return HttpResponse("Logged In")
return HttpResponse("Not Logged In")
def getAllTracks(request):
if request.user.is_authenticated():
return HttpResponse("Authenticated user")
return HttpResponse("Non Authenticated user")
Making the requests:
import requests
resp = requests.post('http://127.0.0.1:8000/login/', {'username': 'akshar', 'password': 'abc'})
print resp.status_code
200 #output
print resp.content
'Logged In' #output
cookies = dict(sessionid=resp.cookies.get('sessionid'))
print cookies
{'sessionid': '1fe38ea7b22b4d4f8d1b391e1ea816c0'} #output
response_two = requests.get('http://127.0.0.1:8000/getAllTracks/', cookies=cookies)
Notice that we pass cookies using cookies keyword argument
print response_two.status_code
200 #output
print response_two.content
'Authenticated user' #output
So, our request.user.is_authenticated() worked properly.
response_three = requests.get('http://127.0.0.1:8000/hogwarts/getAllTracks/')
Notice we do not pass the cookies here.
print response_three.content
'Non Authenticated user' #output
I guess, auth keyword for Requests enables HTTP Basic authentication which is not what is used in Django. You should make a POST request to login url of your project with username and password provided in POST data, after that your Requests instance will receive a session cookie with saved authentication data and will be able to do successful requests to auth-protected views.
Might be easier for you to just set a cookie on initial authentication, pass that back to the client, and then for future requests expect the client to send back that token in the headers, like so:
r = requests.post('http://127.0.0.1:8000', auth=(UN, PW))
self.token = r.cookies['token']
self.headers = {'token': token}
and then in further calls you could, assuming you're in the same class, just do:
r = requests.post('http://127.0.0.1:8000/getAllTracks', headers=self.headers)