401 error for tastypie for api_client - python

Hi I am trying to write a test case for my app. URL = '/api/project/'. I have enabled get, post and put methods along with authentication. But still i get 401 error for post request
class EntryResourceTest(ResourceTestCaseMixin, TestCase):
class Meta:
queryset = Project.objects.all()
resource_name = 'project'
allowed_methods = ['get', 'post', 'put']
authentication = Authentication()
authorization = Authorization()
def setUp(self):
super(EntryResourceTest, self).setUp()
# Create a user.
self.username = 'daniel'
self.password = 'pass'
self.user = User.objects.create_user(self.username, 'daniel#example.com', self.password)
def login(self):
return self.api_client.client.login(
username=self.username, password=self.password)
def get_credentials(self):
return self.create_basic(username=self.username, password=self.password)
def test_post_list(self):
self.login()
req_get = self.api_client.get('/api/project/', format='json', authentication=self.get_credentials()) # -> get works and i get 200 status code
req_post = self.api_client.post('/api/project/', format='json', data=self.post_data, authentication=self.get_credentials())
I run the test case using following command , and here get works fine but not the post request. And get request works even if i don't pass authentication parameter as it uses the default login which i have defined in self.login()
django-admin test myapp.api.project.tests

Use BasicAuthentication instead of Authentication.
self.create_basic(...) create a headers for BasicAuthentication.
def create_basic(self, username, password):
"""
Creates & returns the HTTP ``Authorization`` header for use with BASIC
Auth.
"""

Related

DRF Testing, Login view does work when loging in from Postman, but it throws error in APITestCase

I am new to DRF and for learning I am writing test for my DRF Rest API. I am trying to test Login view, however, the test is failing even though I am providing correct credentials. Unusual thing is that it works perfectly fine when I am making a login request from Postman
I tried analyzing the data I provide, however I don't find any issues. In the test case I create new User and then try to log in.
My Test case:
def test_login_user(self):
"""
Ensure user can log in
"""
username = 'TestUserLogin'
password = 'test978453442'
url = reverse('login-list')
user = User.objects.create(
email='testUserLogin#test.com',
first_name='Matheus',
last_name='Smith',
password=password,
title='professor',
username=username,
groups=Group.objects.get(name=GROUPS[0])
)
response = self.client.post(url, {'username': username, 'password': password}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
My Token Generator class
class CustomExpiringObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
"""
Override!
Create token everytime this endpoint is called
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
if hasattr(user, 'auth_token'):
user.auth_token.delete()
token = Token.objects.create(user=user)
return Response({'token': token.key})
My Login View:
class LoginView(ViewSet):
serializer_class = AuthTokenSerializer
def create(self, request):
return CustomExpiringObtainAuthToken().as_view()(request=request._request)

django allauth login test can't login correctly

I'm trying to write a login test for django allauth but although the app works correctly and I can login, when I write a login test it fails to login correctly and I get an assertion error Response didn't redirect as expected: Response code was 200 (expected 302)
302 != 200 because of a validation error `'The e-mail address and/or password you specified are not correct'
(currently test_login fails but the other two tests pass)
Looking at the tests in django all-auth here:
https://github.com/pennersr/django-allauth/blob/master/allauth/account/tests.py
I can see that they also create an EmailAddress object when testing login - I'm not sure why or if this is needed?
class TestLoginView(TestCase):
def setUp(self):
self.client = Client()
self.username = "test#test.com"
self.password = "password"
user = get_user_model().objects.create_user(self.username, self.password)
# EmailAddress.objects.create(user=user, email=self.username, primary=True, verified=True)
def test_login(self):
response = self.client.post(reverse(
settings.LOGIN_URL), {"login": self.username, "password": self.password}
)
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False)
def test_wrong_login(self):
response = self.client.post(reverse(
settings.LOGIN_URL), {"login": "bad#login.com", "password": "wrong"}
)
validation_error = 'The e-mail address and/or password you specified are not correct'
assert validation_error in response.content.decode('utf-8')
def test_redirect_when_authenticated(self):
self.client.force_login(self.user)
resp = self.client.get(reverse(settings.LOGIN_URL))
url = settings.LOGIN_REDIRECT_URL + '/' + self.username + '/' + 'detail'
self.assertRedirects(resp, url, fetch_redirect_response=False)
Changing the user creation to using keyword args fixed it, ie:
self.user = get_user_model().objects.create_user(username=self.username, password=self.password)
alternatively you can drop the seperate Email model creation too and just do
get_user_model().objects.create_user(username=username, email=username, password=password)

AssertionError: 302 != 200

I tried to create a test in my tests.py
class TaskViewTests(TestCase):
def test_task_view_with_no_task(self):
"""
If no task exist, an appropriate message should be displayed.
"""
userName = 'esutek'
response = self.client.get(reverse('actuser:task',args=(userName,)))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No task are available.")
self.assertQuerysetEqual(response.context['taskList'], [])
However it gives me this error message.
I don't have any clue why this happened. I just followed the tutorial.
actuser:task
views.py
def task(request, userName):
""" User task list in actInbox
"""
user = ActuserViewModel()
user.get_task_list(userName)
return render(request, 'actuser/task.html', {
'userName': userName,
'taskList': user.taskList,
'dateToday': user.dateToday,
})
viewmodels.py
def get_task_list(self, userName):
self.taskList = Task.objects.filter(executor = userName, parent_task_id=EMPTY_UUID).order_by('due_date')
#get date now with this format 05/11
self.dateToday = datetime.date.today()
Actually I got 2 urls...
this is from the project
url(r'^(?P<userName>[0-9a-zA-Z--]+)/', include('actuser.urls', namespace="actuser")),
and this one is from actuser.urls
url(r'^task/$', views.task, name='task'),
HTTP 302 means that you are redirected to some other URL. You can do a redirect intentionally if you use a RedirectView for example, or accidentally if you forget to write slash at the end of the request URL and you have APPEND_SLASH enabled (in that case, you get HTTP 301 instead of 302).
You need a slash at the end:
url(r'^(?P<userName>[0-9a-zA-Z-]+)/task/$', ...
You could be getting a redirect if your view requires login.
You need to login first, this is a good example of how to do it:
Django: test failing on a view with #login_required
Briefly:
class LoginTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
def testLogin(self):
self.client.login(username='john', password='johnpassword')
response = self.client.get(reverse('testlogin-view'))
self.assertEqual(response.status_code, 200)

(unittest) Testing Flask-Security: Cannot get past login page

I'm trying to add tests to my basic app. Accessing everything requires login.
Here's my test case class:
class MyAppTestCase(FlaskTestCaseMixin):
def _create_app(self):
raise NotImplementedError
def _create_fixtures(self):
self.user = EmployeeFactory()
def setUp(self):
super(MyAppTestCase, self).setUp()
self.app = self._create_app()
self.client = self.app.test_client()
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
self._create_fixtures()
self._create_csrf_token()
def tearDown(self):
super(MyAppTestCase, self).tearDown()
db.drop_all()
self.app_context.pop()
def _post(self, route, data=None, content_type=None, follow_redirects=True, headers=None):
content_type = content_type or 'application/x-www-form-urlencoded'
return self.client.post(route, data=data, follow_redirects=follow_redirects, content_type=content_type, headers=headers)
def _login(self, email=None, password=None):
email = email or self.user.email
password = password or 'password'
data = {
'email': email,
'password': password,
'remember': 'y'
}
return self._post('/login', data=data)
class MyFrontendTestCase(MyAppTestCase):
def _create_app(self):
return create_app(settings)
def setUp(self):
super(MyFrontendTestCase, self).setUp()
self._login()
I am running my tests use nosetests in Terminal like so: source my_env/bin/activate && nosetests --exe
Basic tests like these fail:
class CoolTestCase(MyFrontendTestCase):
def test_logged_in(self):
r = self._login()
self.assertIn('MyAppName', r.data)
def test_authenticated_access(self):
r = self.get('/myroute/')
self.assertIn('MyAppName', r.data)
From the output, I see that r.data is just the HTML of the login page with no errors (e.g., wrong username or password) or alerts ("Please log in to access this page").
I am logging in during the setUp process, so test_authenticated_access should have let me either access /myroute/ or redirect me to the login page with the flashed message "Please log in to access this page". But it didn't.
I can't figure out what's wrong. I based my test off of ones I found in Flask documentation and this app boilerplate
I finally figured it out after a week of killing myself...
There were several problems:
There is some conflict between Flask-Security and Flask-SSLify. Although the automatic https redirection works fine on the actual web server, in the tests it prevent logging in completely. I figured this out by making fake test routes and trying to POST random data. No POST in the test client worked. To fix this, I had to change my test config DEBUG to True. Note that this is not a great workaround, since stuff like CSS and JS won't be compiled and other app behavior might be different...
Flask-Security does not work with factory_boy(or I was not using factory_boy correctly?). I was trying to create users and roles through factory_boy because I saw some sample app tutorial that used it. After I fixed the above problem, it kept telling me that the user did not exist. I ended up stealing the code from Flask-Security's own tests for creating fake users with different roles.
The problematic code:
session = db.create_scoped_session()
class RoleFactory(SQLAlchemyModelFactory):
FACTORY_FOR = Role
FACTORY_SESSION = session
id = Sequence(int)
name = 'admin'
description = 'Administrator'
class EmployeeFactory(SQLAlchemyModelFactory):
FACTORY_FOR = Employee
FACTORY_SESSION = session
id = Sequence(int)
email = Sequence(lambda n: 'user{0}#app.com'.format(n))
password = LazyAttribute(lambda a: encrypt_password('password'))
username = Sequence(lambda n: 'user{0}'.format(n))
#last_login_at = datetime.utcnow()
#current_login_at = datetime.utcnow()
last_login_ip = '127.0.0.1'
current_login_ip = '127.0.0.1'
login_count = 1
roles = LazyAttribute(lambda _: [RoleFactory()])
active = True
This was my test case:
class MyAppTestCase(FlaskTestCaseMixin, MyTestCase):
def _create_app(self):
raise NotImplementedError
def _create_fixtures(self):
#self.user = EmployeeFactory()
populate_data(1)
def setUp(self):
super(MyAppTestCase, self).setUp()
self.app = self._create_app()
self.client = self.app.test_client()
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
self._create_fixtures()
self._create_csrf_token()
def tearDown(self):
super(MyAppTestCase, self).tearDown()
db.drop_all()
self.app_context.pop()
def _post(self, route, data=None, content_type=None, follow_redirects=True, headers=None):
content_type = content_type or 'application/x-www-form-urlencoded'
return self.client.post(route, data=data, follow_redirects=follow_redirects, content_type=content_type, headers=headers)
def _login(self, email=None, password=None):
email = email or 'matt#lp.com' #self.user.email
password = password or 'password'
data = {
'email': email,
'password': password,
'remember': 'y'
}
return self._post('/login', data=data)
I had a problem with login/logout on tests with Flask-Security (that uses Flask-Login) app also. I solved this problem by mocking flask_login._get_user function and setting app.login_manager._login_disabled to True.
Context manager that mocks user:
class LoginLogoutMixin(object):
#contextmanager
def login(self, user):
mock_get_user = patch('flask_login._get_user', Mock(return_value=user))
self.app.login_manager._login_disabled = True
mock_get_user.start()
yield
mock_get_user.stop()
self.app.login_manager._login_disabled = False
And an example of it's usage:
class ProtectedViewTestCase(LoginLogoutMixin, TestCase):
def setUp(self):
super(ProtectedViewTestCase, self).setUp()
# create user here
def test_protected_view(self):
with self.login(self.user):
rv = self.client.get(url_for('protected_view'))
self.assert200(rv)

Passing user credentials from Tastypie to Django Model

I am using custom authentication in Django-Tastypie, which returns the username and password when authentication succeeds. In the ModelResource instance, once the authentication is successful, the username and password are available:
class TestResource(ModelResource):
class Meta:
queryset = Test.remote_supported.get_all(username,password)
resource_name = 'test'
filtering = {
'id' : ALL,
}
detail_allowed_methods = ['get', 'post', 'patch']
authorization = Authorization()
authentication = MyCustomAuth()
always_return_data = True
def __init__(self, api_name=None):
self.username = None
self.password = None
super(TestResource, self).__init__(api_name)
def is_authenticated(self, request):
auth_result = self._meta.authentication.is_authenticated(request)
if isinstance(auth_result, HttpResponse):
raise ImmediateHttpResponse(response=auth_result)
if not auth_result is True:
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
# this is where I receive the username and password from my custom auth
self.username, self.password = self._meta.authentication.get_credentials()
This code obviously does not work, because that username and that password are not available in the Metaclass, and even if it were, changing it would not affect only this instance, but all instances, which is not my intention, because the scope of username and password should be per RESTful query.
This is what my model looks like:
class RemoteAuthSupported(models.Manager):
def get_all(self, username, password):
# ... here I do some custom operations with the username and password
return super(RemoteAuthSupported, self).get_query_set()
class Test(models.Model):
objects = models.Manager()
remote_supported = RemoteAuthSupported()
# ... the field declarations follow here ... #
The reason I am trying to do this is that I am using a non-ORM data source with my Django application, which requires authentication of its own, but with the same username and password. What would be the method of handling this parameter passing from a Tastypie ModelResource to a Django Model? I probably should mention here that there is no user model in use.
What you could do is override the obj_get_list method that returns the list of resources for a request. Also, you could set the username and password to the request object itself, so the paramters get carried over the request-response path. Also, you'd need to set the queryset to all().
def obj_get_list(self, bundle, **kwargs):
original = super(TestResource, self).obj_get_list(bundle, **kwargs)
request = bundle.request
return original.get_all(request.username, request.password)
Or the other way round - you could add custom authorization that would filter the objects list. The request attributes part still holds.
class MyAuth(Authorization):
def authorized_read_list(self, objects, bundle):
request = bundle.request
return objects.get_all(request.username, request.password)
If you would rather imitate queryset using get_all than just alternating list endpoint, you could override the get_object_list.
def get_object_list(self, request):
original = super(TestResource, self).get_object_list(request)
return original.get_all(request.username, request.password)
I looked into the Tastypie documentation, and it seems like the following could be a solution, albeit amateurish:
class TestResource(ModelResource):
class Meta:
queryset = Test.remote_supported.get_all('dummyusername','dummypassword')
resource_name = 'test'
filtering = {
'id' : ALL,
}
detail_allowed_methods = ['get', 'post', 'patch']
authorization = Authorization()
authentication = MyCustomAuth()
always_return_data = True
def get_object_list(self, request):
"""
This method calls a clone of the queryset declared in the Metaclass.
It is called every time a query is executed.
Here the actual username and password is passed to the model.
"""
return Site.remote_supported.get_all(self.username,self.password)
The problem was that the queryset declaration happened in the Metaclass only once, not per query, so the username and password acquired from the http request could not be passed on to the query in the Meta level. But since get_object_list() clones the declaration and performs the actual call with the updates argument values, this gets the job done. It is working for me, but I feel there should be a better solution for doing this.

Categories

Resources