Files
edx-platform/lms/djangoapps/certificates/tests/test_support_views.py
Peter Fogg 96cc38951d Disable audit certificates for new audit enrollments.
Two new certificate statuses are introduced, 'audit_passing' and
'audit_notpassing'. These signal that the GeneratedCertificate is not
to be displayed as a cert to the user, and that they either passed or
did not. This allows us to retain existing grading logic, as well as
maintaining correctness in analytics and reporting.

Ineligible certificates are hidden by using the
`eligible_certificates` manager on GeneratedCertificate. Some places
in the coe (largely reporting, analytics, and management commands) use
the default `objects` manager, since they need access to all
certificates.

ECOM-3040
ECOM-3515
2016-01-22 10:27:55 -05:00

440 lines
15 KiB
Python

"""
Tests for certificate app views used by the support team.
"""
import json
import ddt
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from opaque_keys.edx.keys import CourseKey
from student.tests.factories import UserFactory
from student.models import CourseEnrollment
from student.roles import GlobalStaff, SupportStaffRole
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from certificates.models import GeneratedCertificate, CertificateStatuses
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
class CertificateSupportTestCase(ModuleStoreTestCase):
"""
Base class for tests of the certificate support views.
"""
SUPPORT_USERNAME = "support"
SUPPORT_EMAIL = "support@example.com"
SUPPORT_PASSWORD = "support"
STUDENT_USERNAME = "student"
STUDENT_EMAIL = "student@example.com"
STUDENT_PASSWORD = "student"
CERT_COURSE_KEY = CourseKey.from_string("edX/DemoX/Demo_Course")
COURSE_NOT_EXIST_KEY = CourseKey.from_string("test/TestX/Test_Course_Not_Exist")
EXISTED_COURSE_KEY_1 = CourseKey.from_string("test1/Test1X/Test_Course_Exist_1")
EXISTED_COURSE_KEY_2 = CourseKey.from_string("test2/Test2X/Test_Course_Exist_2")
CERT_GRADE = 0.89
CERT_STATUS = CertificateStatuses.downloadable
CERT_MODE = "verified"
CERT_DOWNLOAD_URL = "http://www.example.com/cert.pdf"
def setUp(self):
"""
Create a support team member and a student with a certificate.
Log in as the support team member.
"""
super(CertificateSupportTestCase, self).setUp()
CourseFactory(
org=CertificateSupportTestCase.EXISTED_COURSE_KEY_1.org,
course=CertificateSupportTestCase.EXISTED_COURSE_KEY_1.course,
run=CertificateSupportTestCase.EXISTED_COURSE_KEY_1.run,
)
# Create the support staff user
self.support = UserFactory(
username=self.SUPPORT_USERNAME,
email=self.SUPPORT_EMAIL,
password=self.SUPPORT_PASSWORD,
)
SupportStaffRole().add_users(self.support)
# Create a student
self.student = UserFactory(
username=self.STUDENT_USERNAME,
email=self.STUDENT_EMAIL,
password=self.STUDENT_PASSWORD,
)
# Create certificates for the student
self.cert = GeneratedCertificate.eligible_certificates.create(
user=self.student,
course_id=self.CERT_COURSE_KEY,
grade=self.CERT_GRADE,
status=self.CERT_STATUS,
mode=self.CERT_MODE,
download_url=self.CERT_DOWNLOAD_URL,
)
# Login as support staff
success = self.client.login(username=self.SUPPORT_USERNAME, password=self.SUPPORT_PASSWORD)
self.assertTrue(success, msg="Couldn't log in as support staff")
@ddt.ddt
class CertificateSearchTests(CertificateSupportTestCase):
"""
Tests for the certificate search end-point used by the support team.
"""
def setUp(self):
"""
Create a course
"""
super(CertificateSearchTests, self).setUp()
self.course = CourseFactory()
self.course.cert_html_view_enabled = True
#course certificate configurations
certificates = [
{
'id': 1,
'name': 'Name 1',
'description': 'Description 1',
'course_title': 'course_title_1',
'signatories': [],
'version': 1,
'is_active': True
}
]
self.course.certificates = {'certificates': certificates}
self.course.save() # pylint: disable=no-member
self.store.update_item(self.course, self.user.id)
@ddt.data(
(GlobalStaff, True),
(SupportStaffRole, True),
(None, False),
)
@ddt.unpack
def test_access_control(self, role, has_access):
# Create a user and log in
user = UserFactory(username="foo", password="foo")
success = self.client.login(username="foo", password="foo")
self.assertTrue(success, msg="Could not log in")
# Assign the user to the role
if role is not None:
role().add_users(user)
# Retrieve the page
response = self._search("foo")
if has_access:
self.assertContains(response, json.dumps([]))
else:
self.assertEqual(response.status_code, 403)
@ddt.data(
(CertificateSupportTestCase.STUDENT_USERNAME, True),
(CertificateSupportTestCase.STUDENT_EMAIL, True),
("bar", False),
("bar@example.com", False),
("", False),
(CertificateSupportTestCase.STUDENT_USERNAME, False, 'invalid_key'),
(CertificateSupportTestCase.STUDENT_USERNAME, False, unicode(CertificateSupportTestCase.COURSE_NOT_EXIST_KEY)),
(CertificateSupportTestCase.STUDENT_USERNAME, True, unicode(CertificateSupportTestCase.EXISTED_COURSE_KEY_1)),
)
@ddt.unpack
def test_search(self, user_filter, expect_result, course_filter=None):
response = self._search(user_filter, course_filter)
if expect_result:
self.assertEqual(response.status_code, 200)
results = json.loads(response.content)
self.assertEqual(len(results), 1)
else:
self.assertEqual(response.status_code, 400)
def test_results(self):
response = self._search(self.STUDENT_USERNAME)
self.assertEqual(response.status_code, 200)
results = json.loads(response.content)
self.assertEqual(len(results), 1)
retrieved_cert = results[0]
self.assertEqual(retrieved_cert["username"], self.STUDENT_USERNAME)
self.assertEqual(retrieved_cert["course_key"], unicode(self.CERT_COURSE_KEY))
self.assertEqual(retrieved_cert["created"], self.cert.created_date.isoformat())
self.assertEqual(retrieved_cert["modified"], self.cert.modified_date.isoformat())
self.assertEqual(retrieved_cert["grade"], unicode(self.CERT_GRADE))
self.assertEqual(retrieved_cert["status"], self.CERT_STATUS)
self.assertEqual(retrieved_cert["type"], self.CERT_MODE)
self.assertEqual(retrieved_cert["download_url"], self.CERT_DOWNLOAD_URL)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_download_link(self):
self.cert.course_id = self.course.id # pylint: disable=no-member
self.cert.download_url = ''
self.cert.save()
response = self._search(self.STUDENT_USERNAME)
self.assertEqual(response.status_code, 200)
results = json.loads(response.content)
self.assertEqual(len(results), 1)
retrieved_cert = results[0]
self.assertEqual(
retrieved_cert["download_url"],
reverse(
'certificates:html_view',
kwargs={"user_id": self.student.id, "course_id": self.course.id} # pylint: disable=no-member
)
)
def _search(self, user_filter, course_filter=None):
"""Execute a search and return the response. """
url = reverse("certificates:search") + "?user=" + user_filter
if course_filter:
url += '&course_id=' + course_filter
return self.client.get(url)
@ddt.ddt
class CertificateRegenerateTests(CertificateSupportTestCase):
"""
Tests for the certificate regeneration end-point used by the support team.
"""
def setUp(self):
"""
Create a course and enroll the student in the course.
"""
super(CertificateRegenerateTests, self).setUp()
self.course = CourseFactory(
org=self.CERT_COURSE_KEY.org,
course=self.CERT_COURSE_KEY.course,
run=self.CERT_COURSE_KEY.run,
)
CourseEnrollment.enroll(self.student, self.CERT_COURSE_KEY, self.CERT_MODE)
@ddt.data(
(GlobalStaff, True),
(SupportStaffRole, True),
(None, False),
)
@ddt.unpack
def test_access_control(self, role, has_access):
# Create a user and log in
user = UserFactory(username="foo", password="foo")
success = self.client.login(username="foo", password="foo")
self.assertTrue(success, msg="Could not log in")
# Assign the user to the role
if role is not None:
role().add_users(user)
# Make a POST request
# Since we're not passing valid parameters, we'll get an error response
# but at least we'll know we have access
response = self._regenerate()
if has_access:
self.assertEqual(response.status_code, 400)
else:
self.assertEqual(response.status_code, 403)
def test_regenerate_certificate(self):
response = self._regenerate(
course_key=self.course.id, # pylint: disable=no-member
username=self.STUDENT_USERNAME,
)
self.assertEqual(response.status_code, 200)
# Check that the user's certificate was updated
# Since the student hasn't actually passed the course,
# we'd expect that the certificate status will be "notpassing"
cert = GeneratedCertificate.eligible_certificates.get(user=self.student)
self.assertEqual(cert.status, CertificateStatuses.notpassing)
def test_regenerate_certificate_missing_params(self):
# Missing username
response = self._regenerate(course_key=self.CERT_COURSE_KEY)
self.assertEqual(response.status_code, 400)
# Missing course key
response = self._regenerate(username=self.STUDENT_USERNAME)
self.assertEqual(response.status_code, 400)
def test_regenerate_no_such_user(self):
response = self._regenerate(
course_key=unicode(self.CERT_COURSE_KEY),
username="invalid_username",
)
self.assertEqual(response.status_code, 400)
def test_regenerate_no_such_course(self):
response = self._regenerate(
course_key=CourseKey.from_string("edx/invalid/course"),
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 400)
def test_regenerate_user_is_not_enrolled(self):
# Unenroll the user
CourseEnrollment.unenroll(self.student, self.CERT_COURSE_KEY)
# Can no longer regenerate certificates for the user
response = self._regenerate(
course_key=self.CERT_COURSE_KEY,
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 400)
def test_regenerate_user_has_no_certificate(self):
# Delete the user's certificate
GeneratedCertificate.eligible_certificates.all().delete()
# Should be able to regenerate
response = self._regenerate(
course_key=self.CERT_COURSE_KEY,
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 200)
# A new certificate is created
num_certs = GeneratedCertificate.eligible_certificates.filter(user=self.student).count()
self.assertEqual(num_certs, 1)
def _regenerate(self, course_key=None, username=None):
"""Call the regeneration end-point and return the response. """
url = reverse("certificates:regenerate_certificate_for_user")
params = {}
if course_key is not None:
params["course_key"] = course_key
if username is not None:
params["username"] = username
return self.client.post(url, params)
@ddt.ddt
class CertificateGenerateTests(CertificateSupportTestCase):
"""
Tests for the certificate generation end-point used by the support team.
"""
def setUp(self):
"""
Create a course and enroll the student in the course.
"""
super(CertificateGenerateTests, self).setUp()
self.course = CourseFactory(
org=self.EXISTED_COURSE_KEY_2.org,
course=self.EXISTED_COURSE_KEY_2.course,
run=self.EXISTED_COURSE_KEY_2.run
)
CourseEnrollment.enroll(self.student, self.EXISTED_COURSE_KEY_2, self.CERT_MODE)
@ddt.data(
(GlobalStaff, True),
(SupportStaffRole, True),
(None, False),
)
@ddt.unpack
def test_access_control(self, role, has_access):
# Create a user and log in
user = UserFactory(username="foo", password="foo")
success = self.client.login(username="foo", password="foo")
self.assertTrue(success, msg="Could not log in")
# Assign the user to the role
if role is not None:
role().add_users(user)
# Make a POST request
# Since we're not passing valid parameters, we'll get an error response
# but at least we'll know we have access
response = self._generate()
if has_access:
self.assertEqual(response.status_code, 400)
else:
self.assertEqual(response.status_code, 403)
def test_generate_certificate(self):
response = self._generate(
course_key=self.course.id, # pylint: disable=no-member
username=self.STUDENT_USERNAME,
)
self.assertEqual(response.status_code, 200)
def test_generate_certificate_missing_params(self):
# Missing username
response = self._generate(course_key=self.EXISTED_COURSE_KEY_2)
self.assertEqual(response.status_code, 400)
# Missing course key
response = self._generate(username=self.STUDENT_USERNAME)
self.assertEqual(response.status_code, 400)
def test_generate_no_such_user(self):
response = self._generate(
course_key=unicode(self.EXISTED_COURSE_KEY_2),
username="invalid_username",
)
self.assertEqual(response.status_code, 400)
def test_generate_no_such_course(self):
response = self._generate(
course_key=CourseKey.from_string("edx/invalid/course"),
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 400)
def test_generate_user_is_not_enrolled(self):
# Unenroll the user
CourseEnrollment.unenroll(self.student, self.EXISTED_COURSE_KEY_2)
# Can no longer regenerate certificates for the user
response = self._generate(
course_key=self.EXISTED_COURSE_KEY_2,
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 400)
def test_generate_user_has_no_certificate(self):
# Delete the user's certificate
GeneratedCertificate.eligible_certificates.all().delete()
# Should be able to generate
response = self._generate(
course_key=self.EXISTED_COURSE_KEY_2,
username=self.STUDENT_USERNAME
)
self.assertEqual(response.status_code, 200)
# A new certificate is created
num_certs = GeneratedCertificate.eligible_certificates.filter(user=self.student).count()
self.assertEqual(num_certs, 1)
def _generate(self, course_key=None, username=None):
"""Call the generation end-point and return the response. """
url = reverse("certificates:generate_certificate_for_user")
params = {}
if course_key is not None:
params["course_key"] = course_key
if username is not None:
params["username"] = username
return self.client.post(url, params)