""" Test scenarios for the crowdsource hinter xblock. """ import json import unittest from django.conf import settings from django.core.urlresolvers import reverse from nose.plugins.attrib import attr from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from openedx.core.lib.url_utils import quote_slashes from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory class TestCrowdsourceHinter(SharedModuleStoreTestCase, LoginEnrollmentTestCase): """ Create the test environment with the crowdsourcehinter xblock. """ STUDENTS = [ {'email': 'view@test.com', 'password': 'foo'}, {'email': 'view2@test.com', 'password': 'foo'} ] XBLOCK_NAMES = ['crowdsourcehinter'] @classmethod def setUpClass(cls): # Nose runs setUpClass methods even if a class decorator says to skip # the class: https://github.com/nose-devs/nose/issues/946 # So, skip the test class here if we are not in the LMS. if settings.ROOT_URLCONF != 'lms.urls': raise unittest.SkipTest('Test only valid in lms') super(TestCrowdsourceHinter, cls).setUpClass() cls.course = CourseFactory.create( display_name='CrowdsourceHinter_Test_Course' ) with cls.store.bulk_operations(cls.course.id, emit_signals=False): cls.chapter = ItemFactory.create( parent=cls.course, display_name='Overview' ) cls.section = ItemFactory.create( parent=cls.chapter, display_name='Welcome' ) cls.unit = ItemFactory.create( parent=cls.section, display_name='New Unit' ) cls.xblock = ItemFactory.create( parent=cls.unit, category='crowdsourcehinter', display_name='crowdsourcehinter' ) cls.course_url = reverse( 'courseware_section', kwargs={ 'course_id': cls.course.id.to_deprecated_string(), 'chapter': 'Overview', 'section': 'Welcome', } ) def setUp(self): super(TestCrowdsourceHinter, self).setUp() for idx, student in enumerate(self.STUDENTS): username = "u{}".format(idx) self.create_account(username, student['email'], student['password']) self.activate_user(student['email']) self.staff_user = GlobalStaffFactory() def get_handler_url(self, handler, xblock_name=None): """ Get url for the specified xblock handler """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] return reverse('xblock_handler', kwargs={ 'course_id': self.course.id.to_deprecated_string(), 'usage_id': quote_slashes(self.course.id.make_usage_key('crowdsourcehinter', xblock_name). to_deprecated_string()), 'handler': handler, 'suffix': '' }) def enroll_student(self, email, password): """ Student login and enroll for the course """ self.login(email, password) self.enroll(self.course, verify=True) def enroll_staff(self, staff): """ Staff login and enroll for the course """ email = staff.email password = 'test' self.login(email, password) self.enroll(self.course, verify=True) def initialize_database_by_id(self, handler, resource_id, times, xblock_name=None): """ Call a ajax event (vote, delete, endorse) on a resource by its id several times """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] url = self.get_handler_url(handler, xblock_name) for _ in range(times): self.client.post(url, json.dumps({'id': resource_id}), '') def call_event(self, handler, resource, xblock_name=None): """ Call a ajax event (add, edit, flag, etc.) by specifying the resource it takes """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] url = self.get_handler_url(handler, xblock_name) return self.client.post(url, json.dumps(resource), '') def check_event_response_by_element(self, handler, resource, resp_key, resp_val, xblock_name=None): """ Call the event specified by the handler with the resource, and check whether the element (resp_key) in response is as expected (resp_val) """ if xblock_name is None: xblock_name = TestCrowdsourceHinter.XBLOCK_NAMES[0] resp = self.call_event(handler, resource, xblock_name) self.assertEqual(resp[resp_key], resp_val) self.assert_request_status_code(200, self.course_url) @attr(shard=1) class TestHinterFunctions(TestCrowdsourceHinter): """ Check that the essential functions of the hinter work as expected. Tests cover the basic process of receiving a hint, adding a new hint, and rating/reporting hints. """ def test_get_hint_with_no_hints(self): """ Check that a generic statement is returned when no default/specific hints exist """ result = self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}, 'crowdsourcehinter') expected = {'BestHint': 'Sorry, there are no hints for this answer.', 'StudentAnswer': 'incorrect answer 1', 'HintCategory': False} self.assertEqual(json.loads(result.content), expected) def test_add_new_hint(self): """ Test the ability to add a new specific hint """ self.enroll_student(self.STUDENTS[0]['email'], self.STUDENTS[0]['password']) data = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) result = self.call_event('add_new_hint', data) expected = {'success': True, 'result': 'Hint added'} self.assertEqual(json.loads(result.content), expected) def test_get_hint(self): """ Check that specific hints are returned """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) result = self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) expected = {'BestHint': 'new hint for answer 1', 'StudentAnswer': 'incorrect answer 1', 'HintCategory': 'ErrorResponse'} self.assertEqual(json.loads(result.content), expected) def test_rate_hint_upvote(self): """ Test hint upvoting """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) data = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1', 'student_rating': 'upvote' } expected = {'success': True} result = self.call_event('rate_hint', data) self.assertEqual(json.loads(result.content), expected) def test_rate_hint_downvote(self): """ Test hint downvoting """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) data = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1', 'student_rating': 'downvote' } expected = {'success': True} result = self.call_event('rate_hint', data) self.assertEqual(json.loads(result.content), expected) def test_report_hint(self): """ Test hint reporting """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) data = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1', 'student_rating': 'report' } expected = {'rating': 'reported', 'hint': 'new hint for answer 1'} result = self.call_event('rate_hint', data) self.assertEqual(json.loads(result.content), expected) def test_dont_show_reported_hint(self): """ Check that reported hints are returned """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) data = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1', 'student_rating': 'report' } self.call_event('rate_hint', data) result = self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) expected = {'BestHint': 'Sorry, there are no hints for this answer.', 'StudentAnswer': 'incorrect answer 1', 'HintCategory': False} self.assertEqual(json.loads(result.content), expected) def test_get_used_hint_answer_data(self): """ Check that hint/answer information from previous submissions are returned upon correctly answering the problem """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) self.call_event('get_used_hint_answer_data', "") submission = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission) self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) result = self.call_event('get_used_hint_answer_data', "") expected = {'new hint for answer 1': 'incorrect answer 1'} self.assertEqual(json.loads(result.content), expected) def test_show_best_hint(self): """ Check that the most upvoted hint is shown """ self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) submission1 = {'new_hint_submission': 'new hint for answer 1', 'answer': 'incorrect answer 1'} submission2 = {'new_hint_submission': 'new hint for answer 1 to report', 'answer': 'incorrect answer 1'} self.call_event('add_new_hint', submission1) self.call_event('add_new_hint', submission2) data_upvote = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1 to report', 'student_rating': 'upvote' } self.call_event('rate_hint', data_upvote) data_downvote = { 'student_answer': 'incorrect answer 1', 'hint': 'new hint for answer 1 to report', 'student_rating': 'report' } self.call_event('rate_hint', data_downvote) result = self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}) expected = {'BestHint': 'new hint for answer 1', 'StudentAnswer': 'incorrect answer 1', 'HintCategory': 'ErrorResponse'} self.assertEqual(json.loads(result.content), expected)