test_crowdsource_hinter.py 11.8 KB
Newer Older
solashirai committed
1 2 3 4
"""
Test scenarios for the crowdsource hinter xblock.
"""
import json
5 6 7
import unittest

from nose.plugins.attrib import attr
solashirai committed
8 9 10 11 12 13

from django.core.urlresolvers import reverse

from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory

14 15
from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase
from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory
solashirai committed
16 17
from lms.djangoapps.lms_xblock.runtime import quote_slashes

18
from django.conf import settings
solashirai committed
19

solashirai committed
20 21 22

class TestCrowdsourceHinter(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
    """
solashirai committed
23
    Create the test environment with the crowdsourcehinter xblock.
solashirai committed
24 25 26 27 28
    """
    STUDENTS = [
        {'email': 'view@test.com', 'password': 'foo'},
        {'email': 'view2@test.com', 'password': 'foo'}
    ]
29
    XBLOCK_NAMES = ['crowdsourcehinter']
solashirai committed
30

31 32 33 34 35 36 37 38 39 40 41
    @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'
solashirai committed
42
        )
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
        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'
            )
solashirai committed
58

59
        cls.course_url = reverse(
solashirai committed
60 61
            'courseware_section',
            kwargs={
62
                'course_id': cls.course.id.to_deprecated_string(),
solashirai committed
63 64 65 66 67
                'chapter': 'Overview',
                'section': 'Welcome',
            }
        )

68 69
    def setUp(self):
        super(TestCrowdsourceHinter, self).setUp()
solashirai committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
        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(),
solashirai committed
85 86
            'usage_id': quote_slashes(self.course.id.make_usage_key('crowdsourcehinter', xblock_name).
                                      to_deprecated_string()),
solashirai committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
            '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)

107 108 109 110 111 112 113 114 115 116 117
    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}), '')

solashirai committed
118 119 120 121 122 123 124 125
    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)
126
        return self.client.post(url, json.dumps(resource), '')
solashirai committed
127 128 129 130 131 132 133 134 135 136 137 138

    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)

solashirai committed
139

140
@attr(shard=1)
solashirai committed
141 142
class TestHinterFunctions(TestCrowdsourceHinter):
    """
solashirai committed
143 144 145
    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.
solashirai committed
146 147 148 149 150
    """
    def test_get_hint_with_no_hints(self):
        """
        Check that a generic statement is returned when no default/specific hints exist
        """
151
        result = self.call_event('get_hint', {'submittedanswer': 'ans=incorrect+answer+1'}, 'crowdsourcehinter')
solashirai committed
152 153
        expected = {'BestHint': 'Sorry, there are no hints for this answer.', 'StudentAnswer': 'incorrect answer 1',
                    'HintCategory': False}
154
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
155 156 157 158 159 160 161 162

    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'})
solashirai committed
163
        result = self.call_event('add_new_hint', data)
solashirai committed
164
        expected = {'success': True,
solashirai committed
165
                    'result': 'Hint added'}
166
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
167 168 169 170 171 172 173 174 175 176 177 178

    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'}
179
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

    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)
196
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

    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)
213
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

    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)
230
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

    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}
249
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263

    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'}
264
        self.assertEqual(json.loads(result.content), expected)
solashirai committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291

    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'}
292
        self.assertEqual(json.loads(result.content), expected)