Merge pull request #35606 from openedx/michaelroytman/COSMO-521-expiration_datetime-VerificationAttempt-IDVerificationService
VerificationAttempt.expiration_datetime field may be None.
This commit is contained in:
@@ -1229,6 +1229,22 @@ class VerificationAttempt(StatusModel):
|
||||
"""When called, returns true or false based on the type of VerificationAttempt"""
|
||||
return not self.hide_status_from_user
|
||||
|
||||
def active_at_datetime(self, deadline):
|
||||
"""Check whether the verification was active at a particular datetime.
|
||||
|
||||
Arguments:
|
||||
deadline (datetime): The date at which the verification was active
|
||||
(created before and expiration datetime is after today).
|
||||
|
||||
Returns:
|
||||
bool
|
||||
|
||||
"""
|
||||
return (
|
||||
self.created_at <= deadline and
|
||||
(self.expiration_datetime is None or self.expiration_datetime > now())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def retire_user(cls, user_id):
|
||||
"""
|
||||
|
||||
@@ -176,10 +176,20 @@ class IDVerificationService:
|
||||
if verifications:
|
||||
attempt = verifications[0]
|
||||
for verification in verifications:
|
||||
if verification.expiration_datetime > now() and verification.status == 'approved':
|
||||
# Always select the LATEST non-expired approved verification if there is such
|
||||
if attempt.status != 'approved' or (
|
||||
attempt.expiration_datetime < verification.expiration_datetime
|
||||
# If a verification has no expiration_datetime, it's implied that it never expires, so we should still
|
||||
# consider verifications in the approved state that have no expiration date.
|
||||
if (
|
||||
not verification.expiration_datetime or
|
||||
verification.expiration_datetime > now()
|
||||
) and verification.status == 'approved':
|
||||
# Always select the LATEST non-expired approved verification if there is such.
|
||||
if (
|
||||
attempt.status != 'approved' or
|
||||
(
|
||||
attempt.expiration_datetime and
|
||||
verification.expiration_datetime and
|
||||
attempt.expiration_datetime < verification.expiration_datetime
|
||||
)
|
||||
):
|
||||
attempt = verification
|
||||
|
||||
@@ -188,7 +198,7 @@ class IDVerificationService:
|
||||
|
||||
user_status['should_display'] = attempt.should_display_status_to_user()
|
||||
|
||||
if attempt.expiration_datetime < now() and attempt.status == 'approved':
|
||||
if attempt.expiration_datetime and attempt.expiration_datetime < now() and attempt.status == 'approved':
|
||||
if user_status['should_display']:
|
||||
user_status['status'] = 'expired'
|
||||
user_status['error'] = _("Your {platform_name} verification has expired.").format(
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationAttempt
|
||||
|
||||
|
||||
class TestVerificationBase(TestCase):
|
||||
@@ -52,11 +52,14 @@ class TestVerificationBase(TestCase):
|
||||
|
||||
# Active immediately before expiration date
|
||||
expiration = attempt.expiration_datetime
|
||||
before_expiration = expiration - timedelta(seconds=1)
|
||||
assert attempt.active_at_datetime(before_expiration)
|
||||
if expiration:
|
||||
before_expiration = expiration - timedelta(seconds=1)
|
||||
assert attempt.active_at_datetime(before_expiration)
|
||||
|
||||
# Not active after the expiration date
|
||||
attempt.expiration_date = now() - timedelta(days=1)
|
||||
field = 'expiration_datetime' if isinstance(attempt, VerificationAttempt) else 'expiration_date'
|
||||
setattr(attempt, field, now() - timedelta(days=1))
|
||||
# attempt.expiration_date = now() - timedelta(days=1)
|
||||
attempt.save()
|
||||
assert not attempt.active_at_datetime(now())
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from lms.djangoapps.verify_student.models import (
|
||||
PhotoVerification,
|
||||
SoftwareSecurePhotoVerification,
|
||||
SSOVerification,
|
||||
VerificationAttempt,
|
||||
VerificationException
|
||||
)
|
||||
from lms.djangoapps.verify_student.tests import TestVerificationBase
|
||||
@@ -437,3 +438,14 @@ class ManualVerificationTest(TestVerificationBase):
|
||||
user = UserFactory.create()
|
||||
verification = ManualVerification.objects.create(user=user)
|
||||
self.verification_active_at_datetime(verification)
|
||||
|
||||
|
||||
class VerificationAttemptTest(TestVerificationBase):
|
||||
"""
|
||||
Tests for the VerificationAttempt model
|
||||
"""
|
||||
|
||||
def test_active_at_datetime(self):
|
||||
user = UserFactory.create()
|
||||
attempt = VerificationAttempt.objects.create(user=user)
|
||||
self.verification_active_at_datetime(attempt)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Tests for the service classes in verify_student.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -273,6 +274,29 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
}
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
def test_approved_verification_attempt_verification(self):
|
||||
with freeze_time('2015-01-02'):
|
||||
# test for when Verification Attempt verification has been created
|
||||
VerificationAttempt.objects.create(user=self.user, status='approved')
|
||||
status = IDVerificationService.user_status(self.user)
|
||||
expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '',
|
||||
'status_date': datetime.now(utc)}
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
def test_denied_verification_attempt_verification(self):
|
||||
with freeze_time('2015-2-02'):
|
||||
# create denied photo verification for the user, make sure the denial
|
||||
# is handled properly
|
||||
VerificationAttempt.objects.create(
|
||||
user=self.user, status='denied'
|
||||
)
|
||||
status = IDVerificationService.user_status(self.user)
|
||||
expected_status = {
|
||||
'status': 'must_reverify', 'error': '',
|
||||
'should_display': True, 'verification_expiry': '', 'status_date': '',
|
||||
}
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
def test_approved_sso_verification(self):
|
||||
with freeze_time('2015-03-02'):
|
||||
# test for when sso verification has been created
|
||||
@@ -303,22 +327,19 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
'status_date': datetime.now(utc)}
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
@ddt.data(
|
||||
'submitted',
|
||||
'denied',
|
||||
'approved',
|
||||
'created',
|
||||
'ready',
|
||||
'must_retry'
|
||||
@ddt.idata(itertools.product(
|
||||
[SoftwareSecurePhotoVerification, VerificationAttempt],
|
||||
['submitted', 'denied', 'approved', 'created', 'ready', 'must_retry'])
|
||||
)
|
||||
def test_expiring_software_secure_verification(self, new_status):
|
||||
def test_expiring_software_secure_verification(self, value):
|
||||
verification_model, new_status = value
|
||||
with freeze_time('2015-07-11') as frozen_datetime:
|
||||
# create approved photo verification for the user
|
||||
SoftwareSecurePhotoVerification.objects.create(user=self.user, status='approved')
|
||||
verification_model.objects.create(user=self.user, status='approved')
|
||||
expiring_datetime = datetime.now(utc)
|
||||
frozen_datetime.move_to('2015-07-14')
|
||||
# create another according to status passed in.
|
||||
SoftwareSecurePhotoVerification.objects.create(user=self.user, status=new_status)
|
||||
verification_model.objects.create(user=self.user, status=new_status)
|
||||
status_date = expiring_datetime
|
||||
if new_status == 'approved':
|
||||
status_date = datetime.now(utc)
|
||||
@@ -327,13 +348,16 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
status = IDVerificationService.user_status(self.user)
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
def test_expired_verification(self):
|
||||
@ddt.data(SoftwareSecurePhotoVerification, VerificationAttempt)
|
||||
def test_expired_verification(self, verification_model):
|
||||
with freeze_time('2015-07-11') as frozen_datetime:
|
||||
# create approved photo verification for the user
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
key = 'expiration_datetime' if verification_model == VerificationAttempt else 'expiration_date'
|
||||
|
||||
# create approved verification for the user
|
||||
verification_model.objects.create(
|
||||
user=self.user,
|
||||
status='approved',
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
**{key: now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])},
|
||||
)
|
||||
frozen_datetime.move_to('2016-07-11')
|
||||
expected_status = {
|
||||
@@ -348,28 +372,28 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
status = IDVerificationService.user_status(self.user)
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
@ddt.data(
|
||||
'submitted',
|
||||
'denied',
|
||||
'approved',
|
||||
'created',
|
||||
'ready',
|
||||
'must_retry'
|
||||
@ddt.idata(itertools.product(
|
||||
[SoftwareSecurePhotoVerification, VerificationAttempt],
|
||||
['submitted', 'denied', 'approved', 'created', 'ready', 'must_retry'])
|
||||
)
|
||||
def test_reverify_after_expired(self, new_status):
|
||||
def test_reverify_after_expired(self, value):
|
||||
verification_model, new_status = value
|
||||
with freeze_time('2015-07-11') as frozen_datetime:
|
||||
# create approved photo verification for the user
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
key = 'expiration_datetime' if verification_model == VerificationAttempt else 'expiration_date'
|
||||
|
||||
# create approved verification for the user
|
||||
verification_model.objects.create(
|
||||
user=self.user,
|
||||
status='approved',
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
**{key: now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])},
|
||||
)
|
||||
|
||||
frozen_datetime.move_to('2016-07-12')
|
||||
# create another according to status passed in.
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
verification_model.objects.create(
|
||||
user=self.user,
|
||||
status=new_status,
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
**{key: now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])},
|
||||
)
|
||||
|
||||
check_status = new_status
|
||||
@@ -390,9 +414,10 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
|
||||
@ddt.data(
|
||||
SSOVerification,
|
||||
ManualVerification
|
||||
ManualVerification,
|
||||
VerificationAttempt
|
||||
)
|
||||
def test_override_verification(self, verification_type):
|
||||
def test_override_verification(self, verification_model):
|
||||
with freeze_time('2015-07-11') as frozen_datetime:
|
||||
# create approved photo verification for the user
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
@@ -401,19 +426,27 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
)
|
||||
frozen_datetime.move_to('2015-07-14')
|
||||
verification_type.objects.create(
|
||||
|
||||
key = 'expiration_datetime' if verification_model == VerificationAttempt else 'expiration_date'
|
||||
verification_model.objects.create(
|
||||
user=self.user,
|
||||
status='approved',
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
**{key: now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])},
|
||||
)
|
||||
expected_status = {
|
||||
'status': 'approved', 'error': '', 'should_display': False,
|
||||
'status': 'approved', 'error': '',
|
||||
'should_display': verification_model == VerificationAttempt,
|
||||
'verification_expiry': '', 'status_date': now()
|
||||
}
|
||||
status = IDVerificationService.user_status(self.user)
|
||||
self.assertDictEqual(status, expected_status)
|
||||
|
||||
def test_denied_after_approved_verification(self):
|
||||
@ddt.data(
|
||||
SSOVerification,
|
||||
ManualVerification,
|
||||
VerificationAttempt
|
||||
)
|
||||
def test_denied_after_approved_verification(self, verification_model):
|
||||
with freeze_time('2015-07-11') as frozen_datetime:
|
||||
# create approved photo verification for the user
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
@@ -423,10 +456,12 @@ class TestIDVerificationServiceUserStatus(TestCase):
|
||||
)
|
||||
expected_date = now()
|
||||
frozen_datetime.move_to('2015-07-14')
|
||||
SoftwareSecurePhotoVerification.objects.create(
|
||||
|
||||
key = 'expiration_datetime' if verification_model == VerificationAttempt else 'expiration_date'
|
||||
verification_model.objects.create(
|
||||
user=self.user,
|
||||
status='denied',
|
||||
expiration_date=now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
|
||||
**{key: now() + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])},
|
||||
)
|
||||
expected_status = {
|
||||
'status': 'approved', 'error': '', 'should_display': True,
|
||||
|
||||
Reference in New Issue
Block a user