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