[MICROBA-1025] - Update management command to use the same logic that the Instructor Dashboard uses - Fix bug in management command where processing stopped when encountering a user that did not exist - Add more logging - Add and update tests where needed
1338 lines
53 KiB
Python
1338 lines
53 KiB
Python
"""Tests for the certificates panel of the instructor dash. """
|
|
|
|
|
|
import contextlib
|
|
import io
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
import pytest
|
|
import pytz
|
|
from config_models.models import cache
|
|
from django.conf import settings
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.test.utils import override_settings
|
|
from django.urls import reverse
|
|
|
|
from capa.xqueue_interface import XQueueInterface
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from common.djangoapps.student.models import CourseEnrollment
|
|
from lms.djangoapps.certificates import api as certs_api
|
|
from lms.djangoapps.certificates.models import (
|
|
CertificateGenerationConfiguration,
|
|
CertificateInvalidation,
|
|
CertificateStatuses,
|
|
CertificateWhitelist,
|
|
GeneratedCertificate
|
|
)
|
|
from lms.djangoapps.certificates.tests.factories import (
|
|
CertificateInvalidationFactory,
|
|
CertificateWhitelistFactory,
|
|
GeneratedCertificateFactory
|
|
)
|
|
from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory, InstructorFactory, UserFactory
|
|
from lms.djangoapps.grades.tests.utils import mock_passing_grade
|
|
from lms.djangoapps.verify_student.services import IDVerificationService
|
|
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
|
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
|
from xmodule.modulestore.tests.factories import CourseFactory
|
|
|
|
|
|
@ddt.ddt
|
|
class CertificatesInstructorDashTest(SharedModuleStoreTestCase):
|
|
"""Tests for the certificate panel of the instructor dash. """
|
|
|
|
ERROR_REASON = "An error occurred!"
|
|
DOWNLOAD_URL = "http://www.example.com/abcd123/cert.pdf"
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
cls.url = reverse(
|
|
'instructor_dashboard',
|
|
kwargs={'course_id': str(cls.course.id)}
|
|
)
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.instructor = InstructorFactory(course_key=self.course.id)
|
|
|
|
# Need to clear the cache for model-based configuration
|
|
cache.clear()
|
|
|
|
# Enable the certificate generation feature
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
|
|
def test_visible_only_to_global_staff(self):
|
|
# Instructors don't see the certificates section
|
|
self.client.login(username=self.instructor.username, password="test")
|
|
self._assert_certificates_visible(False)
|
|
|
|
# Global staff can see the certificates section
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
self._assert_certificates_visible(True)
|
|
|
|
def test_visible_only_when_feature_flag_enabled(self):
|
|
# Disable the feature flag
|
|
CertificateGenerationConfiguration.objects.create(enabled=False)
|
|
cache.clear()
|
|
|
|
# Now even global staff can't see the certificates section
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
self._assert_certificates_visible(False)
|
|
|
|
@ddt.data("started", "error", "success")
|
|
def test_show_certificate_status(self, status):
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
with self._certificate_status("honor", status):
|
|
self._assert_certificate_status("honor", status)
|
|
|
|
def test_show_enabled_button(self):
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
|
|
# Initially, no example certs are generated, so
|
|
# the enable button should be disabled
|
|
self._assert_enable_certs_button_is_disabled()
|
|
|
|
with self._certificate_status("honor", "success"):
|
|
# Certs are disabled for the course, so the enable button should be shown
|
|
self._assert_enable_certs_button(True)
|
|
|
|
# Enable certificates for the course
|
|
certs_api.set_cert_generation_enabled(self.course.id, True)
|
|
|
|
# Now the "disable" button should be shown
|
|
self._assert_enable_certs_button(False)
|
|
|
|
def test_can_disable_even_after_failure(self):
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
|
|
with self._certificate_status("honor", "error"):
|
|
# When certs are disabled for a course, then don't allow them
|
|
# to be enabled if certificate generation doesn't complete successfully
|
|
certs_api.set_cert_generation_enabled(self.course.id, False)
|
|
self._assert_enable_certs_button_is_disabled()
|
|
|
|
# However, if certificates are already enabled, allow them
|
|
# to be disabled even if an error has occurred
|
|
certs_api.set_cert_generation_enabled(self.course.id, True)
|
|
self._assert_enable_certs_button(False)
|
|
|
|
@mock.patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_show_enabled_button_for_html_certs(self):
|
|
"""
|
|
Tests `Enable Student-Generated Certificates` button is enabled
|
|
and `Generate Example Certificates` button is not available if
|
|
course has Web/HTML certificates view enabled.
|
|
"""
|
|
self.course.cert_html_view_enabled = True
|
|
self.course.save()
|
|
self.store.update_item(self.course, self.global_staff.id)
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
response = self.client.get(self.url)
|
|
self.assertContains(response, 'Enable Student-Generated Certificates')
|
|
self.assertContains(response, 'enable-certificates-submit')
|
|
self.assertNotContains(response, 'Generate Example Certificates')
|
|
|
|
@mock.patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
|
|
def test_buttons_for_html_certs_in_self_paced_course(self):
|
|
"""
|
|
Tests `Enable Student-Generated Certificates` button is enabled
|
|
and `Generate Certificates` button is not available if
|
|
course has Web/HTML certificates view enabled on a self paced course.
|
|
"""
|
|
self.course.cert_html_view_enabled = True
|
|
self.course.save()
|
|
self.store.update_item(self.course, self.global_staff.id)
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
response = self.client.get(self.url)
|
|
self.assertContains(response, 'Enable Student-Generated Certificates')
|
|
self.assertContains(response, 'enable-certificates-submit')
|
|
self.assertNotContains(response, 'Generate Certificates')
|
|
self.assertNotContains(response, 'btn-start-generating-certificates')
|
|
|
|
def _assert_certificates_visible(self, is_visible):
|
|
"""Check that the certificates section is visible on the instructor dash. """
|
|
response = self.client.get(self.url)
|
|
if is_visible:
|
|
self.assertContains(response, "Student-Generated Certificates")
|
|
else:
|
|
self.assertNotContains(response, "Student-Generated Certificates")
|
|
|
|
@contextlib.contextmanager
|
|
def _certificate_status(self, description, status):
|
|
"""Configure the certificate status by mocking the certificates API. """
|
|
patched = 'lms.djangoapps.instructor.views.instructor_dashboard.certs_api.example_certificates_status'
|
|
with mock.patch(patched) as certs_api_status:
|
|
cert_status = [{
|
|
'description': description,
|
|
'status': status
|
|
}]
|
|
|
|
if status == 'error':
|
|
cert_status[0]['error_reason'] = self.ERROR_REASON
|
|
if status == 'success':
|
|
cert_status[0]['download_url'] = self.DOWNLOAD_URL
|
|
|
|
certs_api_status.return_value = cert_status
|
|
yield
|
|
|
|
def _assert_certificate_status(self, cert_name, expected_status):
|
|
"""Check the certificate status display on the instructor dash. """
|
|
response = self.client.get(self.url)
|
|
|
|
if expected_status == 'started':
|
|
expected = f'Generating example {cert_name} certificate'
|
|
self.assertContains(response, expected)
|
|
elif expected_status == 'error':
|
|
expected = self.ERROR_REASON
|
|
self.assertContains(response, expected)
|
|
elif expected_status == 'success':
|
|
expected = self.DOWNLOAD_URL
|
|
self.assertContains(response, expected)
|
|
else:
|
|
self.fail(f"Invalid certificate status: {expected_status}")
|
|
|
|
def _assert_enable_certs_button_is_disabled(self):
|
|
"""Check that the "enable student-generated certificates" button is disabled. """
|
|
response = self.client.get(self.url)
|
|
expected_html = '<button class="is-disabled" disabled>Enable Student-Generated Certificates</button>'
|
|
self.assertContains(response, expected_html)
|
|
|
|
def _assert_enable_certs_button(self, is_enabled):
|
|
"""Check whether the button says "enable" or "disable" cert generation. """
|
|
response = self.client.get(self.url)
|
|
expected_html = (
|
|
'Enable Student-Generated Certificates' if is_enabled
|
|
else 'Disable Student-Generated Certificates'
|
|
)
|
|
self.assertContains(response, expected_html)
|
|
|
|
|
|
@override_settings(CERT_QUEUE='certificates')
|
|
@ddt.ddt
|
|
class CertificatesInstructorApiTest(SharedModuleStoreTestCase):
|
|
"""Tests for the certificates end-points in the instructor dash API. """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.instructor = InstructorFactory(course_key=self.course.id)
|
|
self.user = UserFactory()
|
|
CourseEnrollment.enroll(self.user, self.course.id)
|
|
|
|
# Enable certificate generation
|
|
cache.clear()
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
|
|
@ddt.data('generate_example_certificates', 'enable_certificate_generation')
|
|
def test_allow_only_global_staff(self, url_name):
|
|
url = reverse(url_name, kwargs={'course_id': self.course.id})
|
|
|
|
# Instructors do not have access
|
|
self.client.login(username=self.instructor.username, password='test')
|
|
response = self.client.post(url)
|
|
assert response.status_code == 403
|
|
|
|
# Global staff have access
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
response = self.client.post(url)
|
|
assert response.status_code == 302
|
|
|
|
def test_generate_example_certificates(self):
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse(
|
|
'generate_example_certificates',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
response = self.client.post(url)
|
|
|
|
# Expect a redirect back to the instructor dashboard
|
|
self._assert_redirects_to_instructor_dash(response)
|
|
|
|
# Expect that certificate generation started
|
|
# Cert generation will fail here because XQueue isn't configured,
|
|
# but the status should at least not be None.
|
|
status = certs_api.example_certificates_status(self.course.id)
|
|
assert status is not None
|
|
|
|
@ddt.data(True, False)
|
|
def test_enable_certificate_generation(self, is_enabled):
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse(
|
|
'enable_certificate_generation',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
params = {'certificates-enabled': 'true' if is_enabled else 'false'}
|
|
response = self.client.post(url, data=params)
|
|
|
|
# Expect a redirect back to the instructor dashboard
|
|
self._assert_redirects_to_instructor_dash(response)
|
|
|
|
# Expect that certificate generation is now enabled for the course
|
|
actual_enabled = certs_api.cert_generation_enabled(self.course.id)
|
|
assert is_enabled == actual_enabled
|
|
|
|
def _assert_redirects_to_instructor_dash(self, response):
|
|
"""Check that the response redirects to the certificates section. """
|
|
expected_redirect = reverse(
|
|
'instructor_dashboard',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
expected_redirect += '#view-certificates'
|
|
self.assertRedirects(response, expected_redirect)
|
|
|
|
def test_certificate_generation_api_without_global_staff(self):
|
|
"""
|
|
Test certificates generation api endpoint returns permission denied if
|
|
user who made the request is not member of global staff.
|
|
"""
|
|
user = UserFactory.create()
|
|
self.client.login(username=user.username, password='test')
|
|
url = reverse(
|
|
'start_certificate_generation',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
|
|
response = self.client.post(url)
|
|
assert response.status_code == 403
|
|
|
|
self.client.login(username=self.instructor.username, password='test')
|
|
response = self.client.post(url)
|
|
assert response.status_code == 403
|
|
|
|
def test_certificate_generation_api_with_global_staff(self):
|
|
"""
|
|
Test certificates generation api endpoint returns success status when called with
|
|
valid course key
|
|
"""
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse(
|
|
'start_certificate_generation',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
|
|
response = self.client.post(url)
|
|
assert response.status_code == 200
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
assert res_json['message'] is not None
|
|
assert res_json['task_id'] is not None
|
|
|
|
def test_certificate_regeneration_success(self):
|
|
"""
|
|
Test certificate regeneration is successful when accessed with 'certificate_statuses'
|
|
present in GeneratedCertificate table.
|
|
"""
|
|
|
|
# Create a generated Certificate of some user with status 'downloadable'
|
|
GeneratedCertificateFactory.create(
|
|
user=self.user,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='honor'
|
|
)
|
|
|
|
# Login the client and access the url with 'certificate_statuses'
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse('start_certificate_regeneration', kwargs={'course_id': str(self.course.id)})
|
|
response = self.client.post(url, data={'certificate_statuses': [CertificateStatuses.downloadable]})
|
|
|
|
# Assert 200 status code in response
|
|
assert response.status_code == 200
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert request is successful
|
|
assert res_json['success']
|
|
|
|
# Assert success message
|
|
assert res_json['message'] ==\
|
|
'Certificate regeneration task has been started.' \
|
|
' You can view the status of the generation task in the "Pending Tasks" section.'
|
|
|
|
@override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) - timedelta(days=1))
|
|
@ddt.data(
|
|
(CertificateStatuses.generating, 'ID Verified', 'approved'),
|
|
(CertificateStatuses.unverified, 'Not ID Verified', 'denied'),
|
|
)
|
|
@ddt.unpack
|
|
def test_verified_users_with_audit_certs(self, expected_cert_status, verification_output, id_verification_status):
|
|
"""
|
|
Test certificate regeneration for verified users with audit certificates.
|
|
|
|
Scenario:
|
|
Enroll user in a course in audit mode,
|
|
User passed the course and now he has `audit_passing` certificate status,
|
|
User switched to verified mode and is ID verified,
|
|
Regenerate certificate for it,
|
|
Modified certificate status is `generating` if user is ID verified otherwise `unverified`.
|
|
"""
|
|
# Check that user is enrolled in audit mode.
|
|
enrollment = CourseEnrollment.get_enrollment(self.user, self.course.id)
|
|
assert enrollment.mode == CourseMode.AUDIT
|
|
|
|
with mock_passing_grade():
|
|
# Generate certificate for user and check that user has a audit passing certificate.
|
|
cert_status = certs_api.generate_user_certificates(
|
|
student=self.user,
|
|
course_key=self.course.id,
|
|
course=self.course,
|
|
)
|
|
|
|
# Check that certificate status is 'audit_passing'.
|
|
assert cert_status == CertificateStatuses.audit_passing
|
|
|
|
# Update user enrollment mode to verified mode.
|
|
enrollment.update_enrollment(mode=CourseMode.VERIFIED)
|
|
assert enrollment.mode == CourseMode.VERIFIED
|
|
|
|
# Create and assert user's ID verification record.
|
|
SoftwareSecurePhotoVerificationFactory.create(user=self.user, status=id_verification_status)
|
|
actual_verification_status = IDVerificationService.verification_status_for_user(
|
|
self.user,
|
|
enrollment.mode
|
|
)
|
|
assert actual_verification_status == verification_output
|
|
|
|
# Login the client and access the url with 'audit_passing' status.
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse(
|
|
'start_certificate_regeneration',
|
|
kwargs={'course_id': str(self.course.id)}
|
|
)
|
|
|
|
with mock.patch.object(XQueueInterface, 'send_to_queue') as mock_send:
|
|
mock_send.return_value = (0, None)
|
|
response = self.client.post(
|
|
url,
|
|
{'certificate_statuses': [CertificateStatuses.audit_passing]}
|
|
)
|
|
|
|
# Assert 200 status code in response
|
|
assert response.status_code == 200
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert request is successful
|
|
assert res_json['success']
|
|
|
|
# Assert success message
|
|
assert res_json['message'] ==\
|
|
'Certificate regeneration task has been started.' \
|
|
' You can view the status of the generation task in the "Pending Tasks" section.'
|
|
|
|
# Now, check whether user has audit certificate.
|
|
cert = certs_api.get_certificate_for_user(self.user.username, self.course.id)
|
|
assert cert['status'] != CertificateStatuses.audit_passing
|
|
assert cert['status'] == expected_cert_status
|
|
|
|
def test_certificate_regeneration_error(self):
|
|
"""
|
|
Test certificate regeneration errors out when accessed with either empty list of 'certificate_statuses' or
|
|
the 'certificate_statuses' that are not present in GeneratedCertificate table.
|
|
"""
|
|
# Create a dummy course and GeneratedCertificate with the same status as the one we will use to access
|
|
# 'start_certificate_regeneration' but their error message should be displayed as GeneratedCertificate
|
|
# belongs to a different course
|
|
dummy_course = CourseFactory.create()
|
|
GeneratedCertificateFactory.create(
|
|
user=self.user,
|
|
course_id=dummy_course.id,
|
|
status=CertificateStatuses.generating,
|
|
mode='honor'
|
|
)
|
|
|
|
# Login the client and access the url without 'certificate_statuses'
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
url = reverse('start_certificate_regeneration', kwargs={'course_id': str(self.course.id)})
|
|
response = self.client.post(url)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] ==\
|
|
'Please select one or more certificate statuses that require certificate regeneration.'
|
|
|
|
# Access the url passing 'certificate_statuses' that are not present in db
|
|
url = reverse('start_certificate_regeneration', kwargs={'course_id': str(self.course.id)})
|
|
response = self.client.post(url, data={'certificate_statuses': [CertificateStatuses.generating]})
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == 'Please select certificate statuses from the list only.'
|
|
|
|
|
|
@override_settings(CERT_QUEUE='certificates')
|
|
@ddt.ddt
|
|
class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
|
|
"""Tests for the generate certificates end-points in the instructor dash API. """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.instructor = InstructorFactory(course_key=self.course.id)
|
|
self.user = UserFactory()
|
|
self.user2 = UserFactory()
|
|
CourseEnrollment.enroll(self.user, self.course.id)
|
|
CourseEnrollment.enroll(self.user2, self.course.id)
|
|
self.url = reverse('certificate_exception_view', kwargs={'course_id': str(self.course.id)})
|
|
|
|
certificate_white_list_item = CertificateWhitelistFactory.create(
|
|
user=self.user2,
|
|
course_id=self.course.id,
|
|
)
|
|
|
|
self.certificate_exception = dict(
|
|
created="",
|
|
notes="Test Notes for Test Certificate Exception",
|
|
user_email='',
|
|
user_id='',
|
|
user_name=str(self.user.username)
|
|
)
|
|
|
|
self.certificate_exception_in_db = dict(
|
|
id=certificate_white_list_item.id,
|
|
user_name=certificate_white_list_item.user.username,
|
|
notes=certificate_white_list_item.notes,
|
|
user_email=certificate_white_list_item.user.email,
|
|
user_id=certificate_white_list_item.user.id,
|
|
)
|
|
|
|
# Enable certificate generation
|
|
cache.clear()
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
|
|
def test_certificate_exception_added_successfully(self):
|
|
"""
|
|
Test certificates exception addition api endpoint returns success status and updated certificate exception data
|
|
when called with valid course key and certificate exception data
|
|
"""
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
# Assert successful request processing
|
|
assert response.status_code == 200
|
|
certificate_exception = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Certificate Exception Updated data
|
|
assert certificate_exception['user_email'] == self.user.email
|
|
assert certificate_exception['user_name'] == self.user.username
|
|
assert certificate_exception['user_id'] == self.user.id
|
|
|
|
def test_certificate_exception_invalid_username_error(self):
|
|
"""
|
|
Test certificates exception addition api endpoint returns failure when called with
|
|
invalid username.
|
|
"""
|
|
invalid_user = 'test_invalid_user_name'
|
|
self.certificate_exception.update({'user_name': invalid_user})
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == f'{invalid_user} does not exist in the LMS. Please check your spelling and retry.'
|
|
|
|
def test_certificate_exception_missing_username_and_email_error(self):
|
|
"""
|
|
Test certificates exception addition api endpoint returns failure when called with
|
|
missing username/email.
|
|
"""
|
|
self.certificate_exception.update({'user_name': '', 'user_email': ''})
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] ==\
|
|
'Student username/email field is required and can not be empty.' \
|
|
' Kindly fill in username/email and then press "Add to Exception List" button.'
|
|
|
|
def test_certificate_exception_duplicate_user_error(self):
|
|
"""
|
|
Test certificates exception addition api endpoint returns failure when called with
|
|
username/email that already exists in 'CertificateWhitelist' table.
|
|
"""
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception_in_db),
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
|
|
user = self.certificate_exception_in_db['user_name']
|
|
# Assert Error Message
|
|
assert res_json['message'] == f'Student (username/email={user}) already in certificate exception list.'
|
|
|
|
def test_certificate_exception_same_user_in_two_different_courses(self):
|
|
"""
|
|
Test certificates exception addition api endpoint in scenario when same
|
|
student is added to two different courses.
|
|
"""
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
assert response.status_code == 200
|
|
certificate_exception = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Certificate Exception Updated data
|
|
assert certificate_exception['user_email'] == self.user.email
|
|
assert certificate_exception['user_name'] == self.user.username
|
|
assert certificate_exception['user_id'] == self.user.id
|
|
|
|
course2 = CourseFactory.create()
|
|
url_course2 = reverse(
|
|
'certificate_exception_view',
|
|
kwargs={'course_id': str(course2.id)}
|
|
)
|
|
|
|
# add certificate exception for same user in a different course
|
|
self.client.post(
|
|
url_course2,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
certificate_exception = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Certificate Exception Updated data
|
|
assert certificate_exception['user_email'] == self.user.email
|
|
assert certificate_exception['user_name'] == self.user.username
|
|
assert certificate_exception['user_id'] == self.user.id
|
|
|
|
def test_certificate_exception_user_not_enrolled_error(self):
|
|
"""
|
|
Test certificates exception addition api endpoint returns failure when called with
|
|
username/email that is not enrolled in the given course.
|
|
"""
|
|
# Un-enroll student from the course
|
|
CourseEnrollment.unenroll(self.user, self.course.id)
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == (
|
|
f"Student {self.user.username} is not enrolled in this course. Please check your spelling and retry."
|
|
)
|
|
|
|
def test_certificate_exception_removed_successfully(self):
|
|
"""
|
|
Test certificates exception removal api endpoint returns success status
|
|
when called with valid course key and certificate exception id
|
|
"""
|
|
GeneratedCertificateFactory.create(
|
|
user=self.user2,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
grade='1.0'
|
|
)
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception_in_db),
|
|
content_type='application/json',
|
|
REQUEST_METHOD='DELETE'
|
|
)
|
|
# Assert successful request processing
|
|
assert response.status_code == 204
|
|
|
|
# Verify that certificate exception successfully removed from CertificateWhitelist and GeneratedCertificate
|
|
with pytest.raises(ObjectDoesNotExist):
|
|
CertificateWhitelist.objects.get(user=self.user2, course_id=self.course.id)
|
|
GeneratedCertificate.eligible_certificates.get(
|
|
user=self.user2, course_id=self.course.id, status__not=CertificateStatuses.unavailable
|
|
)
|
|
|
|
def test_remove_certificate_exception_invalid_request_error(self):
|
|
"""
|
|
Test certificates exception removal api endpoint returns error
|
|
when called without certificate exception id
|
|
"""
|
|
# Try to delete certificate exception without passing valid data
|
|
response = self.client.post(
|
|
self.url,
|
|
data='Test Invalid data',
|
|
content_type='application/json',
|
|
REQUEST_METHOD='DELETE'
|
|
)
|
|
# Assert error on request
|
|
assert response.status_code == 400
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
# Assert Error Message
|
|
assert res_json['message'] ==\
|
|
'The record is not in the correct format. Please add a valid username or email address.'
|
|
|
|
def test_remove_certificate_exception_non_existing_error(self):
|
|
"""
|
|
Test certificates exception removal api endpoint returns error
|
|
when called with non existing certificate exception id
|
|
"""
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json',
|
|
REQUEST_METHOD='DELETE'
|
|
)
|
|
# Assert error on request
|
|
assert response.status_code == 400
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request not successful
|
|
assert not res_json['success']
|
|
# Assert Error Message
|
|
assert res_json['message'] == (
|
|
f"Error occurred removing the allowlist entry for student {self.user.username}. Please refresh the page "
|
|
"and try again"
|
|
)
|
|
|
|
def test_certificate_invalidation_already_exists(self):
|
|
"""
|
|
Test to confirm an error message is raised when generating a certificate exception for a learner that already
|
|
has an active certificate invalidation.
|
|
"""
|
|
# generate a certificate for the test learner in our course
|
|
generated_certificate = GeneratedCertificateFactory.create(
|
|
user=self.user,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='honor',
|
|
)
|
|
|
|
# create a certificate invalidation tied to the generated certificate
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
)
|
|
|
|
# attempt to add learner to the allowlist, expect an error
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_exception),
|
|
content_type='application/json',
|
|
REQUEST_METHOD='POST'
|
|
)
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
assert response.status_code == 400
|
|
assert res_json['message'] == (
|
|
f"Student {self.user.username} is already on the certificate invalidation list and cannot be added to "
|
|
"the certificate exception list."
|
|
)
|
|
|
|
|
|
@override_settings(CERT_QUEUE='certificates')
|
|
@ddt.ddt
|
|
class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
|
|
"""Tests for the generate certificates end-points in the instructor dash API. """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.instructor = InstructorFactory(course_key=self.course.id)
|
|
self.user = UserFactory()
|
|
CourseEnrollment.enroll(self.user, self.course.id)
|
|
certificate_exception = CertificateWhitelistFactory.create(
|
|
user=self.user,
|
|
course_id=self.course.id,
|
|
)
|
|
|
|
self.certificate_exception = dict(
|
|
id=certificate_exception.id,
|
|
user_name=certificate_exception.user.username,
|
|
notes=certificate_exception.notes,
|
|
user_email=certificate_exception.user.email,
|
|
user_id=certificate_exception.user.id,
|
|
)
|
|
|
|
# Enable certificate generation
|
|
cache.clear()
|
|
CertificateGenerationConfiguration.objects.create(enabled=True)
|
|
self.client.login(username=self.global_staff.username, password='test')
|
|
|
|
def test_generate_certificate_exceptions_all_students(self):
|
|
"""
|
|
Test generate certificates exceptions api endpoint returns success
|
|
when called with existing certificate exception
|
|
"""
|
|
url = reverse(
|
|
'generate_certificate_exceptions',
|
|
kwargs={'course_id': str(self.course.id), 'generate_for': 'all'}
|
|
)
|
|
|
|
response = self.client.post(
|
|
url,
|
|
content_type='application/json'
|
|
)
|
|
# Assert Success
|
|
assert response.status_code == 200
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request is successful
|
|
assert res_json['success']
|
|
# Assert Message
|
|
assert res_json['message'] == 'Certificate generation started for white listed students.'
|
|
|
|
def test_generate_certificate_exceptions_whitelist_not_generated(self):
|
|
"""
|
|
Test generate certificates exceptions api endpoint returns success
|
|
when calling with new certificate exception.
|
|
"""
|
|
url = reverse(
|
|
'generate_certificate_exceptions',
|
|
kwargs={'course_id': str(self.course.id), 'generate_for': 'new'}
|
|
)
|
|
|
|
response = self.client.post(
|
|
url,
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert Success
|
|
assert response.status_code == 200
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request is successful
|
|
assert res_json['success']
|
|
# Assert Message
|
|
assert res_json['message'] == 'Certificate generation started for white listed students.'
|
|
|
|
def test_generate_certificate_exceptions_generate_for_incorrect_value(self):
|
|
"""
|
|
Test generate certificates exceptions api endpoint returns error
|
|
when calling with generate_for without 'new' or 'all' value.
|
|
"""
|
|
url = reverse(
|
|
'generate_certificate_exceptions',
|
|
kwargs={'course_id': str(self.course.id), 'generate_for': ''}
|
|
)
|
|
|
|
response = self.client.post(
|
|
url,
|
|
content_type='application/json'
|
|
)
|
|
|
|
# Assert Failure
|
|
assert response.status_code == 400
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Request is not successful
|
|
assert not res_json['success']
|
|
# Assert Message
|
|
assert res_json['message'] == 'Invalid data, generate_for must be "new" or "all".'
|
|
|
|
|
|
@ddt.ddt
|
|
class TestCertificatesInstructorApiBulkWhiteListExceptions(SharedModuleStoreTestCase):
|
|
"""
|
|
Test Bulk certificates white list exceptions from csv file
|
|
"""
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
cls.url = reverse('generate_bulk_certificate_exceptions',
|
|
kwargs={'course_id': cls.course.id})
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.enrolled_user_1 = UserFactory(
|
|
username='TestStudent1',
|
|
email='test_student1@example.com',
|
|
first_name='Enrolled',
|
|
last_name='Student'
|
|
)
|
|
self.enrolled_user_2 = UserFactory(
|
|
username='TestStudent2',
|
|
email='test_student2@example.com',
|
|
first_name='Enrolled',
|
|
last_name='Student'
|
|
)
|
|
|
|
self.not_enrolled_student = UserFactory(
|
|
username='NotEnrolledStudent',
|
|
email='nonenrolled@test.com',
|
|
first_name='NotEnrolled',
|
|
last_name='Student'
|
|
)
|
|
CourseEnrollment.enroll(self.enrolled_user_1, self.course.id)
|
|
CourseEnrollment.enroll(self.enrolled_user_2, self.course.id)
|
|
|
|
# Global staff can see the certificates section
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
|
|
def test_create_white_list_exception_record(self):
|
|
"""
|
|
Happy path test to create a single new white listed record
|
|
"""
|
|
csv_content = b"test_student1@example.com,dummy_notes\n" \
|
|
b"test_student2@example.com,dummy_notes"
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['general_errors']) == 0
|
|
assert len(data['row_errors']['data_format_error']) == 0
|
|
assert len(data['row_errors']['user_not_exist']) == 0
|
|
assert len(data['row_errors']['user_already_white_listed']) == 0
|
|
assert len(data['row_errors']['user_not_enrolled']) == 0
|
|
assert len(data['success']) == 2
|
|
assert len(CertificateWhitelist.objects.all()) == 2
|
|
|
|
def test_invalid_data_format_in_csv(self):
|
|
"""
|
|
Try uploading a CSV file with invalid data formats and verify the errors.
|
|
"""
|
|
csv_content = b"test_student1@example.com,test,1,USA\n" \
|
|
b"test_student2@example.com,test,1"
|
|
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['row_errors']['data_format_error']) == 2
|
|
assert len(data['general_errors']) == 0
|
|
assert len(data['success']) == 0
|
|
assert len(CertificateWhitelist.objects.all()) == 0
|
|
|
|
def test_file_upload_type_not_csv(self):
|
|
"""
|
|
Try uploading some non-CSV file e.g. .JPG file and verify that it is rejected
|
|
"""
|
|
uploaded_file = SimpleUploadedFile("temp.jpg", io.BytesIO(b"some initial binary data: \x00\x01").read())
|
|
response = self.client.post(self.url, {'students_list': uploaded_file})
|
|
assert response.status_code == 200
|
|
data = json.loads(response.content.decode('utf-8'))
|
|
assert len(data['general_errors']) != 0
|
|
assert data['general_errors'][0] ==\
|
|
'Make sure that the file you upload is in CSV format with no extraneous characters or rows.'
|
|
|
|
def test_bad_file_upload_type(self):
|
|
"""
|
|
Try uploading CSV file with invalid binary data and verify that it is rejected
|
|
"""
|
|
uploaded_file = SimpleUploadedFile("temp.csv", io.BytesIO(b"some initial binary data: \x00\x01").read())
|
|
response = self.client.post(self.url, {'students_list': uploaded_file})
|
|
assert response.status_code == 200
|
|
data = json.loads(response.content.decode('utf-8'))
|
|
assert len(data['general_errors']) != 0
|
|
assert data['general_errors'][0] == 'Could not read uploaded file.'
|
|
|
|
def test_invalid_email_in_csv(self):
|
|
"""
|
|
Test failure case of a poorly formatted email field
|
|
"""
|
|
csv_content = b"test_student.example.com,dummy_notes"
|
|
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['row_errors']['user_not_exist']) == 1
|
|
assert len(data['success']) == 0
|
|
assert len(CertificateWhitelist.objects.all()) == 0
|
|
|
|
def test_csv_user_not_enrolled(self):
|
|
"""
|
|
If the user is not enrolled in the course then there should be a user_not_enrolled error.
|
|
"""
|
|
csv_content = b"nonenrolled@test.com,dummy_notes"
|
|
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['row_errors']['user_not_enrolled']) == 1
|
|
assert len(data['general_errors']) == 0
|
|
assert len(data['success']) == 0
|
|
|
|
def test_certificate_exception_already_exist(self):
|
|
"""
|
|
Test error if existing user is already in certificates exception list.
|
|
"""
|
|
CertificateWhitelist.objects.create(
|
|
user=self.enrolled_user_1,
|
|
course_id=self.course.id,
|
|
whitelist=True,
|
|
notes=''
|
|
)
|
|
csv_content = b"test_student1@example.com,dummy_notes"
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['row_errors']['user_already_white_listed']) == 1
|
|
assert len(data['general_errors']) == 0
|
|
assert len(data['success']) == 0
|
|
assert len(CertificateWhitelist.objects.all()) == 1
|
|
|
|
def test_csv_file_not_attached(self):
|
|
"""
|
|
Test when the user does not attach a file
|
|
"""
|
|
csv_content = b"test_student1@example.com,dummy_notes\n" \
|
|
b"test_student2@example.com,dummy_notes"
|
|
|
|
uploaded_file = SimpleUploadedFile("temp.csv", csv_content)
|
|
|
|
response = self.client.post(self.url, {'file_not_found': uploaded_file})
|
|
assert response.status_code == 200
|
|
data = json.loads(response.content.decode('utf-8'))
|
|
assert len(data['general_errors']) == 1
|
|
assert len(data['success']) == 0
|
|
|
|
def test_certificate_invalidation_already_exists(self):
|
|
"""
|
|
Test to confirm an error message is raised when generating a certificate exception for a learner appears in the
|
|
CSV file who has an active certificate invalidation.
|
|
"""
|
|
# generate a certificate for the test learner in our course
|
|
generated_certificate = GeneratedCertificateFactory.create(
|
|
user=self.enrolled_user_1,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='honor',
|
|
)
|
|
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
)
|
|
|
|
# attempt to add learner to the allowlist, expect an error
|
|
csv_content = b"test_student1@example.com,notes"
|
|
data = self.upload_file(csv_content=csv_content)
|
|
assert len(data['row_errors']['user_on_certificate_invalidation_list']) == 1
|
|
assert data['row_errors']['user_on_certificate_invalidation_list'][0] == 'user "TestStudent1" in row# 1'
|
|
|
|
def upload_file(self, csv_content):
|
|
"""
|
|
Upload a csv file.
|
|
:return json data
|
|
"""
|
|
uploaded_file = SimpleUploadedFile("temp.csv", csv_content)
|
|
response = self.client.post(self.url, {'students_list': uploaded_file})
|
|
assert response.status_code == 200
|
|
data = json.loads(response.content.decode('utf-8'))
|
|
return data
|
|
|
|
|
|
@ddt.ddt
|
|
class CertificateInvalidationViewTests(SharedModuleStoreTestCase):
|
|
"""
|
|
Test certificate invalidation view.
|
|
"""
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
cls.url = reverse('certificate_invalidation_view',
|
|
kwargs={'course_id': cls.course.id})
|
|
cls.notes = "Test notes."
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.global_staff = GlobalStaffFactory()
|
|
self.enrolled_user_1 = UserFactory(
|
|
username='TestStudent1',
|
|
email='test_student1@example.com',
|
|
first_name='Enrolled',
|
|
last_name='Student',
|
|
)
|
|
self.enrolled_user_2 = UserFactory(
|
|
username='TestStudent2',
|
|
email='test_student2@example.com',
|
|
first_name='Enrolled',
|
|
last_name='Student',
|
|
)
|
|
|
|
self.not_enrolled_student = UserFactory(
|
|
username='NotEnrolledStudent',
|
|
email='nonenrolled@test.com',
|
|
first_name='NotEnrolled',
|
|
last_name='Student',
|
|
)
|
|
CourseEnrollment.enroll(self.enrolled_user_1, self.course.id)
|
|
CourseEnrollment.enroll(self.enrolled_user_2, self.course.id)
|
|
|
|
self.generated_certificate = GeneratedCertificateFactory.create(
|
|
user=self.enrolled_user_1,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='honor',
|
|
)
|
|
|
|
self.certificate_invalidation_data = dict(
|
|
user=self.enrolled_user_1.username,
|
|
notes=self.notes,
|
|
)
|
|
|
|
# Global staff can see the certificates section
|
|
self.client.login(username=self.global_staff.username, password="test")
|
|
|
|
def test_invalidate_certificate(self):
|
|
"""
|
|
Test user can invalidate a generated certificate.
|
|
"""
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
# Assert successful request processing
|
|
assert response.status_code == 200
|
|
result = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Certificate Exception Updated data
|
|
assert result['user'] == self.enrolled_user_1.username
|
|
assert result['invalidated_by'] == self.global_staff.username
|
|
assert result['notes'] == self.notes
|
|
|
|
# Verify that CertificateInvalidation record has been created in the database i.e. no DoesNotExist error
|
|
try:
|
|
CertificateInvalidation.objects.get(
|
|
generated_certificate=self.generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
notes=self.notes,
|
|
active=True,
|
|
)
|
|
except ObjectDoesNotExist:
|
|
self.fail("The certificate is not invalidated.")
|
|
|
|
# Validate generated certificate was invalidated
|
|
generated_certificate = GeneratedCertificate.eligible_certificates.get(
|
|
user=self.enrolled_user_1,
|
|
course_id=self.course.id,
|
|
)
|
|
assert not generated_certificate.is_valid()
|
|
|
|
def test_missing_username_and_email_error(self):
|
|
"""
|
|
Test error message if user name or email is missing.
|
|
"""
|
|
self.certificate_invalidation_data.update({'user': ''})
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == \
|
|
'Student username/email field is required and can not be empty.' \
|
|
' Kindly fill in username/email and then press "Invalidate Certificate" button.'
|
|
|
|
def test_invalid_user_name_error(self):
|
|
"""
|
|
Test error message if invalid user name is given.
|
|
"""
|
|
invalid_user = "test_invalid_user_name"
|
|
|
|
self.certificate_invalidation_data.update({"user": invalid_user})
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == f'{invalid_user} does not exist in the LMS. Please check your spelling and retry.'
|
|
|
|
def test_no_generated_certificate_error(self):
|
|
"""
|
|
Test error message if there is no generated certificate for the student.
|
|
"""
|
|
self.certificate_invalidation_data.update({"user": self.enrolled_user_2.username})
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == 'The student {student} does not have certificate for the course {course}. Kindly verify student username/email and the selected course are correct and try again.'.format(student=self.enrolled_user_2.username, course=self.course.number) # pylint: disable=line-too-long
|
|
|
|
def test_certificate_already_invalid_error(self):
|
|
"""
|
|
Test error message if certificate for the student is already invalid.
|
|
"""
|
|
# Invalidate user certificate
|
|
self.generated_certificate.invalidate()
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == 'Certificate for student {user} is already invalid, kindly verify that certificate was generated for this student and then proceed.'.format(user=self.enrolled_user_1.username) # pylint: disable=line-too-long
|
|
|
|
def test_duplicate_certificate_invalidation_error(self):
|
|
"""
|
|
Test error message if certificate invalidation for the student is already present.
|
|
"""
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=self.generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
)
|
|
# Invalidate user certificate
|
|
self.generated_certificate.invalidate()
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == 'Certificate of {user} has already been invalidated. Please check your spelling and retry.'.format(user=self.enrolled_user_1.username) # pylint: disable=line-too-long
|
|
|
|
def test_remove_certificate_invalidation(self):
|
|
"""
|
|
Test that user can remove certificate invalidation.
|
|
"""
|
|
# Invalidate user certificate
|
|
self.generated_certificate.invalidate()
|
|
|
|
CertificateInvalidationFactory.create(
|
|
generated_certificate=self.generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
)
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
REQUEST_METHOD='DELETE'
|
|
)
|
|
|
|
# Assert 204 status code in response
|
|
assert response.status_code == 204
|
|
|
|
# Verify that certificate invalidation successfully removed from database
|
|
with pytest.raises(ObjectDoesNotExist):
|
|
CertificateInvalidation.objects.get(
|
|
generated_certificate=self.generated_certificate,
|
|
invalidated_by=self.global_staff,
|
|
active=True,
|
|
)
|
|
|
|
def test_remove_certificate_invalidation_error(self):
|
|
"""
|
|
Test error message if certificate invalidation does not exists.
|
|
"""
|
|
# Invalidate user certificate
|
|
self.generated_certificate.invalidate()
|
|
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
REQUEST_METHOD='DELETE'
|
|
)
|
|
|
|
# Assert 400 status code in response
|
|
assert response.status_code == 400
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
|
|
# Assert Error Message
|
|
assert res_json['message'] == 'Certificate Invalidation does not exist, Please refresh the page and try again.'
|
|
|
|
def test_learner_already_on_certificate_exception_list(self):
|
|
"""
|
|
Test to make sure we don't allow a single to learner to appear on both the certificate exception and
|
|
invalidation lists.
|
|
"""
|
|
# add test learner to the allowlist
|
|
CertificateWhitelistFactory.create(user=self.enrolled_user_1, course_id=self.course.id)
|
|
|
|
# now try and add them to the invalidation list, expect an error
|
|
response = self.client.post(
|
|
self.url,
|
|
data=json.dumps(self.certificate_invalidation_data),
|
|
content_type='application/json',
|
|
)
|
|
|
|
res_json = json.loads(response.content.decode('utf-8'))
|
|
assert response.status_code == 400
|
|
assert res_json['message'] == (
|
|
f"The student {self.enrolled_user_1.username} appears on the Certificate Exception list in course "
|
|
f"{self.course.id}. Please remove them from the Certificate Exception list before attempting to "
|
|
"invalidate their certificate."
|
|
)
|