I try to create a unittest for method logout, I have this for this purpose:
response = client.get('/api/v1/logout')
self.assertEquals(response.status_code, 200)
but in my logout controller I have this:
permission_classes = (IsAuthenticated,)
thus I changed my above code to this:
response = self.client.post('/api/v1/login', data={'username': 'testuser', 'password': '12345678'})
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.json()['access_token'])
response = client.get('/api/v1/logout')
self.assertEquals(response.status_code, 200)
but when I run my test I get 401 as result
self.assertEquals(response.status_code, 200)
AssertionError: 401 != 200
I am not sure how can I pass token to my request
tldr, for login use https://www.django-rest-framework.org/api-guide/testing/#forcing-authentication
Why ?
unittest as the name suggests should test only a single unit at a time.
Here we're testing two things, even though not explicitly but implicitly we're.
The login logic : We're passing username and password and taking access_token from response [even though we're not asserting here but this will affect next block]
The logout logic : We're using access_token from previous blocking and testing logout functionality based on that.
I strongly think that we should testing only single component at a time in unit test else call it integration test.
For our current scenario we can write two test cases:
Test login logic : Pass username and password and assert correct access_token is returned.
Test the logout logic : Mock the login logic and only test logout logic.
For mocking login we can use something like this https://www.django-rest-framework.org/api-guide/testing/#forcing-authentication
Related
I'm upgrading our django app from 3.0.5 to 3.2.9, and I'm having some issues with API tests. The response returned has apparently changed, and I would like to know why.
self.user.is_superuser = False
self.user.save()
self.assertEqual(self.user.get_all_permissions(), set())
put_url = reverse(..., kwargs={"pk": 1})
put_data = {
...
}
response = self.client.put(put_url, json.dumps(put_data), content_type="application/json")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.data, {"detail": "You do not have permission to perform this action."})
This used to pass, but now response.data contains
{'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}
and of course the tests fail.
The odd thing is that the error code remains 403, not 401. Is there a way to just have the string returned in detail?
Edit:
Login is done in setUp like so:
def setUp(self):
self.user = User.objects.get(username="test-user")
self.client.login(username=self.user, password="test")
and login returns true. Auth backends are GoogleOAuth2 and ModelBackend, so I guess ModelBackend is used in this case
However, when I try the request on the frontend with the user without permissions I get the expected response {"detail":"You do not have permission to perform this action."}
Edit 2:
I found another case, which is probably related:
self.user.is_superuser = False
self.user.save()
self.assertEqual(self.user.get_all_permissions(), set())
retrieve_url = reverse(..., kwargs={"pk": 1})
response = self.client.get(retrieve_url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
p = Permission.objects.get(codename="view_...")
self.user.user_permissions.add(p)
response = self.client.get(retrieve_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
The first assert (for 403) passes, but then the second one (after adding the permission) fails, because 403 is returned again.
It appears that in 3.2 after setting self.user.is_superuser = False the django backend will log the user out, thus resulting in not_authenticated, but the response returning the invalid error code 403, instead of 401.
After logging the user back in (each time after revoking superuser status) all tests pass as they did before on django 3.0.5.
I'm creating a Flask app with JWT Authorization and trying to test services with PyTest.
I successfully added tests to endpoints, but when I trying to add unit tests for certain function I can't access current user, because flask_jwt_extended.get_current_user() returns None.
Here is simple example:
#api.route('/listings', methods=['POST'])
#jwt_required
def create_listing():
payload = request.json
listing = listing_svc.create(payload)
return listing
def create(payload):
listing = ListingSchema().load(payload, db.session).data
class ListingSchema(ModelSchema):
id = field_for(Project, 'id', dump_only=True)
creator_user_id = field_for(Project, 'creator_user_id')
# ...
#pre_load
def set_creator_id(self, data):
current_user = flask_jwt_extended.get_current_user()
data['creator_user_id'] = current_user.id
It works when I authorize and send a request using app_context:
with client.application.app_context():
rv = client.post('/listings',
# ...
)
But what I need is to test create function without sending a request to client. In this case flask_jwt_extended.get_current_user() returns None, so I think I should set request context some way before running this function.
I tried to do this...
fake_payload = {}
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
create(fake_payload)
but still getting current_user is None
This is how I get token:
def login(email, password):
user = get_by_email(email)
if user and check_password_hash(user.password, password):
return access_token = flask_jwt_extended.create_access_token(identity=email)
If you are writing unit tests, using mock can be helpful. For jwt authorization with flask-jwt-extended you can patch the verify_jwt_in_request method which is called from the jwt_required decorator. Then you can also patch the get_jwt_identity function to return a test username. For example:
from unittest.mock import patch
#patch('path.to.some.code.get_jwt_identity')
#patch('flask_jwt_extended.view_decorators.verify_jwt_in_request')
def test_some_code(mock_jwt_required, mock_jwt_identity):
mock_jwt_identity.return_value = 'user1'
# Test jwt protected endpoint
Note: This patch is specific to latest package version flask-jwt-extended==3.21.0. The code may change with new versions.
Question from a long time ago but here is the solution for further readers.
You need to activate the app_context, then the request_context and finally call the function decorator is calling, which is verify_jwt_in_request:
fake_payload = {}
with client.application.app_context():
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
verify_jwt_in_request()
create(fake_payload)
and now you have your current_user set
If you truly want to unit test you need to unit test one function at a time. This is true test driven development in my opinion. So first write tests for create then load and so on. Use patching to mock the functionality of calls to other functions.
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')
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')
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)