[MICROBA-1594] * Update course certificate generation logic when ID verification fails to check if the enrollment mode requires IDV. This change fixes an issue where Honor certificates could not be generated in an Open edX installation unless a manual IDV override was added for the student.
799 lines
32 KiB
Python
799 lines
32 KiB
Python
"""
|
|
Tests for certificate generation handler
|
|
"""
|
|
import logging
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
from django.conf import settings
|
|
from django.test import override_settings
|
|
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
|
from lms.djangoapps.certificates.data import CertificateStatuses
|
|
from lms.djangoapps.certificates.generation_handler import (
|
|
_can_generate_allowlist_certificate,
|
|
_can_generate_certificate_for_status,
|
|
_can_generate_regular_certificate,
|
|
_generate_regular_certificate_task,
|
|
_set_allowlist_cert_status,
|
|
_set_regular_cert_status,
|
|
generate_allowlist_certificate_task,
|
|
generate_certificate_task,
|
|
is_on_certificate_allowlist
|
|
)
|
|
from lms.djangoapps.certificates.models import GeneratedCertificate
|
|
from lms.djangoapps.certificates.tests.factories import (
|
|
CertificateAllowlistFactory,
|
|
CertificateInvalidationFactory,
|
|
GeneratedCertificateFactory
|
|
)
|
|
from lms.djangoapps.grades.api import CourseGradeFactory
|
|
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'
|
|
COURSE_OVERVIEW_METHOD = 'lms.djangoapps.certificates.generation_handler.get_course_overview_or_none'
|
|
CCX_COURSE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_ccx_course'
|
|
GET_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._get_course_grade'
|
|
INTEGRITY_ENABLED_METHOD = 'lms.djangoapps.certificates.generation_handler.is_integrity_signature_enabled'
|
|
ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
|
|
PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_passing_grade'
|
|
WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled'
|
|
|
|
|
|
@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False))
|
|
@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_mode = CourseMode.VERIFIED
|
|
self.grade = CourseGradeFactory().read(self.user, self.course_run)
|
|
self.enrollment = CourseEnrollmentFactory(
|
|
user=self.user,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=self.enrollment_mode,
|
|
)
|
|
|
|
# Add user to the allowlist
|
|
CertificateAllowlistFactory.create(course_id=self.course_run_key, user=self.user)
|
|
|
|
def test_is_on_allowlist(self):
|
|
"""
|
|
Test the presence of the user on the allowlist
|
|
"""
|
|
assert is_on_certificate_allowlist(self.user, self.course_run_key)
|
|
|
|
def test_is_on_allowlist_false(self):
|
|
"""
|
|
Test the absence of the user on the allowlist
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
CertificateAllowlistFactory.create(course_id=self.course_run_key, user=u, allowlist=False)
|
|
assert not is_on_certificate_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, CourseMode.VERIFIED) == expected_response
|
|
|
|
def test_generation_status_mode_changed_from_verified(self):
|
|
"""
|
|
Test handling of certificate statuses when the mode has changed from verified to audit
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.downloadable,
|
|
)
|
|
|
|
assert not _can_generate_certificate_for_status(u, key, CourseMode.AUDIT)
|
|
|
|
def test_generation_status_mode_changed_from_audit(self):
|
|
"""
|
|
Test handling of certificate statuses when the mode has changed from audit to verified
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.audit,
|
|
status=CertificateStatuses.downloadable,
|
|
)
|
|
|
|
assert _can_generate_certificate_for_status(u, key, CourseMode.VERIFIED)
|
|
|
|
def test_generation_status_mode_changed_from_audit_not_downloadable(self):
|
|
"""
|
|
Test handling of certificate statuses when the mode has changed from audit to verified but the cert is not
|
|
downloadable
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.audit,
|
|
status=CertificateStatuses.unverified,
|
|
)
|
|
|
|
assert _can_generate_certificate_for_status(u, key, CourseMode.VERIFIED)
|
|
|
|
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, None)
|
|
|
|
def test_handle_invalid(self):
|
|
"""
|
|
Test handling of an invalid user/course run combo
|
|
"""
|
|
u = UserFactory()
|
|
|
|
assert not _can_generate_allowlist_certificate(u, self.course_run_key, self.enrollment_mode)
|
|
assert not generate_allowlist_certificate_task(u, self.course_run_key)
|
|
assert not generate_certificate_task(u, self.course_run_key)
|
|
assert _set_allowlist_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
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, self.enrollment_mode)
|
|
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 generate_certificate_task(self.user, self.course_run_key)
|
|
|
|
@ddt.data(False, True)
|
|
def test_can_generate_not_verified(self, idv_retired):
|
|
"""
|
|
Test handling when the user's id is not verified
|
|
"""
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
|
|
self.assertEqual(idv_retired,
|
|
_can_generate_allowlist_certificate(self.user, self.course_run_key, self.enrollment_mode))
|
|
self.assertIsNot(idv_retired,
|
|
_set_allowlist_cert_status(
|
|
self.user, self.course_run_key,
|
|
self.enrollment_mode, self.grade) == CertificateStatuses.unverified)
|
|
|
|
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
|
|
mode = None
|
|
grade = None
|
|
CertificateAllowlistFactory.create(course_id=key, user=u)
|
|
assert not _can_generate_allowlist_certificate(u, key, mode)
|
|
assert _set_allowlist_cert_status(u, key, mode, grade) is None
|
|
|
|
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
|
|
mode = CourseMode.AUDIT
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=key,
|
|
is_active=True,
|
|
mode=mode,
|
|
)
|
|
CertificateAllowlistFactory.create(course_id=key, user=u)
|
|
|
|
assert not _can_generate_allowlist_certificate(u, key, mode)
|
|
assert _set_allowlist_cert_status(u, key, mode, self.grade) is None
|
|
|
|
def test_can_generate_not_allowlisted(self):
|
|
"""
|
|
Test handling when user is not on the certificate allowlist.
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
assert not _can_generate_allowlist_certificate(u, key, self.enrollment_mode)
|
|
assert _set_allowlist_cert_status(u, key, self.enrollment_mode, self.grade) is None
|
|
|
|
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=CourseMode.VERIFIED,
|
|
)
|
|
cert = GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.downloadable
|
|
)
|
|
CertificateAllowlistFactory.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, self.enrollment_mode)
|
|
assert _set_allowlist_cert_status(u, key, self.enrollment_mode, self.grade) == CertificateStatuses.unavailable
|
|
|
|
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, self.enrollment_mode)
|
|
assert _set_allowlist_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_can_generate_no_overview(self):
|
|
"""
|
|
Test handling when the course overview is missing
|
|
"""
|
|
with mock.patch(COURSE_OVERVIEW_METHOD, return_value=None):
|
|
assert not _can_generate_allowlist_certificate(self.user, self.course_run_key, self.enrollment_mode)
|
|
assert _set_allowlist_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_cert_status_downloadable(self):
|
|
"""
|
|
Test cert status when status is already downloadable
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.downloadable
|
|
)
|
|
|
|
assert _set_allowlist_cert_status(u, key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_generate_allowlist_honor_cert(self):
|
|
"""
|
|
Test that verifies we can generate an Honor cert for an Open edX installation configured to support Honor
|
|
certificates.
|
|
"""
|
|
course_run = CourseFactory()
|
|
course_run_key = course_run.id # pylint: disable=no-member
|
|
enrollment_mode = CourseMode.HONOR
|
|
CourseEnrollmentFactory(
|
|
user=self.user,
|
|
course_id=course_run_key,
|
|
is_active=True,
|
|
mode=enrollment_mode,
|
|
)
|
|
|
|
CertificateAllowlistFactory.create(course_id=course_run_key, user=self.user)
|
|
|
|
# Enable Honor Certificates and verify we can generate an AllowList certificate
|
|
with override_settings(FEATURES={**settings.FEATURES, 'DISABLE_HONOR_CERTIFICATES': False}):
|
|
assert _can_generate_allowlist_certificate(self.user, course_run_key, enrollment_mode)
|
|
|
|
# Disable Honor Certificates and verify we cannot generate an AllowList certificate
|
|
with override_settings(FEATURES={**settings.FEATURES, 'DISABLE_HONOR_CERTIFICATES': True}):
|
|
assert not _can_generate_allowlist_certificate(self.user, course_run_key, enrollment_mode)
|
|
|
|
|
|
@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False))
|
|
@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_mode = CourseMode.VERIFIED
|
|
self.grade = CourseGradeFactory().read(self.user, self.course_run)
|
|
self.enrollment = CourseEnrollmentFactory(
|
|
user=self.user,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=self.enrollment_mode,
|
|
)
|
|
|
|
def test_handle_valid(self):
|
|
"""
|
|
Test handling of a valid user/course run combo.
|
|
"""
|
|
assert _can_generate_regular_certificate(self.user, self.course_run_key, self.enrollment_mode, self.grade)
|
|
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) is True
|
|
|
|
def test_handle_invalid(self):
|
|
"""
|
|
Test handling of an invalid user/course run combo
|
|
"""
|
|
other_user = UserFactory()
|
|
mode = None
|
|
grade = None
|
|
assert not _can_generate_regular_certificate(other_user, self.course_run_key, mode, grade)
|
|
assert not generate_certificate_task(other_user, self.course_run_key)
|
|
assert not _generate_regular_certificate_task(other_user, self.course_run_key)
|
|
|
|
def test_handle_no_grade(self):
|
|
"""
|
|
Test handling when the grade is none
|
|
"""
|
|
with mock.patch(GET_GRADE_METHOD, return_value=None):
|
|
assert generate_certificate_task(self.user, self.course_run_key)
|
|
|
|
def test_handle_audit_status(self):
|
|
"""
|
|
Test handling of a user who is not passing and is enrolled in audit mode
|
|
"""
|
|
different_user = UserFactory()
|
|
mode = CourseMode.AUDIT
|
|
CourseEnrollmentFactory(
|
|
user=different_user,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=mode,
|
|
)
|
|
|
|
assert _set_regular_cert_status(different_user, self.course_run_key, mode, self.grade) is None
|
|
assert not _generate_regular_certificate_task(different_user, self.course_run_key)
|
|
|
|
def test_handle_not_passing_id_verified_no_cert(self):
|
|
"""
|
|
Test handling of a user who is not passing and is id verified and has no cert
|
|
"""
|
|
different_user = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=different_user,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
|
|
with mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert _set_regular_cert_status(different_user, self.course_run_key, self.enrollment_mode,
|
|
self.grade) is None
|
|
assert not _generate_regular_certificate_task(different_user, self.course_run_key)
|
|
|
|
def test_handle_not_passing_id_verified_cert(self):
|
|
"""
|
|
Test handling of a user who is not passing and is id verified and has a cert
|
|
"""
|
|
different_user = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=different_user,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=different_user,
|
|
course_id=self.course_run_key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.generating,
|
|
)
|
|
|
|
with mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert _set_regular_cert_status(different_user, self.course_run_key, self.enrollment_mode, self.grade) == \
|
|
CertificateStatuses.notpassing
|
|
assert _generate_regular_certificate_task(different_user, self.course_run_key) is True
|
|
assert not _can_generate_regular_certificate(different_user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
|
|
@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, CourseMode.VERIFIED) == 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, None)
|
|
|
|
@ddt.data(False, True)
|
|
def test_can_generate_not_verified_cert(self, idv_retired):
|
|
"""
|
|
Test handling when the user's id is not verified and they have a cert
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.generating
|
|
)
|
|
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
|
|
self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key,
|
|
self.enrollment_mode, self.grade))
|
|
self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
|
|
self.grade) == CertificateStatuses.unverified)
|
|
|
|
@ddt.data(False, True)
|
|
def test_can_generate_not_verified_no_cert(self, idv_retired):
|
|
"""
|
|
Test handling when the user's id is not verified and they don't have a cert
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
|
|
self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key,
|
|
self.enrollment_mode, self.grade))
|
|
self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
|
|
self.grade) == CertificateStatuses.unverified)
|
|
|
|
@ddt.data(False, True)
|
|
def test_can_generate_not_verified_not_passing(self, idv_retired):
|
|
"""
|
|
Test handling when the user's id is not verified and the user is not passing
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.generating
|
|
)
|
|
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \
|
|
mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
|
|
if idv_retired:
|
|
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) \
|
|
== CertificateStatuses.notpassing
|
|
else:
|
|
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
@ddt.data(False, True)
|
|
def test_can_generate_not_verified_not_passing_allowlist(self, idv_retired):
|
|
"""
|
|
Test handling when the user's id is not verified and the user is not passing but is on the allowlist
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.generating
|
|
)
|
|
CertificateAllowlistFactory(course_id=self.course_run_key, user=u)
|
|
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \
|
|
mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
|
|
if idv_retired:
|
|
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
|
|
self.grade) == CertificateStatuses.notpassing
|
|
else:
|
|
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
|
|
self.grade) == CertificateStatuses.unverified
|
|
|
|
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_regular_certificate(self.user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
assert _set_regular_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
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_regular_certificate(self.user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
assert _set_regular_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_can_generate_not_passing_no_cert(self):
|
|
"""
|
|
Test handling when the user does not have a passing grade and no cert exists
|
|
"""
|
|
with mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert not _can_generate_regular_certificate(self.user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
assert _set_regular_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_can_generate_not_passing_cert(self):
|
|
"""
|
|
Test handling when the user does not have a passing grade and a cert exists
|
|
"""
|
|
u = UserFactory()
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=self.course_run_key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.generating
|
|
)
|
|
|
|
with mock.patch(PASSING_GRADE_METHOD, return_value=False):
|
|
assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
|
|
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
|
|
self.grade) == CertificateStatuses.notpassing
|
|
|
|
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
|
|
mode = None
|
|
grade = None
|
|
assert not _can_generate_regular_certificate(u, key, mode, grade)
|
|
assert _set_regular_cert_status(u, key, mode, grade) is None
|
|
|
|
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
|
|
mode = CourseMode.AUDIT
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=key,
|
|
is_active=True,
|
|
mode=mode,
|
|
)
|
|
|
|
assert not _can_generate_regular_certificate(u, key, mode, self.grade)
|
|
assert _set_regular_cert_status(u, key, mode, self.grade) is None
|
|
|
|
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=CourseMode.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_regular_certificate(u, key, self.enrollment_mode, self.grade)
|
|
assert _set_regular_cert_status(u, key, self.enrollment_mode, self.grade) == CertificateStatuses.unavailable
|
|
|
|
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_regular_certificate(self.user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
assert _set_regular_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_can_generate_no_overview(self):
|
|
"""
|
|
Test handling when the course overview is missing
|
|
"""
|
|
with mock.patch(COURSE_OVERVIEW_METHOD, return_value=None):
|
|
assert not _can_generate_regular_certificate(self.user, self.course_run_key, self.enrollment_mode,
|
|
self.grade)
|
|
assert _set_regular_cert_status(self.user, self.course_run_key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_cert_status_downloadable(self):
|
|
"""
|
|
Test cert status when status is already downloadable
|
|
"""
|
|
u = UserFactory()
|
|
cr = CourseFactory()
|
|
key = cr.id # pylint: disable=no-member
|
|
CourseEnrollmentFactory(
|
|
user=u,
|
|
course_id=key,
|
|
is_active=True,
|
|
mode=CourseMode.VERIFIED,
|
|
)
|
|
GeneratedCertificateFactory(
|
|
user=u,
|
|
course_id=key,
|
|
mode=GeneratedCertificate.MODES.verified,
|
|
status=CertificateStatuses.downloadable
|
|
)
|
|
|
|
assert _set_regular_cert_status(u, key, self.enrollment_mode, self.grade) is None
|
|
|
|
def test_can_generate_honor_cert(self):
|
|
"""
|
|
Test that verifies we can generate an Honor cert for an Open edX installation configured to support Honor
|
|
certificates.
|
|
"""
|
|
course_run = CourseFactory()
|
|
course_run_key = course_run.id # pylint: disable=no-member
|
|
enrollment_mode = CourseMode.HONOR
|
|
grade = CourseGradeFactory().read(self.user, course_run)
|
|
CourseEnrollmentFactory(
|
|
user=self.user,
|
|
course_id=course_run_key,
|
|
is_active=True,
|
|
mode=enrollment_mode,
|
|
)
|
|
|
|
# Enable Honor Certificates and verify we can generate a certificate
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(PASSING_GRADE_METHOD, return_value=True), \
|
|
override_settings(FEATURES={**settings.FEATURES, 'DISABLE_HONOR_CERTIFICATES': False}):
|
|
assert _can_generate_regular_certificate(self.user, course_run_key, enrollment_mode, grade)
|
|
|
|
# Disable Honor Certificates and verify we cannot generate a certificate
|
|
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
|
|
mock.patch(PASSING_GRADE_METHOD, return_value=True), \
|
|
override_settings(FEATURES={**settings.FEATURES, 'DISABLE_HONOR_CERTIFICATES': True}):
|
|
assert not _can_generate_regular_certificate(self.user, course_run_key, enrollment_mode, grade)
|