diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 948b3e1cc4..1fb4d71d9b 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -12,6 +12,8 @@ from rest_framework import status from django.conf import settings from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory +from course_modes.models import CourseMode +from util.models import RateLimitConfiguration from util.testing import UrlResetMixin from enrollment import api from enrollment.errors import CourseEnrollmentError @@ -37,6 +39,11 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase): def setUp(self): """ Create a course and user, then log in. """ super(EnrollmentTest, self).setUp() + + rate_limit_config = RateLimitConfiguration.current() + rate_limit_config.enabled = False + rate_limit_config.save() + self.course = CourseFactory.create() self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD) self.other_user = UserFactory.create() @@ -276,6 +283,35 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase): self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("No course ", resp.content) + def test_enrollment_throttle_for_user(self): + """Make sure a user requests do not exceed the maximum number of requests""" + rate_limit_config = RateLimitConfiguration.current() + rate_limit_config.enabled = True + rate_limit_config.save() + CourseModeFactory.create( + course_id=self.course.id, + mode_slug=CourseMode.HONOR, + mode_display_name=CourseMode.HONOR, + ) + + for attempt in range(0, 50): + expected_status = status.HTTP_429_TOO_MANY_REQUESTS if attempt >= 40 else status.HTTP_200_OK + self._create_enrollment(expected_status=expected_status) + + def test_enrollment_throttle_for_service(self): + """Make sure a service can call the enrollment API as many times as needed. """ + rate_limit_config = RateLimitConfiguration.current() + rate_limit_config.enabled = True + rate_limit_config.save() + CourseModeFactory.create( + course_id=self.course.id, + mode_slug=CourseMode.HONOR, + mode_display_name=CourseMode.HONOR, + ) + + for attempt in range(0, 50): + self._create_enrollment(as_server=True) + def _create_enrollment(self, course_id=None, username=None, expected_status=status.HTTP_200_OK, email_opt_in=None, as_server=False): """Enroll in the course and verify the URL we are sent to. """ course_id = unicode(self.course.id) if course_id is None else course_id diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index d8136aca0b..59057a413e 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -24,12 +24,6 @@ from enrollment.errors import ( ) -class EnrollmentUserThrottle(UserRateThrottle): - """Limit the number of requests users can make to the enrollment API.""" - # TODO Limit significantly after performance testing. # pylint: disable=fixme - rate = '50/second' - - class ApiKeyPermissionMixIn(object): """ This mixin is used to provide a convenience function for doing individual permission checks @@ -49,6 +43,14 @@ class ApiKeyPermissionMixIn(object): return ApiKeyHeaderPermission().has_permission(request, self) +class EnrollmentUserThrottle(UserRateThrottle, ApiKeyPermissionMixIn): + """Limit the number of requests users can make to the enrollment API.""" + rate = '40/minute' + + def allow_request(self, request, view): + return self.has_api_key_permissions(request) or super(EnrollmentUserThrottle, self).allow_request(request, view) + + @can_disable_rate_limit class EnrollmentView(APIView, ApiKeyPermissionMixIn): """