From 6a520c130593264829e4fac60ee0b6274abcd5da Mon Sep 17 00:00:00 2001 From: Alie Langston Date: Mon, 23 Aug 2021 09:52:01 -0400 Subject: [PATCH] feat: add signal receivers for edx_name_affirmation --- CHANGELOG.rst | 4 + edx_name_affirmation/__init__.py | 2 +- edx_name_affirmation/apps.py | 15 + edx_name_affirmation/handlers.py | 186 +++++++++++ edx_name_affirmation/statuses.py | 32 ++ edx_name_affirmation/tests/test_handlers.py | 343 ++++++++++++++++++++ edx_name_affirmation/urls.py | 2 +- requirements/base.txt | 12 +- requirements/ci.txt | 8 +- requirements/constraints.txt | 2 + requirements/dev.txt | 50 +-- requirements/doc.txt | 16 +- requirements/pip-tools.txt | 4 +- requirements/quality.txt | 30 +- requirements/test.in | 1 + requirements/test.txt | 18 +- 16 files changed, 646 insertions(+), 79 deletions(-) create mode 100644 edx_name_affirmation/handlers.py create mode 100644 edx_name_affirmation/tests/test_handlers.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1549c65..a0e3726 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,10 @@ Change Log Unreleased ~~~~~~~~~~ +[0.8.0] - 2021-08-30 +~~~~~~~~~~~~~~~~~~~~ +* Add signal receivers for IDV and proctoring attempts + [0.7.0] - 2021-08-26 ~~~~~~~~~~~~~~~~~~~~ * Add verified_name_enabled and use_verified_name_for_certs to the GET response of VerifiedNameHistoryView. diff --git a/edx_name_affirmation/__init__.py b/edx_name_affirmation/__init__.py index d805a2e..51ce805 100644 --- a/edx_name_affirmation/__init__.py +++ b/edx_name_affirmation/__init__.py @@ -2,6 +2,6 @@ Django app housing name affirmation logic. """ -__version__ = '0.7.0' +__version__ = '0.8.0' default_app_config = 'edx_name_affirmation.apps.EdxNameAffirmationConfig' # pylint: disable=invalid-name diff --git a/edx_name_affirmation/apps.py b/edx_name_affirmation/apps.py index 0616bcc..2d49689 100644 --- a/edx_name_affirmation/apps.py +++ b/edx_name_affirmation/apps.py @@ -19,5 +19,20 @@ class EdxNameAffirmationConfig(AppConfig): 'regex': '^api/', 'relative_path': 'urls', } + }, + 'signals_config': { + 'lms.djangoapp': { + 'relative_path': 'handlers', + 'receivers': [ + { + 'receiver_func_name': 'idv_attempt_handler', + 'signal_path': 'lms.djangoapps.verify_student.signals.idv_update_signal', + }, + { + 'receiver_func_name': 'proctoring_attempt_handler', + 'signal_path': 'edx_proctoring.signals.exam_attempt_status_signal', + } + ], + } } } diff --git a/edx_name_affirmation/handlers.py b/edx_name_affirmation/handlers.py new file mode 100644 index 0000000..638d461 --- /dev/null +++ b/edx_name_affirmation/handlers.py @@ -0,0 +1,186 @@ +# pylint: disable=logging-format-interpolation + +"""" +Signal handlers for IDV and proctoring service signals +""" + +import logging + +from django.contrib.auth import get_user_model + +from edx_name_affirmation.models import VerifiedName +from edx_name_affirmation.statuses import VerifiedNameStatus +from edx_name_affirmation.toggles import is_verified_name_enabled + +User = get_user_model() + +log = logging.getLogger(__name__) + + +def idv_attempt_handler(attempt_id, user_id, status, full_name, profile_name, **kwargs): + """ + Receiver for IDV attempt updates + + Args: + attempt_id(int): ID associated with the IDV attempt + user_id(int): ID associated with the IDV attempt's user + status(str): status in IDV language for the IDV attempt + full_name(str): name to be used as verified name + profile_name(str): user's current profile name + """ + if not is_verified_name_enabled(): + return + + trigger_status = VerifiedNameStatus.trigger_state_change_from_idv(status) + verified_names = VerifiedName.objects.filter(user__id=user_id, verified_name=full_name).order_by('-created') + if verified_names: + # if there are VerifiedName objects, we want to update existing entries + # for each attempt with no attempt id (either proctoring or idv), update attempt id + updated_for_attempt_id = verified_names.filter( + proctored_exam_attempt_id=None, + verification_attempt_id=None + ).update(verification_attempt_id=attempt_id) + + if updated_for_attempt_id: + log.info( + 'Updated VerifiedNames for user={user_id} to verification_attempt_id={attempt_id}'.format( + user_id=user_id, + attempt_id=attempt_id, + ) + ) + + # then for all matching attempt ids, update the status + if trigger_status: + verified_names.filter( + verification_attempt_id=attempt_id, + proctored_exam_attempt_id=None + ).update(status=trigger_status) + + log.info( + 'Updated VerifiedNames for user={user_id} with verification_attempt_id={attempt_id} to ' + 'have status={status}'.format( + user_id=user_id, + attempt_id=attempt_id, + status=trigger_status + ) + ) + else: + # otherwise if there are no entries, we want to create one. + user = User.objects.get(id=user_id) + verified_name = VerifiedName.objects.create( + user=user, + verified_name=full_name, + profile_name=profile_name, + verification_attempt_id=attempt_id, + status=(trigger_status if trigger_status else VerifiedNameStatus.PENDING), + ) + log.error( + 'Created VerifiedName for user={user_id} to have status={status} ' + 'and verification_attempt_id={attempt_id}, because no matching ' + 'attempt_id or verified_name were found.'.format( + user_id=user_id, + attempt_id=attempt_id, + status=verified_name.status + ) + ) + + +def proctoring_attempt_handler( + attempt_id, + user_id, + status, + full_name, + profile_name, + is_practice_exam, + is_proctored, + backend_supports_onboarding, + **kwargs +): + """ + Receiver for proctored exam attempt updates. + + Args: + attempt_id(int): ID associated with the proctored exam attempt + user_id(int): ID associated with the proctored exam attempt's user + status(str): status in proctoring language for the proctored exam attempt + full_name(str): name to be used as verified name + profile_name(str): user's current profile name + is_practice_exam(boolean): if the exam attempt is for a practice exam + is_proctored(boolean): if the exam attempt is for a proctored exam + backend_supports_onboarding(boolean): if the exam attempt is for an exam with a backend that supports onboarding + """ + if not is_verified_name_enabled(): + return + + # We only care about updates from onboarding exams, or from non-practice proctored exams with a backend that + # does not support onboarding. This is because those two event types are guaranteed to contain verification events, + # whereas timed exams and proctored exams with a backend that does support onboarding are not guaranteed + is_onboarding_exam = is_practice_exam and is_proctored and backend_supports_onboarding + reviewable_proctored_exam = is_proctored and not is_practice_exam and not backend_supports_onboarding + if not (is_onboarding_exam or reviewable_proctored_exam): + return + + # check if approved VerifiedName already exists for the user + verified_name = VerifiedName.objects.filter( + user__id=user_id, + status=VerifiedNameStatus.APPROVED + ).order_by('-created').first() + if verified_name: + approved_verified_name = verified_name.verified_name + is_full_name_approved = approved_verified_name == full_name + if not is_full_name_approved: + log.warning( + 'Full name for proctored_exam_attempt_id={attempt_id} is not equal ' + 'to the most recent verified name verified_name_id={name_id}.'.format( + attempt_id=attempt_id, + name_id=verified_name.id + ) + ) + return + + trigger_status = VerifiedNameStatus.trigger_state_change_from_proctoring(status) + + verified_name = VerifiedName.objects.filter( + user__id=user_id, + proctored_exam_attempt_id=attempt_id + ).order_by('-created').first() + if verified_name: + # if a verified name for the given attempt ID exists, update it if the status should trigger a transition + if trigger_status: + verified_name.status = trigger_status + verified_name.save() + log.info( + 'Updated VerifiedName for user={user_id} with proctored_exam_attempt_id={attempt_id} ' + 'to have status={status}'.format( + user_id=user_id, + attempt_id=attempt_id, + status=trigger_status + ) + ) + else: + if full_name and profile_name: + # if they do not already have an approved VerifiedName, create one + user = User.objects.get(id=user_id) + VerifiedName.objects.create( + user=user, + verified_name=full_name, + proctored_exam_attempt_id=attempt_id, + status=(trigger_status if trigger_status else VerifiedNameStatus.PENDING), + profile_name=profile_name + ) + log.info( + 'Created VerifiedName for user={user_id} to have status={status} ' + 'and proctored_exam_attempt_id={attempt_id}'.format( + user_id=user_id, + attempt_id=attempt_id, + status=trigger_status + ) + ) + else: + log.error( + 'Cannot create VerifiedName for user={user_id} for proctored_exam_attempt_id={attempt_id} ' + 'because neither profile name nor full name were provided'.format( + user_id=user_id, + attempt_id=attempt_id, + ) + ) diff --git a/edx_name_affirmation/statuses.py b/edx_name_affirmation/statuses.py index 4ef4b46..14df14c 100644 --- a/edx_name_affirmation/statuses.py +++ b/edx_name_affirmation/statuses.py @@ -31,3 +31,35 @@ class VerifiedNameStatus(str, Enum): SUBMITTED = "submitted" APPROVED = "approved" DENIED = "denied" + + @classmethod + def trigger_state_change_from_idv(cls, idv_status): + """ + Return the translated IDV status if it should trigger a state transition, otherwise return None + """ + # mapping from an idv status (key) to it's associated verified name status (value). We only want to + # include idv statuses that would cause a status transition for a verified name + idv_state_transition_mapping = { + 'created': cls.PENDING, + 'submitted': cls.SUBMITTED, + 'approved': cls.APPROVED, + 'denied': cls.DENIED + } + + return idv_state_transition_mapping.get(idv_status, None) + + @classmethod + def trigger_state_change_from_proctoring(cls, proctoring_status): + """ + Return the translated proctoring status if it should trigger a state transition, otherwise return None + """ + # mapping from an proctoring status (key) to it's associated verified name status (value). We only want to + # include proctoring statuses that would cause a status transition for a verified name + proctoring_state_transition_mapping = { + 'created': cls.PENDING, + 'submitted': cls.SUBMITTED, + 'verified': cls.APPROVED, + 'rejected': cls.DENIED + } + + return proctoring_state_transition_mapping.get(proctoring_status, None) diff --git a/edx_name_affirmation/tests/test_handlers.py b/edx_name_affirmation/tests/test_handlers.py new file mode 100644 index 0000000..ad51522 --- /dev/null +++ b/edx_name_affirmation/tests/test_handlers.py @@ -0,0 +1,343 @@ +""" +Tests for signals.py +""" + +import ddt +from edx_toggles.toggles.testutils import override_waffle_flag +from mock import patch + +from django.contrib.auth import get_user_model +from django.test import TestCase + +from edx_name_affirmation.handlers import idv_attempt_handler, proctoring_attempt_handler +from edx_name_affirmation.models import VerifiedName +from edx_name_affirmation.statuses import VerifiedNameStatus +from edx_name_affirmation.toggles import VERIFIED_NAME_FLAG + +User = get_user_model() + + +@override_waffle_flag(VERIFIED_NAME_FLAG, active=True) +class SignalTestCase(TestCase): + """ + Test case for signals.py + """ + + def setUp(self): # pylint: disable=super-method-not-called + self.user = User(username='tester', email='tester@test.com') + self.user.save() + self.verified_name = 'Jonathan Smith' + self.profile_name = 'Jon Smith' + self.idv_attempt_id = 1111111 + self.proctoring_attempt_id = 2222222 + + +@ddt.ddt +class IDVSignalTests(SignalTestCase): + """ + Test for idv_attempt_handler + """ + + def test_idv_create_verified_name(self): + """ + Test that if no verified name exists for the name or attempt id, create one + """ + idv_attempt_handler( + self.idv_attempt_id, + self.user.id, + 'created', + self.verified_name, + self.profile_name + ) + + # make sure that verifiedname is created with relevant data + verified_name = VerifiedName.objects.get(verification_attempt_id=self.idv_attempt_id) + self.assertEqual(verified_name.status, VerifiedNameStatus.PENDING) + self.assertEqual(verified_name.verification_attempt_id, self.idv_attempt_id) + self.assertEqual(verified_name.verified_name, self.verified_name) + self.assertEqual(verified_name.profile_name, self.profile_name) + + @ddt.data( + ('created', VerifiedNameStatus.PENDING), + ('must_retry', VerifiedNameStatus.PENDING), + ('submitted', VerifiedNameStatus.SUBMITTED), + ('approved', VerifiedNameStatus.APPROVED), + ('denied', VerifiedNameStatus.DENIED) + ) + @ddt.unpack + def test_idv_update_multiple_verified_names(self, idv_status, expected_status): + """ + If a VerifiedName(s) for a user and verified name exist, ensure that it is updated properly + """ + # create multiple VerifiedNames + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + ) + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + ) + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + verification_attempt_id=self.idv_attempt_id + ) + + idv_attempt_handler( + self.idv_attempt_id, + self.user.id, + idv_status, + self.verified_name, + self.profile_name + ) + + # check that the attempt id and status have been updated for all three VerifiedNames + self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 3) + self.assertEqual(len(VerifiedName.objects.filter(status=expected_status)), 3) + + def test_idv_does_not_update_verified_name_by_proctoring(self): + """ + If the idv handler is triggered, ensure that the idv attempt info does not update any verified name + records that have a proctoring attempt id + """ + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + proctored_exam_attempt_id=self.proctoring_attempt_id, + status=VerifiedNameStatus.DENIED + ) + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name + ) + + idv_attempt_handler( + self.idv_attempt_id, + self.user.id, + 'submitted', + self.verified_name, + self.profile_name + ) + + # check that the attempt id and status have only been updated for the record that does not have a proctored + # exam attempt id + self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(status=VerifiedNameStatus.SUBMITTED)), 1) + + @ddt.data( + ('created', VerifiedNameStatus.PENDING), + ('must_retry', VerifiedNameStatus.PENDING), + ('submitted', VerifiedNameStatus.SUBMITTED), + ('approved', VerifiedNameStatus.APPROVED), + ('denied', VerifiedNameStatus.DENIED) + ) + @ddt.unpack + def test_idv_update_one_verified_name(self, idv_status, expected_status): + """ + If a VerifiedName(s) for a user and verified name exist, ensure that it is updated properly + """ + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + verification_attempt_id=self.idv_attempt_id + ) + + idv_attempt_handler( + self.idv_attempt_id, + self.user.id, + idv_status, + self.verified_name, + self.profile_name + ) + + # check that the attempt id and status have been updated for all three VerifiedNames + self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(status=expected_status)), 1) + + @override_waffle_flag(VERIFIED_NAME_FLAG, active=False) + def test_idv_handler_with_flag_disabled(self): + """ + Test that no attempt is created if the waffle flag is disabled + """ + + idv_attempt_handler( + self.idv_attempt_id, + self.user.id, + 'submitted', + self.verified_name, + self.profile_name + ) + + self.assertEqual(len(VerifiedName.objects.filter()), 0) + + +@ddt.ddt +class ProctoringSignalTests(SignalTestCase): + """ + Test for proctoring_attempt_handler + """ + + @ddt.data( + ('created', VerifiedNameStatus.PENDING), + ('started', VerifiedNameStatus.PENDING), + ('submitted', VerifiedNameStatus.SUBMITTED), + ('verified', VerifiedNameStatus.APPROVED), + ('rejected', VerifiedNameStatus.DENIED) + ) + @ddt.unpack + def test_proctoring_update_status_for_attempt_id(self, proctoring_status, expected_status): + """ + If a verified name with an attempt ID already exists, update the VerifiedName status + """ + # create a verified name with an attempt id + verified_name = VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + proctored_exam_attempt_id=self.proctoring_attempt_id, + ) + object_id = verified_name.id + + proctoring_attempt_handler( + self.proctoring_attempt_id, + self.user.id, + proctoring_status, + self.verified_name, + self.profile_name, + True, + True, + True + ) + # make sure that status on verified name is correct + verified_name_query = VerifiedName.objects.filter(id=object_id) + self.assertEqual(len(verified_name_query), 1) + verified_name = verified_name_query.first() + self.assertEqual(verified_name.status, expected_status) + + def test_proctoring_create_verified_name(self): + """ + Test that if no verified name exists for the name or attempt id, create one + """ + proctoring_attempt_handler( + self.proctoring_attempt_id, + self.user.id, + 'created', + self.verified_name, + self.profile_name, + True, + True, + True + ) + + # make sure that verifiedname is created with relevant data + verified_name_query = VerifiedName.objects.filter(proctored_exam_attempt_id=self.proctoring_attempt_id) + self.assertEqual(len(verified_name_query), 1) + verified_name = verified_name_query.first() + self.assertEqual(verified_name.status, VerifiedNameStatus.PENDING) + + # test for log + + @ddt.data( + (None, None, True, True, True), + ('John', 'John', False, False, False), + ('John', 'John', False, True, True), + ('John', 'John', True, True, False) + ) + @ddt.unpack + def test_proctoring_does_not_create_name( + self, + verified_name, + profile_name, + is_practice, + is_proctored, + backend_supports_onboarding + ): + """ + Test that if we receive a signal for an attempt id that we do not yet have a verified name for, + we do not create a verified name under certain conditions. + """ + + # test for signal that does not contain verified or profile name + proctoring_attempt_handler( + self.proctoring_attempt_id, + self.user.id, + 'created', + verified_name, + profile_name, + is_practice, + is_proctored, + backend_supports_onboarding + ) + + self.assertEqual(len(VerifiedName.objects.filter()), 0) + + @ddt.data( + True, + False + ) + @patch('logging.Logger.warning') + def test_proctoring_log_with_existing_approved_verified_name(self, should_names_differ, mock_logger): + """ + Test that we log a warning when we receive a proctoring signal that has a different full_name + than the existing approved verified name + """ + verified_name = VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + proctored_exam_attempt_id=self.proctoring_attempt_id, + status=VerifiedNameStatus.APPROVED + ) + + proctoring_attempt_handler( + self.proctoring_attempt_id, + self.user.id, + 'created', + ('John' if should_names_differ else self.verified_name), + ('John' if should_names_differ else self.profile_name), + True, + True, + True + ) + + log_str = ( + 'Full name for proctored_exam_attempt_id={attempt_id} is not equal to the most recent verified ' + 'name verified_name_id={verified_name_id}.' + ).format( + attempt_id=self.proctoring_attempt_id, + verified_name_id=verified_name.id + ) + + self.assertEqual(len(VerifiedName.objects.filter()), 1) + if should_names_differ: + mock_logger.assert_called_with(log_str) + else: + # check that log is not called if the names do not differ + with self.assertRaises(AssertionError): + mock_logger.assert_called_with(log_str) + + @override_waffle_flag(VERIFIED_NAME_FLAG, active=False) + def test_proctoring_handler_with_flag_disabled(self): + """ + Test that no attempt is created if the waffle flag is disabled + """ + + proctoring_attempt_handler( + self.proctoring_attempt_id, + self.user.id, + 'created', + self.verified_name, + self.profile_name, + True, + True, + True + ) + + self.assertEqual(len(VerifiedName.objects.filter()), 0) diff --git a/edx_name_affirmation/urls.py b/edx_name_affirmation/urls.py index 9463b50..7bb5c78 100644 --- a/edx_name_affirmation/urls.py +++ b/edx_name_affirmation/urls.py @@ -5,7 +5,7 @@ from edx_name_affirmation import views -app_name = u'edx_name_affirmation' +app_name = 'edx_name_affirmation' urlpatterns = [ url( diff --git a/requirements/base.txt b/requirements/base.txt index c532d95..2f3ac07 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -14,7 +14,7 @@ click==8.0.1 # via code-annotations code-annotations==1.2.0 # via edx-toggles -cryptography==3.4.7 +cryptography==3.4.8 # via pyjwt django==2.2.24 # via @@ -53,13 +53,15 @@ drf-jwt==1.19.0 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # edx-drf-extensions -edx-django-utils==4.2.0 +edx-django-utils==4.3.0 # via # django-config-models # edx-drf-extensions # edx-toggles edx-drf-extensions==6.6.0 - # via -r requirements/base.in + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.in edx-opaque-keys==2.2.2 # via edx-drf-extensions edx-toggles==4.2.0 @@ -72,7 +74,7 @@ jinja2==3.0.1 # via code-annotations markupsafe==2.0.1 # via jinja2 -newrelic==6.6.0.162 +newrelic==6.8.1.164 # via edx-django-utils pbr==5.6.0 # via stevedore @@ -113,7 +115,7 @@ six==1.16.0 # python-dateutil sqlparse==0.4.1 # via django -stevedore==3.3.0 +stevedore==3.4.0 # via # code-annotations # edx-django-utils diff --git a/requirements/ci.txt b/requirements/ci.txt index ac0a67e..e133293 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -27,7 +27,9 @@ packaging==21.0 platformdirs==2.2.0 # via virtualenv pluggy==0.13.1 - # via tox + # via + # -c requirements/constraints.txt + # tox py==1.10.0 # via tox pyparsing==2.4.7 @@ -40,7 +42,7 @@ six==1.16.0 # virtualenv toml==0.10.2 # via tox -tox==3.24.1 +tox==3.24.3 # via # -r requirements/ci.in # tox-battery @@ -48,5 +50,5 @@ tox-battery==0.6.1 # via -r requirements/ci.in urllib3==1.26.6 # via requests -virtualenv==20.7.0 +virtualenv==20.7.2 # via tox diff --git a/requirements/constraints.txt b/requirements/constraints.txt index d91704b..22fb193 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -10,3 +10,5 @@ # Common constraints for edx repos -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + +pluggy<=0.13.1 # diff-cover, tox, and pytest are creating version conflicts for pluggy, pinning to resolve diff --git a/requirements/dev.txt b/requirements/dev.txt index 46567f6..91c1b66 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,7 +4,7 @@ # # make upgrade # -astroid==2.6.6 +astroid==2.7.2 # via # -r requirements/quality.txt # pylint @@ -13,7 +13,7 @@ backports.entry-points-selectable==1.1.0 # via # -r requirements/ci.txt # virtualenv -bleach==4.0.0 +bleach==4.1.0 # via # -r requirements/quality.txt # readme-renderer @@ -22,10 +22,6 @@ certifi==2021.5.30 # -r requirements/ci.txt # -r requirements/quality.txt # requests -cffi==1.14.6 - # via - # -r requirements/quality.txt - # cryptography chardet==4.0.0 # via diff-cover charset-normalizer==2.0.4 @@ -59,11 +55,7 @@ coverage==5.5 # via # -r requirements/ci.txt # codecov -cryptography==3.4.7 - # via - # -r requirements/quality.txt - # secretstorage -diff-cover==6.2.1 +diff-cover==6.3.4 # via -r requirements/dev.in distlib==0.3.2 # via @@ -94,7 +86,7 @@ idna==3.2 # -r requirements/ci.txt # -r requirements/quality.txt # requests -importlib-metadata==4.6.3 +importlib-metadata==4.7.1 # via # -r requirements/quality.txt # keyring @@ -105,11 +97,6 @@ isort==5.9.3 # via # -r requirements/quality.txt # pylint -jeepney==0.7.1 - # via - # -r requirements/quality.txt - # keyring - # secretstorage jinja2==3.0.1 # via # -r requirements/quality.txt @@ -118,7 +105,7 @@ jinja2==3.0.1 # jinja2-pluralize jinja2-pluralize==0.3.0 # via diff-cover -keyring==23.0.1 +keyring==23.1.0 # via # -r requirements/quality.txt # twine @@ -159,9 +146,12 @@ pkginfo==1.7.1 platformdirs==2.2.0 # via # -r requirements/ci.txt + # -r requirements/quality.txt + # pylint # virtualenv pluggy==0.13.1 # via + # -c requirements/constraints.txt # -r requirements/ci.txt # diff-cover # tox @@ -173,18 +163,14 @@ py==1.10.0 # tox pycodestyle==2.7.0 # via -r requirements/quality.txt -pycparser==2.20 - # via - # -r requirements/quality.txt - # cffi pydocstyle==6.1.1 # via -r requirements/quality.txt -pygments==2.9.0 +pygments==2.10.0 # via # -r requirements/quality.txt # diff-cover # readme-renderer -pylint==2.9.6 +pylint==2.10.2 # via # -r requirements/quality.txt # edx-lint @@ -243,10 +229,6 @@ rfc3986==1.5.0 # twine rstcheck==3.3.1 # via -r requirements/quality.txt -secretstorage==3.3.1 - # via - # -r requirements/quality.txt - # keyring six==1.16.0 # via # -r requirements/ci.txt @@ -265,7 +247,7 @@ sqlparse==0.4.1 # via # -r requirements/quality.txt # django -stevedore==3.3.0 +stevedore==3.4.0 # via # -r requirements/quality.txt # code-annotations @@ -279,11 +261,11 @@ toml==0.10.2 # -r requirements/quality.txt # pylint # tox -tomli==1.2.0 +tomli==1.2.1 # via # -r requirements/pip-tools.txt # pep517 -tox==3.24.1 +tox==3.24.3 # via # -r requirements/ci.txt # tox-battery @@ -291,7 +273,7 @@ tox-battery==0.6.1 # via # -r requirements/ci.txt # -r requirements/dev.in -tqdm==4.62.0 +tqdm==4.62.2 # via # -r requirements/quality.txt # twine @@ -302,7 +284,7 @@ urllib3==1.26.6 # -r requirements/ci.txt # -r requirements/quality.txt # requests -virtualenv==20.7.0 +virtualenv==20.7.2 # via # -r requirements/ci.txt # tox @@ -310,7 +292,7 @@ webencodings==0.5.1 # via # -r requirements/quality.txt # bleach -wheel==0.36.2 +wheel==0.37.0 # via # -r requirements/pip-tools.txt # pip-tools diff --git a/requirements/doc.txt b/requirements/doc.txt index 2d5079c..d22f9b6 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -8,7 +8,7 @@ alabaster==0.7.12 # via sphinx babel==2.9.1 # via sphinx -bleach==4.0.0 +bleach==4.1.0 # via readme-renderer certifi==2021.5.30 # via requests @@ -20,7 +20,7 @@ click==8.0.1 # via code-annotations code-annotations==1.2.0 # via edx-toggles -cryptography==3.4.7 +cryptography==3.4.8 # via pyjwt django==2.2.24 # via @@ -67,13 +67,15 @@ drf-jwt==1.19.0 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # edx-drf-extensions -edx-django-utils==4.2.0 +edx-django-utils==4.3.0 # via # django-config-models # edx-drf-extensions # edx-toggles edx-drf-extensions==6.6.0 - # via -r requirements/base.in + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.in edx-opaque-keys==2.2.2 # via edx-drf-extensions edx-sphinx-theme==3.0.0 @@ -92,7 +94,7 @@ jinja2==3.0.1 # sphinx markupsafe==2.0.1 # via jinja2 -newrelic==6.6.0.162 +newrelic==6.8.1.164 # via edx-django-utils packaging==21.0 # via @@ -108,7 +110,7 @@ pycparser==2.20 # via cffi pycryptodomex==3.10.1 # via pyjwkest -pygments==2.9.0 +pygments==2.10.0 # via # doc8 # readme-renderer @@ -178,7 +180,7 @@ sphinxcontrib-serializinghtml==1.1.5 # via sphinx sqlparse==0.4.1 # via django -stevedore==3.3.0 +stevedore==3.4.0 # via # code-annotations # doc8 diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index aca9c26..b0269a8 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -10,9 +10,9 @@ pep517==0.11.0 # via pip-tools pip-tools==6.2.0 # via -r requirements/pip-tools.in -tomli==1.2.0 +tomli==1.2.1 # via pep517 -wheel==0.36.2 +wheel==0.37.0 # via # -r requirements/pip-tools.in # pip-tools diff --git a/requirements/quality.txt b/requirements/quality.txt index 6b57b26..7a2ca2f 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -4,16 +4,14 @@ # # make upgrade # -astroid==2.6.6 +astroid==2.7.2 # via # pylint # pylint-celery -bleach==4.0.0 +bleach==4.1.0 # via readme-renderer certifi==2021.5.30 # via requests -cffi==1.14.6 - # via cryptography charset-normalizer==2.0.4 # via requests click==8.0.1 @@ -27,8 +25,6 @@ code-annotations==1.2.0 # via edx-lint colorama==0.4.4 # via twine -cryptography==3.4.7 - # via secretstorage django==2.2.24 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt @@ -42,7 +38,7 @@ edx-lint==5.0.0 # via -r requirements/quality.in idna==3.2 # via requests -importlib-metadata==4.6.3 +importlib-metadata==4.7.1 # via # keyring # twine @@ -50,13 +46,9 @@ isort==5.9.3 # via # -r requirements/quality.in # pylint -jeepney==0.7.1 - # via - # keyring - # secretstorage jinja2==3.0.1 # via code-annotations -keyring==23.0.1 +keyring==23.1.0 # via twine lazy-object-proxy==1.6.0 # via astroid @@ -70,15 +62,15 @@ pbr==5.6.0 # via stevedore pkginfo==1.7.1 # via twine +platformdirs==2.2.0 + # via pylint pycodestyle==2.7.0 # via -r requirements/quality.in -pycparser==2.20 - # via cffi pydocstyle==6.1.1 # via -r requirements/quality.in -pygments==2.9.0 +pygments==2.10.0 # via readme-renderer -pylint==2.9.6 +pylint==2.10.2 # via # edx-lint # pylint-celery @@ -112,8 +104,6 @@ rfc3986==1.5.0 # via twine rstcheck==3.3.1 # via -r requirements/quality.in -secretstorage==3.3.1 - # via keyring six==1.16.0 # via # bleach @@ -123,13 +113,13 @@ snowballstemmer==2.1.0 # via pydocstyle sqlparse==0.4.1 # via django -stevedore==3.3.0 +stevedore==3.4.0 # via code-annotations text-unidecode==1.3 # via python-slugify toml==0.10.2 # via pylint -tqdm==4.62.0 +tqdm==4.62.2 # via twine twine==3.4.2 # via -r requirements/quality.in diff --git a/requirements/test.in b/requirements/test.in index 8ea122f..8e7101e 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -4,6 +4,7 @@ -r base.txt # Core dependencies for this package ddt +mock pytest-cov # pytest extension for code coverage statistics pytest-django # pytest extension for better Django support code-annotations # provides commands used by the pii_check make target. diff --git a/requirements/test.txt b/requirements/test.txt index 4edab55..3086e45 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -29,7 +29,7 @@ code-annotations==1.2.0 # edx-toggles coverage==5.5 # via pytest-cov -cryptography==3.4.7 +cryptography==3.4.8 # via # -r requirements/base.txt # pyjwt @@ -74,14 +74,16 @@ drf-jwt==1.19.0 # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt # edx-drf-extensions -edx-django-utils==4.2.0 +edx-django-utils==4.3.0 # via # -r requirements/base.txt # django-config-models # edx-drf-extensions # edx-toggles edx-drf-extensions==6.6.0 - # via -r requirements/base.txt + # via + # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt + # -r requirements/base.txt edx-opaque-keys==2.2.2 # via # -r requirements/base.txt @@ -106,7 +108,9 @@ markupsafe==2.0.1 # via # -r requirements/base.txt # jinja2 -newrelic==6.6.0.162 +mock==4.0.3 + # via -r requirements/test.in +newrelic==6.8.1.164 # via # -r requirements/base.txt # edx-django-utils @@ -117,7 +121,9 @@ pbr==5.6.0 # -r requirements/base.txt # stevedore pluggy==0.13.1 - # via pytest + # via + # -c requirements/constraints.txt + # pytest psutil==5.8.0 # via # -r requirements/base.txt @@ -194,7 +200,7 @@ sqlparse==0.4.1 # via # -r requirements/base.txt # django -stevedore==3.3.0 +stevedore==3.4.0 # via # -r requirements/base.txt # code-annotations