diff --git a/edx_name_affirmation/models.py b/edx_name_affirmation/models.py index 14ef24b..08e109b 100644 --- a/edx_name_affirmation/models.py +++ b/edx_name_affirmation/models.py @@ -7,10 +7,16 @@ from simple_history.models import HistoricalRecords from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist from django.db import models from edx_name_affirmation.statuses import VerifiedNameStatus +try: + from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification +except ImportError: + SoftwareSecurePhotoVerification = None + User = get_user_model() @@ -45,6 +51,20 @@ class Meta: db_table = 'nameaffirmation_verifiedname' verbose_name = 'verified name' + @property + def verification_attempt_status(self): + "Returns the status associated with its SoftwareSecurePhotoVerification with verification_attempt_id if any." + + if not self.verification_attempt_id or not SoftwareSecurePhotoVerification: + return None + + try: + verification = SoftwareSecurePhotoVerification.objects.get(id=self.verification_attempt_id) + return verification.status + + except ObjectDoesNotExist: + return None + class VerifiedNameConfig(ConfigurationModel): """ diff --git a/edx_name_affirmation/serializers.py b/edx_name_affirmation/serializers.py index b67bc88..464519d 100644 --- a/edx_name_affirmation/serializers.py +++ b/edx_name_affirmation/serializers.py @@ -19,6 +19,7 @@ class VerifiedNameSerializer(serializers.ModelSerializer): verified_name = serializers.CharField(required=True) profile_name = serializers.CharField(required=True) verification_attempt_id = serializers.IntegerField(required=False, allow_null=True) + verification_attempt_status = serializers.CharField(required=False, allow_null=True) proctored_exam_attempt_id = serializers.IntegerField(required=False, allow_null=True) status = serializers.CharField(required=False, allow_null=True) @@ -30,7 +31,7 @@ class Meta: fields = ( "id", "created", "username", "verified_name", "profile_name", "verification_attempt_id", - "proctored_exam_attempt_id", "status" + "verification_attempt_status", "proctored_exam_attempt_id", "status" ) def validate_verified_name(self, verified_name): diff --git a/edx_name_affirmation/tests/test_models.py b/edx_name_affirmation/tests/test_models.py index 1f22e35..dc2aac5 100644 --- a/edx_name_affirmation/tests/test_models.py +++ b/edx_name_affirmation/tests/test_models.py @@ -1,8 +1,10 @@ """ Tests for Name Affirmation models """ +from unittest.mock import patch from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist from django.test import TestCase from edx_name_affirmation.models import VerifiedName @@ -16,6 +18,9 @@ class VerifiedNameModelTests(TestCase): Test suite for the VerifiedName models """ def setUp(self): + self.idv_attempt_id = 34455 + self.idv_attempt_status = 'submitted' + self.idv_attempt_id_notfound = 404 self.verified_name = 'Test Tester' self.user = User.objects.create(username='modelTester', email='model@tester.com') self.verified_name = VerifiedName.objects.create( @@ -29,11 +34,11 @@ def test_histories(self): """ Test the model history is recording records as expected """ + verified_name_history = self.verified_name.history.all().order_by('history_date') assert len(verified_name_history) == 1 - idv_attempt_id = 34455 self.verified_name.status = VerifiedNameStatus.APPROVED - self.verified_name.verification_attempt_id = idv_attempt_id + self.verified_name.verification_attempt_id = self.idv_attempt_id self.verified_name.save() verified_name_history = self.verified_name.history.all().order_by('history_date') assert len(verified_name_history) == 2 @@ -44,4 +49,37 @@ def test_histories(self): second_history_record = verified_name_history[1] assert second_history_record.status == VerifiedNameStatus.APPROVED - assert second_history_record.verification_attempt_id == idv_attempt_id + assert second_history_record.verification_attempt_id == self.idv_attempt_id + + @patch('edx_name_affirmation.models.SoftwareSecurePhotoVerification') + def test_verification_status(self, sspv_mock): + """ + Test the model history is recording records as expected + """ + + idv_attempt_id_notfound_status = None + + sspv_mock.objects.get = self._mocked_model_get + + self.verified_name.verification_attempt_id = self.idv_attempt_id_notfound + assert self.verified_name.verification_attempt_status is idv_attempt_id_notfound_status + + self.verified_name.verification_attempt_id = self.idv_attempt_id + assert self.verified_name.verification_attempt_status is self.idv_attempt_status + + # Helper methods + + def _obj(self, dictionary): + "Helper method to turn a dict into an object. Used to mock below." + + return type('obj', (object,), dictionary) + + def _mocked_model_get(self, id): # pylint: disable=redefined-builtin + "Helper method to mock the behavior of SoftwareSecurePhotoVerification model. Used to mock below." + if id == self.idv_attempt_id_notfound: + raise ObjectDoesNotExist + + if id == self.idv_attempt_id: + return self._obj({'status': self.idv_attempt_status}) + + return self._obj({'status': None}) diff --git a/edx_name_affirmation/tests/test_views.py b/edx_name_affirmation/tests/test_views.py index 650643d..eb1eb86 100644 --- a/edx_name_affirmation/tests/test_views.py +++ b/edx_name_affirmation/tests/test_views.py @@ -2,6 +2,7 @@ All tests for edx_name_affirmation views """ import json +from unittest.mock import PropertyMock, patch import ddt @@ -357,6 +358,7 @@ def _get_expected_data( 'verified_name': verified_name_obj.verified_name, 'profile_name': verified_name_obj.profile_name, 'verification_attempt_id': verified_name_obj.verification_attempt_id, + 'verification_attempt_status': None, 'proctored_exam_attempt_id': verified_name_obj.proctored_exam_attempt_id, 'status': verified_name_obj.status, 'use_verified_name_for_certs': use_verified_name_for_certs, @@ -379,6 +381,25 @@ def test_get(self): data = json.loads(response.content.decode('utf-8')) self.assertEqual(data, expected_response) + @patch('edx_name_affirmation.api.VerifiedName.verification_attempt_status', new_callable=PropertyMock) + def test_get_with_idv_status(self, verification_attempt_status_mock): + mocked_idv_status = 'approved' + + verification_attempt_status_mock.return_value = mocked_idv_status + verified_name_history = self._create_verified_name_history(self.user) + expected_response = self._get_expected_response(self.user, verified_name_history) + + # replacing the expected response results with the mocked status + for row in expected_response['results']: + row['verification_attempt_status'] = mocked_idv_status + + response = self.client.get(reverse('edx_name_affirmation:verified_name_history')) + + self.assertEqual(response.status_code, 200) + data = json.loads(response.content.decode('utf-8')) + + self.assertEqual(data, expected_response) + def test_get_bools(self): verified_name_history = self._create_verified_name_history(self.user) expected_response = self._get_expected_response( @@ -456,6 +477,7 @@ def _get_expected_response( 'verified_name': verified_name_obj.verified_name, 'profile_name': verified_name_obj.profile_name, 'verification_attempt_id': verified_name_obj.verification_attempt_id, + 'verification_attempt_status': None, 'proctored_exam_attempt_id': verified_name_obj.proctored_exam_attempt_id, 'status': verified_name_obj.status }