Files
edx-platform/lms/djangoapps/certificates/tests/test_generation_handler.py
Justin Hynes 13d174fdda fix: update v2 certificate generation eligibility logic
[MICROBA-1109]
* Remove the requirement for the `auto_certificate_generation` waffle switch to be enabled in order for a learner to be eligible for a certificate. This switch should control _when_ a certificate is generated, not _if_ a certificate will be generated for a learner.
* Move the `auto_certificate_generation_enabled` check to happen before we try to generate v2 certificates in signals.py
* Update unit tests
* Update documentation
2021-04-06 08:50:54 -04:00

445 lines
16 KiB
Python

"""
Tests for certificate generation handler
"""
import logging
from unittest import mock
import ddt
from edx_toggles.toggles.testutils import override_waffle_flag
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.certificates.generation_handler import (
CERTIFICATES_USE_ALLOWLIST,
CERTIFICATES_USE_UPDATED,
is_using_certificate_allowlist,
_is_using_v2_course_certificates,
_can_generate_allowlist_certificate,
_can_generate_certificate_for_status,
_can_generate_v2_certificate,
can_generate_certificate_task,
generate_allowlist_certificate_task,
generate_certificate_task,
generate_regular_certificate_task,
is_using_certificate_allowlist_and_is_on_allowlist
)
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
from lms.djangoapps.certificates.tests.factories import (
CertificateInvalidationFactory,
CertificateWhitelistFactory,
GeneratedCertificateFactory
)
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
log = logging.getLogger(__name__)
BETA_TESTER_METHOD = 'lms.djangoapps.certificates.generation_handler._is_beta_tester'
CCX_COURSE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_ccx_course'
ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._has_passing_grade'
WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled'
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=True)
@mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True))
@mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True))
@ddt.ddt
class AllowlistTests(ModuleStoreTestCase):
"""
Tests for handling allowlist certificates
"""
def setUp(self):
super().setUp()
# Create user, a course run, and an enrollment
self.user = UserFactory()
self.course_run = CourseFactory()
self.course_run_key = self.course_run.id # pylint: disable=no-member
self.enrollment = CourseEnrollmentFactory(
user=self.user,
course_id=self.course_run_key,
is_active=True,
mode="verified",
)
# Whitelist user
CertificateWhitelistFactory.create(course_id=self.course_run_key, user=self.user)
def test_is_using_allowlist_true(self):
"""
Test the allowlist flag
"""
assert is_using_certificate_allowlist(self.course_run_key)
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=False)
def test_is_using_allowlist_false(self):
"""
Test the allowlist flag without the override
"""
assert not is_using_certificate_allowlist(self.course_run_key)
def test_is_using_allowlist_and_is_on_list(self):
"""
Test the allowlist flag and the presence of the user on the list
"""
assert is_using_certificate_allowlist_and_is_on_allowlist(self.user, self.course_run_key)
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=False)
def test_is_using_allowlist_and_is_on_list_with_flag_off(self):
"""
Test the allowlist flag and the presence of the user on the list when the flag is off
"""
assert not is_using_certificate_allowlist_and_is_on_allowlist(self.user, self.course_run_key)
def test_is_using_allowlist_and_is_on_list_true(self):
"""
Test the allowlist flag and the presence of the user on the list when the user is not on the list
"""
u = UserFactory()
CourseEnrollmentFactory(
user=u,
course_id=self.course_run_key,
is_active=True,
mode="verified",
)
CertificateWhitelistFactory.create(course_id=self.course_run_key, user=u, whitelist=False)
assert not is_using_certificate_allowlist_and_is_on_allowlist(u, self.course_run_key)
@ddt.data(
(CertificateStatuses.deleted, True),
(CertificateStatuses.deleting, True),
(CertificateStatuses.downloadable, False),
(CertificateStatuses.error, True),
(CertificateStatuses.generating, True),
(CertificateStatuses.notpassing, True),
(CertificateStatuses.restricted, True),
(CertificateStatuses.unavailable, True),
(CertificateStatuses.audit_passing, True),
(CertificateStatuses.audit_notpassing, True),
(CertificateStatuses.honor_passing, True),
(CertificateStatuses.unverified, True),
(CertificateStatuses.invalidated, True),
(CertificateStatuses.requesting, True))
@ddt.unpack
def test_generation_status(self, status, expected_response):
"""
Test handling of certificate statuses
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
GeneratedCertificateFactory(
user=u,
course_id=key,
mode=GeneratedCertificate.MODES.verified,
status=status,
)
assert _can_generate_certificate_for_status(u, key) == expected_response
def test_generation_status_for_none(self):
"""
Test handling of certificate statuses for a non-existent cert
"""
assert _can_generate_certificate_for_status(None, None)
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=False)
def test_handle_invalid(self):
"""
Test handling of an invalid user/course run combo
"""
assert not _can_generate_allowlist_certificate(self.user, self.course_run_key)
assert not generate_allowlist_certificate_task(self.user, self.course_run_key)
assert not can_generate_certificate_task(self.user, self.course_run_key)
assert not generate_certificate_task(self.user, self.course_run_key)
def test_handle_valid(self):
"""
Test handling of a valid user/course run combo
"""
assert _can_generate_allowlist_certificate(self.user, self.course_run_key)
assert generate_allowlist_certificate_task(self.user, self.course_run_key)
def test_handle_valid_general_methods(self):
"""
Test handling of a valid user/course run combo for the general (non-allowlist) generation methods
"""
assert can_generate_certificate_task(self.user, self.course_run_key)
assert generate_certificate_task(self.user, self.course_run_key)
def test_can_generate_not_verified(self):
"""
Test handling when the user's id is not verified
"""
with mock.patch(ID_VERIFIED_METHOD, return_value=False):
assert not _can_generate_allowlist_certificate(self.user, self.course_run_key)
def test_can_generate_not_enrolled(self):
"""
Test handling when user is not enrolled
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CertificateWhitelistFactory.create(course_id=key, user=u)
assert not _can_generate_allowlist_certificate(u, key)
def test_can_generate_audit(self):
"""
Test handling when user is enrolled in audit mode
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode="audit",
)
CertificateWhitelistFactory.create(course_id=key, user=u)
assert not _can_generate_allowlist_certificate(u, key)
def test_can_generate_not_whitelisted(self):
"""
Test handling when user is not whitelisted
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode="verified",
)
assert not _can_generate_allowlist_certificate(u, key)
def test_can_generate_invalidated(self):
"""
Test handling when user is on the invalidate list
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode="verified",
)
cert = GeneratedCertificateFactory(
user=u,
course_id=key,
mode=GeneratedCertificate.MODES.verified,
status=CertificateStatuses.downloadable
)
CertificateWhitelistFactory.create(course_id=key, user=u)
CertificateInvalidationFactory.create(
generated_certificate=cert,
invalidated_by=self.user,
active=True
)
assert not _can_generate_allowlist_certificate(u, key)
def test_can_generate_web_cert_disabled(self):
"""
Test handling when web certs are not enabled
"""
with mock.patch(WEB_CERTS_METHOD, return_value=False):
assert not _can_generate_allowlist_certificate(self.user, self.course_run_key)
@override_waffle_flag(CERTIFICATES_USE_UPDATED, active=True)
@mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True))
@mock.patch(CCX_COURSE_METHOD, mock.Mock(return_value=False))
@mock.patch(PASSING_GRADE_METHOD, mock.Mock(return_value=True))
@mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True))
@ddt.ddt
class CertificateTests(ModuleStoreTestCase):
"""
Tests for handling course certificates
"""
def setUp(self):
super().setUp()
# Create user, a course run, and an enrollment
self.user = UserFactory()
self.course_run = CourseFactory()
self.course_run_key = self.course_run.id # pylint: disable=no-member
self.enrollment = CourseEnrollmentFactory(
user=self.user,
course_id=self.course_run_key,
is_active=True,
mode="verified",
)
def test_handle_valid(self):
"""
Test handling of a valid user/course run combo.
"""
assert _can_generate_v2_certificate(self.user, self.course_run_key)
assert can_generate_certificate_task(self.user, self.course_run_key)
assert generate_certificate_task(self.user, self.course_run_key)
def test_handle_valid_task(self):
"""
Test handling of a valid user/course run combo.
We test generate_certificate_task() and generate_regular_certificate_task() separately since they both
generate a cert.
"""
assert generate_regular_certificate_task(self.user, self.course_run_key)
@override_waffle_flag(CERTIFICATES_USE_UPDATED, active=False)
def test_handle_invalid(self):
"""
Test handling of an invalid user/course run combo
"""
assert not _can_generate_v2_certificate(self.user, self.course_run_key)
assert not can_generate_certificate_task(self.user, self.course_run_key)
assert not generate_certificate_task(self.user, self.course_run_key)
assert not generate_regular_certificate_task(self.user, self.course_run_key)
def test_is_using_updated_true(self):
"""
Test the updated flag
"""
assert _is_using_v2_course_certificates(self.course_run_key)
@override_waffle_flag(CERTIFICATES_USE_UPDATED, active=False)
def test_is_using_updated_false(self):
"""
Test the updated flag without the override
"""
assert not _is_using_v2_course_certificates(self.course_run_key)
@ddt.data(
(CertificateStatuses.deleted, True),
(CertificateStatuses.deleting, True),
(CertificateStatuses.downloadable, False),
(CertificateStatuses.error, True),
(CertificateStatuses.generating, True),
(CertificateStatuses.notpassing, True),
(CertificateStatuses.restricted, True),
(CertificateStatuses.unavailable, True),
(CertificateStatuses.audit_passing, True),
(CertificateStatuses.audit_notpassing, True),
(CertificateStatuses.honor_passing, True),
(CertificateStatuses.unverified, True),
(CertificateStatuses.invalidated, True),
(CertificateStatuses.requesting, True))
@ddt.unpack
def test_generation_status(self, status, expected_response):
"""
Test handling of certificate statuses
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
GeneratedCertificateFactory(
user=u,
course_id=key,
mode=GeneratedCertificate.MODES.verified,
status=status,
)
assert _can_generate_certificate_for_status(u, key) == expected_response
def test_generation_status_for_none(self):
"""
Test handling of certificate statuses for a non-existent cert
"""
assert _can_generate_certificate_for_status(None, None)
def test_can_generate_not_verified(self):
"""
Test handling when the user's id is not verified
"""
with mock.patch(ID_VERIFIED_METHOD, return_value=False):
assert not _can_generate_v2_certificate(self.user, self.course_run_key)
def test_can_generate_ccx(self):
"""
Test handling when the course is a CCX (custom edX) course
"""
with mock.patch(CCX_COURSE_METHOD, return_value=True):
assert not _can_generate_v2_certificate(self.user, self.course_run_key)
def test_can_generate_beta_tester(self):
"""
Test handling when the user is a beta tester
"""
with mock.patch(BETA_TESTER_METHOD, return_value=True):
assert not _can_generate_v2_certificate(self.user, self.course_run_key)
def test_can_generate_failing_grade(self):
"""
Test handling when the user does not have a passing grade
"""
with mock.patch(PASSING_GRADE_METHOD, return_value=False):
assert not _can_generate_v2_certificate(self.user, self.course_run_key)
def test_can_generate_not_enrolled(self):
"""
Test handling when user is not enrolled
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
assert not _can_generate_v2_certificate(u, key)
def test_can_generate_audit(self):
"""
Test handling when user is enrolled in audit mode
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode="audit",
)
assert not _can_generate_v2_certificate(u, key)
def test_can_generate_invalidated(self):
"""
Test handling when user is on the invalidate list
"""
u = UserFactory()
cr = CourseFactory()
key = cr.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=key,
is_active=True,
mode="verified",
)
cert = GeneratedCertificateFactory(
user=u,
course_id=key,
mode=GeneratedCertificate.MODES.verified,
status=CertificateStatuses.downloadable
)
CertificateInvalidationFactory.create(
generated_certificate=cert,
invalidated_by=self.user,
active=True
)
assert not _can_generate_v2_certificate(u, key)
def test_can_generate_web_cert_disabled(self):
"""
Test handling when web certs are not enabled
"""
with mock.patch(WEB_CERTS_METHOD, return_value=False):
assert not _can_generate_v2_certificate(self.user, self.course_run_key)