Merge pull request #29155 from edx/mroytman/MST-1130-management-command-cleanup

feat: Add Management Command to Re-Emit SoftwareSecurePhotoVerification post_save Signal
This commit is contained in:
Michael Roytman
2021-11-01 15:01:49 -04:00
committed by GitHub
2 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
"""
Tests for django admin command `trigger_softwaresecurephotoverifications_post_save_signal`
in the verify_student module
"""
from django.conf import settings
from django.core.management import call_command
from freezegun import freeze_time
from unittest.mock import call, patch, ANY
from common.test.utils import MockS3BotoMixin
from lms.djangoapps.verify_student.tests import TestVerificationBase
from lms.djangoapps.verify_student.tests.test_models import (
FAKE_SETTINGS,
mock_software_secure_post,
)
# Lots of patching to stub in our own settings, and HTTP posting
@patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS)
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
@patch('lms.djangoapps.verify_student.models.requests.post', new=mock_software_secure_post)
class TestTriggerSoftwareSecurePhotoVerificationsPostSaveSignal(MockS3BotoMixin, TestVerificationBase):
"""
Tests for django admin command `trigger_softwaresecurephotoverifications_post_save_signal`
in the verify_student module
"""
def setUp(self):
super().setUp()
with freeze_time("2021-10-30 12:00:00"):
self._create_attempts(4)
with freeze_time("2021-11-01 03:00:00"):
self._create_attempts(4)
def _create_attempts(self, num_attempts):
for _ in range(num_attempts):
self.create_and_submit_attempt_for_user()
@patch('lms.djangoapps.verify_student.signals.idv_update_signal.send')
def test_command(self, send_idv_update_mock):
call_command('trigger_softwaresecurephotoverifications_post_save_signal', start_date_time='2021-10-31 06:00:00')
# The UserFactory instantiates first_name and last_name using a Sequence, which provide integers to
# generate unique values. A Sequence maintains its state across test runs, so the value of a given Sequence,
# and therefore, the value of photo_id_name and full_name, depends on the order that tests using the UserFactory
# run, as the Sequence is not reset after each test suite. This makes asserting on the exact value
# of photo_id_name and full_name, derived from first_name and last_name, difficult.
# Resetting the Sequence with UserFactory.reset_sequence causes failures in other tests that rely on
# these values. In any case, we primarily care about the signal being called with the correct sender,
# attempt_id, and user_id, as the signal is tested elsewhere.
expected_calls = [
call(
sender='idv_update', attempt_id=5, user_id=5, status='must_retry',
photo_id_name=ANY, full_name=ANY
),
call(
sender='idv_update', attempt_id=6, user_id=6, status='must_retry',
photo_id_name=ANY, full_name=ANY
),
call(
sender='idv_update', attempt_id=7, user_id=7, status='must_retry',
photo_id_name=ANY, full_name=ANY
),
call(
sender='idv_update', attempt_id=8, user_id=8, status='must_retry',
photo_id_name=ANY, full_name=ANY
),
]
print(send_idv_update_mock.mock_calls)
self.assertEqual(send_idv_update_mock.call_count, 4)
send_idv_update_mock.assert_has_calls(expected_calls, any_order=True)

View File

@@ -0,0 +1,68 @@
"""
Django admin command to save SoftwareSecurePhotoVerifications given an iterable of
verification_ids, thereby re-emitting the post_save signal.
"""
import datetime
import logging
import time
from django.core.management.base import BaseCommand
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Command to save SoftwareSecurePhotoVerification model instances based on a provided interable of verifiction_ids.
This re-emits the post_save signal.
"""
def add_arguments(self, parser):
parser.add_argument(
'--start_date_time',
action='store',
dest='start_date_time',
type=str,
help='First date time for SoftwareSecurePhotoVerifications to resave; '
'should be formatted as 2020-12-02 00:00:00.'
)
parser.add_argument(
'--batch_size',
action='store',
dest='batch_size',
type=int,
default=300,
help='Maximum number of SoftwareSecurePhotoVerifications to process. '
'This helps avoid overloading the database while updating large amount of data.'
)
parser.add_argument(
'--sleep_time',
action='store',
dest='sleep_time',
type=int,
default=10,
help='Sleep time in seconds between update of batches'
)
def handle(self, *args, **options):
start_date = datetime.datetime.strptime(options['start_date_time'], '%Y-%m-%d %H:%M:%S')
batch_size = options['batch_size']
sleep_time = options['sleep_time']
count = 0
verifications_after_start = SoftwareSecurePhotoVerification.objects.filter(created_at__gte=start_date)
for verification in verifications_after_start:
logger.info(
'Saving SoftwareSecurePhotoVerification with id=%(id)s',
{'id': verification.id}
)
verification.save()
count += 1
if count == batch_size:
time.sleep(sleep_time)
count = 0