Merge pull request #27609 from edx/bseverino/integrity-signature-post
[MST-782] POST endpoint for integrity signature
This commit is contained in:
20
openedx/core/djangoapps/agreements/serializers.py
Normal file
20
openedx/core/djangoapps/agreements/serializers.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Serializers for the Agreements app
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
|
||||
from openedx.core.djangoapps.agreements.models import IntegritySignature
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
|
||||
|
||||
class IntegritySignatureSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for the IntegritySignature model
|
||||
"""
|
||||
username = serializers.CharField(source='user.username')
|
||||
course_id = CourseKeyField(source='course_key')
|
||||
created_at = serializers.DateTimeField(source='created')
|
||||
|
||||
class Meta:
|
||||
model = IntegritySignature()
|
||||
fields = ('username', 'course_id', 'created_at')
|
||||
@@ -1,20 +1,26 @@
|
||||
"""
|
||||
Tests for agreements views
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.urls import reverse
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from freezegun import freeze_time
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory, AdminFactory
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from openedx.core.djangoapps.agreements.api import (
|
||||
create_integrity_signature,
|
||||
get_integrity_signatures_for_course,
|
||||
)
|
||||
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from ..api import create_integrity_signature
|
||||
from ..toggles import ENABLE_INTEGRITY_SIGNATURE
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
|
||||
@@ -161,3 +167,54 @@ class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase):
|
||||
)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_post_integrity_signature(self):
|
||||
response = self.client.post(
|
||||
reverse('integrity_signature', kwargs={'course_id': self.course_id})
|
||||
)
|
||||
self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id)
|
||||
|
||||
# Check that the course has a signature created
|
||||
signatures = get_integrity_signatures_for_course(self.course_id)
|
||||
self.assertEqual(len(signatures), 1)
|
||||
self.assertEqual(signatures[0].user.username, self.USERNAME)
|
||||
|
||||
def test_post_duplicate_integrity_signature(self):
|
||||
# Create a signature
|
||||
original_response = self.client.post(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
+ '?username={}'.format(self.other_user.username)
|
||||
)
|
||||
|
||||
# Attempt to create a new signature in the future
|
||||
with freeze_time(datetime.now() + timedelta(days=1)):
|
||||
new_response = self.client.post(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
)
|
||||
|
||||
# The created_at field in the response should equal the original time created
|
||||
self.assertEqual(
|
||||
original_response.data['created_at'],
|
||||
new_response.data['created_at'],
|
||||
)
|
||||
|
||||
# The course should not have a second signature
|
||||
signatures = get_integrity_signatures_for_course(self.course_id)
|
||||
self.assertEqual(len(signatures), 1)
|
||||
self.assertEqual(signatures[0].user.username, self.USERNAME)
|
||||
|
||||
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False)
|
||||
def test_post_integrity_signature_no_waffle_flag(self):
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Views served by the agreements app. """
|
||||
"""
|
||||
Views served by the Agreements app
|
||||
"""
|
||||
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from rest_framework import status
|
||||
@@ -10,9 +12,12 @@ from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
|
||||
from .api import get_integrity_signature
|
||||
from .toggles import is_integrity_signature_enabled
|
||||
from openedx.core.djangoapps.agreements.api import (
|
||||
create_integrity_signature,
|
||||
get_integrity_signature,
|
||||
)
|
||||
from openedx.core.djangoapps.agreements.serializers import IntegritySignatureSerializer
|
||||
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
|
||||
|
||||
|
||||
def is_user_course_or_global_staff(user, course_id):
|
||||
@@ -26,7 +31,7 @@ def is_user_course_or_global_staff(user, course_id):
|
||||
|
||||
class AuthenticatedAPIView(APIView):
|
||||
"""
|
||||
Authenticated API View.
|
||||
Authenticated API View.
|
||||
"""
|
||||
authentication_classes = (SessionAuthentication, JwtAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
@@ -44,6 +49,10 @@ class IntegritySignatureView(AuthenticatedAPIView):
|
||||
** Scenarios **
|
||||
?username=xyz
|
||||
returns an existing signed integrity agreement for the given user and course
|
||||
|
||||
HTTP POST
|
||||
* If an integrity signature does not exist for the user + course, creates one and
|
||||
returns it. If one does exist, returns the existing signature.
|
||||
"""
|
||||
|
||||
def get(self, request, course_id):
|
||||
@@ -85,12 +94,30 @@ class IntegritySignatureView(AuthenticatedAPIView):
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
created_at = str(signature.created)
|
||||
serializer = IntegritySignatureSerializer(signature)
|
||||
return Response(serializer.data)
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'course_id': course_id,
|
||||
'created_at': created_at,
|
||||
}
|
||||
def post(self, request, course_id):
|
||||
"""
|
||||
Create an integrity signature for the requesting user and course. If a signature
|
||||
already exists, returns the existing signature instead of creating a new one.
|
||||
|
||||
return Response(data)
|
||||
/api/agreements/v1/integrity_signature/{course_id}
|
||||
|
||||
Example response:
|
||||
{
|
||||
username: "janedoe",
|
||||
course_id: "org.2/course_2/Run_2",
|
||||
created_at: "2021-04-23T18:25:43.511Z"
|
||||
}
|
||||
"""
|
||||
# check that waffle flag is enabled
|
||||
if not is_integrity_signature_enabled():
|
||||
return Response(
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
username = request.user.username
|
||||
signature = create_integrity_signature(username, course_id)
|
||||
serializer = IntegritySignatureSerializer(signature)
|
||||
return Response(serializer.data)
|
||||
|
||||
Reference in New Issue
Block a user