diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 2872ea2708..b6200b3007 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -183,7 +183,7 @@ def certificate_downloadable_status(student, course_key): if current_status['status'] == CertificateStatuses.downloadable: response_data['is_downloadable'] = True - response_data['download_url'] = current_status['download_url'] + response_data['download_url'] = get_certificate_url(student.id, course_key) return response_data diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index d264a67a93..ade38ceb67 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -31,8 +31,57 @@ 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_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 + + 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(ModuleStoreTestCase): +class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTestCase): """Tests for the `certificate_downloadable_status` helper function. """ def setUp(self): @@ -48,7 +97,7 @@ class CertificateDownloadableStatusTests(ModuleStoreTestCase): self.request_factory = RequestFactory() - def test_user_cert_status_with_generating(self): + def test_cert_status_with_generating(self): GeneratedCertificateFactory.create( user=self.student, course_id=self.course.id, @@ -65,7 +114,7 @@ class CertificateDownloadableStatusTests(ModuleStoreTestCase): } ) - def test_user_cert_status_with_error(self): + def test_cert_status_with_error(self): GeneratedCertificateFactory.create( user=self.student, course_id=self.course.id, @@ -82,7 +131,7 @@ class CertificateDownloadableStatusTests(ModuleStoreTestCase): } ) - def test_user_with_out_cert(self): + def test_without_cert(self): self.assertEqual( certs_api.certificate_downloadable_status(self.student_no_cert, self.course.id), { @@ -92,7 +141,7 @@ class CertificateDownloadableStatusTests(ModuleStoreTestCase): } ) - def test_user_with_downloadable_cert(self): + def test_with_downloadable_pdf_cert(self): GeneratedCertificateFactory.create( user=self.student, course_id=self.course.id, @@ -110,10 +159,29 @@ class CertificateDownloadableStatusTests(ModuleStoreTestCase): } ) + @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 self._mock_passing_grade(): + certs_api.generate_user_certificates(self.student, self.course.id) + + self.assertEqual( + certs_api.certificate_downloadable_status(self.student, self.course.id), + { + 'is_downloadable': True, + 'is_generating': 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, + ), + } + ) + @attr('shard_1') @override_settings(CERT_QUEUE='certificates') -class GenerateUserCertificatesTest(EventTestMixin, ModuleStoreTestCase): +class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, ModuleStoreTestCase): """Tests for generating certificates for students. """ ERROR_REASON = "Kaboom!" @@ -185,46 +253,6 @@ class GenerateUserCertificatesTest(EventTestMixin, ModuleStoreTestCase): url = certs_api.get_certificate_url(self.student.id, self.course.id) self.assertEqual(url, "") - @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 - - 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') @ddt.ddt diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index 31de1c10b7..a509e0098c 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -8,7 +8,7 @@ from django.template import defaultfilters from courseware.access import has_access from student.models import CourseEnrollment, User -from certificates.models import certificate_status_for_student, CertificateStatuses +from certificates.api import certificate_downloadable_status from xmodule.course_module import DEFAULT_START_DATE @@ -102,10 +102,12 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): def get_certificate(self, model): """Returns the information about the user's certificate in the course.""" - certificate_info = certificate_status_for_student(model.user, model.course_id) - if certificate_info['status'] == CertificateStatuses.downloadable: + certificate_info = certificate_downloadable_status(model.user, model.course_id) + if certificate_info['is_downloadable']: return { - "url": certificate_info['download_url'], + 'url': self.context['request'].build_absolute_uri( + certificate_info['download_url'] + ), } else: return {} diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 84b333d74d..ae188ff729 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -1,6 +1,7 @@ """ Tests for users API """ +# pylint: disable=no-member import datetime import ddt from mock import patch @@ -11,6 +12,7 @@ from django.utils import timezone from django.template import defaultfilters from django.test import RequestFactory +from certificates.api import generate_user_certificates from certificates.models import CertificateStatuses from certificates.tests.factories import GeneratedCertificateFactory from courseware.access_response import ( @@ -184,7 +186,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest certificate_data = response.data[0]['certificate'] self.assertDictEqual(certificate_data, {}) - def test_certificate(self): + def test_pdf_certificate(self): self.login_and_enroll() certificate_url = "http://test_certificate_url" @@ -200,6 +202,27 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest certificate_data = response.data[0]['certificate'] self.assertEquals(certificate_data['url'], certificate_url) + @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True}) + def test_web_certificate(self): + self.login_and_enroll() + + self.course.cert_html_view_enabled = True + self.store.update_item(self.course, self.user.id) + + with patch('courseware.grades.grade') as mock_grade: + mock_grade.return_value = {'grade': 'Pass', 'percent': 0.75} + generate_user_certificates(self.user, self.course.id) + + response = self.api_response() + certificate_data = response.data[0]['certificate'] + self.assertRegexpMatches( + certificate_data['url'], + r'http.*/certificates/user/{user_id}/course/{course_id}'.format( + user_id=self.user.id, + course_id=self.course.id, + ) + ) + def test_no_facebook_url(self): self.login_and_enroll()