I know how to write django test code.
But I don't know how to test with views that inlcude requests.get code
View code is like this.
class CertificateAPIView(APIView):
permission_classes = [IsAuthenticated, IsOwner]
def put(self, request):
imp = request.data.get("imp")
response = requests.get('another server')
uid = response.get('uid')
if uid == None:
return Response(
{"detail": ValidationError(_("Certification is failed"))},
status=status.HTTP_400_BAD_REQUEST,
)
... more condition branch logics.
return ...
imp value is automatically created by 'another server' that I am not managing.
This code is related with another server response and check condition branch with response.
But before getting response with imp vlue, we can't check whether response is error.
Related
I'm using Django 3 and the auth contrib module. I want to override the reset password procedure via an API and so I have a method that looks like this ....
class CompleteResetPasswordView(PasswordResetConfirmView):
#method_decorator(csrf_exempt)
def dispatch(self, request):
body = json.loads(request.body)
self.data = body
# validate user, password, and confirm password
if user is not None:
...
return Response('Success', status=HTTP_200_OK)
...
return Response('Error', status=HTTP_500_INTERNAL_SERVER_ERROR)
When the user cannot be validated and the "return Response('Error', status=HTTP_500_INTERNAL_SERVER_ERROR)" is invoked, this error is returned
AssertionError at /password-reset-complete
.accepted_renderer not set on Response
I would liek a JSON response (in addition to the 500 status code) to be returned to the view, but haven't figured out how to manipulate what I have to make this happen.
I am trying to test a Django Rest Framework view. When I call my endpoint from a real api client, pk is correctly set. When it is called from the test, pk is None.
class ResourceViewSet(ModelViewSet):
serializer_class = ResourceSerializer
#action(detail=True)
def foo(self, request, pk=None):
print(pk) # None when called by the test
def test_foo(client: Client, db):
request = factory.post(f'/api/resource/1/foo/')
view = ResourceViewSet.as_view({'post': 'foo'})
response = view(request)
How should I fix my test?
When testing the view directly as you are doing, you are bypassing the url resolving/mapping logic. Therefore, you should pass the parameters as args/kwargs, in the end you are calling the foo function:
def test_foo(client: Client, db):
request = factory.post(f'/api/resource/1/foo/')
view = ResourceViewSet.as_view({'post': 'foo'})
response = view(request, pk=1)
If you'd like to test the whole stack, also the urls, I'd recommend you use the APIClient.
I'm continuing my journey of testing my Django Rest Framework application as I add new views and more functionality. I must admit, at this stage, I'm finding testing harder than actually coding and building my app. I feel that there are far fewer resources on testing DRF available than there are resources talking about building a REST framework with DRF. C'est la vie, though, I soldier on.
My issue that I'm currently facing is that I'm receiving a 403 error when testing one of my DRF ViewSets. I can confirm that the view, and its permissions work fine when using the browser or a regular python script to access the endpoint.
Let's start with the model that is used in my ViewSet
class QuizTracking(models.Model):
case_attempt = models.ForeignKey(CaseAttempt, on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
Of note here is that there is a FK to a user. This is used when determining permissions.
Here is my test function. I have not included code for the entire class for the sake of brevity.
def test_question_retrieve(self):
"""
Check that quiz tracking/ID returns a 200 OK and the content is correct
"""
jim = User(username='jimmy', password='monkey123', email='jimmy#jim.com')
jim.save()
quiz_tracking = QuizTracking(answer=self.answer, case_attempt=self.case_attempt, user=jim)
quiz_tracking.save()
request = self.factory.get(f'/api/v1/progress/quiz-tracking/{quiz_tracking.id}')
# How do I refernce my custom permission using Permission.objects.get() ?
# permission = Permission.objects.get()
# jim.user_permissions.add(permission)
self.test_user.refresh_from_db()
force_authenticate(request, user=jim)
response = self.quiz_detail_view(request, pk=quiz_tracking.id)
print(response.data)
print(jim.id)
print(quiz_tracking.user.id)
self.assertContains(response, 'answer')
self.assertEqual(response.status_code, status.HTTP_200_OK)
In the above code, I define a user, jim and a quiz_tracking object owned by jim.
I build my request, force_authenticate the requst and the execute my request and store the response in response.
The interesting things to note here are:
- jim.id and quiz_tracking.user.id are the same value
- I receive a 403 response with
{'detail': ErrorDetail(string='You do not have permission to perform this action.', code='permission_denied')}
You may have noticed that I have commented out permission = Permission.objects.get() My understanding is that I need to pass this my Permission Class, which in my case is IsUser. However, there is no record of this in my DB and hence Permission.objects.get('IsUSer') call fails.
So my questions are as follows:
- How do I authenticate my request so that I receive a 200 OK?
- Do I need to add a permission to my user in my tests, and if so, which permission and with what syntax?
Below is my view and below that is my custom permission file defining IsUser
class QuickTrackingViewSet(viewsets.ModelViewSet):
serializer_class = QuizTrackingSerializser
def get_queryset(self):
return QuizTracking.objects.all().filter(user=self.request.user)
def get_permissions(self):
if self.action == 'list':
self.permission_classes = [IsUser, ]
elif self.action == 'retrieve':
self.permission_classes = [IsUser, ]
return super(self.__class__, self).get_permissions()
N.B. If I comment out def get_permissions(), then my test passes without problem.
My custom permission.py
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_superuser
class IsUser(BasePermission):
def has_object_permission(self, request, view, obj):
if request.user:
if request.user.is_superuser:
return True
else:
return obj == request.user
else:
return False
Cheers,
C
It seems that the problem is your permission IsUser.
You compare QuizTracking instance with user instance.
change this line
return obj == request.user
to
return obj.user.id == request.user.id
One off topic suggestion
You can write your IsUser class in the following way which is easier to read
class IsUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return request.user.is_superuser or obj.user.id == request.user.id
Note that has_object_permission is only called if the view level has_permission returns True.
My problem: I'm writing tests and I'm getting 302 responses instead of 200. I figure this is kind of expected because when a user isn't logged in and isn't assigned to the group Employee_Management, they will always be redirected.
So I'm trying to create a user and add it to the group, but I have no idea how to check if the user is actually logged in, or if it is part of the group. I get no errors from the setUp, but the test still fails and gives me a 302. So my guess is I've messed up my setUp. Ideas?
view:
#method_decorator(group_required('Employee_Management'), name='dispatch')
class ListActiveView(TemplateView):
def get(self, request):
users = User.objects.all().exclude(is_superuser=True)
return render(request, 'user/list_active.html', {
'users': users,
})
urls:
app_name = 'user'
urlpatterns = [path('list_active/', ListActiveView.as_view(), name='list_active')]
test:
class TestListActive(TestCase):
def setUp(self):
user = User.objects.create(username='testuser', password='testuserpass')
emp_man = Group.objects.create(name='Employee_Management')
user.groups.add(emp_man)
c = Client()
c.login(username='testuser', password='testuserpass')
def test_list_active_url(self):
response = self.client.get(reverse('user:list_active'))
self.assertEquals(response.status_code, 200)
Your problem is that you use two Client instances.
The first one (c) is the one you create in setUp, which your user is logged to but that is never used again.
The other one is the TestCase instance's client (self.client) which you use in test_list_active_url and which your user is not logged in to.
To fix that, always use the TestCase instance's client:
class TestListActive(TestCase):
def setUp(self):
user = User.objects.create(username='testuser', password='testuserpass')
emp_man = Group.objects.create(name='Employee_Management')
user.groups.add(emp_man)
self.client.login(username='testuser', password='testuserpass')
def test_list_active_url(self):
response = self.client.get(reverse('user:list_active'))
self.assertEquals(response.status_code, 200)
I am using django 1.11.9
I want to add client_id and client_secret to the django POST request.
Here is how my middleware.py file looks like:
class LoginMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# auth_header = get_authorization_header(request)
# Code to be executed for each request before
# the view (and later middleware) are called.
#Add Django authentication app client data to the request
request.POST = request.POST.copy()
request.POST['client_id'] = '12345678'
request.POST['client_secret'] = '12345678'
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
Middleware is being successfully processed when I check it with a debugger. Thought when a view is called the 'client_id' and 'client_secret' fields are missing in the request.
After some experimenting i figure out that request is not getting updated and when it is called in a different view, it returns old values.
I am later using request in rest_framework_social_oauth2. And this is the point when 'client_id' and 'client_secret' disappear.
class ConvertTokenView(CsrfExemptMixin, OAuthLibMixin, APIView):
"""
Implements an endpoint to convert a provider token to an access token
The endpoint is used in the following flows:
* Authorization code
* Client credentials
"""
server_class = SocialTokenServer
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = KeepRequestCore
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
import pdb ; pdb.set_trace()
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request.data.items():
request._request.POST[key] = value
url, headers, body, status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=status)
for k, v in headers.items():
response[k] = v
return response
I need to add client_id and client_secret to the request body, so it can be later used by rest_framework_social_oauth2.
What could be the problem? How to properly update the request?
As you're working with request and processing a request, you have to implement process_request method, so the result will be something like:
class LoginMiddleware(object):
def process_request(self, request):
request.session['client_id'] = '12345678'
and then in your view:
def your_view(request):
client_id = request.session['client_id']