test_split_module.py 10.8 KB
Newer Older
1 2 3 4
"""
Test for split test XModule
"""
from django.core.urlresolvers import reverse
5
from mock import MagicMock
6
from nose.plugins.attrib import attr
7

8 9
from courseware.module_render import get_module_for_descriptor
from courseware.model_data import FieldDataCache
10
from student.tests.factories import UserFactory, CourseEnrollmentFactory
11
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
Toby Lawrence committed
12
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
13
from xmodule.partitions.partitions import Group, UserPartition
14
from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
15 16


17
@attr('shard_1')
18
class SplitTestBase(SharedModuleStoreTestCase):
19 20 21 22
    """
    Sets up a basic course and user for split test testing.
    Also provides tests of rendered HTML for two user_tag conditions, 0 and 1.
    """
23
    __test__ = False
24 25 26 27
    COURSE_NUMBER = 'split-test-base'
    ICON_CLASSES = None
    TOOLTIPS = None
    VISIBLE_CONTENT = None
28

29 30 31 32
    @classmethod
    def setUpClass(cls):
        super(SplitTestBase, cls).setUpClass()
        cls.partition = UserPartition(
33 34 35 36 37 38 39 40 41
            0,
            'first_partition',
            'First Partition',
            [
                Group(0, 'alpha'),
                Group(1, 'beta')
            ]
        )

42 43 44
        cls.course = CourseFactory.create(
            number=cls.COURSE_NUMBER,
            user_partitions=[cls.partition]
45 46
        )

47 48
        cls.chapter = ItemFactory.create(
            parent_location=cls.course.location,
49 50 51
            category="chapter",
            display_name="test chapter",
        )
52 53
        cls.sequential = ItemFactory.create(
            parent_location=cls.chapter.location,
54 55 56 57
            category="sequential",
            display_name="Split Test Tests",
        )

58 59 60
    def setUp(self):
        super(SplitTestBase, self).setUp()

61 62 63 64
        self.student = UserFactory.create()
        CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
        self.client.login(username=self.student.username, password='test')

65 66 67
        self.included_usage_keys = None
        self.excluded_usage_keys = None

68
    def _video(self, parent, group):
69 70 71 72
        """
        Returns a video component with parent ``parent``
        that is intended to be displayed to group ``group``.
        """
73 74 75 76 77 78 79
        return ItemFactory.create(
            parent_location=parent.location,
            category="video",
            display_name="Group {} Sees This Video".format(group),
        )

    def _problem(self, parent, group):
80 81 82 83
        """
        Returns a problem component with parent ``parent``
        that is intended to be displayed to group ``group``.
        """
84 85 86 87 88 89 90 91
        return ItemFactory.create(
            parent_location=parent.location,
            category="problem",
            display_name="Group {} Sees This Problem".format(group),
            data="<h1>No Problem Defined Yet!</h1>",
        )

    def _html(self, parent, group):
92 93 94 95
        """
        Returns an html component with parent ``parent``
        that is intended to be displayed to group ``group``.
        """
96 97 98 99 100 101 102 103 104 105 106 107 108 109
        return ItemFactory.create(
            parent_location=parent.location,
            category="html",
            display_name="Group {} Sees This HTML".format(group),
            data="Some HTML for group {}".format(group),
        )

    def test_split_test_0(self):
        self._check_split_test(0)

    def test_split_test_1(self):
        self._check_split_test(1)

    def _check_split_test(self, user_tag):
110 111 112
        """Checks that the right compentents are rendered for user with ``user_tag``"""
        # This explicitly sets the user_tag for self.student to ``user_tag``
        UserCourseTagFactory(
113 114 115 116 117 118
            user=self.student,
            course_id=self.course.id,
            key='xblock.partition_service.partition_{0}'.format(self.partition.id),
            value=str(user_tag)
        )

119 120
        resp = self.client.get(reverse(
            'courseware_section',
121
            kwargs={'course_id': self.course.id.to_deprecated_string(),
122 123
                    'chapter': self.chapter.url_name,
                    'section': self.sequential.url_name}
124 125 126 127
        ))
        content = resp.content

        # Assert we see the proper icon in the top display
128
        self.assertIn('<button class="{} inactive progress-0 nav-item"'.format(self.ICON_CLASSES[user_tag]), content)
129 130 131 132
        # And proper tooltips
        for tooltip in self.TOOLTIPS[user_tag]:
            self.assertIn(tooltip, content)

133 134 135 136 137 138
        unicode_content = content.decode("utf-8")
        for key in self.included_usage_keys[user_tag]:
            self.assertIn(unicode(key), unicode_content)

        for key in self.excluded_usage_keys[user_tag]:
            self.assertNotIn(unicode(key), unicode_content)
139 140 141 142 143 144

        # Assert that we can see the data from the appropriate test condition
        for visible in self.VISIBLE_CONTENT[user_tag]:
            self.assertIn(visible, content)


145
class TestSplitTestVert(SplitTestBase):
146
    """
147
    Tests a sequential whose top-level vertical is determined by a split test.
148 149 150
    """
    __test__ = True

151
    COURSE_NUMBER = 'test-split-test-vert-vert'
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

    ICON_CLASSES = [
        'seq_problem',
        'seq_video',
    ]
    TOOLTIPS = [
        ['Condition 0 vertical'],
        ['Condition 1 vertical'],
    ]
    # Data is html encoded, because it's inactive inside the
    # sequence until javascript is executed
    VISIBLE_CONTENT = [
        ['class=&#34;problems-wrapper'],
        ['Some HTML for group 1']
    ]

    def setUp(self):
169 170
        # We define problem compenents that we need but don't explicitly call elsewhere.
        # pylint: disable=unused-variable
171
        super(TestSplitTestVert, self).setUp()
172

173 174
        c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0")
        c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1")
175 176

        split_test = ItemFactory.create(
177
            parent_location=self.sequential.location,
178 179 180
            category="split_test",
            display_name="Split test",
            user_partition_id='0',
181
            group_id_to_child={"0": c0_url, "1": c1_url},
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
        )

        cond0vert = ItemFactory.create(
            parent_location=split_test.location,
            category="vertical",
            display_name="Condition 0 vertical",
            location=c0_url,
        )
        video0 = self._video(cond0vert, 0)
        problem0 = self._problem(cond0vert, 0)

        cond1vert = ItemFactory.create(
            parent_location=split_test.location,
            category="vertical",
            display_name="Condition 1 vertical",
            location=c1_url,
        )
        video1 = self._video(cond1vert, 1)
        html1 = self._html(cond1vert, 1)

202 203 204 205
        self.included_usage_keys = [
            [video0.location, problem0.location],
            [video1.location, html1.location],
        ]
206

207 208 209 210 211 212 213
        self.excluded_usage_keys = [
            [video1.location, html1.location],
            [video0.location, problem0.location],
        ]


class TestVertSplitTestVert(SplitTestBase):
214
    """
215
    Tests a sequential whose top-level vertical contains a split test determining content within that vertical.
216 217 218
    """
    __test__ = True

219
    COURSE_NUMBER = 'test-vert-split-test-vert'
220 221 222 223 224 225

    ICON_CLASSES = [
        'seq_problem',
        'seq_video',
    ]
    TOOLTIPS = [
226 227
        ['Split test vertical'],
        ['Split test vertical'],
228 229 230 231 232 233 234 235 236 237
    ]

    # Data is html encoded, because it's inactive inside the
    # sequence until javascript is executed
    VISIBLE_CONTENT = [
        ['class=&#34;problems-wrapper'],
        ['Some HTML for group 1']
    ]

    def setUp(self):
238 239
        # We define problem compenents that we need but don't explicitly call elsewhere.
        # pylint: disable=unused-variable
240
        super(TestVertSplitTestVert, self).setUp()
241

242 243 244 245 246
        vert1 = ItemFactory.create(
            parent_location=self.sequential.location,
            category="vertical",
            display_name="Split test vertical",
        )
247 248
        c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0")
        c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1")
249 250

        split_test = ItemFactory.create(
251
            parent_location=vert1.location,
252 253 254
            category="split_test",
            display_name="Split test",
            user_partition_id='0',
255
            group_id_to_child={"0": c0_url, "1": c1_url},
256 257 258 259 260
        )

        cond0vert = ItemFactory.create(
            parent_location=split_test.location,
            category="vertical",
261 262
            display_name="Condition 0 Vertical",
            location=c0_url
263 264 265 266 267 268 269
        )
        video0 = self._video(cond0vert, 0)
        problem0 = self._problem(cond0vert, 0)

        cond1vert = ItemFactory.create(
            parent_location=split_test.location,
            category="vertical",
270 271
            display_name="Condition 1 Vertical",
            location=c1_url
272 273 274
        )
        video1 = self._video(cond1vert, 1)
        html1 = self._html(cond1vert, 1)
275

276 277 278 279 280 281 282 283 284 285
        self.included_usage_keys = [
            [video0.location, problem0.location],
            [video1.location, html1.location],
        ]

        self.excluded_usage_keys = [
            [video1.location, html1.location],
            [video0.location, problem0.location],
        ]

286

287
@attr('shard_1')
288
class SplitTestPosition(SharedModuleStoreTestCase):
289 290 291
    """
    Check that we can change positions in a course with partitions defined
    """
292 293 294 295
    @classmethod
    def setUpClass(cls):
        super(SplitTestPosition, cls).setUpClass()
        cls.partition = UserPartition(
296 297 298 299 300 301 302 303 304
            0,
            'first_partition',
            'First Partition',
            [
                Group(0, 'alpha'),
                Group(1, 'beta')
            ]
        )

305 306
        cls.course = CourseFactory.create(
            user_partitions=[cls.partition]
307 308
        )

309 310
        cls.chapter = ItemFactory.create(
            parent_location=cls.course.location,
311 312 313 314
            category="chapter",
            display_name="test chapter",
        )

315 316 317
    def setUp(self):
        super(SplitTestPosition, self).setUp()

318 319 320 321 322 323 324 325 326 327 328 329
        self.student = UserFactory.create()
        CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
        self.client.login(username=self.student.username, password='test')

    def test_changing_position_works(self):
        # Make a mock FieldDataCache for this course, so we can get the course module
        mock_field_data_cache = FieldDataCache([self.course], self.course.id, self.student)
        course = get_module_for_descriptor(
            self.student,
            MagicMock(name='request'),
            self.course,
            mock_field_data_cache,
330 331
            self.course.id,
            course=self.course
332 333 334 335 336
        )

        # Now that we have the course, change the position and save, nothing should explode!
        course.position = 2
        course.save()