Esempio n. 1
0
def generate_example_certificates(course_key):
    """Generate example certificates for a course.

    Example certificates are used to validate that certificates
    are configured correctly for the course.  Staff members can
    view the example certificates before enabling
    the self-generated certificates button for students.

    Several example certificates may be generated for a course.
    For example, if a course offers both verified and honor certificates,
    examples of both types of certificate will be generated.

    If an error occurs while starting the certificate generation
    job, the errors will be recorded in the database and
    can be retrieved using `example_certificate_status()`.

    Arguments:
        course_key (CourseKey): The course identifier.

    Returns:
        None

    """
    xqueue = XQueueCertInterface()
    for cert in ExampleCertificateSet.create_example_set(course_key):
        xqueue.add_example_cert(cert)
Esempio n. 2
0
def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch',
                               forced_grade=None):
    """
    It will add the add-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'. It also emits
    `edx.certificate.created` event for analytics.

    Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        course (Course): Optionally provide the course object; if not provided
            it will be loaded.
        insecure - (Boolean)
        generation_mode - who has requested certificate generation. Its value should `batch`
        in case of django command and `self` if student initiated the request.
        forced_grade - a string indicating to replace grade parameter. if present grading
                       will be skipped.
    """
    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    if not course:
        course = modulestore().get_course(course_key, depth=0)

    generate_pdf = not has_html_certificates_enabled(course)

    cert = xqueue.add_cert(
        student,
        course_key,
        course=course,
        generate_pdf=generate_pdf,
        forced_grade=forced_grade
    )
    # If cert_status is not present in certificate valid_statuses (for example unverified) then
    # add_cert returns None and raises AttributeError while accesing cert attributes.
    if cert is None:
        return

    if CertificateStatuses.is_passing_status(cert.status):
        emit_certificate_event('created', student, course_key, course, {
            'user_id': student.id,
            'course_id': unicode(course_key),
            'certificate_id': cert.verify_uuid,
            'enrollment_mode': cert.mode,
            'generation_mode': generation_mode
        })
    return cert.status
Esempio n. 3
0
def regenerate_user_certificates(student, course_key, course=None,
                                 forced_grade=None, template_file=None, insecure=False):
    """
    It will add the regen-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'.

    Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        course (Course): Optionally provide the course object; if not provided
            it will be loaded.
        grade_value - The grade string, such as "Distinction"
        template_file - The template file used to render this certificate
        insecure - (Boolean)
    """
    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    if not course:
        course = modulestore().get_course(course_key, depth=0)

    generate_pdf = not has_html_certificates_enabled(course)
    log.info(
        "Started regenerating certificates for user %s in course %s with generate_pdf status: %s",
        student.username, unicode(course_key), generate_pdf
    )

    return xqueue.regen_cert(
        student,
        course_key,
        course=course,
        forced_grade=forced_grade,
        template_file=template_file,
        generate_pdf=generate_pdf
    )
Esempio n. 4
0
 def setUp(self):
     super(XQueueCertInterfaceAddCertificateTest, self).setUp()
     self.user = UserFactory.create()
     self.course = CourseFactory.create()
     self.enrollment = CourseEnrollmentFactory(
         user=self.user,
         course_id=self.course.id,
         is_active=True,
         mode="honor",
     )
     self.xqueue = XQueueCertInterface()
     self.user_2 = UserFactory.create()
     SoftwareSecurePhotoVerificationFactory.create(user=self.user_2, status='approved')
Esempio n. 5
0
 def setUp(self):
     super().setUp()
     self.xqueue = XQueueCertInterface()
Esempio n. 6
0
class XQueueCertInterfaceExampleCertificateTest(TestCase):
    """Tests for the XQueue interface for certificate generation. """

    COURSE_KEY = CourseLocator(org='test', course='test', run='test')

    TEMPLATE = 'test.pdf'
    DESCRIPTION = 'test'
    ERROR_MSG = 'Kaboom!'

    def setUp(self):
        super().setUp()
        self.xqueue = XQueueCertInterface()

    def test_add_example_cert(self):
        cert = self._create_example_cert()
        with self._mock_xqueue() as mock_send:
            self.xqueue.add_example_cert(cert)

        # Verify that the correct payload was sent to the XQueue
        self._assert_queue_task(mock_send, cert)

        # Verify the certificate status
        assert cert.status == ExampleCertificate.STATUS_STARTED

    def test_add_example_cert_error(self):
        cert = self._create_example_cert()
        with self._mock_xqueue(success=False):
            self.xqueue.add_example_cert(cert)

        # Verify the error status of the certificate
        assert cert.status == ExampleCertificate.STATUS_ERROR
        assert self.ERROR_MSG in cert.error_reason

    def _create_example_cert(self):
        """Create an example certificate. """
        cert_set = ExampleCertificateSet.objects.create(course_key=self.COURSE_KEY)
        return ExampleCertificate.objects.create(
            example_cert_set=cert_set,
            description=self.DESCRIPTION,
            template=self.TEMPLATE
        )

    @contextmanager
    def _mock_xqueue(self, success=True):
        """Mock the XQueue method for sending a task to the queue. """
        with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
            mock_send.return_value = (0, None) if success else (1, self.ERROR_MSG)
            yield mock_send

    def _assert_queue_task(self, mock_send, cert):
        """Check that the task was added to the queue. """
        expected_header = {
            'lms_key': cert.access_key,
            'lms_callback_url': f'https://edx.org/update_example_certificate?key={cert.uuid}',
            'queue_name': 'certificates'
        }

        expected_body = {
            'action': 'create',
            'username': cert.uuid,
            'name': 'John Doë',
            'course_id': str(self.COURSE_KEY),
            'template_pdf': 'test.pdf',
            'example_certificate': True
        }

        assert mock_send.called

        __, kwargs = mock_send.call_args_list[0]
        actual_header = json.loads(kwargs['header'])
        actual_body = json.loads(kwargs['body'])

        assert expected_header == actual_header
        assert expected_body == actual_body
Esempio n. 7
0
def generate_user_certificates(student,
                               course_key,
                               course=None,
                               insecure=False,
                               generation_mode='batch',
                               forced_grade=None):
    """
    It will add the add-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'. It also emits
    `edx.certificate.created` event for analytics.

    Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        course (Course): Optionally provide the course object; if not provided
            it will be loaded.
        insecure - (Boolean)
        generation_mode - who has requested certificate generation. Its value should `batch`
        in case of django command and `self` if student initiated the request.
        forced_grade - a string indicating to replace grade parameter. if present grading
                       will be skipped.
    """

    if not course:
        course = modulestore().get_course(course_key, depth=0)

    beta_testers_queryset = list_with_level(course, u'beta')

    if beta_testers_queryset.filter(username=student.username):
        message = u'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.'
        log.info(message.format(course_key, student.username))
        return

    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    generate_pdf = not has_html_certificates_enabled(course)

    cert = xqueue.add_cert(student,
                           course_key,
                           course=course,
                           generate_pdf=generate_pdf,
                           forced_grade=forced_grade)

    message = u'Queued Certificate Generation task for {user} : {course}'
    log.info(message.format(user=student.id, course=course_key))

    # If cert_status is not present in certificate valid_statuses (for example unverified) then
    # add_cert returns None and raises AttributeError while accesing cert attributes.
    if cert is None:
        return

    if CertificateStatuses.is_passing_status(cert.status):
        emit_certificate_event(
            'created', student, course_key, course, {
                'user_id': student.id,
                'course_id': six.text_type(course_key),
                'certificate_id': cert.verify_uuid,
                'enrollment_mode': cert.mode,
                'generation_mode': generation_mode
            })
    return cert.status
def generate_user_certificates(student,
                               course_key,
                               course=None,
                               insecure=False,
                               generation_mode='batch',
                               forced_grade=None):
    """
    It will add the add-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'. It also emits
    `edx.certificate.created` event for analytics.

    This method has not yet been updated (it predates the certificates revamp). If modifying this method,
    see also generate_user_certificates() in generation.py (which is very similar but is called from a celery task).
    In the future these methods will be unified.

    Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        course (Course): Optionally provide the course object; if not provided
            it will be loaded.
        insecure - (Boolean)
        generation_mode - who has requested certificate generation. Its value should `batch`
        in case of django command and `self` if student initiated the request.
        forced_grade - a string indicating to replace grade parameter. if present grading
                       will be skipped.
    """
    if is_using_certificate_allowlist_and_is_on_allowlist(student, course_key):
        # Note that this will launch an asynchronous task, and so cannot return the certificate status. This is a
        # change from the older certificate code that tries to immediately create a cert.
        log.info(
            f'{course_key} is using allowlist certificates, and the user {student.id} is on its allowlist. '
            f'Attempt will be made to regenerate an allowlist certificate.')
        return generate_allowlist_certificate_task(student, course_key)

    if not course:
        course = modulestore().get_course(course_key, depth=0)

    beta_testers_queryset = list_with_level(course, 'beta')

    if beta_testers_queryset.filter(username=student.username):
        message = 'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.'
        log.info(message.format(student.username, course_key))
        return

    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    generate_pdf = not has_html_certificates_enabled(course)

    cert = xqueue.add_cert(student,
                           course_key,
                           course=course,
                           generate_pdf=generate_pdf,
                           forced_grade=forced_grade)

    message = 'Queued Certificate Generation task for {user} : {course}'
    log.info(message.format(user=student.id, course=course_key))

    # If cert_status is not present in certificate valid_statuses (for example unverified) then
    # add_cert returns None and raises AttributeError while accessing cert attributes.
    if cert is None:
        return

    if CertificateStatuses.is_passing_status(cert.status):
        emit_certificate_event(
            'created', student, course_key, course, {
                'user_id': student.id,
                'course_id': str(course_key),
                'certificate_id': cert.verify_uuid,
                'enrollment_mode': cert.mode,
                'generation_mode': generation_mode
            })
    return cert.status
Esempio n. 9
0
class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
    """Test the "add to queue" operation of the XQueue interface. """
    def setUp(self):
        super().setUp()
        self.user = UserFactory.create()
        self.course = CourseFactory.create()
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            is_active=True,
            mode="honor",
        )
        self.xqueue = XQueueCertInterface()
        self.user_2 = UserFactory.create()
        SoftwareSecurePhotoVerificationFactory.create(user=self.user_2,
                                                      status='approved')

    def test_add_cert_callback_url(self):

        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user, self.course.id)

        # Verify that the task was sent to the queue with the correct callback URL
        assert mock_send.called
        __, kwargs = mock_send.call_args_list[0]
        actual_header = json.loads(kwargs['header'])
        assert 'https://edx.org/update_certificate?key=' in actual_header[
            'lms_callback_url']

    def test_no_create_action_in_queue_for_html_view_certs(self):
        """
        Tests there is no certificate create message in the queue if generate_pdf is False
        """
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                self.xqueue.add_cert(self.user,
                                     self.course.id,
                                     generate_pdf=False)

        # Verify that add_cert method does not add message to queue
        assert not mock_send.called
        certificate = GeneratedCertificate.eligible_certificates.get(
            user=self.user, course_id=self.course.id)
        assert certificate.status == CertificateStatuses.downloadable
        assert certificate.verify_uuid is not None

    @ddt.data('honor', 'audit')
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) -
                       timedelta(days=1))
    def test_add_cert_with_honor_certificates(self, mode):
        """Test certificates generations for honor and audit modes."""
        template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format(
            id=self.course.id)
        mock_send = self.add_cert_to_queue(mode)
        if modes_api.is_eligible_for_certificate(mode):
            self.assert_certificate_generated(mock_send, mode, template_name)
        else:
            self.assert_ineligible_certificate_generated(mock_send, mode)

    @ddt.data('credit', 'verified')
    def test_add_cert_with_verified_certificates(self, mode):
        """Test if enrollment mode is verified or credit along with valid
        software-secure verification than verified certificate should be generated.
        """
        template_name = 'certificate-template-{id.org}-{id.course}-verified.pdf'.format(
            id=self.course.id)

        mock_send = self.add_cert_to_queue(mode)
        self.assert_certificate_generated(mock_send, 'verified', template_name)

    @ddt.data((True, CertificateStatuses.audit_passing),
              (False, CertificateStatuses.generating))
    @ddt.unpack
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) -
                       timedelta(days=1))
    def test_ineligible_cert_allowlisted(self, disable_audit_cert, status):
        """
        Test that audit mode students receive a certificate if DISABLE_AUDIT_CERTIFICATES
        feature is set to false
        """
        # Enroll as audit
        CourseEnrollmentFactory(user=self.user_2,
                                course_id=self.course.id,
                                is_active=True,
                                mode='audit')
        # Add student to the allowlist
        CertificateAllowlistFactory(course_id=self.course.id, user=self.user_2)

        features = settings.FEATURES
        features['DISABLE_AUDIT_CERTIFICATES'] = disable_audit_cert
        with override_settings(FEATURES=features) and mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        certificate = GeneratedCertificate.certificate_for_student(
            self.user_2, self.course.id)
        assert certificate is not None
        assert certificate.mode == 'audit'
        assert certificate.status == status

    def add_cert_to_queue(self, mode):
        """
        Dry method for course enrollment and adding request to
        queue. Returns a mock object containing information about the
        `XQueueInterface.send_to_queue` method, which can be used in other
        assertions.
        """
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=mode,
        )
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)
                return mock_send

    def assert_certificate_generated(self, mock_send, expected_mode,
                                     expected_template_name):
        """
        Assert that a certificate was generated with the correct mode and
        template type.
        """
        # Verify that the task was sent to the queue with the correct callback URL
        assert mock_send.called
        __, kwargs = mock_send.call_args_list[0]

        actual_header = json.loads(kwargs['header'])
        assert 'https://edx.org/update_certificate?key=' in actual_header[
            'lms_callback_url']

        body = json.loads(kwargs['body'])
        assert expected_template_name in body['template_pdf']

        certificate = GeneratedCertificate.eligible_certificates.get(
            user=self.user_2, course_id=self.course.id)
        assert certificate.mode == expected_mode

    def assert_ineligible_certificate_generated(self, mock_send,
                                                expected_mode):
        """
        Assert that an ineligible certificate was generated with the
        correct mode.
        """
        # Ensure the certificate was not generated
        assert not mock_send.called

        certificate = GeneratedCertificate.objects.get(
            user=self.user_2, course_id=self.course.id)

        assert certificate.status in (CertificateStatuses.audit_passing,
                                      CertificateStatuses.audit_notpassing)
        assert certificate.mode == expected_mode

    @ddt.data(
        (CertificateStatuses.restricted, False),
        (CertificateStatuses.deleting, False),
        (CertificateStatuses.generating, True),
        (CertificateStatuses.unavailable, True),
        (CertificateStatuses.deleted, True),
        (CertificateStatuses.error, True),
        (CertificateStatuses.notpassing, True),
        (CertificateStatuses.downloadable, True),
        (CertificateStatuses.auditing, True),
    )
    @ddt.unpack
    def test_add_cert_statuses(self, status, should_generate):
        """
        Test that certificates can or cannot be generated with the given
        certificate status.
        """
        with patch(
                'lms.djangoapps.certificates.queue.certificate_status_for_student',
                Mock(return_value={'status': status})):
            mock_send = self.add_cert_to_queue('verified')
            if should_generate:
                assert mock_send.called
            else:
                assert not mock_send.called

    @ddt.data(
        # Eligible and should stay that way
        (CertificateStatuses.downloadable, timedelta(days=-2), 'Pass',
         CertificateStatuses.generating),
        # Ensure that certs in the wrong state can be fixed by regeneration
        (CertificateStatuses.downloadable, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # Ineligible and should stay that way
        (CertificateStatuses.audit_passing, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # As above
        (CertificateStatuses.audit_notpassing, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # As above
        (CertificateStatuses.audit_notpassing, timedelta(hours=-1), None,
         CertificateStatuses.audit_notpassing),
    )
    @ddt.unpack
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) -
                       timedelta(days=1))
    def test_regen_audit_certs_eligibility(self, status, created_delta, grade,
                                           expected_status):
        """
        Test that existing audit certificates remain eligible even if cert
        generation is re-run.
        """
        # Create an existing audit enrollment and certificate
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=CourseMode.AUDIT,
        )
        created_date = datetime.now(pytz.UTC) + created_delta
        with freezegun.freeze_time(created_date):
            GeneratedCertificateFactory(
                user=self.user_2,
                course_id=self.course.id,
                grade='1.0',
                status=status,
                mode=GeneratedCertificate.MODES.audit,
            )

        # Run grading/cert generation again
        with mock_passing_grade(letter_grade=grade):
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        assert GeneratedCertificate.objects.get(
            user=self.user_2,
            course_id=self.course.id).status == expected_status

    def test_regen_cert_with_pdf_certificate(self):
        """
        Test that regenerating a PDF certificate logs a warning message and the certificate
        status remains unchanged.
        """
        download_url = 'http://www.example.com/certificate.pdf'
        # Create an existing verified enrollment and certificate
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=CourseMode.VERIFIED,
        )
        GeneratedCertificateFactory(user=self.user_2,
                                    course_id=self.course.id,
                                    grade='1.0',
                                    status=CertificateStatuses.downloadable,
                                    mode=GeneratedCertificate.MODES.verified,
                                    download_url=download_url)

        self._assert_pdf_cert_generation_discontinued_logs(download_url)

    def test_add_cert_with_existing_pdf_certificate(self):
        """
        Test that adding a certificate for existing PDF certificates logs a  warning
        message and the certificate status remains unchanged.
        """
        download_url = 'http://www.example.com/certificate.pdf'
        # Create an existing verified enrollment and certificate
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=CourseMode.VERIFIED,
        )
        GeneratedCertificateFactory(user=self.user_2,
                                    course_id=self.course.id,
                                    grade='1.0',
                                    status=CertificateStatuses.downloadable,
                                    mode=GeneratedCertificate.MODES.verified,
                                    download_url=download_url)

        self._assert_pdf_cert_generation_discontinued_logs(download_url,
                                                           add_cert=True)

    def _assert_pdf_cert_generation_discontinued_logs(self,
                                                      download_url,
                                                      add_cert=False):
        """Assert PDF certificate generation discontinued logs."""
        with LogCapture(LOGGER.name) as log:
            if add_cert:
                self.xqueue.add_cert(self.user_2, self.course.id)
            else:
                self.xqueue.regen_cert(self.user_2, self.course.id)
            log.check_present(
                (LOGGER.name, 'WARNING',
                 ("PDF certificate generation discontinued, canceling "
                  "PDF certificate generation for student {student_id} "
                  "in course '{course_id}' "
                  "with status '{status}' "
                  "and download_url '{download_url}'.").format(
                      student_id=self.user_2.id,
                      course_id=str(self.course.id),
                      status=CertificateStatuses.downloadable,
                      download_url=download_url)))
Esempio n. 10
0
def generate_user_certificates(student,
                               course_key,
                               course=None,
                               insecure=False,
                               generation_mode='batch',
                               forced_grade=None):
    """
    It will add the add-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'. It also emits
    `edx.certificate.created` event for analytics.

    This method has not yet been updated (it predates the certificates revamp). If modifying this method,
    see also generate_user_certificates() in generation_handler.py (which is very similar but is not called from a
    celery task). In the future these methods will be unified.

   Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        course (Course): Optionally provide the course object; if not provided
            it will be loaded.
        insecure - (Boolean)
        generation_mode - who has requested certificate generation. Its value should `batch`
        in case of django command and `self` if student initiated the request.
        forced_grade - a string indicating to replace grade parameter. if present grading
                       will be skipped.
    """
    beta_testers_queryset = list_with_level_from_course_key(course_key, 'beta')
    if beta_testers_queryset.filter(username=student.username):
        log.info(
            f"Canceling Certificate Generation task for user {student.id} : {course_key}. User is a Beta Tester."
        )
        return

    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    course_overview = get_course_overview(course_key)
    generate_pdf = not has_html_certificates_enabled_from_course_overview(
        course_overview)

    cert = xqueue.add_cert(student,
                           course_key,
                           course=course,
                           generate_pdf=generate_pdf,
                           forced_grade=forced_grade)

    log.info(
        f"Queued Certificate Generation task for {student.id} : {course_key}")

    # If cert_status is not present in certificate valid_statuses (for example unverified) then
    # add_cert returns None and raises AttributeError while accessing cert attributes.
    if cert is None:
        return

    if CertificateStatuses.is_passing_status(cert.status):
        emit_certificate_event(
            'created', student, course_key, course_overview, {
                'user_id': student.id,
                'course_id': str(course_key),
                'certificate_id': cert.verify_uuid,
                'enrollment_mode': cert.mode,
                'generation_mode': generation_mode
            })
    return cert.status
Esempio n. 11
0
class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
    """Test the "add to queue" operation of the XQueue interface. """
    shard = 1

    def setUp(self):
        super(XQueueCertInterfaceAddCertificateTest, self).setUp()
        self.user = UserFactory.create()
        self.course = CourseFactory.create()
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            is_active=True,
            mode="honor",
        )
        self.xqueue = XQueueCertInterface()
        self.user_2 = UserFactory.create()
        SoftwareSecurePhotoVerificationFactory.create(user=self.user_2,
                                                      status='approved')

    def test_add_cert_callback_url(self):

        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user, self.course.id)

        # Verify that the task was sent to the queue with the correct callback URL
        self.assertTrue(mock_send.called)
        __, kwargs = mock_send.call_args_list[0]
        actual_header = json.loads(kwargs['header'])
        self.assertIn('https://edx.org/update_certificate?key=',
                      actual_header['lms_callback_url'])

    def test_no_create_action_in_queue_for_html_view_certs(self):
        """
        Tests there is no certificate create message in the queue if generate_pdf is False
        """
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                self.xqueue.add_cert(self.user,
                                     self.course.id,
                                     generate_pdf=False)

        # Verify that add_cert method does not add message to queue
        self.assertFalse(mock_send.called)
        certificate = GeneratedCertificate.eligible_certificates.get(
            user=self.user, course_id=self.course.id)
        self.assertEqual(certificate.status, CertificateStatuses.downloadable)
        self.assertIsNotNone(certificate.verify_uuid)

    @ddt.data('honor', 'audit')
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) -
                       timedelta(days=1))
    def test_add_cert_with_honor_certificates(self, mode):
        """Test certificates generations for honor and audit modes."""
        template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format(
            id=self.course.id)
        mock_send = self.add_cert_to_queue(mode)
        if CourseMode.is_eligible_for_certificate(mode):
            self.assert_certificate_generated(mock_send, mode, template_name)
        else:
            self.assert_ineligible_certificate_generated(mock_send, mode)

    @ddt.data('credit', 'verified')
    def test_add_cert_with_verified_certificates(self, mode):
        """Test if enrollment mode is verified or credit along with valid
        software-secure verification than verified certificate should be generated.
        """
        template_name = 'certificate-template-{id.org}-{id.course}-verified.pdf'.format(
            id=self.course.id)

        mock_send = self.add_cert_to_queue(mode)
        self.assert_certificate_generated(mock_send, 'verified', template_name)

    def test_ineligible_cert_whitelisted(self):
        """Test that audit mode students can receive a certificate if they are whitelisted."""
        # Enroll as audit
        CourseEnrollmentFactory(user=self.user_2,
                                course_id=self.course.id,
                                is_active=True,
                                mode='audit')
        # Whitelist student
        CertificateWhitelistFactory(course_id=self.course.id, user=self.user_2)

        # Generate certs
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        # Assert cert generated correctly
        self.assertTrue(mock_send.called)
        certificate = GeneratedCertificate.certificate_for_student(
            self.user_2, self.course.id)
        self.assertIsNotNone(certificate)
        self.assertEqual(certificate.mode, 'audit')

    def add_cert_to_queue(self, mode):
        """
        Dry method for course enrollment and adding request to
        queue. Returns a mock object containing information about the
        `XQueueInterface.send_to_queue` method, which can be used in other
        assertions.
        """
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=mode,
        )
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)
                return mock_send

    def assert_certificate_generated(self, mock_send, expected_mode,
                                     expected_template_name):
        """
        Assert that a certificate was generated with the correct mode and
        template type.
        """
        # Verify that the task was sent to the queue with the correct callback URL
        self.assertTrue(mock_send.called)
        __, kwargs = mock_send.call_args_list[0]

        actual_header = json.loads(kwargs['header'])
        self.assertIn('https://edx.org/update_certificate?key=',
                      actual_header['lms_callback_url'])

        body = json.loads(kwargs['body'])
        self.assertIn(expected_template_name, body['template_pdf'])

        certificate = GeneratedCertificate.eligible_certificates.get(
            user=self.user_2, course_id=self.course.id)
        self.assertEqual(certificate.mode, expected_mode)

    def assert_ineligible_certificate_generated(self, mock_send,
                                                expected_mode):
        """
        Assert that an ineligible certificate was generated with the
        correct mode.
        """
        # Ensure the certificate was not generated
        self.assertFalse(mock_send.called)

        certificate = GeneratedCertificate.objects.get(
            user=self.user_2, course_id=self.course.id)

        self.assertIn(certificate.status,
                      (CertificateStatuses.audit_passing,
                       CertificateStatuses.audit_notpassing))
        self.assertEqual(certificate.mode, expected_mode)

    @ddt.data(
        (CertificateStatuses.restricted, False),
        (CertificateStatuses.deleting, False),
        (CertificateStatuses.generating, True),
        (CertificateStatuses.unavailable, True),
        (CertificateStatuses.deleted, True),
        (CertificateStatuses.error, True),
        (CertificateStatuses.notpassing, True),
        (CertificateStatuses.downloadable, True),
        (CertificateStatuses.auditing, True),
    )
    @ddt.unpack
    def test_add_cert_statuses(self, status, should_generate):
        """
        Test that certificates can or cannot be generated with the given
        certificate status.
        """
        with patch(
                'lms.djangoapps.certificates.queue.certificate_status_for_student',
                Mock(return_value={'status': status})):
            mock_send = self.add_cert_to_queue('verified')
            if should_generate:
                self.assertTrue(mock_send.called)
            else:
                self.assertFalse(mock_send.called)

    @ddt.data(
        # Eligible and should stay that way
        (CertificateStatuses.downloadable, timedelta(days=-2), 'Pass',
         CertificateStatuses.generating),
        # Ensure that certs in the wrong state can be fixed by regeneration
        (CertificateStatuses.downloadable, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # Ineligible and should stay that way
        (CertificateStatuses.audit_passing, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # As above
        (CertificateStatuses.audit_notpassing, timedelta(hours=-1), 'Pass',
         CertificateStatuses.audit_passing),
        # As above
        (CertificateStatuses.audit_notpassing, timedelta(hours=-1), None,
         CertificateStatuses.audit_notpassing),
    )
    @ddt.unpack
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) -
                       timedelta(days=1))
    def test_regen_audit_certs_eligibility(self, status, created_delta, grade,
                                           expected_status):
        """
        Test that existing audit certificates remain eligible even if cert
        generation is re-run.
        """
        # Create an existing audit enrollment and certificate
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=CourseMode.AUDIT,
        )
        created_date = datetime.now(pytz.UTC) + created_delta
        with freezegun.freeze_time(created_date):
            GeneratedCertificateFactory(
                user=self.user_2,
                course_id=self.course.id,
                grade='1.0',
                status=status,
                mode=GeneratedCertificate.MODES.audit,
            )

        # Run grading/cert generation again
        with mock_passing_grade(letter_grade=grade):
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        self.assertEqual(
            GeneratedCertificate.objects.get(user=self.user_2,
                                             course_id=self.course.id).status,
            expected_status)
Esempio n. 12
0
 def setUp(self):
     super(XQueueCertInterfaceExampleCertificateTest, self).setUp()
     self.xqueue = XQueueCertInterface()
Esempio n. 13
0
class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
    """Test the "add to queue" operation of the XQueue interface. """
    shard = 1

    def setUp(self):
        super(XQueueCertInterfaceAddCertificateTest, self).setUp()
        self.user = UserFactory.create()
        self.course = CourseFactory.create()
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            is_active=True,
            mode="honor",
        )
        self.xqueue = XQueueCertInterface()
        self.user_2 = UserFactory.create()
        SoftwareSecurePhotoVerificationFactory.create(user=self.user_2, status='approved')

    def test_add_cert_callback_url(self):

        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user, self.course.id)

        # Verify that the task was sent to the queue with the correct callback URL
        self.assertTrue(mock_send.called)
        __, kwargs = mock_send.call_args_list[0]
        actual_header = json.loads(kwargs['header'])
        self.assertIn('https://edx.org/update_certificate?key=', actual_header['lms_callback_url'])

    def test_no_create_action_in_queue_for_html_view_certs(self):
        """
        Tests there is no certificate create message in the queue if generate_pdf is False
        """
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                self.xqueue.add_cert(self.user, self.course.id, generate_pdf=False)

        # Verify that add_cert method does not add message to queue
        self.assertFalse(mock_send.called)
        certificate = GeneratedCertificate.eligible_certificates.get(user=self.user, course_id=self.course.id)
        self.assertEqual(certificate.status, CertificateStatuses.downloadable)
        self.assertIsNotNone(certificate.verify_uuid)

    @ddt.data('honor', 'audit')
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) - timedelta(days=1))
    def test_add_cert_with_honor_certificates(self, mode):
        """Test certificates generations for honor and audit modes."""
        template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format(
            id=self.course.id
        )
        mock_send = self.add_cert_to_queue(mode)
        if CourseMode.is_eligible_for_certificate(mode):
            self.assert_certificate_generated(mock_send, mode, template_name)
        else:
            self.assert_ineligible_certificate_generated(mock_send, mode)

    @ddt.data('credit', 'verified')
    def test_add_cert_with_verified_certificates(self, mode):
        """Test if enrollment mode is verified or credit along with valid
        software-secure verification than verified certificate should be generated.
        """
        template_name = 'certificate-template-{id.org}-{id.course}-verified.pdf'.format(
            id=self.course.id
        )

        mock_send = self.add_cert_to_queue(mode)
        self.assert_certificate_generated(mock_send, 'verified', template_name)

    def test_ineligible_cert_whitelisted(self):
        """Test that audit mode students can receive a certificate if they are whitelisted."""
        # Enroll as audit
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode='audit'
        )
        # Whitelist student
        CertificateWhitelistFactory(course_id=self.course.id, user=self.user_2)

        # Generate certs
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        # Assert cert generated correctly
        self.assertTrue(mock_send.called)
        certificate = GeneratedCertificate.certificate_for_student(self.user_2, self.course.id)
        self.assertIsNotNone(certificate)
        self.assertEqual(certificate.mode, 'audit')

    def add_cert_to_queue(self, mode):
        """
        Dry method for course enrollment and adding request to
        queue. Returns a mock object containing information about the
        `XQueueInterface.send_to_queue` method, which can be used in other
        assertions.
        """
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=mode,
        )
        with mock_passing_grade():
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)
                return mock_send

    def assert_certificate_generated(self, mock_send, expected_mode, expected_template_name):
        """
        Assert that a certificate was generated with the correct mode and
        template type.
        """
        # Verify that the task was sent to the queue with the correct callback URL
        self.assertTrue(mock_send.called)
        __, kwargs = mock_send.call_args_list[0]

        actual_header = json.loads(kwargs['header'])
        self.assertIn('https://edx.org/update_certificate?key=', actual_header['lms_callback_url'])

        body = json.loads(kwargs['body'])
        self.assertIn(expected_template_name, body['template_pdf'])

        certificate = GeneratedCertificate.eligible_certificates.get(user=self.user_2, course_id=self.course.id)
        self.assertEqual(certificate.mode, expected_mode)

    def assert_ineligible_certificate_generated(self, mock_send, expected_mode):
        """
        Assert that an ineligible certificate was generated with the
        correct mode.
        """
        # Ensure the certificate was not generated
        self.assertFalse(mock_send.called)

        certificate = GeneratedCertificate.objects.get(
            user=self.user_2,
            course_id=self.course.id
        )

        self.assertIn(certificate.status, (CertificateStatuses.audit_passing, CertificateStatuses.audit_notpassing))
        self.assertEqual(certificate.mode, expected_mode)

    @ddt.data(
        (CertificateStatuses.restricted, False),
        (CertificateStatuses.deleting, False),
        (CertificateStatuses.generating, True),
        (CertificateStatuses.unavailable, True),
        (CertificateStatuses.deleted, True),
        (CertificateStatuses.error, True),
        (CertificateStatuses.notpassing, True),
        (CertificateStatuses.downloadable, True),
        (CertificateStatuses.auditing, True),
    )
    @ddt.unpack
    def test_add_cert_statuses(self, status, should_generate):
        """
        Test that certificates can or cannot be generated with the given
        certificate status.
        """
        with patch(
            'lms.djangoapps.certificates.queue.certificate_status_for_student',
            Mock(return_value={'status': status})
        ):
            mock_send = self.add_cert_to_queue('verified')
            if should_generate:
                self.assertTrue(mock_send.called)
            else:
                self.assertFalse(mock_send.called)

    @ddt.data(
        # Eligible and should stay that way
        (
            CertificateStatuses.downloadable,
            timedelta(days=-2),
            'Pass',
            CertificateStatuses.generating
        ),
        # Ensure that certs in the wrong state can be fixed by regeneration
        (
            CertificateStatuses.downloadable,
            timedelta(hours=-1),
            'Pass',
            CertificateStatuses.audit_passing
        ),
        # Ineligible and should stay that way
        (
            CertificateStatuses.audit_passing,
            timedelta(hours=-1),
            'Pass',
            CertificateStatuses.audit_passing
        ),
        # As above
        (
            CertificateStatuses.audit_notpassing,
            timedelta(hours=-1),
            'Pass',
            CertificateStatuses.audit_passing
        ),
        # As above
        (
            CertificateStatuses.audit_notpassing,
            timedelta(hours=-1),
            None,
            CertificateStatuses.audit_notpassing
        ),
    )
    @ddt.unpack
    @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) - timedelta(days=1))
    def test_regen_audit_certs_eligibility(self, status, created_delta, grade, expected_status):
        """
        Test that existing audit certificates remain eligible even if cert
        generation is re-run.
        """
        # Create an existing audit enrollment and certificate
        CourseEnrollmentFactory(
            user=self.user_2,
            course_id=self.course.id,
            is_active=True,
            mode=CourseMode.AUDIT,
        )
        created_date = datetime.now(pytz.UTC) + created_delta
        with freezegun.freeze_time(created_date):
            GeneratedCertificateFactory(
                user=self.user_2,
                course_id=self.course.id,
                grade='1.0',
                status=status,
                mode=GeneratedCertificate.MODES.audit,
            )

        # Run grading/cert generation again
        with mock_passing_grade(letter_grade=grade):
            with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
                mock_send.return_value = (0, None)
                self.xqueue.add_cert(self.user_2, self.course.id)

        self.assertEqual(
            GeneratedCertificate.objects.get(user=self.user_2, course_id=self.course.id).status,
            expected_status
        )
Esempio n. 14
0
 def setUp(self):
     super(XQueueCertInterfaceExampleCertificateTest, self).setUp()
     self.xqueue = XQueueCertInterface()
Esempio n. 15
0
class XQueueCertInterfaceExampleCertificateTest(TestCase):
    """Tests for the XQueue interface for certificate generation. """
    shard = 1

    COURSE_KEY = CourseLocator(org='test', course='test', run='test')

    TEMPLATE = 'test.pdf'
    DESCRIPTION = 'test'
    ERROR_MSG = 'Kaboom!'

    def setUp(self):
        super(XQueueCertInterfaceExampleCertificateTest, self).setUp()
        self.xqueue = XQueueCertInterface()

    def test_add_example_cert(self):
        cert = self._create_example_cert()
        with self._mock_xqueue() as mock_send:
            self.xqueue.add_example_cert(cert)

        # Verify that the correct payload was sent to the XQueue
        self._assert_queue_task(mock_send, cert)

        # Verify the certificate status
        self.assertEqual(cert.status, ExampleCertificate.STATUS_STARTED)

    def test_add_example_cert_error(self):
        cert = self._create_example_cert()
        with self._mock_xqueue(success=False):
            self.xqueue.add_example_cert(cert)

        # Verify the error status of the certificate
        self.assertEqual(cert.status, ExampleCertificate.STATUS_ERROR)
        self.assertIn(self.ERROR_MSG, cert.error_reason)

    def _create_example_cert(self):
        """Create an example certificate. """
        cert_set = ExampleCertificateSet.objects.create(course_key=self.COURSE_KEY)
        return ExampleCertificate.objects.create(
            example_cert_set=cert_set,
            description=self.DESCRIPTION,
            template=self.TEMPLATE
        )

    @contextmanager
    def _mock_xqueue(self, success=True):
        """Mock the XQueue method for sending a task to the queue. """
        with patch.object(XQueueInterface, 'send_to_queue') as mock_send:
            mock_send.return_value = (0, None) if success else (1, self.ERROR_MSG)
            yield mock_send

    def _assert_queue_task(self, mock_send, cert):
        """Check that the task was added to the queue. """
        expected_header = {
            'lms_key': cert.access_key,
            'lms_callback_url': 'https://edx.org/update_example_certificate?key={key}'.format(key=cert.uuid),
            'queue_name': 'certificates'
        }

        expected_body = {
            'action': 'create',
            'username': cert.uuid,
            'name': u'John Doë',
            'course_id': unicode(self.COURSE_KEY),
            'template_pdf': 'test.pdf',
            'example_certificate': True
        }

        self.assertTrue(mock_send.called)

        __, kwargs = mock_send.call_args_list[0]
        actual_header = json.loads(kwargs['header'])
        actual_body = json.loads(kwargs['body'])

        self.assertEqual(expected_header, actual_header)
        self.assertEqual(expected_body, actual_body)