"""Tests for the certificates Python API. """ from contextlib import contextmanager import ddt from django.test import TestCase, RequestFactory from django.test.utils import override_settings from mock import patch from opaque_keys.edx.locator import CourseLocator from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from student.models import CourseEnrollment from student.tests.factories import UserFactory from course_modes.tests.factories import CourseModeFactory from config_models.models import cache from certificates import api as certs_api from certificates.models import ( CertificateStatuses, CertificateGenerationConfiguration, ExampleCertificate, GeneratedCertificate ) from certificates.queue import XQueueCertInterface, XQueueAddToQueueError from certificates.tests.factories import GeneratedCertificateFactory class CertificateDownloadableStatusTests(ModuleStoreTestCase): """Tests for the `certificate_downloadable_status` helper function. """ 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_user_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, 'download_url': None } ) def test_user_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, 'download_url': None } ) def test_user_with_out_cert(self): self.assertEqual( certs_api.certificate_downloadable_status(self.student_no_cert, self.course.id), { 'is_downloadable': False, 'is_generating': False, 'download_url': None } ) def test_user_with_downloadable_cert(self): 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, 'download_url': 'www.google.com' } ) @override_settings(CERT_QUEUE='certificates') class GenerateUserCertificatesTest(ModuleStoreTestCase): """Tests for generating certificates for students. """ ERROR_REASON = "Kaboom!" def setUp(self): super(GenerateUserCertificatesTest, self).setUp() self.student = UserFactory() 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 self._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.objects.get(user=self.student, course_id=self.course.id) self.assertEqual(cert.status, 'generating') def test_xqueue_submit_task_error(self): with self._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.objects.get(user=self.student, course_id=self.course.id) self.assertEqual(cert.status, 'error') self.assertIn(self.ERROR_REASON, cert.error_reason) @contextmanager def _mock_passing_grade(self): """Mock the grading function to always return a passing grade. """ symbol = 'courseware.grades.grade' with patch(symbol) as mock_grade: mock_grade.return_value = {'grade': 'Pass', 'percent': 0.75} yield @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 @ddt.ddt class CertificateGenerationEnabledTest(TestCase): """Test enabling/disabling self-generated certificates for a course. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') def setUp(self): super(CertificateGenerationEnabledTest, self).setUp() # 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) 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) 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 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(course_id=self.COURSE_KEY, mode_slug='honor') CourseModeFactory(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)