Commit 25958feb by Jonathan Piacenti

Add XBlock Badging Service.

parent 112a1435
"""
Badging service for XBlocks
"""
from badges.models import BadgeClass
class BadgingService(object):
"""
A class that provides functions for managing badges which XBlocks can use.
"""
get_badge_class = BadgeClass.get_badge_class
......@@ -17,19 +17,15 @@ from certificates.tests.test_models import TEST_DATA_ROOT
from student.tests.factories import UserFactory
class ImageFetchingMixin(object):
def get_image(name):
"""
Provides the ability to grab a badge image from the test data root.
Get one of the test images from the test data directory.
"""
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'))
return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png'))
@attr('shard_1')
class BadgeImageConfigurationTest(TestCase, ImageFetchingMixin):
class BadgeImageConfigurationTest(TestCase):
"""
Test the validation features of BadgeImageConfiguration.
"""
......@@ -38,10 +34,10 @@ class BadgeImageConfigurationTest(TestCase, ImageFetchingMixin):
"""
Verify that creating two configurations as default is not permitted.
"""
CourseCompleteImageConfiguration(mode='test', icon=self.get_image('good'), default=True).save()
CourseCompleteImageConfiguration(mode='test', icon=get_image('good'), default=True).save()
self.assertRaises(
ValidationError,
CourseCompleteImageConfiguration(mode='test2', icon=self.get_image('good'), default=True).full_clean
CourseCompleteImageConfiguration(mode='test2', icon=get_image('good'), default=True).full_clean
)
def test_runs_validator(self):
......@@ -50,7 +46,7 @@ class BadgeImageConfigurationTest(TestCase, ImageFetchingMixin):
"""
self.assertRaises(
ValidationError,
CourseCompleteImageConfiguration(mode='test2', icon=self.get_image('unbalanced')).full_clean
CourseCompleteImageConfiguration(mode='test2', icon=get_image('unbalanced')).full_clean
)
......@@ -60,7 +56,7 @@ class DummyBackend(object):
"""
class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
class BadgeClassTest(ModuleStoreTestCase):
"""
Test BadgeClass functionality
"""
......@@ -80,7 +76,7 @@ class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
# 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=self.get_image('good')
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')
......@@ -97,11 +93,11 @@ class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
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=self.get_image('good')
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=self.get_image('good'),
criteria='test', display_name='Testola', image_file_handle=get_image('good'),
course_id=course_key,
)
self.assertNotEqual(badge_class.id, course_badge_class.id)
......@@ -114,7 +110,7 @@ class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
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=self.get_image('good')
image_file_handle=get_image('good')
)
# This should have been saved before being passed back.
self.assertTrue(badge_class.id)
......@@ -152,7 +148,7 @@ class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
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=self.get_image('unbalanced')
image_file_handle=get_image('unbalanced')
)
def test_get_for_user(self):
......@@ -185,7 +181,7 @@ class BadgeClassTest(ModuleStoreTestCase, ImageFetchingMixin):
ValidationError,
BadgeClass(
slug='test', issuing_component='test2', criteria='test3',
description='test4', image=self.get_image('unbalanced')
description='test4', image=get_image('unbalanced')
).full_clean
)
......@@ -221,7 +217,7 @@ class BadgeAssertionTest(ModuleStoreTestCase):
self.assertEqual(course_scoped_assertions, course_assertions)
class ValidBadgeImageTest(TestCase, ImageFetchingMixin):
class ValidBadgeImageTest(TestCase):
"""
Tests the badge image field validator.
"""
......@@ -229,18 +225,18 @@ class ValidBadgeImageTest(TestCase, ImageFetchingMixin):
"""
Verify that saving a valid badge image is no problem.
"""
validate_badge_image(self.get_image('good'))
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(self.get_image('unbalanced'))
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 = self.get_image('large')
large = get_image('large')
self.assertRaises(ValidationError, validate_badge_image, large)
......@@ -6,6 +6,7 @@ import re
from django.core.urlresolvers import reverse
from django.conf import settings
from badges.service import BadgingService
from openedx.core.djangoapps.user_api.course_tag import api as user_course_tag_api
from request_cache.middleware import RequestCache
import xblock.reference.plugins
......@@ -213,6 +214,8 @@ class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
)
services['settings'] = SettingsService()
services['user_tags'] = UserTagsService(self)
if settings.FEATURES["ENABLE_OPENBADGES"]:
services['badging'] = BadgingService()
self.request_token = kwargs.pop('request_token', None)
super(LmsModuleSystem, self).__init__(**kwargs)
......
......@@ -5,16 +5,23 @@ Tests of the LMS XBlock Runtime and associated utilities
from django.contrib.auth.models import User
from django.conf import settings
from ddt import ddt, data
from mock import Mock
from unittest import TestCase
from django.test import TestCase
from mock import Mock, patch
from urlparse import urlparse
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator, SlashSeparatedCourseKey
from badges.tests.factories import BadgeClassFactory
from badges.tests.test_models import get_image
from lms.djangoapps.lms_xblock.runtime import quote_slashes, unquote_slashes, LmsModuleSystem
from xblock.fields import ScopeIds
from xmodule.modulestore.django import ModuleI18nService
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xblock.exceptions import NoSuchServiceError
from student.tests.factories import UserFactory
TEST_STRINGS = [
'',
'foobar',
......@@ -141,9 +148,7 @@ class TestUserServiceAPI(TestCase):
def setUp(self):
super(TestUserServiceAPI, self).setUp()
self.course_id = SlashSeparatedCourseKey("org", "course", "run")
self.user = User(username='runtime_robot', email='runtime_robot@edx.org', password='test', first_name='Robot')
self.user.save()
self.user = UserFactory.create()
def mock_get_real_user(_anon_id):
"""Just returns the test user"""
......@@ -186,6 +191,67 @@ class TestUserServiceAPI(TestCase):
self.runtime.service(self.mock_block, 'user_tags').get_tag('fake_scope', self.key)
class TestBadgingService(TestCase):
"""Test the badging service interface"""
def setUp(self):
super(TestBadgingService, self).setUp()
self.course_id = CourseKey.from_string('course-v1:org+course+run')
self.user = User(username='test_robot', email='test_robot@edx.org', password='test', first_name='Test')
self.user.save()
self.mock_block = Mock()
self.mock_block.service_declaration.return_value = 'needs'
def create_runtime(self):
"""
Create the testing runtime.
"""
def mock_get_real_user(_anon_id):
"""Just returns the test user"""
return self.user
return LmsModuleSystem(
static_url='/static',
track_function=Mock(),
get_module=Mock(),
render_template=Mock(),
replace_urls=str,
course_id=self.course_id,
get_real_user=mock_get_real_user,
descriptor_runtime=Mock(),
)
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
def test_service_rendered(self):
runtime = self.create_runtime()
self.assertTrue(runtime.service(self.mock_block, 'badging'))
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': False})
def test_no_service_rendered(self):
runtime = self.create_runtime()
self.assertFalse(runtime.service(self.mock_block, 'badging'))
@patch.dict(settings.FEATURES, {'ENABLE_OPENBADGES': True})
def test_get_badge_class(self):
runtime = self.create_runtime()
badge_service = runtime.service(self.mock_block, 'badging')
premade_badge_class = BadgeClassFactory.create()
# Ignore additional parameters. This class already exists.
# We should get back the first class we created, rather than a new one.
badge_class = badge_service.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)
class TestI18nService(ModuleStoreTestCase):
""" Test ModuleI18nService """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment