Skip to content
Snippets Groups Projects
Commit 42802b09 authored by robinwilliam.hundt's avatar robinwilliam.hundt
Browse files

Some backend tests

parent 6864782c
No related branches found
No related tags found
1 merge request!123Resolve "Remove unfinished Assignments from Tutors/Reviewers"
Pipeline #325280 passed
...@@ -34,6 +34,7 @@ coverage_html/ ...@@ -34,6 +34,7 @@ coverage_html/
.idea/ .idea/
.vscode/ .vscode/
anon-export/ anon-export/
public/
# node # node
node_modules node_modules
......
from drf_dynamic_fields import DynamicFieldsMixin
from rest_framework import serializers from rest_framework import serializers
class DynamicFieldsModelSerializer(DynamicFieldsMixin, class DynamicFieldsModelSerializer(serializers.ModelSerializer):
serializers.ModelSerializer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass # Don't pass the 'fields' arg up to the superclass
......
from django.test import Client, TestCase
from rest_framework import status 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 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 @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.data = make_test_data(data_dict={ cls.data = make_data()
'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'
}
]}
)
def setUp(self): def setUp(self):
client = Client() self.client = APIClient()
client.force_login(user=self.data['reviewers'][0]) self.client.force_login(user=self.data['reviewers'][0])
self.response = client.post('/api/export/json/', content_type='application/json') self.response = self.client.post('/api/export/json/')
def test_can_access(self): def test_can_access(self):
self.assertEqual(status.HTTP_200_OK, self.response.status_code) self.assertEqual(status.HTTP_200_OK, self.response.status_code)
def test_data_is_correct(self): def test_data_is_correct(self):
# for some reason the data is not automatically parsed... # due to using the client, we need to parse the json
student1, student2 = json.loads(self.response.content) student1, student2 = self.response.data
self.assertIn('Matrikel', student1) self.assertIn('Matrikel', student1)
self.assertIn('Matrikel', student2) self.assertIn('Matrikel', student2)
...@@ -102,6 +104,9 @@ class ExportJSONTest(TestCase): ...@@ -102,6 +104,9 @@ class ExportJSONTest(TestCase):
self.assertEqual('student01', student1['Username']) self.assertEqual('student01', student1['Username'])
self.assertEqual('student02', student2['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', student1['Scores'][0]['type'])
self.assertEqual('01. Sort', student2['Scores'][0]['type']) self.assertEqual('01. Sort', student2['Scores'][0]['type'])
...@@ -113,3 +118,25 @@ class ExportJSONTest(TestCase): ...@@ -113,3 +118,25 @@ class ExportJSONTest(TestCase):
self.assertEqual(0, student1['Scores'][1]['score']) self.assertEqual(0, student1['Scores'][1]['score'])
self.assertEqual(0, student2['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)
...@@ -3,7 +3,7 @@ from rest_framework.test import APIClient, APITestCase ...@@ -3,7 +3,7 @@ from rest_framework.test import APIClient, APITestCase
from core import models from core import models
from core.models import (Submission, SubmissionSubscription, SubmissionType, from core.models import (Submission, SubmissionSubscription, SubmissionType,
SubscriptionEnded, SubscriptionTemporarilyEnded) SubscriptionEnded, SubscriptionTemporarilyEnded, TutorSubmissionAssignment)
from util.factories import GradyUserFactory, make_test_data from util.factories import GradyUserFactory, make_test_data
...@@ -165,13 +165,15 @@ class TestApiEndpoints(APITestCase): ...@@ -165,13 +165,15 @@ class TestApiEndpoints(APITestCase):
]} ]}
) )
def setUp(self):
self.client = APIClient()
def test_ramaining_submissions_for_student(self): def test_ramaining_submissions_for_student(self):
client = APIClient() self.client.force_authenticate(user=self.data['reviewers'][0])
client.force_authenticate(user=self.data['reviewers'][0])
student = self.data['students'][0] student = self.data['students'][0]
response = client.post( response = self.client.post(
'/api/subscription/', { '/api/subscription/', {
'query_type': 'student', 'query_type': 'student',
'query_key': student.student.student_id, 'query_key': student.student.student_id,
...@@ -183,55 +185,50 @@ class TestApiEndpoints(APITestCase): ...@@ -183,55 +185,50 @@ class TestApiEndpoints(APITestCase):
self.assertEqual(0, response.data['available']) self.assertEqual(0, response.data['available'])
def test_remaining_submissions(self): def test_remaining_submissions(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
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']) self.assertEqual(3, response.data['remaining'])
def test_available_submissions_in_stage(self): def test_available_submissions_in_stage(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
client.force_authenticate(user=self.data['tutors'][0])
response = client.post('/api/subscription/', response = self.client.post('/api/subscription/',
{'query_type': 'random', {'query_type': 'random',
'feedback_stage': 'feedback-validation'}) 'feedback_stage': 'feedback-validation'})
self.assertEqual(0, response.data['available']) self.assertEqual(0, response.data['available'])
def test_can_create_a_subscription(self): def test_can_create_a_subscription(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
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) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_create_subscription_and_get_one_assignment(self): def test_create_subscription_and_get_one_assignment(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
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']) self.assertEqual('tutor01', response.data['owner'])
def test_subscription_has_next_and_current_assignment(self): def test_subscription_has_next_and_current_assignment(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
client.force_authenticate(user=self.data['tutors'][0])
response_subscription_create = client.post( response_subscription_create = self.client.post(
'/api/subscription/', {'query_type': 'random'}) '/api/subscription/', {'query_type': 'random'})
subscription_pk = response_subscription_create.data['pk'] subscription_pk = response_subscription_create.data['pk']
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/', { f'/api/assignment/', {
'subscription': subscription_pk 'subscription': subscription_pk
}) })
assignment_pk = response_assignment.data['pk'] assignment_pk = response_assignment.data['pk']
response_subscription = client.get( response_subscription = self.client.get(
f'/api/subscription/{subscription_pk}/') f'/api/subscription/{subscription_pk}/')
self.assertEqual(1, len(response_subscription.data['assignments'])) self.assertEqual(1, len(response_subscription.data['assignments']))
...@@ -239,23 +236,22 @@ class TestApiEndpoints(APITestCase): ...@@ -239,23 +236,22 @@ class TestApiEndpoints(APITestCase):
response_subscription.data['assignments'][0]['pk']) response_subscription.data['assignments'][0]['pk'])
subscription_pk = response_subscription.data['pk'] subscription_pk = response_subscription.data['pk']
response_next = client.post( response_next = self.client.post(
f'/api/assignment/', { f'/api/assignment/', {
'subscription': subscription_pk 'subscription': subscription_pk
}) })
response_detail_subs = \ 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.assertEqual(2, len(response_detail_subs.data['assignments']))
self.assertNotEqual(assignment_pk, response_next.data['pk']) self.assertNotEqual(assignment_pk, response_next.data['pk'])
def test_subscription_can_assign_to_student(self): def test_subscription_can_assign_to_student(self):
client = APIClient() self.client.force_authenticate(user=self.data['reviewers'][0])
client.force_authenticate(user=self.data['reviewers'][0])
student = self.data['students'][0] student = self.data['students'][0]
response = client.post( response = self.client.post(
'/api/subscription/', { '/api/subscription/', {
'query_type': 'student', 'query_type': 'student',
'query_key': student.student.student_id, 'query_key': student.student.student_id,
...@@ -266,24 +262,23 @@ class TestApiEndpoints(APITestCase): ...@@ -266,24 +262,23 @@ class TestApiEndpoints(APITestCase):
self.assertEqual(1, len(assignments)) self.assertEqual(1, len(assignments))
def test_two_tutors_cant_have_assignments_for_same_submission(self): def test_two_tutors_cant_have_assignments_for_same_submission(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
client.force_authenticate(user=self.data['tutors'][0])
subscription = client.post('/api/subscription/', subscription = self.client.post('/api/subscription/',
{'query_type': 'random'}).data {'query_type': 'random'}).data
assignment_fst_tutor = client.post( assignment_fst_tutor = self.client.post(
'/api/assignment/', { '/api/assignment/', {
'subscription': subscription['pk'] 'subscription': subscription['pk']
} }
).data ).data
client.force_authenticate(user=self.data['tutors'][1]) self.client.force_authenticate(user=self.data['tutors'][1])
subscription = client.post('/api/subscription/', subscription = self.client.post('/api/subscription/',
{'query_type': 'random'}).data {'query_type': 'random'}).data
assignment_snd_tutor = client.post( assignment_snd_tutor = self.client.post(
'/api/assignment/', { '/api/assignment/', {
'subscription': subscription['pk'] 'subscription': subscription['pk']
} }
...@@ -292,12 +287,63 @@ class TestApiEndpoints(APITestCase): ...@@ -292,12 +287,63 @@ class TestApiEndpoints(APITestCase):
self.assertNotEqual(assignment_fst_tutor['submission']['pk'], self.assertNotEqual(assignment_fst_tutor['submission']['pk'],
assignment_snd_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): def test_all_stages_of_the_subscription(self):
client = APIClient() self.client.force_authenticate(user=self.data['tutors'][0])
client.force_authenticate(user=self.data['tutors'][0])
# The tutor corrects something # The tutor corrects something
response = client.post( response = self.client.post(
'/api/subscription/', { '/api/subscription/', {
'query_type': 'random', 'query_type': 'random',
'feedback_stage': 'feedback-creation' 'feedback_stage': 'feedback-creation'
...@@ -306,11 +352,12 @@ class TestApiEndpoints(APITestCase): ...@@ -306,11 +352,12 @@ class TestApiEndpoints(APITestCase):
self.assertEqual(status.HTTP_201_CREATED, response.status_code) self.assertEqual(status.HTTP_201_CREATED, response.status_code)
subscription_pk = response.data['pk'] subscription_pk = response.data['pk']
response = client.post( response = self.client.post(
f'/api/assignment/', { f'/api/assignment/', {
'subscription': subscription_pk 'subscription': subscription_pk
}) })
response = client.post( self.assertEqual(status.HTTP_201_CREATED, response.status_code)
response = self.client.post(
f'/api/feedback/', { f'/api/feedback/', {
"score": 23, "score": 23,
"of_submission": response.data['submission']['pk'], "of_submission": response.data['submission']['pk'],
...@@ -323,9 +370,9 @@ class TestApiEndpoints(APITestCase): ...@@ -323,9 +370,9 @@ class TestApiEndpoints(APITestCase):
self.assertEqual(status.HTTP_201_CREATED, response.status_code) self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# some other tutor reviews it # 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/', { '/api/subscription/', {
'query_type': 'random', 'query_type': 'random',
'feedback_stage': 'feedback-validation' 'feedback_stage': 'feedback-validation'
...@@ -334,7 +381,7 @@ class TestApiEndpoints(APITestCase): ...@@ -334,7 +381,7 @@ class TestApiEndpoints(APITestCase):
self.assertEqual(status.HTTP_201_CREATED, response.status_code) self.assertEqual(status.HTTP_201_CREATED, response.status_code)
subscription_pk = response.data['pk'] subscription_pk = response.data['pk']
response = client.post( response = self.client.post(
'/api/assignment/', { '/api/assignment/', {
'subscription': subscription_pk 'subscription': subscription_pk
}) })
...@@ -351,7 +398,7 @@ class TestApiEndpoints(APITestCase): ...@@ -351,7 +398,7 @@ class TestApiEndpoints(APITestCase):
assignment = models.TutorSubmissionAssignment.objects.get( assignment = models.TutorSubmissionAssignment.objects.get(
pk=response.data['pk']) pk=response.data['pk'])
self.assertFalse(assignment.is_done) self.assertFalse(assignment.is_done)
response = client.patch( response = self.client.patch(
'/api/feedback/%s/' % submission_id_in_response, { '/api/feedback/%s/' % submission_id_in_response, {
"score": 20, "score": 20,
"is_final": True, "is_final": True,
......
...@@ -2,7 +2,7 @@ import logging ...@@ -2,7 +2,7 @@ import logging
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework import mixins, status, viewsets 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 rest_framework.response import Response
from core import models, permissions, serializers from core import models, permissions, serializers
...@@ -81,13 +81,6 @@ class AssignmentApiViewSet( ...@@ -81,13 +81,6 @@ class AssignmentApiViewSet(
else: else:
return self.queryset.filter(subscription__owner=self.request.user) 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): def _fetch_assignment(self, serializer):
try: try:
serializer.save() serializer.save()
...@@ -102,9 +95,12 @@ class AssignmentApiViewSet( ...@@ -102,9 +95,12 @@ class AssignmentApiViewSet(
status=status.HTTP_403_FORBIDDEN) status=status.HTTP_403_FORBIDDEN)
return Response(serializer.data, status=status.HTTP_201_CREATED) 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']) @action(detail=False, permission_classes=(IsReviewer,), methods=['get', 'delete'])
def active(self, request): def active(self, request):
print(request.method)
if request.method == 'GET': if request.method == 'GET':
queryset = self.get_queryset().filter(is_done=False) queryset = self.get_queryset().filter(is_done=False)
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
...@@ -113,6 +109,7 @@ class AssignmentApiViewSet( ...@@ -113,6 +109,7 @@ class AssignmentApiViewSet(
self.get_queryset().filter(is_done=False).delete() self.get_queryset().filter(is_done=False).delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@permission_classes((IsTutorOrReviewer,))
def destroy(self, request, pk=None): def destroy(self, request, pk=None):
""" Stop working on the assignment before it is finished """ """ Stop working on the assignment before it is finished """
instance = self.get_object() instance = self.get_object()
...@@ -124,6 +121,7 @@ class AssignmentApiViewSet( ...@@ -124,6 +121,7 @@ class AssignmentApiViewSet(
instance.delete() instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@permission_classes((IsTutorOrReviewer,))
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
context = self.get_serializer_context() context = self.get_serializer_context()
serializer = AssignmentDetailSerializer(data=request.data, serializer = AssignmentDetailSerializer(data=request.data,
...@@ -131,6 +129,7 @@ class AssignmentApiViewSet( ...@@ -131,6 +129,7 @@ class AssignmentApiViewSet(
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
return self._fetch_assignment(serializer) return self._fetch_assignment(serializer)
@permission_classes((IsTutorOrReviewer,))
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
assignment = self.get_object() assignment = self.get_object()
if assignment.subscription.owner != request.user: if assignment.subscription.owner != request.user:
......
...@@ -42,7 +42,6 @@ INSTALLED_APPS = [ ...@@ -42,7 +42,6 @@ INSTALLED_APPS = [
'django_extensions', 'django_extensions',
'rest_framework', 'rest_framework',
'corsheaders', 'corsheaders',
'drf_dynamic_fields',
'drf_yasg', 'drf_yasg',
'core', 'core',
] ]
......
...@@ -4,7 +4,6 @@ djangorestframework-jwt~=1.11.0 ...@@ -4,7 +4,6 @@ djangorestframework-jwt~=1.11.0
djangorestframework~=3.8 djangorestframework~=3.8
git+https://github.com/robinhundt/djangorestframework-camel-case git+https://github.com/robinhundt/djangorestframework-camel-case
Django~=2.1 Django~=2.1
drf-dynamic-fields~=0.3.0
drf-yasg drf-yasg
gevent~=1.3.2 gevent~=1.3.2
gunicorn~=19.7.0 gunicorn~=19.7.0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment