The VERIFIED_NAME_FLAG, the VerifiedNameEnabledView, and the verified_name_enabled key removed from responses for both VerifiedNameView view and VerifiedNameHistoryView were removed as part https://github.com/edx/edx-name-affirmation/pull/12. This was released in version 2.0.0 of the edx-name-affirmation PyPI package. Please see below for additional context for the removal, copied from the name-affirmation commit message. The VERIFIED_NAME_FLAG was added as part https://github.com/edx/edx-name-affirmation/pull/12, [MST-801](https://openedx.atlassian.net/browse/MST-801) in order to control the release of the Verified Name project. It was used for a phased roll out by percentage of users. The release reached a percentage of 50% before it was observed that, due to the way percentage roll out works in django-waffle, the code to create or update VerifiedName records was not working properly. The code was written such that any change to a SoftwareSecurePhotoVerification model instance sent a signal, which was received and handled by the Name Affirmation application. If the VERIFIED_NAME_FLAG was on for the requesting user, a Celery task was launched from the Name Affirmation application to perform the creation of or update to the appropriate VerifiedName model instances based on the verify_student application signal. However, we observed that when SoftwareSecurePhotoVerification records were moved into the "created" or "ready" status, a Celery task in Name Affirmation was created, but when SoftwareSecurePhotoVerification records were moved into the "submitted" status, the corresponding Celery task in Name Affirmation was not created. This caused VerifiedName records to stay in the "pending" state. The django-waffle waffle flag used by the edx-toggle library implements percentage rollout by setting a cookie in a learner's browser session to assign them to the enabled or disabled group. It turns out that the code that submits a SoftwareSecurePhotoVerification record, which moves it into the "submitted" state, happens as part of a Celery task in the verify_student application in the edx-platform. Therefore, we believe that because there is no request object in a Celery task, the edx-toggle code is defaulting to the case where there is no request object. In this case, the code checks whether the flag is enabled for everyone when determining whether the flag is enabled. Because of the percentage rollout (i.e. waffle flag not enabled for everyone), the Celery task in Name Affirmation is not created. This behavior was confirmed by logging added as part of https://github.com/edx/edx-name-affirmation/pull/62. We have determined that we do not need the waffle flag, as we are comfortable that enabling the waffle flag for everyone will fix the issue and are comfortable releasing the feature to all users. For this reason, we are removing references to the flag. [MST-1130](https://openedx.atlassian.net/browse/MST-1130)
227 lines
9.2 KiB
Python
227 lines
9.2 KiB
Python
"""
|
|
Tests for certificate generation
|
|
"""
|
|
import ddt
|
|
import logging
|
|
from unittest import mock
|
|
|
|
from edx_name_affirmation.api import create_verified_name, create_verified_name_config
|
|
from edx_name_affirmation.statuses import VerifiedNameStatus
|
|
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from common.djangoapps.student.models import UserProfile
|
|
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
|
from common.djangoapps.util.testing import EventTestMixin
|
|
from lms.djangoapps.certificates.data import CertificateStatuses
|
|
from lms.djangoapps.certificates.generation import generate_course_certificate
|
|
from lms.djangoapps.certificates.models import GeneratedCertificate
|
|
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
|
|
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
|
from xmodule.modulestore.tests.factories import CourseFactory
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
PROFILE_NAME_METHOD = 'common.djangoapps.student.models_api.get_name'
|
|
|
|
|
|
@ddt.ddt
|
|
class CertificateTests(EventTestMixin, ModuleStoreTestCase):
|
|
"""
|
|
Tests for certificate generation
|
|
"""
|
|
|
|
def setUp(self): # pylint: disable=arguments-differ
|
|
super().setUp('lms.djangoapps.certificates.utils.tracker')
|
|
|
|
# Create user, a course run, and an enrollment
|
|
self.u = UserFactory()
|
|
self.profile = UserProfile.objects.get(user_id=self.u.id)
|
|
self.name = self.profile.name
|
|
self.cr = CourseFactory()
|
|
self.key = self.cr.id # pylint: disable=no-member
|
|
CourseEnrollmentFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
self.gen_mode = 'batch'
|
|
self.grade = '.85'
|
|
self.enrollment_mode = CourseMode.VERIFIED
|
|
|
|
def test_generation(self):
|
|
"""
|
|
Test certificate generation
|
|
"""
|
|
certs = GeneratedCertificate.objects.filter(user=self.u, course_id=self.key)
|
|
assert len(certs) == 0
|
|
|
|
generated_cert = generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable,
|
|
self.enrollment_mode, self.grade, self.gen_mode)
|
|
|
|
self.assert_event_emitted(
|
|
'edx.certificate.created',
|
|
user_id=self.u.id,
|
|
course_id=str(self.key),
|
|
certificate_id=generated_cert.verify_uuid,
|
|
enrollment_mode=generated_cert.mode,
|
|
certificate_url='',
|
|
generation_mode=self.gen_mode
|
|
)
|
|
|
|
certs = GeneratedCertificate.objects.filter(user=self.u, course_id=self.key)
|
|
assert len(certs) == 1
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
assert cert.status == CertificateStatuses.downloadable
|
|
assert cert.mode == self.enrollment_mode
|
|
assert cert.grade == self.grade
|
|
assert cert.name == self.name
|
|
|
|
def test_generation_existing_unverified(self):
|
|
"""
|
|
Test certificate generation when a certificate already exists and we want to mark it as unverified
|
|
"""
|
|
error_reason = 'Some PDF error'
|
|
GeneratedCertificateFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
mode=CourseMode.AUDIT,
|
|
status=CertificateStatuses.error,
|
|
error_reason=error_reason
|
|
)
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
assert cert.error_reason == error_reason
|
|
assert cert.mode == CourseMode.AUDIT
|
|
assert cert.status == CertificateStatuses.error
|
|
|
|
generate_course_certificate(self.u, self.key, CertificateStatuses.unverified, self.enrollment_mode, self.grade,
|
|
self.gen_mode)
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
assert cert.error_reason == ''
|
|
assert cert.status == CertificateStatuses.unverified
|
|
assert cert.mode == self.enrollment_mode
|
|
assert cert.grade == ''
|
|
|
|
def test_generation_existing_downloadable(self):
|
|
"""
|
|
Test certificate generation when a certificate already exists and we want to mark it as downloadable
|
|
"""
|
|
error_reason = 'Some PDF error'
|
|
GeneratedCertificateFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
mode=CourseMode.AUDIT,
|
|
status=CertificateStatuses.error,
|
|
error_reason=error_reason
|
|
)
|
|
|
|
generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable, self.enrollment_mode,
|
|
self.grade, self.gen_mode)
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
assert cert.error_reason == ''
|
|
assert cert.status == CertificateStatuses.downloadable
|
|
assert cert.mode == self.enrollment_mode
|
|
assert cert.grade == self.grade
|
|
|
|
def test_generation_uuid_persists_through_revocation(self):
|
|
"""
|
|
Test that the `verify_uuid` value of a certificate does not change when it is revoked and re-awarded.
|
|
"""
|
|
generated_cert = generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable,
|
|
self.enrollment_mode, self.grade, self.gen_mode)
|
|
assert generated_cert.status, CertificateStatuses.downloadable
|
|
|
|
verify_uuid = generated_cert.verify_uuid
|
|
|
|
generated_cert.invalidate()
|
|
assert generated_cert.status, CertificateStatuses.unavailable
|
|
assert generated_cert.verify_uuid, verify_uuid
|
|
|
|
generated_cert = generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable,
|
|
self.enrollment_mode, self.grade, self.gen_mode)
|
|
assert generated_cert.status, CertificateStatuses.downloadable
|
|
assert generated_cert.verify_uuid, verify_uuid
|
|
|
|
generated_cert.mark_notpassing(mode=generated_cert.mode, grade=50.00)
|
|
assert generated_cert.status, CertificateStatuses.notpassing
|
|
assert generated_cert.verify_uuid, verify_uuid
|
|
|
|
generated_cert = generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable,
|
|
self.enrollment_mode, self.grade, self.gen_mode)
|
|
assert generated_cert.status, CertificateStatuses.downloadable
|
|
assert generated_cert.verify_uuid, verify_uuid
|
|
|
|
def test_generation_creates_verify_uuid_when_needed(self):
|
|
"""
|
|
Test that ensures we will create a verify_uuid when needed.
|
|
"""
|
|
GeneratedCertificateFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
mode=CourseMode.VERIFIED,
|
|
status=CertificateStatuses.unverified,
|
|
verify_uuid=''
|
|
)
|
|
|
|
generated_cert = generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable,
|
|
self.enrollment_mode, self.grade, self.gen_mode)
|
|
assert generated_cert.status, CertificateStatuses.downloadable
|
|
assert generated_cert.verify_uuid != ''
|
|
|
|
def test_generation_missing_profile(self):
|
|
"""
|
|
Test certificate generation when the user profile is missing
|
|
"""
|
|
GeneratedCertificateFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
mode=CourseMode.AUDIT,
|
|
status=CertificateStatuses.unverified
|
|
)
|
|
|
|
with mock.patch(PROFILE_NAME_METHOD, return_value=None):
|
|
generate_course_certificate(self.u, self.key, CertificateStatuses.downloadable, self.enrollment_mode,
|
|
self.grade, self.gen_mode)
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
assert cert.status == CertificateStatuses.downloadable
|
|
assert cert.mode == self.enrollment_mode
|
|
assert cert.grade == self.grade
|
|
assert cert.name == ''
|
|
|
|
@ddt.data((True, VerifiedNameStatus.APPROVED),
|
|
(True, VerifiedNameStatus.DENIED),
|
|
(False, VerifiedNameStatus.PENDING))
|
|
@ddt.unpack
|
|
def test_generation_verified_name(self, should_use_verified_name_for_certs, status):
|
|
"""
|
|
Test that if the user has their preference set to use
|
|
verified name for certificates, their verified name will appear on the certificate rather than
|
|
their profile name.
|
|
"""
|
|
verified_name = 'Jonathan Doe'
|
|
create_verified_name(self.u, verified_name, self.name, status=status)
|
|
create_verified_name_config(self.u, use_verified_name_for_certs=should_use_verified_name_for_certs)
|
|
|
|
GeneratedCertificateFactory(
|
|
user=self.u,
|
|
course_id=self.key,
|
|
mode=CourseMode.AUDIT,
|
|
status=CertificateStatuses.unverified
|
|
)
|
|
|
|
generate_course_certificate(
|
|
self.u, self.key, CertificateStatuses.downloadable, self.enrollment_mode, self.grade, self.gen_mode,
|
|
)
|
|
|
|
cert = GeneratedCertificate.objects.get(user=self.u, course_id=self.key)
|
|
|
|
if should_use_verified_name_for_certs and status == VerifiedNameStatus.APPROVED:
|
|
assert cert.name == verified_name
|
|
else:
|
|
assert cert.name == self.name
|