From 42802b0997f0f2e24ed03a37b994a705f702a497 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Wed, 3 Oct 2018 01:00:59 +0200 Subject: [PATCH] Some backend tests --- .gitignore | 1 + core/serializers/generic.py | 4 +- core/tests/test_export.py | 175 ++++++++++-------- .../test_subscription_assignment_service.py | 139 +++++++++----- core/views/subscription.py | 17 +- grady/settings/default.py | 1 - requirements.txt | 1 - 7 files changed, 204 insertions(+), 134 deletions(-) diff --git a/.gitignore b/.gitignore index 0c997665..a1d7ae81 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ coverage_html/ .idea/ .vscode/ anon-export/ +public/ # node node_modules diff --git a/core/serializers/generic.py b/core/serializers/generic.py index ce377fdd..944c7a30 100644 --- a/core/serializers/generic.py +++ b/core/serializers/generic.py @@ -1,9 +1,7 @@ -from drf_dynamic_fields import DynamicFieldsMixin from rest_framework import serializers -class DynamicFieldsModelSerializer(DynamicFieldsMixin, - serializers.ModelSerializer): +class DynamicFieldsModelSerializer(serializers.ModelSerializer): def __init__(self, *args, **kwargs): # Don't pass the 'fields' arg up to the superclass diff --git a/core/tests/test_export.py b/core/tests/test_export.py index 72609c90..f80a5f99 100644 --- a/core/tests/test_export.py +++ b/core/tests/test_export.py @@ -1,92 +1,94 @@ -from django.test import Client, TestCase from rest_framework import status -from rest_framework.utils import json +from rest_framework.test import APIClient, APITestCase from util.factories import make_test_data -class ExportJSONTest(TestCase): +def make_data(): + return make_test_data(data_dict={ + 'exams': [{ + 'module_reference': 'Test Exam 01', + 'total_score': 100, + 'pass_score': 60, + }], + 'submission_types': [ + { + 'name': '01. Sort', + 'full_score': 35, + 'description': 'Very complicated', + 'solution': 'Trivial!' + }, + { + 'name': '02. Shuffle', + 'full_score': 35, + 'description': 'Very complicated', + 'solution': 'Trivial!' + } + ], + 'students': [ + {'username': 'student01', 'exam': 'Test Exam 01'}, + {'username': 'student02', 'exam': 'Test Exam 01'} + ], + 'reviewers': [ + {'username': 'reviewer'} + ], + 'submissions': [ + { + 'text': 'function blabl\n' + ' on multi lines\n' + ' for blabla in bla:\n' + ' lorem ipsum und so\n', + 'type': '01. Sort', + 'user': 'student01', + 'feedback': { + 'score': 5, + 'is_final': True, + 'feedback_lines': { + '1': [{ + 'text': 'This is very bad!', + 'of_tutor': 'reviewer' + }], + } + } + }, + { + 'text': 'not much', + 'type': '02. Shuffle', + 'user': 'student01' + }, + { + 'text': 'function blabl\n' + ' asasxasx\n' + ' lorem ipsum und so\n', + 'type': '01. Sort', + 'user': 'student02' + }, + { + 'text': 'not much to see here', + 'type': '02. Shuffle', + 'user': 'student02' + } + ]} + ) + + +class ExportJSONTest(APITestCase): @classmethod def setUpTestData(cls): - cls.data = make_test_data(data_dict={ - 'exams': [{ - 'module_reference': 'Test Exam 01', - 'total_score': 100, - 'pass_score': 60, - }], - 'submission_types': [ - { - 'name': '01. Sort', - 'full_score': 35, - 'description': 'Very complicated', - 'solution': 'Trivial!' - }, - { - 'name': '02. Shuffle', - 'full_score': 35, - 'description': 'Very complicated', - 'solution': 'Trivial!' - } - ], - 'students': [ - {'username': 'student01', 'exam': 'Test Exam 01'}, - {'username': 'student02', 'exam': 'Test Exam 01'} - ], - 'reviewers': [ - {'username': 'reviewer'} - ], - 'submissions': [ - { - 'text': 'function blabl\n' - ' on multi lines\n' - ' for blabla in bla:\n' - ' lorem ipsum und so\n', - 'type': '01. Sort', - 'user': 'student01', - 'feedback': { - 'score': 5, - 'is_final': True, - 'feedback_lines': { - '1': [{ - 'text': 'This is very bad!', - 'of_tutor': 'reviewer' - }], - } - - } - }, - { - 'text': 'not much', - 'type': '02. Shuffle', - 'user': 'student01' - }, - { - 'text': 'function blabl\n' - ' asasxasx\n' - ' lorem ipsum und so\n', - 'type': '01. Sort', - 'user': 'student02' - }, - { - 'text': 'not much to see here', - 'type': '02. Shuffle', - 'user': 'student02' - } - ]} - ) + cls.data = make_data() def setUp(self): - client = Client() - client.force_login(user=self.data['reviewers'][0]) - self.response = client.post('/api/export/json/', content_type='application/json') + self.client = APIClient() + self.client.force_login(user=self.data['reviewers'][0]) + self.response = self.client.post('/api/export/json/') def test_can_access(self): self.assertEqual(status.HTTP_200_OK, self.response.status_code) def test_data_is_correct(self): - # for some reason the data is not automatically parsed... - student1, student2 = json.loads(self.response.content) + # due to using the client, we need to parse the json + student1, student2 = self.response.data self.assertIn('Matrikel', student1) self.assertIn('Matrikel', student2) @@ -102,6 +104,9 @@ class ExportJSONTest(TestCase): self.assertEqual('student01', student1['Username']) self.assertEqual('student02', student2['Username']) + self.assertEqual('********', student2['Password']) + self.assertEqual('********', student1['Password']) + self.assertEqual('01. Sort', student1['Scores'][0]['type']) self.assertEqual('01. Sort', student2['Scores'][0]['type']) @@ -113,3 +118,25 @@ class ExportJSONTest(TestCase): self.assertEqual(0, student1['Scores'][1]['score']) self.assertEqual(0, student2['Scores'][1]['score']) + + +class ExportJSONAndSetPasswordsTest(APITestCase): + @classmethod + def setUpTestData(cls): + cls.data = make_data() + + def setUp(self): + self.client = APIClient() + self.client.force_login(user=self.data['reviewers'][0]) + self.response = self.client.post('/api/export/json/', + data={'setPasswords': True}) + + def test_can_access(self): + self.assertEqual(status.HTTP_200_OK, self.response.status_code) + + def test_data_contains_correct_password(self): + student1, student2 = self.response.data + ret = self.client.login(username=student1['Username'], password=student1['Password']) + self.assertTrue(ret) + ret = self.client.login(username=student2['Username'], password=student2['Password']) + self.assertTrue(ret) diff --git a/core/tests/test_subscription_assignment_service.py b/core/tests/test_subscription_assignment_service.py index 55652250..708b088c 100644 --- a/core/tests/test_subscription_assignment_service.py +++ b/core/tests/test_subscription_assignment_service.py @@ -3,7 +3,7 @@ from rest_framework.test import APIClient, APITestCase from core import models from core.models import (Submission, SubmissionSubscription, SubmissionType, - SubscriptionEnded, SubscriptionTemporarilyEnded) + SubscriptionEnded, SubscriptionTemporarilyEnded, TutorSubmissionAssignment) from util.factories import GradyUserFactory, make_test_data @@ -165,13 +165,15 @@ class TestApiEndpoints(APITestCase): ]} ) + def setUp(self): + self.client = APIClient() + def test_ramaining_submissions_for_student(self): - client = APIClient() - client.force_authenticate(user=self.data['reviewers'][0]) + self.client.force_authenticate(user=self.data['reviewers'][0]) student = self.data['students'][0] - response = client.post( + response = self.client.post( '/api/subscription/', { 'query_type': 'student', 'query_key': student.student.student_id, @@ -183,55 +185,50 @@ class TestApiEndpoints(APITestCase): self.assertEqual(0, response.data['available']) def test_remaining_submissions(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - response = client.post('/api/subscription/', {'query_type': 'random'}) + response = self.client.post('/api/subscription/', {'query_type': 'random'}) self.assertEqual(3, response.data['remaining']) def test_available_submissions_in_stage(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - response = client.post('/api/subscription/', - {'query_type': 'random', - 'feedback_stage': 'feedback-validation'}) + response = self.client.post('/api/subscription/', + {'query_type': 'random', + 'feedback_stage': 'feedback-validation'}) self.assertEqual(0, response.data['available']) def test_can_create_a_subscription(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - response = client.post('/api/subscription/', {'query_type': 'random'}) + response = self.client.post('/api/subscription/', {'query_type': 'random'}) self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_create_subscription_and_get_one_assignment(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - response = client.post('/api/subscription/', {'query_type': 'random'}) + response = self.client.post('/api/subscription/', {'query_type': 'random'}) self.assertEqual('tutor01', response.data['owner']) def test_subscription_has_next_and_current_assignment(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - response_subscription_create = client.post( + response_subscription_create = self.client.post( '/api/subscription/', {'query_type': 'random'}) subscription_pk = response_subscription_create.data['pk'] subscription_pk = response_subscription_create.data['pk'] - response_assignment = client.post( + response_assignment = self.client.post( f'/api/assignment/', { 'subscription': subscription_pk }) assignment_pk = response_assignment.data['pk'] - response_subscription = client.get( + response_subscription = self.client.get( f'/api/subscription/{subscription_pk}/') self.assertEqual(1, len(response_subscription.data['assignments'])) @@ -239,23 +236,22 @@ class TestApiEndpoints(APITestCase): response_subscription.data['assignments'][0]['pk']) subscription_pk = response_subscription.data['pk'] - response_next = client.post( + response_next = self.client.post( f'/api/assignment/', { 'subscription': subscription_pk }) response_detail_subs = \ - client.get(f'/api/subscription/{subscription_pk}/') + self.client.get(f'/api/subscription/{subscription_pk}/') self.assertEqual(2, len(response_detail_subs.data['assignments'])) self.assertNotEqual(assignment_pk, response_next.data['pk']) def test_subscription_can_assign_to_student(self): - client = APIClient() - client.force_authenticate(user=self.data['reviewers'][0]) + self.client.force_authenticate(user=self.data['reviewers'][0]) student = self.data['students'][0] - response = client.post( + response = self.client.post( '/api/subscription/', { 'query_type': 'student', 'query_key': student.student.student_id, @@ -266,24 +262,23 @@ class TestApiEndpoints(APITestCase): self.assertEqual(1, len(assignments)) def test_two_tutors_cant_have_assignments_for_same_submission(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) - subscription = client.post('/api/subscription/', - {'query_type': 'random'}).data + subscription = self.client.post('/api/subscription/', + {'query_type': 'random'}).data - assignment_fst_tutor = client.post( + assignment_fst_tutor = self.client.post( '/api/assignment/', { 'subscription': subscription['pk'] } ).data - client.force_authenticate(user=self.data['tutors'][1]) + self.client.force_authenticate(user=self.data['tutors'][1]) - subscription = client.post('/api/subscription/', - {'query_type': 'random'}).data + subscription = self.client.post('/api/subscription/', + {'query_type': 'random'}).data - assignment_snd_tutor = client.post( + assignment_snd_tutor = self.client.post( '/api/assignment/', { 'subscription': subscription['pk'] } @@ -292,12 +287,63 @@ class TestApiEndpoints(APITestCase): self.assertNotEqual(assignment_fst_tutor['submission']['pk'], assignment_snd_tutor['submission']['pk']) + def test_reviewer_can_get_active_assignments(self): + self.client.force_authenticate(user=self.data['tutors'][0]) + subscription = self.client.post('/api/subscription/', + {'query_type': 'random'}).data + + assignment = self.client.post( + '/api/assignment/', { + 'subscription': subscription['pk'] + } + ).data + + # tutors shouldn't have access + res = self.client.get( + '/api/assignment/active/' + ) + self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code) + + self.client.force_authenticate(user=self.data['reviewers'][0]) + active_assignments = self.client.get( + '/api/assignment/active/' + ).data + print(assignment) + self.assertIn(assignment['pk'], [assignment['pk'] for assignment in active_assignments]) + + def test_reviewer_can_delete_active_assignments(self): + self.client.force_authenticate(user=self.data['tutors'][0]) + subscription = self.client.post('/api/subscription/', + {'query_type': 'random'}).data + + assignment = self.client.post( + '/api/assignment/', { + 'subscription': subscription['pk'] + } + ).data + + # tutors shouldn't have access + res = self.client.delete( + '/api/assignment/active/' + ) + self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code) + + self.client.force_authenticate(user=self.data['reviewers'][0]) + res = self.client.delete( + '/api/assignment/active/' + ) + self.assertEqual(status.HTTP_204_NO_CONTENT, res.status_code) + self.assertNotIn( + assignment['pk'], + [assignment.pk for assignment + in TutorSubmissionAssignment.objects.filter(is_done=False)] + ) + def test_all_stages_of_the_subscription(self): - client = APIClient() - client.force_authenticate(user=self.data['tutors'][0]) + self.client.force_authenticate(user=self.data['tutors'][0]) # The tutor corrects something - response = client.post( + response = self.client.post( '/api/subscription/', { 'query_type': 'random', 'feedback_stage': 'feedback-creation' @@ -306,11 +352,12 @@ class TestApiEndpoints(APITestCase): self.assertEqual(status.HTTP_201_CREATED, response.status_code) subscription_pk = response.data['pk'] - response = client.post( + response = self.client.post( f'/api/assignment/', { 'subscription': subscription_pk }) - response = client.post( + self.assertEqual(status.HTTP_201_CREATED, response.status_code) + response = self.client.post( f'/api/feedback/', { "score": 23, "of_submission": response.data['submission']['pk'], @@ -323,9 +370,9 @@ class TestApiEndpoints(APITestCase): self.assertEqual(status.HTTP_201_CREATED, response.status_code) # some other tutor reviews it - client.force_authenticate(user=self.data['tutors'][1]) + self.client.force_authenticate(user=self.data['tutors'][1]) - response = client.post( + response = self.client.post( '/api/subscription/', { 'query_type': 'random', 'feedback_stage': 'feedback-validation' @@ -334,7 +381,7 @@ class TestApiEndpoints(APITestCase): self.assertEqual(status.HTTP_201_CREATED, response.status_code) subscription_pk = response.data['pk'] - response = client.post( + response = self.client.post( '/api/assignment/', { 'subscription': subscription_pk }) @@ -351,7 +398,7 @@ class TestApiEndpoints(APITestCase): assignment = models.TutorSubmissionAssignment.objects.get( pk=response.data['pk']) self.assertFalse(assignment.is_done) - response = client.patch( + response = self.client.patch( '/api/feedback/%s/' % submission_id_in_response, { "score": 20, "is_final": True, diff --git a/core/views/subscription.py b/core/views/subscription.py index 7c60793c..88dc1d3e 100644 --- a/core/views/subscription.py +++ b/core/views/subscription.py @@ -2,7 +2,7 @@ import logging from django.core.exceptions import ObjectDoesNotExist from rest_framework import mixins, status, viewsets -from rest_framework.decorators import action +from rest_framework.decorators import action, permission_classes from rest_framework.response import Response from core import models, permissions, serializers @@ -81,13 +81,6 @@ class AssignmentApiViewSet( else: return self.queryset.filter(subscription__owner=self.request.user) - def get_permissions(self): - if self.action == 'list': - permission_classes = [IsReviewer] - else: - permission_classes = [IsTutorOrReviewer] - return [permission() for permission in permission_classes] - def _fetch_assignment(self, serializer): try: serializer.save() @@ -102,9 +95,12 @@ class AssignmentApiViewSet( status=status.HTTP_403_FORBIDDEN) return Response(serializer.data, status=status.HTTP_201_CREATED) + @permission_classes((IsReviewer,)) + def list(self, *args, **kwargs): + super().list(*args, **kwargs) + @action(detail=False, permission_classes=(IsReviewer,), methods=['get', 'delete']) def active(self, request): - print(request.method) if request.method == 'GET': queryset = self.get_queryset().filter(is_done=False) serializer = self.get_serializer(queryset, many=True) @@ -113,6 +109,7 @@ class AssignmentApiViewSet( self.get_queryset().filter(is_done=False).delete() return Response(status=status.HTTP_204_NO_CONTENT) + @permission_classes((IsTutorOrReviewer,)) def destroy(self, request, pk=None): """ Stop working on the assignment before it is finished """ instance = self.get_object() @@ -124,6 +121,7 @@ class AssignmentApiViewSet( instance.delete() return Response(status=status.HTTP_204_NO_CONTENT) + @permission_classes((IsTutorOrReviewer,)) def create(self, request, *args, **kwargs): context = self.get_serializer_context() serializer = AssignmentDetailSerializer(data=request.data, @@ -131,6 +129,7 @@ class AssignmentApiViewSet( serializer.is_valid(raise_exception=True) return self._fetch_assignment(serializer) + @permission_classes((IsTutorOrReviewer,)) def retrieve(self, request, *args, **kwargs): assignment = self.get_object() if assignment.subscription.owner != request.user: diff --git a/grady/settings/default.py b/grady/settings/default.py index 24cbddfa..d6da2a26 100644 --- a/grady/settings/default.py +++ b/grady/settings/default.py @@ -42,7 +42,6 @@ INSTALLED_APPS = [ 'django_extensions', 'rest_framework', 'corsheaders', - 'drf_dynamic_fields', 'drf_yasg', 'core', ] diff --git a/requirements.txt b/requirements.txt index 5f6f7175..446b8907 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ djangorestframework-jwt~=1.11.0 djangorestframework~=3.8 git+https://github.com/robinhundt/djangorestframework-camel-case Django~=2.1 -drf-dynamic-fields~=0.3.0 drf-yasg gevent~=1.3.2 gunicorn~=19.7.0 -- GitLab