There are a number of Django Signals that are on the modulestore's
SignalHandler class, such as SignalHandler.course_published. These
signals can trigger very expensive processes to occur, such as course
overview or block structures generation. Most of the time, the test
author doesn't care about these side-effects.
This commit does a few things:
* Converts the signals on SignalHandler to be instances of a new
SwitchedSignal class, that allows signal sending to be disabled.
* Creates a SignalIsolationMixin helper similar in spirit to the
CacheIsolationMixin, and adds it to the ModuleStoreIsolationMixin
(and thus to ModuleStoreTestCase and SharedModuleStoreTestCase).
* Converts our various tests to use this new mechanism. In some cases,
this means adjusting query counts downwards because they no longer
have to account for publishing listener actions.
Modulestore generated signals are now muted by default during test runs.
Calls to send() them will result in no-ops. You can choose to enable
specific signals for a given subclass of ModuleStoreTestCase or
SharedModuleStoreTestCase by specifying an ENABLED_SIGNALS class
attribute, like the following example:
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class MyPublishTestCase(ModuleStoreTestCase):
ENABLED_SIGNALS = ['course_published', 'pre_publish']
You should take great care when disabling signals outside of a
ModuleStoreTestCase or SharedModuleStoreTestCase, since they can leak
out into other tests. Be sure to always clean up, and never disable
signals outside of testing. Because signals are essentially process
globals, it can have a lot of unpleasant side-effects if we start
mucking around with them during live requests.
Overall, this change has cut the total test execution time for
edx-platform by a bit over a third, though we still spend a lot in
pre-test setup during our test builds.
[PERF-413]
772 lines
28 KiB
Python
772 lines
28 KiB
Python
"""Tests for the certificates Python API. """
|
|
from contextlib import contextmanager
|
|
import ddt
|
|
from functools import wraps
|
|
import uuid
|
|
|
|
from django.test import TestCase, RequestFactory
|
|
from django.test.utils import override_settings
|
|
from django.conf import settings
|
|
from django.core.urlresolvers import reverse
|
|
from mock import patch
|
|
from nose.plugins.attrib import attr
|
|
from opaque_keys.edx.locator import CourseLocator
|
|
|
|
from config_models.models import cache
|
|
from course_modes.models import CourseMode
|
|
from course_modes.tests.factories import CourseModeFactory
|
|
from courseware.tests.factories import GlobalStaffFactory
|
|
from lms.djangoapps.grades.tests.utils import mock_passing_grade
|
|
from microsite_configuration import microsite
|
|
from student.models import CourseEnrollment
|
|
from student.tests.factories import UserFactory
|
|
from util.testing import EventTestMixin
|
|
from xmodule.modulestore.tests.factories import CourseFactory
|
|
from xmodule.modulestore.tests.django_utils import (
|
|
ModuleStoreTestCase,
|
|
SharedModuleStoreTestCase,
|
|
)
|
|
|
|
from certificates import api as certs_api
|
|
from certificates.models import (
|
|
CertificateStatuses,
|
|
CertificateGenerationConfiguration,
|
|
ExampleCertificate,
|
|
GeneratedCertificate,
|
|
certificate_status_for_student,
|
|
)
|
|
from certificates.queue import XQueueCertInterface, XQueueAddToQueueError
|
|
from certificates.tests.factories import (
|
|
CertificateInvalidationFactory,
|
|
GeneratedCertificateFactory
|
|
)
|
|
|
|
|
|
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
|
|
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
|
|
|
|
|
|
class WebCertificateTestMixin(object):
|
|
"""
|
|
Mixin with helpers for testing Web Certificates.
|
|
"""
|
|
@contextmanager
|
|
def _mock_queue(self, is_successful=True):
|
|
"""
|
|
Mock the "send to XQueue" method to return either success or an error.
|
|
"""
|
|
symbol = 'capa.xqueue_interface.XQueueInterface.send_to_queue'
|
|
with patch(symbol) as mock_send_to_queue:
|
|
if is_successful:
|
|
mock_send_to_queue.return_value = (0, "Successfully queued")
|
|
else:
|
|
mock_send_to_queue.side_effect = XQueueAddToQueueError(1, self.ERROR_REASON)
|
|
|
|
yield mock_send_to_queue
|
|
|
|
def _setup_course_certificate(self):
|
|
"""
|
|
Creates certificate configuration for course
|
|
"""
|
|
certificates = [
|
|
{
|
|
'id': 1,
|
|
'name': 'Test Certificate Name',
|
|
'description': 'Test Certificate Description',
|
|
'course_title': 'tes_course_title',
|
|
'signatories': [],
|
|
'version': 1,
|
|
'is_active': True
|
|
}
|
|
]
|
|
self.course.certificates = {'certificates': certificates}
|
|
self.course.cert_html_view_enabled = True
|
|
self.course.save()
|
|
self.store.update_item(self.course, self.user.id)
|
|
|
|
|
|
@attr(shard=1)
|
|
class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTestCase):
|
|
"""Tests for the `certificate_downloadable_status` helper function. """
|
|
ENABLED_SIGNALS = ['course_published']
|
|
|
|
def setUp(self):
|
|
super(CertificateDownloadableStatusTests, self).setUp()
|
|
|
|
self.student = UserFactory()
|
|
self.student_no_cert = UserFactory()
|
|
self.course = CourseFactory.create(
|
|
org='edx',
|
|
number='verified',
|
|
display_name='Verified Course'
|
|
)
|
|
|
|
self.request_factory = RequestFactory()
|
|
|
|
def test_cert_status_with_generating(self):
|
|
GeneratedCertificateFactory.create(
|
|
user=self.student,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.generating,
|
|
mode='verified'
|
|
)
|
|
self.assertEqual(
|
|
certs_api.certificate_downloadable_status(self.student, self.course.id),
|
|
{
|
|
'is_downloadable': False,
|
|
'is_generating': True,
|
|
'is_unverified': False,
|
|
'download_url': None,
|
|
'uuid': None,
|
|
}
|
|
)
|
|
|
|
def test_cert_status_with_error(self):
|
|
GeneratedCertificateFactory.create(
|
|
user=self.student,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.error,
|
|
mode='verified'
|
|
)
|
|
|
|
self.assertEqual(
|
|
certs_api.certificate_downloadable_status(self.student, self.course.id),
|
|
{
|
|
'is_downloadable': False,
|
|
'is_generating': True,
|
|
'is_unverified': False,
|
|
'download_url': None,
|
|
'uuid': None
|
|
}
|
|
)
|
|
|
|
def test_without_cert(self):
|
|
self.assertEqual(
|
|
certs_api.certificate_downloadable_status(self.student_no_cert, self.course.id),
|
|
{
|
|
'is_downloadable': False,
|
|
'is_generating': False,
|
|
'is_unverified': False,
|
|
'download_url': None,
|
|
'uuid': None,
|
|
}
|
|
)
|
|
|
|
def verify_downloadable_pdf_cert(self):
|
|
"""
|
|
Verifies certificate_downloadable_status returns the
|
|
correct response for PDF certificates.
|
|
"""
|
|
cert = GeneratedCertificateFactory.create(
|
|
user=self.student,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='verified',
|
|
download_url='www.google.com',
|
|
)
|
|
|
|
self.assertEqual(
|
|
certs_api.certificate_downloadable_status(self.student, self.course.id),
|
|
{
|
|
'is_downloadable': True,
|
|
'is_generating': False,
|
|
'is_unverified': False,
|
|
'download_url': 'www.google.com',
|
|
'uuid': cert.verify_uuid
|
|
}
|
|
)
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_pdf_cert_with_html_enabled(self):
|
|
self.verify_downloadable_pdf_cert()
|
|
|
|
def test_pdf_cert_with_html_disabled(self):
|
|
self.verify_downloadable_pdf_cert()
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_with_downloadable_web_cert(self):
|
|
CourseEnrollment.enroll(self.student, self.course.id, mode='honor')
|
|
self._setup_course_certificate()
|
|
with mock_passing_grade():
|
|
certs_api.generate_user_certificates(self.student, self.course.id)
|
|
|
|
cert_status = certificate_status_for_student(self.student, self.course.id)
|
|
self.assertEqual(
|
|
certs_api.certificate_downloadable_status(self.student, self.course.id),
|
|
{
|
|
'is_downloadable': True,
|
|
'is_generating': False,
|
|
'is_unverified': False,
|
|
'download_url': '/certificates/user/{user_id}/course/{course_id}'.format(
|
|
user_id=self.student.id, # pylint: disable=no-member
|
|
course_id=self.course.id,
|
|
),
|
|
'uuid': cert_status['uuid']
|
|
}
|
|
)
|
|
|
|
|
|
@attr(shard=1)
|
|
@ddt.ddt
|
|
class CertificateisInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
|
|
"""Tests for the `is_certificate_invalid` helper function. """
|
|
|
|
def setUp(self):
|
|
super(CertificateisInvalid, self).setUp()
|
|
|
|
self.student = UserFactory()
|
|
self.course = CourseFactory.create(
|
|
org='edx',
|
|
number='verified',
|
|
display_name='Verified Course'
|
|
)
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.request_factory = RequestFactory()
|
|
|
|
def test_method_with_no_certificate(self):
|
|
""" Test the case when there is no certificate for a user for a specific course. """
|
|
course = CourseFactory.create(
|
|
org='edx',
|
|
number='honor',
|
|
display_name='Course 1'
|
|
)
|
|
# Also check query count for 'is_certificate_invalid' method.
|
|
with self.assertNumQueries(1):
|
|
self.assertFalse(
|
|
certs_api.is_certificate_invalid(self.student, course.id)
|
|
)
|
|
|
|
@ddt.data(
|
|
CertificateStatuses.generating,
|
|
CertificateStatuses.downloadable,
|
|
CertificateStatuses.notpassing,
|
|
CertificateStatuses.error,
|
|
CertificateStatuses.unverified,
|
|
CertificateStatuses.deleted,
|
|
CertificateStatuses.unavailable,
|
|
)
|
|
def test_method_with_invalidated_cert(self, status):
|
|
""" Verify that if certificate is marked as invalid than method will return
|
|
True. """
|
|
generated_cert = self._generate_cert(status)
|
|
self._invalidate_certificate(generated_cert, True)
|
|
self.assertTrue(
|
|
certs_api.is_certificate_invalid(self.student, self.course.id)
|
|
)
|
|
|
|
@ddt.data(
|
|
CertificateStatuses.generating,
|
|
CertificateStatuses.downloadable,
|
|
CertificateStatuses.notpassing,
|
|
CertificateStatuses.error,
|
|
CertificateStatuses.unverified,
|
|
CertificateStatuses.deleted,
|
|
CertificateStatuses.unavailable,
|
|
)
|
|
def test_method_with_inactive_invalidated_cert(self, status):
|
|
""" Verify that if certificate is valid but it's invalidated status is
|
|
false than method will return false. """
|
|
generated_cert = self._generate_cert(status)
|
|
self._invalidate_certificate(generated_cert, False)
|
|
self.assertFalse(
|
|
certs_api.is_certificate_invalid(self.student, self.course.id)
|
|
)
|
|
|
|
@ddt.data(
|
|
CertificateStatuses.generating,
|
|
CertificateStatuses.downloadable,
|
|
CertificateStatuses.notpassing,
|
|
CertificateStatuses.error,
|
|
CertificateStatuses.unverified,
|
|
CertificateStatuses.deleted,
|
|
CertificateStatuses.unavailable,
|
|
)
|
|
def test_method_with_all_statues(self, status):
|
|
""" Verify method return True if certificate has valid status but it is
|
|
marked as invalid in CertificateInvalidation table. """
|
|
|
|
certificate = self._generate_cert(status)
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=certificate,
|
|
invalidated_by=self.global_staff,
|
|
active=True
|
|
)
|
|
# Also check query count for 'is_certificate_invalid' method.
|
|
with self.assertNumQueries(2):
|
|
self.assertTrue(
|
|
certs_api.is_certificate_invalid(self.student, self.course.id)
|
|
)
|
|
|
|
def _invalidate_certificate(self, certificate, active):
|
|
""" Dry method to mark certificate as invalid. """
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=certificate,
|
|
invalidated_by=self.global_staff,
|
|
active=active
|
|
)
|
|
# Invalidate user certificate
|
|
certificate.invalidate()
|
|
self.assertFalse(certificate.is_valid())
|
|
|
|
def _generate_cert(self, status):
|
|
""" Dry method to generate certificate. """
|
|
return GeneratedCertificateFactory.create(
|
|
user=self.student,
|
|
course_id=self.course.id,
|
|
status=status,
|
|
mode='verified'
|
|
)
|
|
|
|
|
|
@attr(shard=1)
|
|
class CertificateGetTests(SharedModuleStoreTestCase):
|
|
"""Tests for the `test_get_certificate_for_user` helper function. """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(CertificateGetTests, cls).setUpClass()
|
|
cls.student = UserFactory()
|
|
cls.student_no_cert = UserFactory()
|
|
cls.uuid = uuid.uuid4().hex
|
|
cls.web_cert_course = CourseFactory.create(
|
|
org='edx',
|
|
number='verified_1',
|
|
display_name='Verified Course 1',
|
|
cert_html_view_enabled=True
|
|
)
|
|
cls.pdf_cert_course = CourseFactory.create(
|
|
org='edx',
|
|
number='verified_2',
|
|
display_name='Verified Course 2',
|
|
cert_html_view_enabled=False
|
|
)
|
|
# certificate for the first course
|
|
GeneratedCertificateFactory.create(
|
|
user=cls.student,
|
|
course_id=cls.web_cert_course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='verified',
|
|
download_url='www.google.com',
|
|
grade="0.88",
|
|
verify_uuid=cls.uuid,
|
|
)
|
|
# certificate for the second course
|
|
GeneratedCertificateFactory.create(
|
|
user=cls.student,
|
|
course_id=cls.pdf_cert_course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='honor',
|
|
download_url='www.gmail.com',
|
|
grade="0.99",
|
|
verify_uuid=cls.uuid,
|
|
)
|
|
|
|
def test_get_certificate_for_user(self):
|
|
"""
|
|
Test to get a certificate for a user for a specific course.
|
|
"""
|
|
cert = certs_api.get_certificate_for_user(self.student.username, self.web_cert_course.id)
|
|
|
|
self.assertEqual(cert['username'], self.student.username)
|
|
self.assertEqual(cert['course_key'], self.web_cert_course.id)
|
|
self.assertEqual(cert['type'], CourseMode.VERIFIED)
|
|
self.assertEqual(cert['status'], CertificateStatuses.downloadable)
|
|
self.assertEqual(cert['grade'], "0.88")
|
|
self.assertEqual(cert['download_url'], 'www.google.com')
|
|
|
|
def test_get_certificates_for_user(self):
|
|
"""
|
|
Test to get all the certificates for a user
|
|
"""
|
|
certs = certs_api.get_certificates_for_user(self.student.username)
|
|
self.assertEqual(len(certs), 2)
|
|
self.assertEqual(certs[0]['username'], self.student.username)
|
|
self.assertEqual(certs[1]['username'], self.student.username)
|
|
self.assertEqual(certs[0]['course_key'], self.web_cert_course.id)
|
|
self.assertEqual(certs[1]['course_key'], self.pdf_cert_course.id)
|
|
self.assertEqual(certs[0]['type'], CourseMode.VERIFIED)
|
|
self.assertEqual(certs[1]['type'], CourseMode.HONOR)
|
|
self.assertEqual(certs[0]['status'], CertificateStatuses.downloadable)
|
|
self.assertEqual(certs[1]['status'], CertificateStatuses.downloadable)
|
|
self.assertEqual(certs[0]['grade'], '0.88')
|
|
self.assertEqual(certs[1]['grade'], '0.99')
|
|
self.assertEqual(certs[0]['download_url'], 'www.google.com')
|
|
self.assertEqual(certs[1]['download_url'], 'www.gmail.com')
|
|
|
|
def test_no_certificate_for_user(self):
|
|
"""
|
|
Test the case when there is no certificate for a user for a specific course.
|
|
"""
|
|
self.assertIsNone(
|
|
certs_api.get_certificate_for_user(self.student_no_cert.username, self.web_cert_course.id)
|
|
)
|
|
|
|
def test_no_certificates_for_user(self):
|
|
"""
|
|
Test the case when there are no certificates for a user.
|
|
"""
|
|
self.assertEqual(
|
|
certs_api.get_certificates_for_user(self.student_no_cert.username),
|
|
[]
|
|
)
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_get_web_certificate_url(self):
|
|
"""
|
|
Test the get_certificate_url with a web cert course
|
|
"""
|
|
expected_url = reverse(
|
|
'certificates:render_cert_by_uuid',
|
|
kwargs=dict(certificate_uuid=self.uuid)
|
|
)
|
|
cert_url = certs_api.get_certificate_url(
|
|
user_id=self.student.id, # pylint: disable=no-member
|
|
course_id=self.web_cert_course.id,
|
|
uuid=self.uuid
|
|
)
|
|
self.assertEqual(expected_url, cert_url)
|
|
|
|
expected_url = reverse(
|
|
'certificates:html_view',
|
|
kwargs={
|
|
"user_id": str(self.student.id), # pylint: disable=no-member
|
|
"course_id": unicode(self.web_cert_course.id),
|
|
}
|
|
)
|
|
|
|
cert_url = certs_api.get_certificate_url(
|
|
user_id=self.student.id, # pylint: disable=no-member
|
|
course_id=self.web_cert_course.id
|
|
)
|
|
self.assertEqual(expected_url, cert_url)
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_get_pdf_certificate_url(self):
|
|
"""
|
|
Test the get_certificate_url with a pdf cert course
|
|
"""
|
|
cert_url = certs_api.get_certificate_url(
|
|
user_id=self.student.id, # pylint: disable=no-member
|
|
course_id=self.pdf_cert_course.id,
|
|
uuid=self.uuid
|
|
)
|
|
self.assertEqual('www.gmail.com', cert_url)
|
|
|
|
|
|
@attr(shard=1)
|
|
@override_settings(CERT_QUEUE='certificates')
|
|
class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, ModuleStoreTestCase):
|
|
"""Tests for generating certificates for students. """
|
|
|
|
ERROR_REASON = "Kaboom!"
|
|
ENABLED_SIGNALS = ['course_published']
|
|
|
|
def setUp(self): # pylint: disable=arguments-differ
|
|
super(GenerateUserCertificatesTest, self).setUp('certificates.api.tracker')
|
|
|
|
self.student = UserFactory.create(
|
|
email='joe_user@edx.org',
|
|
username='joeuser',
|
|
password='foo'
|
|
)
|
|
self.student_no_cert = UserFactory()
|
|
self.course = CourseFactory.create(
|
|
org='edx',
|
|
number='verified',
|
|
display_name='Verified Course',
|
|
grade_cutoffs={'cutoff': 0.75, 'Pass': 0.5}
|
|
)
|
|
self.enrollment = CourseEnrollment.enroll(self.student, self.course.id, mode='honor')
|
|
self.request_factory = RequestFactory()
|
|
|
|
def test_new_cert_requests_into_xqueue_returns_generating(self):
|
|
with mock_passing_grade():
|
|
with self._mock_queue():
|
|
certs_api.generate_user_certificates(self.student, self.course.id)
|
|
|
|
# Verify that the certificate has status 'generating'
|
|
cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
|
|
self.assertEqual(cert.status, CertificateStatuses.generating)
|
|
self.assert_event_emitted(
|
|
'edx.certificate.created',
|
|
user_id=self.student.id,
|
|
course_id=unicode(self.course.id),
|
|
certificate_url=certs_api.get_certificate_url(self.student.id, self.course.id),
|
|
certificate_id=cert.verify_uuid,
|
|
enrollment_mode=cert.mode,
|
|
generation_mode='batch'
|
|
)
|
|
|
|
def test_xqueue_submit_task_error(self):
|
|
with mock_passing_grade():
|
|
with self._mock_queue(is_successful=False):
|
|
certs_api.generate_user_certificates(self.student, self.course.id)
|
|
|
|
# Verify that the certificate has been marked with status error
|
|
cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
|
|
self.assertEqual(cert.status, 'error')
|
|
self.assertIn(self.ERROR_REASON, cert.error_reason)
|
|
|
|
def test_generate_user_certificates_with_unverified_cert_status(self):
|
|
"""
|
|
Generate user certificate will not raise exception in case of certificate is None.
|
|
"""
|
|
# generate certificate with unverified status.
|
|
GeneratedCertificateFactory.create(
|
|
user=self.student,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.unverified,
|
|
mode='verified'
|
|
)
|
|
|
|
with mock_passing_grade():
|
|
with self._mock_queue(is_successful=False):
|
|
status = certs_api.generate_user_certificates(self.student, self.course.id)
|
|
self.assertEqual(status, None)
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_new_cert_requests_returns_generating_for_html_certificate(self):
|
|
"""
|
|
Test no message sent to Xqueue if HTML certificate view is enabled
|
|
"""
|
|
self._setup_course_certificate()
|
|
with mock_passing_grade():
|
|
certs_api.generate_user_certificates(self.student, self.course.id)
|
|
|
|
# Verify that the certificate has status 'downloadable'
|
|
cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id)
|
|
self.assertEqual(cert.status, CertificateStatuses.downloadable)
|
|
|
|
@patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False})
|
|
def test_cert_url_empty_with_invalid_certificate(self):
|
|
"""
|
|
Test certificate url is empty if html view is not enabled and certificate is not yet generated
|
|
"""
|
|
url = certs_api.get_certificate_url(self.student.id, self.course.id)
|
|
self.assertEqual(url, "")
|
|
|
|
|
|
@attr(shard=1)
|
|
@ddt.ddt
|
|
class CertificateGenerationEnabledTest(EventTestMixin, TestCase):
|
|
"""Test enabling/disabling self-generated certificates for a course. """
|
|
|
|
COURSE_KEY = CourseLocator(org='test', course='test', run='test')
|
|
|
|
def setUp(self): # pylint: disable=arguments-differ
|
|
super(CertificateGenerationEnabledTest, self).setUp('certificates.api.tracker')
|
|
|
|
# Since model-based configuration is cached, we need
|
|
# to clear the cache before each test.
|
|
cache.clear()
|
|
|
|
@ddt.data(
|
|
(None, None, False),
|
|
(False, None, False),
|
|
(False, True, False),
|
|
(True, None, False),
|
|
(True, False, False),
|
|
(True, True, True)
|
|
)
|
|
@ddt.unpack
|
|
def test_cert_generation_enabled(self, is_feature_enabled, is_course_enabled, expect_enabled):
|
|
if is_feature_enabled is not None:
|
|
CertificateGenerationConfiguration.objects.create(enabled=is_feature_enabled)
|
|
|
|
if is_course_enabled is not None:
|
|
certs_api.set_cert_generation_enabled(self.COURSE_KEY, is_course_enabled)
|
|
cert_event_type = 'enabled' if is_course_enabled else 'disabled'
|
|
event_name = '.'.join(['edx', 'certificate', 'generation', cert_event_type])
|
|
self.assert_event_emitted(
|
|
event_name,
|
|
course_id=unicode(self.COURSE_KEY),
|
|
)
|
|
|
|
self._assert_enabled_for_course(self.COURSE_KEY, expect_enabled)
|
|
|
|
def test_latest_setting_used(self):
|
|
# Enable the feature
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
|
|
# Enable for the course
|
|
certs_api.set_cert_generation_enabled(self.COURSE_KEY, True)
|
|
self._assert_enabled_for_course(self.COURSE_KEY, True)
|
|
|
|
# Disable for the course
|
|
certs_api.set_cert_generation_enabled(self.COURSE_KEY, False)
|
|
self._assert_enabled_for_course(self.COURSE_KEY, False)
|
|
|
|
def test_setting_is_course_specific(self):
|
|
# Enable the feature
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
|
|
# Enable for one course
|
|
certs_api.set_cert_generation_enabled(self.COURSE_KEY, True)
|
|
self._assert_enabled_for_course(self.COURSE_KEY, True)
|
|
|
|
# Should be disabled for another course
|
|
other_course = CourseLocator(org='other', course='other', run='other')
|
|
self._assert_enabled_for_course(other_course, False)
|
|
|
|
def _assert_enabled_for_course(self, course_key, expect_enabled):
|
|
"""Check that self-generated certificates are enabled or disabled for the course. """
|
|
actual_enabled = certs_api.cert_generation_enabled(course_key)
|
|
self.assertEqual(expect_enabled, actual_enabled)
|
|
|
|
|
|
@attr(shard=1)
|
|
class GenerateExampleCertificatesTest(TestCase):
|
|
"""Test generation of example certificates. """
|
|
|
|
COURSE_KEY = CourseLocator(org='test', course='test', run='test')
|
|
|
|
def setUp(self):
|
|
super(GenerateExampleCertificatesTest, self).setUp()
|
|
|
|
def test_generate_example_certs(self):
|
|
# Generate certificates for the course
|
|
CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR)
|
|
with self._mock_xqueue() as mock_queue:
|
|
certs_api.generate_example_certificates(self.COURSE_KEY)
|
|
|
|
# Verify that the appropriate certs were added to the queue
|
|
self._assert_certs_in_queue(mock_queue, 1)
|
|
|
|
# Verify that the certificate status is "started"
|
|
self._assert_cert_status({
|
|
'description': 'honor',
|
|
'status': 'started'
|
|
})
|
|
|
|
def test_generate_example_certs_with_verified_mode(self):
|
|
# Create verified and honor modes for the course
|
|
CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug='honor')
|
|
CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug='verified')
|
|
|
|
# Generate certificates for the course
|
|
with self._mock_xqueue() as mock_queue:
|
|
certs_api.generate_example_certificates(self.COURSE_KEY)
|
|
|
|
# Verify that the appropriate certs were added to the queue
|
|
self._assert_certs_in_queue(mock_queue, 2)
|
|
|
|
# Verify that the certificate status is "started"
|
|
self._assert_cert_status(
|
|
{
|
|
'description': 'verified',
|
|
'status': 'started'
|
|
},
|
|
{
|
|
'description': 'honor',
|
|
'status': 'started'
|
|
}
|
|
)
|
|
|
|
@contextmanager
|
|
def _mock_xqueue(self):
|
|
"""Mock the XQueue method for adding a task to the queue. """
|
|
with patch.object(XQueueCertInterface, 'add_example_cert') as mock_queue:
|
|
yield mock_queue
|
|
|
|
def _assert_certs_in_queue(self, mock_queue, expected_num):
|
|
"""Check that the certificate generation task was added to the queue. """
|
|
certs_in_queue = [call_args[0] for (call_args, __) in mock_queue.call_args_list]
|
|
self.assertEqual(len(certs_in_queue), expected_num)
|
|
for cert in certs_in_queue:
|
|
self.assertTrue(isinstance(cert, ExampleCertificate))
|
|
|
|
def _assert_cert_status(self, *expected_statuses):
|
|
"""Check the example certificate status. """
|
|
actual_status = certs_api.example_certificates_status(self.COURSE_KEY)
|
|
self.assertEqual(list(expected_statuses), actual_status)
|
|
|
|
|
|
def set_microsite(domain):
|
|
"""
|
|
returns a decorator that can be used on a test_case to set a specific microsite for the current test case.
|
|
:param domain: Domain of the new microsite
|
|
"""
|
|
def decorator(func):
|
|
"""
|
|
Decorator to set current microsite according to domain
|
|
"""
|
|
@wraps(func)
|
|
def inner(request, *args, **kwargs):
|
|
"""
|
|
Execute the function after setting up the microsite.
|
|
"""
|
|
microsite.set_by_domain(domain)
|
|
return func(request, *args, **kwargs)
|
|
return inner
|
|
return decorator
|
|
|
|
|
|
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
|
@attr(shard=1)
|
|
class CertificatesBrandingTest(TestCase):
|
|
"""Test certificates branding. """
|
|
|
|
COURSE_KEY = CourseLocator(org='test', course='test', run='test')
|
|
|
|
def setUp(self):
|
|
super(CertificatesBrandingTest, self).setUp()
|
|
|
|
@set_microsite(settings.MICROSITE_CONFIGURATION['test_site']['domain_prefix'])
|
|
def test_certificate_header_data(self):
|
|
"""
|
|
Test that get_certificate_header_context from certificates api
|
|
returns data customized according to site branding.
|
|
"""
|
|
# Generate certificates for the course
|
|
CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR)
|
|
data = certs_api.get_certificate_header_context(is_secure=True)
|
|
|
|
# Make sure there are not unexpected keys in dict returned by 'get_certificate_header_context'
|
|
self.assertItemsEqual(
|
|
data.keys(),
|
|
['logo_src', 'logo_url']
|
|
)
|
|
self.assertIn(
|
|
settings.MICROSITE_CONFIGURATION['test_site']['logo_image_url'],
|
|
data['logo_src']
|
|
)
|
|
|
|
self.assertIn(
|
|
settings.MICROSITE_CONFIGURATION['test_site']['SITE_NAME'],
|
|
data['logo_url']
|
|
)
|
|
|
|
@set_microsite(settings.MICROSITE_CONFIGURATION['test_site']['domain_prefix'])
|
|
def test_certificate_footer_data(self):
|
|
"""
|
|
Test that get_certificate_footer_context from certificates api returns
|
|
data customized according to site branding.
|
|
"""
|
|
# Generate certificates for the course
|
|
CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR)
|
|
data = certs_api.get_certificate_footer_context()
|
|
|
|
# Make sure there are not unexpected keys in dict returned by 'get_certificate_footer_context'
|
|
self.assertItemsEqual(
|
|
data.keys(),
|
|
['company_about_url', 'company_privacy_url', 'company_tos_url']
|
|
)
|
|
|
|
# ABOUT is present in MICROSITE_CONFIGURATION['test_site']["urls"] so web certificate will use that url
|
|
self.assertIn(
|
|
settings.MICROSITE_CONFIGURATION['test_site']["urls"]['ABOUT'],
|
|
data['company_about_url']
|
|
)
|
|
|
|
# PRIVACY is present in MICROSITE_CONFIGURATION['test_site']["urls"] so web certificate will use that url
|
|
self.assertIn(
|
|
settings.MICROSITE_CONFIGURATION['test_site']["urls"]['PRIVACY'],
|
|
data['company_privacy_url']
|
|
)
|
|
|
|
# TOS_AND_HONOR is present in MICROSITE_CONFIGURATION['test_site']["urls"],
|
|
# so web certificate will use that url
|
|
self.assertIn(
|
|
settings.MICROSITE_CONFIGURATION['test_site']["urls"]['TOS_AND_HONOR'],
|
|
data['company_tos_url']
|
|
)
|