286 lines
11 KiB
Python
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)
|