Files
edx-platform/lms/djangoapps/certificates/tests/test_models.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

284 lines
10 KiB
Python

"""Tests for certificate Django models. """
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.test.utils import override_settings
from nose.plugins.attrib import attr
from path import Path as path
from certificates.models import (
ExampleCertificate,
ExampleCertificateSet,
CertificateHtmlViewConfiguration,
CertificateTemplateAsset,
BadgeImageConfiguration,
EligibleCertificateManager,
GeneratedCertificate,
CertificateStatuses,
)
from certificates.tests.factories import GeneratedCertificateFactory
from opaque_keys.edx.locator import CourseLocator
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
FEATURES_INVALID_FILE_PATH = settings.FEATURES.copy()
FEATURES_INVALID_FILE_PATH['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json'
# pylint: disable=invalid-name
TEST_DIR = path(__file__).dirname()
TEST_DATA_DIR = 'common/test/data/'
PLATFORM_ROOT = TEST_DIR.parent.parent.parent.parent
TEST_DATA_ROOT = PLATFORM_ROOT / TEST_DATA_DIR
@attr('shard_1')
class ExampleCertificateTest(TestCase):
"""Tests for the ExampleCertificate model. """
COURSE_KEY = CourseLocator(org='test', course='test', run='test')
DESCRIPTION = 'test'
TEMPLATE = 'test.pdf'
DOWNLOAD_URL = 'http://www.example.com'
ERROR_REASON = 'Kaboom!'
def setUp(self):
super(ExampleCertificateTest, self).setUp()
self.cert_set = ExampleCertificateSet.objects.create(course_key=self.COURSE_KEY)
self.cert = ExampleCertificate.objects.create(
example_cert_set=self.cert_set,
description=self.DESCRIPTION,
template=self.TEMPLATE
)
def test_update_status_success(self):
self.cert.update_status(
ExampleCertificate.STATUS_SUCCESS,
download_url=self.DOWNLOAD_URL
)
self.assertEqual(
self.cert.status_dict,
{
'description': self.DESCRIPTION,
'status': ExampleCertificate.STATUS_SUCCESS,
'download_url': self.DOWNLOAD_URL
}
)
def test_update_status_error(self):
self.cert.update_status(
ExampleCertificate.STATUS_ERROR,
error_reason=self.ERROR_REASON
)
self.assertEqual(
self.cert.status_dict,
{
'description': self.DESCRIPTION,
'status': ExampleCertificate.STATUS_ERROR,
'error_reason': self.ERROR_REASON
}
)
def test_update_status_invalid(self):
with self.assertRaisesRegexp(ValueError, 'status'):
self.cert.update_status('invalid')
def test_latest_status_unavailable(self):
# Delete any existing statuses
ExampleCertificateSet.objects.all().delete()
# Verify that the "latest" status is None
result = ExampleCertificateSet.latest_status(self.COURSE_KEY)
self.assertIs(result, None)
def test_latest_status_is_course_specific(self):
other_course = CourseLocator(org='other', course='other', run='other')
result = ExampleCertificateSet.latest_status(other_course)
self.assertIs(result, None)
@attr('shard_1')
class CertificateHtmlViewConfigurationTest(TestCase):
"""
Test the CertificateHtmlViewConfiguration model.
"""
def setUp(self):
super(CertificateHtmlViewConfigurationTest, self).setUp()
self.configuration_string = """{
"default": {
"url": "http://www.edx.org",
"logo_src": "http://www.edx.org/static/images/logo.png"
},
"honor": {
"logo_src": "http://www.edx.org/static/images/honor-logo.png"
}
}"""
self.config = CertificateHtmlViewConfiguration(configuration=self.configuration_string)
def test_create(self):
"""
Tests creation of configuration.
"""
self.config.save()
self.assertEquals(self.config.configuration, self.configuration_string)
def test_clean_bad_json(self):
"""
Tests if bad JSON string was given.
"""
self.config = CertificateHtmlViewConfiguration(configuration='{"bad":"test"')
self.assertRaises(ValidationError, self.config.clean)
def test_get(self):
"""
Tests get configuration from saved string.
"""
self.config.enabled = True
self.config.save()
expected_config = {
"default": {
"url": "http://www.edx.org",
"logo_src": "http://www.edx.org/static/images/logo.png"
},
"honor": {
"logo_src": "http://www.edx.org/static/images/honor-logo.png"
}
}
self.assertEquals(self.config.get_config(), expected_config)
def test_get_not_enabled_returns_blank(self):
"""
Tests get configuration that is not enabled.
"""
self.config.enabled = False
self.config.save()
self.assertEquals(len(self.config.get_config()), 0)
@override_settings(FEATURES=FEATURES_INVALID_FILE_PATH)
def test_get_no_database_no_file(self):
"""
Tests get configuration that is not enabled.
"""
self.config.configuration = ''
self.config.save()
self.assertEquals(self.config.get_config(), {})
@attr('shard_1')
class BadgeImageConfigurationTest(TestCase):
"""
Test the validation features of BadgeImageConfiguration.
"""
def get_image(self, name):
"""
Get one of the test images from the test data directory.
"""
return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png'))
def create_clean(self, file_obj):
"""
Shortcut to create a BadgeImageConfiguration with a specific file.
"""
BadgeImageConfiguration(mode='honor', icon=file_obj).full_clean()
def test_good_image(self):
"""
Verify that saving a valid badge image is no problem.
"""
good = self.get_image('good')
BadgeImageConfiguration(mode='honor', icon=good).full_clean()
def test_unbalanced_image(self):
"""
Verify that setting an image with an uneven width and height raises an error.
"""
unbalanced = ImageFile(self.get_image('unbalanced'))
self.assertRaises(ValidationError, self.create_clean, unbalanced)
def test_large_image(self):
"""
Verify that setting an image that is too big raises an error.
"""
large = self.get_image('large')
self.assertRaises(ValidationError, self.create_clean, large)
def test_no_double_default(self):
"""
Verify that creating two configurations as default is not permitted.
"""
BadgeImageConfiguration(mode='test', icon=self.get_image('good'), default=True).save()
self.assertRaises(
ValidationError,
BadgeImageConfiguration(mode='test2', icon=self.get_image('good'), default=True).full_clean
)
@attr('shard_1')
class CertificateTemplateAssetTest(TestCase):
"""
Test Assets are uploading/saving successfully for CertificateTemplateAsset.
"""
def test_asset_file_saving_with_actual_name(self):
"""
Verify that asset file is saving with actual name, No hash tag should be appended with the asset filename.
"""
CertificateTemplateAsset(description='test description', asset=SimpleUploadedFile(
'picture1.jpg',
'these are the file contents!')).save()
certificate_template_asset = CertificateTemplateAsset.objects.get(id=1)
self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture1.jpg')
# Now save asset with same file again, New file will be uploaded after deleting the old one with the same name.
certificate_template_asset.asset = SimpleUploadedFile('picture1.jpg', 'file contents')
certificate_template_asset.save()
self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture1.jpg')
# Now replace the asset with another file
certificate_template_asset.asset = SimpleUploadedFile('picture2.jpg', 'file contents')
certificate_template_asset.save()
certificate_template_asset = CertificateTemplateAsset.objects.get(id=1)
self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture2.jpg')
@attr('shard_1')
class EligibleCertificateManagerTest(SharedModuleStoreTestCase):
"""
Test the GeneratedCertificate model's object manager for filtering
out ineligible certs.
"""
@classmethod
def setUpClass(cls):
super(EligibleCertificateManagerTest, cls).setUpClass()
cls.courses = (CourseFactory(), CourseFactory())
def setUp(self):
super(EligibleCertificateManagerTest, self).setUp()
self.user = UserFactory()
self.eligible_cert = GeneratedCertificateFactory.create(
status=CertificateStatuses.downloadable,
user=self.user,
course_id=self.courses[0].id # pylint: disable=no-member
)
self.ineligible_cert = GeneratedCertificateFactory.create(
status=CertificateStatuses.audit_passing,
user=self.user,
course_id=self.courses[1].id # pylint: disable=no-member
)
def test_filter_ineligible_certificates(self):
"""
Verify that the EligibleCertificateManager filters out
certificates marked as ineligible, and that the default object
manager for GeneratedCertificate does not filter them out.
"""
self.assertEqual(list(GeneratedCertificate.eligible_certificates.filter(user=self.user)), [self.eligible_cert])
self.assertEqual(
list(GeneratedCertificate.objects.filter(user=self.user)), # pylint: disable=no-member
[self.eligible_cert, self.ineligible_cert]
)