test_course_info.py 12 KB
Newer Older
1 2 3
"""
Test the course_info xblock
"""
4
import mock
5
from nose.plugins.attrib import attr
6
from pyquery import PyQuery as pq
7
from urllib import urlencode
Ned Batchelder committed
8

9
from ccx_keys.locator import CCXLocator
10
from django.conf import settings
Ned Batchelder committed
11
from django.core.urlresolvers import reverse
12
from django.test.utils import override_settings
13
from opaque_keys.edx.locations import SlashSeparatedCourseKey
14

15
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
16
from util.date_utils import strftime_localized
17 18 19
from xmodule.modulestore.tests.django_utils import (
    ModuleStoreTestCase,
    SharedModuleStoreTestCase,
20
    TEST_DATA_SPLIT_MODULESTORE,
21
    TEST_DATA_MIXED_MODULESTORE
22
)
23 24
from xmodule.modulestore.tests.utils import TEST_DATA_DIR
from xmodule.modulestore.xml_importer import import_course_from_xml
25
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
26
from student.models import CourseEnrollment
27
from student.tests.factories import AdminFactory
28

29 30
from .helpers import LoginEnrollmentTestCase

31 32
from lms.djangoapps.ccx.tests.factories import CcxFactory

33

34
@attr('shard_1')
35
class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
36 37 38
    """
    Tests for the Course Info page
    """
39

40 41 42 43 44 45
    @classmethod
    def setUpClass(cls):
        super(CourseInfoTestCase, cls).setUpClass()
        cls.course = CourseFactory.create()
        cls.page = ItemFactory.create(
            category="course_info", parent_location=cls.course.location,
46 47 48
            data="OOGIE BLOOGIE", display_name="updates"
        )

49
    def test_logged_in_unenrolled(self):
50
        self.setup_user()
51
        url = reverse('info', args=[self.course.id.to_deprecated_string()])
52 53 54
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertIn("OOGIE BLOOGIE", resp.content)
55 56 57 58 59 60 61
        self.assertIn("You are not currently enrolled in this course", resp.content)

    def test_logged_in_enrolled(self):
        self.enroll(self.course)
        url = reverse('info', args=[self.course.id.to_deprecated_string()])
        resp = self.client.get(url)
        self.assertNotIn("You are not currently enrolled in this course", resp.content)
62 63

    def test_anonymous_user(self):
64
        url = reverse('info', args=[self.course.id.to_deprecated_string()])
65 66 67
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertNotIn("OOGIE BLOOGIE", resp.content)
68

69 70 71 72 73 74 75 76 77 78 79 80 81 82
    def test_logged_in_not_enrolled(self):
        self.setup_user()
        url = reverse('info', args=[self.course.id.to_deprecated_string()])
        self.client.get(url)

        # Check whether the user has been enrolled in the course.
        # There was a bug in which users would be automatically enrolled
        # with is_active=False (same as if they enrolled and immediately unenrolled).
        # This verifies that the user doesn't have *any* enrollment record.
        enrollment_exists = CourseEnrollment.objects.filter(
            user=self.user, course_id=self.course.id
        ).exists()
        self.assertFalse(enrollment_exists)

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False})
    def test_non_live_course(self):
        """Ensure that a user accessing a non-live course sees a redirect to
        the student dashboard, not a 404.
        """
        self.setup_user()
        self.enroll(self.course)
        url = reverse('info', args=[unicode(self.course.id)])
        response = self.client.get(url)
        start_date = strftime_localized(self.course.start, 'SHORT_DATE')
        self.assertRedirects(response, '{0}?{1}'.format(reverse('dashboard'), urlencode({'notlive': start_date})))

    def test_nonexistent_course(self):
        self.setup_user()
        url = reverse('info', args=['not/a/course'])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

@attr('shard_1')
class CourseInfoLastAccessedTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
    """
    Tests of the CourseInfo last accessed link.
    """

    def setUp(self):
        super(CourseInfoLastAccessedTestCase, self).setUp()
        self.course = CourseFactory.create()
        self.page = ItemFactory.create(
            category="course_info", parent_location=self.course.location,
            data="OOGIE BLOOGIE", display_name="updates"
        )

116
    def test_last_accessed_courseware_not_shown(self):
117 118 119 120
        """
        Test that the last accessed courseware link is not shown if there
        is no course content.
        """
121 122 123
        SelfPacedConfiguration(enable_course_home_improvements=True).save()
        url = reverse('info', args=(unicode(self.course.id),))
        response = self.client.get(url)
124 125
        content = pq(response.content)
        self.assertEqual(content('.page-header-secondary a').length, 0)
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

    def test_last_accessed_shown(self):
        SelfPacedConfiguration(enable_course_home_improvements=True).save()
        chapter = ItemFactory.create(
            category="chapter", parent_location=self.course.location
        )
        section = ItemFactory.create(
            category='section', parent_location=chapter.location
        )
        section_url = reverse(
            'courseware_section',
            kwargs={
                'section': section.url_name,
                'chapter': chapter.url_name,
                'course_id': self.course.id
            }
        )
        self.client.get(section_url)
        info_url = reverse('info', args=(unicode(self.course.id),))
        info_page_response = self.client.get(info_url)
146 147
        content = pq(info_page_response.content)
        self.assertEqual(content('.page-header-secondary .last-accessed-link').attr('href'), section_url)
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

@attr('shard_1')
class CourseInfoTitleTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
    """
    Tests of the CourseInfo page title.
    """

    def setUp(self):
        super(CourseInfoTitleTestCase, self).setUp()
        self.course = CourseFactory.create()
        self.page = ItemFactory.create(
            category="course_info", parent_location=self.course.location,
            data="OOGIE BLOOGIE", display_name="updates"
        )

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    def test_info_title(self):
        """
        Test the info page on a course without any display_* settings against
        one that does.
        """
        url = reverse('info', args=(unicode(self.course.id),))
        response = self.client.get(url)
        content = pq(response.content)
        expected_title = "Welcome to {org}'s {course_name}!".format(
            org=self.course.display_org_with_default,
            course_name=self.course.display_number_with_default
        )
        display_course = CourseFactory.create(
            org="HogwartZ",
            number="Potions_3",
            display_organization="HogwartsX",
            display_coursenumber="Potions",
            display_name="Introduction_to_Potions"
        )
        display_url = reverse('info', args=(unicode(display_course.id),))
        display_response = self.client.get(display_url)
        display_content = pq(display_response.content)
        expected_display_title = "Welcome to {org}'s {course_name}!".format(
            org=display_course.display_org_with_default,
            course_name=display_course.display_number_with_default
        )
        self.assertIn(
            expected_title,
            content('h1.page-title').contents()
        )
        self.assertIn(
            expected_display_title,
            display_content('h1.page-title').contents()
        )
        self.assertIn(
            display_course.display_name_with_default,
            display_content('h2.page-subtitle').contents()
        )

Adam Palay committed
203

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
    """
    Test for unenrolled student tries to access ccx.
    Note: Only CCX coach can enroll a student in CCX. In sum self-registration not allowed.
    """

    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    @classmethod
    def setUpClass(cls):
        super(CourseInfoTestCaseCCX, cls).setUpClass()
        cls.course = CourseFactory.create()

    def setUp(self):
        super(CourseInfoTestCaseCCX, self).setUp()

        # Create ccx coach account
        self.coach = coach = AdminFactory.create(password="test")
        self.client.login(username=coach.username, password="test")

    def test_redirect_to_dashboard_unenrolled_ccx(self):
        """
        Assert that when unenroll student tries to access ccx do not allow him self-register.
        Redirect him to his student dashboard
        """
        # create ccx
        ccx = CcxFactory(course_id=self.course.id, coach=self.coach)
        ccx_locator = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id))

        self.setup_user()
        url = reverse('info', args=[ccx_locator])
        response = self.client.get(url)
        expected = reverse('dashboard')
        self.assertRedirects(response, expected, status_code=302, target_status_code=200)


240
@attr('shard_1')
Adam Palay committed
241
class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
242 243 244
    """
    Tests for the Course Info page for an XML course
    """
245
    MODULESTORE = TEST_DATA_MIXED_MODULESTORE
246

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
    def setUp(self):
        """
        Set up the tests
        """
        super(CourseInfoTestCaseXML, self).setUp()

        # The following test course (which lives at common/test/data/2014)
        # is closed; we're testing that a course info page still appears when
        # the course is already closed
        self.xml_course_key = self.store.make_course_key('edX', 'detached_pages', '2014')
        import_course_from_xml(
            self.store,
            'test_user',
            TEST_DATA_DIR,
            source_dirs=['2014'],
            static_content_store=None,
            target_id=self.xml_course_key,
            raise_on_failure=True,
            create_if_not_present=True,
        )
Adam Palay committed
267

268 269 270
        # this text appears in that course's course info page
        # common/test/data/2014/info/updates.html
        self.xml_data = "course info 463139"
Adam Palay committed
271

272 273 274
    @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_logged_in_xml(self):
        self.setup_user()
275
        url = reverse('info', args=[self.xml_course_key.to_deprecated_string()])
276 277 278 279 280 281
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertIn(self.xml_data, resp.content)

    @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_anonymous_user_xml(self):
282
        url = reverse('info', args=[self.xml_course_key.to_deprecated_string()])
283 284 285
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertNotIn(self.xml_data, resp.content)
286 287 288 289 290 291 292 293


@attr('shard_1')
@override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False))
class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
    """
    Tests for the info page of self-paced courses.
    """
294
    ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
295

296 297 298 299 300 301
    @classmethod
    def setUpClass(cls):
        super(SelfPacedCourseInfoTestCase, cls).setUpClass()
        cls.instructor_paced_course = CourseFactory.create(self_paced=False)
        cls.self_paced_course = CourseFactory.create(self_paced=True)

302
    def setUp(self):
303
        SelfPacedConfiguration(enabled=True).save()
304 305 306 307 308 309 310 311 312 313 314 315 316 317
        super(SelfPacedCourseInfoTestCase, self).setUp()
        self.setup_user()

    def fetch_course_info_with_queries(self, course, sql_queries, mongo_queries):
        """
        Fetch the given course's info page, asserting the number of SQL
        and Mongo queries.
        """
        url = reverse('info', args=[unicode(course.id)])
        with self.assertNumQueries(sql_queries):
            with check_mongo_calls(mongo_queries):
                resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)

318
    def test_num_queries_instructor_paced(self):
319
        self.fetch_course_info_with_queries(self.instructor_paced_course, 21, 4)
320 321

    def test_num_queries_self_paced(self):
322
        self.fetch_course_info_with_queries(self.self_paced_course, 21, 4)