Merge pull request #27609 from edx/bseverino/integrity-signature-post

[MST-782] POST endpoint for integrity signature
This commit is contained in:
Bianca Severino
2021-05-13 13:07:34 -04:00
committed by GitHub
3 changed files with 120 additions and 16 deletions

View 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')

View File

@@ -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)

View File

@@ -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)