Files
edx-platform/lms/djangoapps/badges/tests/test_models.py
2016-08-03 16:23:02 -04:00

286 lines
11 KiB
Python

"""
Tests for the Badges app models.
"""
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile
from django.core.files.storage import default_storage
from django.db.utils import IntegrityError
from django.test import TestCase
from django.test.utils import override_settings
from mock import patch, Mock
from nose.plugins.attrib import attr
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from badges.models import (
CourseCompleteImageConfiguration, validate_badge_image, BadgeClass, BadgeAssertion,
CourseBadgesDisabledError
)
from badges.tests.factories import BadgeClassFactory, BadgeAssertionFactory, RandomBadgeClassFactory
from certificates.tests.test_models import TEST_DATA_ROOT
from student.tests.factories import UserFactory
def get_image(name):
"""
Get one of the test images from the test data directory.
"""
return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png'))
@attr(shard=1)
class BadgeImageConfigurationTest(TestCase):
"""
Test the validation features of BadgeImageConfiguration.
"""
def test_no_double_default(self):
"""
Verify that creating two configurations as default is not permitted.
"""
CourseCompleteImageConfiguration(mode='test', icon=get_image('good'), default=True).save()
self.assertRaises(
ValidationError,
CourseCompleteImageConfiguration(mode='test2', icon=get_image('good'), default=True).full_clean
)
def test_runs_validator(self):
"""
Verify that the image validator is triggered when cleaning the model.
"""
self.assertRaises(
ValidationError,
CourseCompleteImageConfiguration(mode='test2', icon=get_image('unbalanced')).full_clean
)
class DummyBackend(object):
"""
Dummy badge backend, used for testing.
"""
award = Mock()
class BadgeClassTest(ModuleStoreTestCase):
"""
Test BadgeClass functionality
"""
def setUp(self):
super(BadgeClassTest, self).setUp()
self.addCleanup(self.cleanup_uploads)
def cleanup_uploads(self):
"""
Remove all files uploaded as badges.
"""
upload_to = BadgeClass._meta.get_field('image').upload_to # pylint: disable=protected-access
if default_storage.exists(upload_to):
(_, files) = default_storage.listdir(upload_to)
for uploaded_file in files:
default_storage.delete(upload_to + '/' + uploaded_file)
# Need full path to make sure class names line up.
@override_settings(BADGING_BACKEND='lms.djangoapps.badges.tests.test_models.DummyBackend')
def test_backend(self):
"""
Verify the BadgeClass fetches the backend properly.
"""
self.assertIsInstance(BadgeClass().backend, DummyBackend)
def test_get_badge_class_preexisting(self):
"""
Verify fetching a badge first grabs existing badges.
"""
premade_badge_class = BadgeClassFactory.create()
# Ignore additional parameters. This class already exists.
badge_class = BadgeClass.get_badge_class(
slug='test_slug', issuing_component='test_component', description='Attempted override',
criteria='test', display_name='Testola', image_file_handle=get_image('good')
)
# These defaults are set on the factory.
self.assertEqual(badge_class.criteria, 'https://example.com/syllabus')
self.assertEqual(badge_class.display_name, 'Test Badge')
self.assertEqual(badge_class.description, "Yay! It's a test badge.")
# File name won't always be the same.
self.assertEqual(badge_class.image.path, premade_badge_class.image.path)
def test_unique_for_course(self):
"""
Verify that the course_id is used in fetching existing badges or creating new ones.
"""
course_key = CourseFactory.create().location.course_key
premade_badge_class = BadgeClassFactory.create(course_id=course_key)
badge_class = BadgeClass.get_badge_class(
slug='test_slug', issuing_component='test_component', description='Attempted override',
criteria='test', display_name='Testola', image_file_handle=get_image('good')
)
course_badge_class = BadgeClass.get_badge_class(
slug='test_slug', issuing_component='test_component', description='Attempted override',
criteria='test', display_name='Testola', image_file_handle=get_image('good'),
course_id=course_key,
)
self.assertNotEqual(badge_class.id, course_badge_class.id)
self.assertEqual(course_badge_class.id, premade_badge_class.id)
def test_get_badge_class_course_disabled(self):
"""
Verify attempting to fetch a badge class for a course which does not issue badges raises an
exception.
"""
course_key = CourseFactory.create(metadata={'issue_badges': False}).location.course_key
with self.assertRaises(CourseBadgesDisabledError):
BadgeClass.get_badge_class(
slug='test_slug', issuing_component='test_component', description='Attempted override',
criteria='test', display_name='Testola', image_file_handle=get_image('good'),
course_id=course_key,
)
def test_get_badge_class_create(self):
"""
Verify fetching a badge creates it if it doesn't yet exist.
"""
badge_class = BadgeClass.get_badge_class(
slug='new_slug', issuing_component='new_component', description='This is a test',
criteria='https://example.com/test_criteria', display_name='Super Badge',
image_file_handle=get_image('good')
)
# This should have been saved before being passed back.
self.assertTrue(badge_class.id)
self.assertEqual(badge_class.slug, 'new_slug')
self.assertEqual(badge_class.issuing_component, 'new_component')
self.assertEqual(badge_class.description, 'This is a test')
self.assertEqual(badge_class.criteria, 'https://example.com/test_criteria')
self.assertEqual(badge_class.display_name, 'Super Badge')
self.assertEqual(badge_class.image.name.rsplit('/', 1)[-1], 'good.png')
def test_get_badge_class_nocreate(self):
"""
Test returns None if the badge class does not exist.
"""
badge_class = BadgeClass.get_badge_class(
slug='new_slug', issuing_component='new_component', create=False
)
self.assertIsNone(badge_class)
# Run this twice to verify there wasn't a background creation of the badge.
badge_class = BadgeClass.get_badge_class(
slug='new_slug', issuing_component='new_component', description=None,
criteria=None, display_name=None,
image_file_handle=None, create=False
)
self.assertIsNone(badge_class)
def test_get_badge_class_image_validate(self):
"""
Verify handing a broken image to get_badge_class raises a validation error upon creation.
"""
self.assertRaises(
ValidationError,
BadgeClass.get_badge_class,
slug='new_slug', issuing_component='new_component', description='This is a test',
criteria='https://example.com/test_criteria', display_name='Super Badge',
image_file_handle=get_image('unbalanced')
)
def test_get_badge_class_data_validate(self):
"""
Verify handing incomplete data for required fields when making a badge class raises an Integrity error.
"""
self.assertRaises(
IntegrityError,
BadgeClass.get_badge_class,
slug='new_slug', issuing_component='new_component',
image_file_handle=get_image('good')
)
def test_get_for_user(self):
"""
Make sure we can get an assertion for a user if there is one.
"""
user = UserFactory.create()
badge_class = BadgeClassFactory.create()
self.assertFalse(badge_class.get_for_user(user))
assertion = BadgeAssertionFactory.create(badge_class=badge_class, user=user)
self.assertEqual(list(badge_class.get_for_user(user)), [assertion])
@override_settings(BADGING_BACKEND='lms.djangoapps.badges.backends.badgr.BadgrBackend', BADGR_API_TOKEN='test')
@patch('lms.djangoapps.badges.backends.badgr.BadgrBackend.award')
def test_award(self, mock_award):
"""
Verify that the award command calls the award function on the backend with the right parameters.
"""
user = UserFactory.create()
badge_class = BadgeClassFactory.create()
badge_class.award(user, evidence_url='http://example.com/evidence')
self.assertTrue(mock_award.called)
mock_award.assert_called_with(badge_class, user, evidence_url='http://example.com/evidence')
def test_runs_validator(self):
"""
Verify that the image validator is triggered when cleaning the model.
"""
self.assertRaises(
ValidationError,
BadgeClass(
slug='test', issuing_component='test2', criteria='test3',
description='test4', image=get_image('unbalanced')
).full_clean
)
class BadgeAssertionTest(ModuleStoreTestCase):
"""
Tests for the BadgeAssertion model
"""
def test_assertions_for_user(self):
"""
Verify that grabbing all assertions for a user behaves as expected.
This function uses object IDs because for some reason Jenkins trips up
on its assertItemsEqual check here despite the items being equal.
"""
user = UserFactory()
assertions = [BadgeAssertionFactory.create(user=user).id for _i in range(3)]
course = CourseFactory.create()
course_key = course.location.course_key
course_badges = [RandomBadgeClassFactory(course_id=course_key) for _i in range(3)]
course_assertions = [
BadgeAssertionFactory.create(user=user, badge_class=badge_class).id for badge_class in course_badges
]
assertions.extend(course_assertions)
assertions.sort()
assertions_for_user = [badge.id for badge in BadgeAssertion.assertions_for_user(user)]
assertions_for_user.sort()
self.assertEqual(assertions_for_user, assertions)
course_scoped_assertions = [
badge.id for badge in BadgeAssertion.assertions_for_user(user, course_id=course_key)
]
course_scoped_assertions.sort()
self.assertEqual(course_scoped_assertions, course_assertions)
class ValidBadgeImageTest(TestCase):
"""
Tests the badge image field validator.
"""
def test_good_image(self):
"""
Verify that saving a valid badge image is no problem.
"""
validate_badge_image(get_image('good'))
def test_unbalanced_image(self):
"""
Verify that setting an image with an uneven width and height raises an error.
"""
unbalanced = ImageFile(get_image('unbalanced'))
self.assertRaises(ValidationError, validate_badge_image, unbalanced)
def test_large_image(self):
"""
Verify that setting an image that is too big raises an error.
"""
large = get_image('large')
self.assertRaises(ValidationError, validate_badge_image, large)