def test_initial_verification_for_user(self): """Test that method 'get_initial_verification' of model 'SoftwareSecurePhotoVerification' always returns the initial verification with field 'photo_id_key' set against a user. """ user = UserFactory.create() # No initial verification for the user result = SoftwareSecurePhotoVerification.get_initial_verification( user=user) self.assertIs(result, None) # Make an initial verification with 'photo_id_key' attempt = SoftwareSecurePhotoVerification( user=user, photo_id_key="dummy_photo_id_key") attempt.status = 'approved' attempt.save() # Check that method 'get_initial_verification' returns the correct # initial verification attempt first_result = SoftwareSecurePhotoVerification.get_initial_verification( user=user) self.assertIsNotNone(first_result) # Now create a second verification without 'photo_id_key' attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'submitted' attempt.save() # Test method 'get_initial_verification' still returns the correct # initial verification attempt which have 'photo_id_key' set second_result = SoftwareSecurePhotoVerification.get_initial_verification( user=user) self.assertIsNotNone(second_result) self.assertEqual(second_result, first_result) # Test method 'get_initial_verification' returns None after expiration expired_future = datetime.utcnow() + timedelta( days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1)) with freeze_time(expired_future): third_result = SoftwareSecurePhotoVerification.get_initial_verification( user) self.assertIsNone(third_result) # Test method 'get_initial_verification' returns correct attempt after system expiration, # but within earliest allowed override. expired_future = datetime.utcnow() + timedelta( days=(FAKE_SETTINGS['DAYS_GOOD_FOR'] + 1)) earliest_allowed = datetime.utcnow() - timedelta(days=1) with freeze_time(expired_future): fourth_result = SoftwareSecurePhotoVerification.get_initial_verification( user, earliest_allowed) self.assertIsNotNone(fourth_result) self.assertEqual(fourth_result, first_result)
def test_active_for_user(self): """ Make sure we can retrive a user's active (in progress) verification attempt. """ user = UserFactory.create() # This user has no active at the moment... assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user)) # Create an attempt and mark it ready... attempt = SoftwareSecurePhotoVerification(user=user) attempt.mark_ready() assert_equals(attempt, SoftwareSecurePhotoVerification.active_for_user(user)) # A new user won't see this... user2 = UserFactory.create() user2.save() assert_is_none(SoftwareSecurePhotoVerification.active_for_user(user2)) # If it's got a different status, it doesn't count for status in ["submitted", "must_retry", "approved", "denied"]: attempt.status = status attempt.save() assert_is_none( SoftwareSecurePhotoVerification.active_for_user(user)) # But if we create yet another one and mark it ready, it passes again. attempt_2 = SoftwareSecurePhotoVerification(user=user) attempt_2.mark_ready() assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # And if we add yet another one with a later created time, we get that # one instead. We always want the most recent attempt marked ready() attempt_3 = SoftwareSecurePhotoVerification( user=user, created_at=attempt_2.created_at + timedelta(days=1)) attempt_3.save() # We haven't marked attempt_3 ready yet, so attempt_2 still wins assert_equals(attempt_2, SoftwareSecurePhotoVerification.active_for_user(user)) # Now we mark attempt_3 ready and expect it to come back attempt_3.mark_ready() assert_equals(attempt_3, SoftwareSecurePhotoVerification.active_for_user(user))
def _submit_attempt(self, user, face_image, photo_id_image=None, initial_verification=None): """ Submit a verification attempt. Arguments: user (User): The user making the attempt. face_image (str): Decoded face image data. Keyword Arguments: photo_id_image (str or None): Decoded photo ID image data. initial_verification (SoftwareSecurePhotoVerification): The initial verification attempt. """ attempt = SoftwareSecurePhotoVerification(user=user) # We will always have face image data, so upload the face image attempt.upload_face_image(face_image) # If an ID photo wasn't submitted, re-use the ID photo from the initial attempt. # Earlier validation rules ensure that at least one of these is available. if photo_id_image is not None: attempt.upload_photo_id_image(photo_id_image) elif initial_verification is None: # Earlier validation should ensure that we never get here. log.error( "Neither a photo ID image or initial verification attempt provided. " "Parameter validation in the view should prevent this from happening!" ) # Submit the attempt attempt.mark_ready() attempt.submit(copy_id_photo_from=initial_verification) return attempt
def test_parse_error_msg_success(self): user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'denied' attempt.error_msg = '[{"userPhotoReasons": ["Face out of view"]}, {"photoIdReasons": ["Photo hidden/No photo", "ID name not provided"]}]' parsed_error_msg = attempt.parsed_error_msg() self.assertEquals(parsed_error_msg, ['id_image_missing_name', 'user_image_not_clear', 'id_image_not_clear'])
def test_parse_error_msg_success(self): user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'denied' attempt.error_msg = '[{"photoIdReasons": ["Not provided"]}]' parsed_error_msg = attempt.parsed_error_msg() self.assertEquals("No photo ID was provided.", parsed_error_msg)
def _verify_user(): if not SoftwareSecurePhotoVerification.user_is_verified(user): obj = SoftwareSecurePhotoVerification( user=user, photo_id_key="dummy_photo_id_key") obj.status = 'approved' obj.submitted_at = datetime.datetime.now() obj.reviewing_user = User.objects.get(username='******') obj.save()
def create_and_submit(self, user): """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ attempt = SoftwareSecurePhotoVerification(user=user) attempt.upload_face_image("Fake Data") attempt.upload_photo_id_image("More Fake Data") attempt.mark_ready() attempt.submit() return attempt
def create_and_submit(self, user): """ Helper method that lets us create new SoftwareSecurePhotoVerifications """ attempt = SoftwareSecurePhotoVerification(user=user) attempt.upload_face_image("Fake Data") attempt.upload_photo_id_image("More Fake Data") attempt.mark_ready() attempt.submit() attempt.expiry_date = now() + timedelta(days=FAKE_SETTINGS["DAYS_GOOD_FOR"]) return attempt
def test_state_transitions(self): """ Make sure we can't make unexpected status transitions. The status transitions we expect are:: → → → must_retry ↑ ↑ ↓ created → ready → submitted → approved ↓ ↑ ↓ ↓ → → denied """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) assert_equals(attempt.status, "created") # These should all fail because we're in the wrong starting state. assert_raises(VerificationException, attempt.submit) assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) # Now let's fill in some values so that we can pass the mark_ready() call attempt.mark_ready() assert_equals(attempt.status, "ready") # ready (can't approve or deny unless it's "submitted") assert_raises(VerificationException, attempt.approve) assert_raises(VerificationException, attempt.deny) DENY_ERROR_MSG = '[{"photoIdReasons": ["Not provided"]}]' # must_retry attempt.status = "must_retry" attempt.system_error("System error") attempt.approve() attempt.status = "must_retry" attempt.deny(DENY_ERROR_MSG) # submitted attempt.status = "submitted" attempt.deny(DENY_ERROR_MSG) attempt.status = "submitted" attempt.approve() # approved assert_raises(VerificationException, attempt.submit) attempt.approve() # no-op attempt.system_error( "System error") # no-op, something processed it without error attempt.deny(DENY_ERROR_MSG) # denied assert_raises(VerificationException, attempt.submit) attempt.deny(DENY_ERROR_MSG) # no-op attempt.system_error( "System error") # no-op, something processed it without error attempt.approve()
def test_no_approved_verification(self): """Test that method 'get_recent_verification' of model 'SoftwareSecurePhotoVerification' returns None if no 'approved' verification are found """ user = UserFactory.create() SoftwareSecurePhotoVerification(user=user) result = SoftwareSecurePhotoVerification.get_recent_verification(user=user) self.assertIs(result, None)
def test_user_has_ever_been_verified(self): """ Test to make sure we correctly answer whether a user has ever been verified. """ # Missing user assert not IDVerificationService.user_has_ever_been_verified(None) # User without any attempts photo_user = UserFactory.create() assert not IDVerificationService.user_has_ever_been_verified( photo_user) # User without an approved attempt attempt = SoftwareSecurePhotoVerification(user=photo_user, status='submitted') attempt.save() assert not IDVerificationService.user_has_ever_been_verified( photo_user) # User with a submitted, then an approved attempt attempt = SoftwareSecurePhotoVerification(user=photo_user, status='approved') attempt.save() assert IDVerificationService.user_has_ever_been_verified(photo_user) # User with a manual approved attempt manual_user = UserFactory.create() attempt = ManualVerification(user=manual_user, status='approved') attempt.save() assert IDVerificationService.user_has_ever_been_verified(manual_user) # User with 2 manual approved attempts attempt = ManualVerification(user=manual_user, status='approved') attempt.save() assert IDVerificationService.user_has_ever_been_verified(manual_user) # User with an SSO approved attempt, then a must_retry attempt sso_user = UserFactory.create() attempt = SSOVerification(user=sso_user, status='approved') attempt.save() attempt = SSOVerification(user=sso_user, status='must_retry') attempt.save() assert IDVerificationService.user_has_ever_been_verified(sso_user)
def create_and_submit(self): """Helper method to create a generic submission and send it.""" user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Rust\u01B4" attempt.upload_face_image("Just pretend this is image data") attempt.upload_photo_id_image("Hey, we're a photo ID") attempt.mark_ready() attempt.submit() return attempt
def test_retire_nonuser(self): """ Attempt to Retire User with no records in table """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) # User with no records in table assert not attempt.retire_user(user_id=user.id) # No user assert not attempt.retire_user(user_id=47)
def test_get_verification_from_receipt(self): result = SoftwareSecurePhotoVerification.get_verification_from_receipt( '') assert result is None user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = PhotoVerification.STATUS.submitted attempt.save() receipt_id = attempt.receipt_id result = SoftwareSecurePhotoVerification.get_verification_from_receipt( receipt_id) assert result is not None
def test_name_preset(self): """ If a name was set when creating the photo verification (from name affirmation / verified name flow) it should not be overwritten by the profile name """ user = UserFactory.create() user.profile.name = "Profile" preset_attempt = SoftwareSecurePhotoVerification(user=user) preset_attempt.name = "Preset" preset_attempt.mark_ready() assert "Preset" == preset_attempt.name
def test_expiration_date_null(self): """ Test if the `expiration_date` field is null, `expiration_datetime` returns a default expiration date based on the time the entry was created. """ user = UserFactory.create() verification = SoftwareSecurePhotoVerification(user=user) verification.expiration_date = None verification.save() assert verification.expiration_datetime == ( verification.created_at + timedelta(days=FAKE_SETTINGS['DAYS_GOOD_FOR']))
def test_deprecated_expiry_date(self): """ Test `expiration_datetime` returns `expiry_date` if it is not null. """ user = UserFactory.create() with freeze_time(now()): verification = SoftwareSecurePhotoVerification(user=user) # First, assert that expiration_date is set correctly assert verification.expiration_datetime == ( now() + timedelta(days=FAKE_SETTINGS['DAYS_GOOD_FOR'])) verification.expiry_date = now() + timedelta(days=10) # Then, assert that expiration_datetime favors expiry_date's value if set assert verification.expiration_datetime == (now() + timedelta(days=10))
def test_submission_while_testing_flag_is_true(self): """ Test that a fake value is set for field 'photo_id_key' of user's initial verification when the feature flag 'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING' is enabled. """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = "test-user" attempt.upload_photo_id_image("Image data") attempt.mark_ready() attempt.submit() self.assertEqual(attempt.photo_id_key, "fake-photo-id-key")
def create_upload_and_submit_attempt_for_user(self, user=None): """ Helper method to create a generic submission with photos for a user and send it. """ if not user: user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = u"Rust\u01B4" attempt.upload_face_image("Just pretend this is image data") attempt.upload_photo_id_image("Hey, we're a photo ID") attempt.mark_ready() return self.submit_attempt(attempt)
def test_parse_error_msg_failure(self): user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'denied' # when we can't parse into json bad_messages = { 'Not Provided', '[{"IdReasons": ["Not provided"]}]', '{"IdReasons": ["Not provided"]}', u'[{"ïḋṚëäṡöṅṡ": ["Ⓝⓞⓣ ⓟⓡⓞⓥⓘⓓⓔⓓ "]}]', } for msg in bad_messages: attempt.error_msg = msg parsed_error_msg = attempt.parsed_error_msg() self.assertEquals(parsed_error_msg, "There was an error verifying your ID photos.")
def test_name_freezing(self): """ You can change your name prior to marking a verification attempt ready, but changing your name afterwards should not affect the value in the in the attempt record. Basically, we want to always know what your name was when you submitted it. """ user = UserFactory.create() user.profile.name = "Jack \u01B4" # gratuious non-ASCII char to test encodings attempt = SoftwareSecurePhotoVerification(user=user) user.profile.name = "Clyde \u01B4" attempt.mark_ready() user.profile.name = "Rusty \u01B4" assert 'Clyde ƴ' == attempt.name
def test_user_is_verified(self): """ Test to make sure we correctly answer whether a user has been verified. """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) attempt.save() # If it's any of these, they're not verified... for status in ["created", "ready", "denied", "submitted", "must_retry"]: attempt.status = status attempt.save() assert_false(SoftwareSecurePhotoVerification.user_is_verified(user), status) attempt.status = "approved" attempt.save() assert_true(SoftwareSecurePhotoVerification.user_is_verified(user), attempt.status)
def test_update_expiry_email_date_for_user(self): """Test that method update_expiry_email_date_for_user of model 'SoftwareSecurePhotoVerification' set expiry_email_date if the most recent approved verification is expired. """ email_config = getattr(settings, 'VERIFICATION_EXPIRY_EMAIL', {'DAYS_RANGE': 1, 'RESEND_DAYS': 15}) user = UserFactory.create() verification = SoftwareSecurePhotoVerification(user=user) verification.expiry_date = now() - timedelta(days=FAKE_SETTINGS['DAYS_GOOD_FOR']) verification.status = 'approved' verification.save() self.assertIsNone(verification.expiry_email_date) SoftwareSecurePhotoVerification.update_expiry_email_date_for_user(user, email_config) result = SoftwareSecurePhotoVerification.get_recent_verification(user=user) self.assertIsNotNone(result.expiry_email_date)
def test_get_recent_verification_expiry_null(self): """Test that method 'get_recent_verification' of model 'SoftwareSecurePhotoVerification' will return None when expiry_date is NULL for 'approved' verifications based on updated_at value. """ user = UserFactory.create() attempt = None for _ in range(2): # Make an approved verification attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = PhotoVerification.STATUS.approved attempt.save() # Test method 'get_recent_verification' returns None # as attempts don't have an expiry_date recent_verification = SoftwareSecurePhotoVerification.get_recent_verification( user=user) self.assertIsNone(recent_verification)
def test_user_has_valid_or_pending(self): """ Determine whether we have to prompt this user to verify, or if they've already at least initiated a verification submission. """ user = UserFactory.create() attempt = SoftwareSecurePhotoVerification(user=user) # If it's any of these statuses, they don't have anything outstanding for status in ["created", "ready", "denied"]: attempt.status = status attempt.save() assert_false(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status) # Any of these, and we are. Note the benefit of the doubt we're giving # -- must_retry, and submitted both count until we hear otherwise for status in ["submitted", "must_retry", "approved"]: attempt.status = status attempt.save() assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user), status)
def test_get_recent_verification(self): """Test that method 'get_recent_verification' of model 'SoftwareSecurePhotoVerification' always returns the most recent 'approved' verification based on updated_at set against a user. """ user = UserFactory.create() attempt = None for _ in range(2): # Make an approved verification attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = 'approved' attempt.save() # Test method 'get_recent_verification' returns the most recent # verification attempt based on updated_at recent_verification = SoftwareSecurePhotoVerification.get_recent_verification(user=user) self.assertIsNotNone(recent_verification) self.assertEqual(recent_verification.id, attempt.id)
def test_get_recent_verification(self): """Test that method 'get_recent_verification' of model 'SoftwareSecurePhotoVerification' always returns the most recent 'approved' verification based on updated_at set against a user. """ user = UserFactory.create() attempt = None for _ in range(2): # Make an approved verification attempt = SoftwareSecurePhotoVerification(user=user) attempt.status = PhotoVerification.STATUS.approved attempt.expiration_date = datetime.now() attempt.save() # Test method 'get_recent_verification' returns the most recent # verification attempt based on updated_at recent_verification = SoftwareSecurePhotoVerification.get_recent_verification( user=user) assert recent_verification is not None assert recent_verification.id == attempt.id
def test_retire_user(self): user = UserFactory.create() user.profile.name = u"Enrique" attempt = SoftwareSecurePhotoVerification(user=user) attempt.mark_ready() attempt.status = "submitted" attempt.photo_id_image_url = "https://example.com/test/image/img.jpg" attempt.face_image_url = "https://example.com/test/face/img.jpg" attempt.approve() # Before Delete assert_equals(attempt.name, user.profile.name) assert_equals(attempt.photo_id_image_url, 'https://example.com/test/image/img.jpg') assert_equals(attempt.face_image_url, 'https://example.com/test/face/img.jpg') # Attempt self.assertTrue( SoftwareSecurePhotoVerification.delete_by_user_value(user, "user")) # Reattempt self.assertFalse( SoftwareSecurePhotoVerification.delete_by_user_value(user, "user"))