test_views.py 29.5 KB
Newer Older
1
# coding=UTF-8
2 3 4
"""
Tests courseware views.py
"""
5
import cgi
6 7
from datetime import datetime
from pytz import UTC
8
import unittest
9
import ddt
Diana Huang committed
10

Joe Blaylock committed
11
from django.conf import settings
12
from django.contrib.auth.models import User, AnonymousUser
13
from django.core.urlresolvers import reverse
14 15 16 17
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
David Baumgold committed
18
from edxmako.middleware import MakoMiddleware
19
from edxmako.tests import mako_middleware_process_request
20 21
from mock import MagicMock, patch, create_autospec
from opaque_keys.edx.locations import Location, SlashSeparatedCourseKey
22

23
import courseware.views as views
24 25 26
from xmodule.modulestore.tests.django_utils import (
    TEST_DATA_MOCK_MODULESTORE, TEST_DATA_MIXED_TOY_MODULESTORE
)
27
from course_modes.models import CourseMode
28
import shoppingcart
29 30 31 32 33
from student.models import CourseEnrollment
from student.tests.factories import AdminFactory, UserFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
34
from util.tests.test_date_utils import fake_ugettext, fake_pgettext
35
from util.views import ensure_valid_course_key
36

Jay Zoldak committed
37

38
class TestJumpTo(ModuleStoreTestCase):
39
    """
40
    Check the jumpto link for a course.
41
    """
42
    MODULESTORE = TEST_DATA_MIXED_TOY_MODULESTORE
43

44
    def setUp(self):
45
        super(TestJumpTo, self).setUp()
46
        # Use toy course from XML
47
        self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
48

Jay Zoldak committed
49
    def test_jumpto_invalid_location(self):
50
        location = self.course_key.make_usage_key(None, 'NoSuchPlace')
51 52 53
        # This is fragile, but unfortunately the problem is that within the LMS we
        # can't use the reverse calls from the CMS
        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
Jay Zoldak committed
54 55 56
        response = self.client.get(jumpto_url)
        self.assertEqual(response.status_code, 404)

57
    @unittest.skip
Jay Zoldak committed
58
    def test_jumpto_from_chapter(self):
59 60
        location = self.course_key.make_usage_key('chapter', 'Overview')
        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
Jay Zoldak committed
61 62 63
        expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)
64

65
    @unittest.skip
66
    def test_jumpto_id(self):
67
        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview')
68 69 70 71 72
        expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)

    def test_jumpto_id_invalid_location(self):
73 74
        location = Location('edX', 'toy', 'NoSuchPlace', None, None, None)
        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
75 76 77
        response = self.client.get(jumpto_url)
        self.assertEqual(response.status_code, 404)

Jay Zoldak committed
78

79
@ddt.ddt
80
class ViewsTestCase(ModuleStoreTestCase):
81 82 83
    """
    Tests for views.py methods.
    """
Deena Wang committed
84
    def setUp(self):
85
        super(ViewsTestCase, self).setUp()
Don Mitchell committed
86 87 88 89 90
        self.course = CourseFactory.create()
        self.chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)  # pylint: disable=no-member
        self.section = ItemFactory.create(category='sequential', parent_location=self.chapter.location, due=datetime(2013, 9, 18, 11, 30, 00))
        self.vertical = ItemFactory.create(category='vertical', parent_location=self.section.location)
        self.component = ItemFactory.create(category='problem', parent_location=self.vertical.location)
91

92
        self.course_key = self.course.id
93
        self.user = UserFactory(username='dummy', password='123456', email='test@mit.edu')
94
        self.date = datetime(2013, 1, 22, tzinfo=UTC)
95
        self.enrollment = CourseEnrollment.enroll(self.user, self.course_key)
96 97
        self.enrollment.created = self.date
        self.enrollment.save()
98
        self.request_factory = RequestFactory()
Deena Wang committed
99
        chapter = 'Overview'
100
        self.chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)
Deena Wang committed
101

102 103
        self.org = u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"
        self.org_html = "<p>'+Stark/Industries+'</p>"
104

105 106
    @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
    @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
107 108 109 110
    def test_course_about_in_cart(self):
        in_cart_span = '<span class="add-to-cart">'
        # don't mock this course due to shopping cart existence checking
        course = CourseFactory.create(org="new", number="unenrolled", display_name="course")
111
        request = self.request_factory.get(reverse('about_course', args=[course.id.to_deprecated_string()]))
112
        request.user = AnonymousUser()
113
        response = views.course_about(request, course.id.to_deprecated_string())
114 115 116 117 118
        self.assertEqual(response.status_code, 200)
        self.assertNotIn(in_cart_span, response.content)

        # authenticated user with nothing in cart
        request.user = self.user
119
        response = views.course_about(request, course.id.to_deprecated_string())
120 121 122 123 124 125
        self.assertEqual(response.status_code, 200)
        self.assertNotIn(in_cart_span, response.content)

        # now add the course to the cart
        cart = shoppingcart.models.Order.get_cart_for_user(self.user)
        shoppingcart.models.PaidCourseRegistration.add_to_order(cart, course.id)
126
        response = views.course_about(request, course.id.to_deprecated_string())
127 128 129
        self.assertEqual(response.status_code, 200)
        self.assertIn(in_cart_span, response.content)

Deena Wang committed
130
    def test_user_groups(self):
131
        # depreciated function
Deena Wang committed
132 133
        mock_user = MagicMock()
        mock_user.is_authenticated.return_value = False
134
        self.assertEqual(views.user_groups(mock_user), [])
Jay Zoldak committed
135

Deena Wang committed
136
    def test_get_current_child(self):
137
        self.assertIsNone(views.get_current_child(MagicMock()))
Deena Wang committed
138 139
        mock_xmodule = MagicMock()
        mock_xmodule.position = -1
Jay Zoldak committed
140
        mock_xmodule.get_display_items.return_value = ['one', 'two']
141
        self.assertEqual(views.get_current_child(mock_xmodule), 'one')
Deena Wang committed
142 143 144 145 146 147 148 149 150 151 152
        mock_xmodule_2 = MagicMock()
        mock_xmodule_2.position = 3
        mock_xmodule_2.get_display_items.return_value = []
        self.assertIsNone(views.get_current_child(mock_xmodule_2))

    def test_redirect_to_course_position(self):
        mock_module = MagicMock()
        mock_module.descriptor.id = 'Underwater Basketweaving'
        mock_module.position = 3
        mock_module.get_display_items.return_value = []
        self.assertRaises(Http404, views.redirect_to_course_position,
153
                          mock_module, views.CONTENT_DEPTH)
Deena Wang committed
154

155 156 157 158
    def test_invalid_course_id(self):
        response = self.client.get('/courses/MITx/3.091X/')
        self.assertEqual(response.status_code, 404)

159 160 161 162
    def test_incomplete_course_id(self):
        response = self.client.get('/courses/MITx/')
        self.assertEqual(response.status_code, 404)

163 164 165 166
    def test_index_invalid_position(self):
        request_url = '/'.join([
            '/courses',
            self.course.id.to_deprecated_string(),
167
            'courseware',
168 169 170 171
            self.chapter.location.name,
            self.section.location.name,
            'f'
        ])
172
        self.client.login(username=self.user.username, password="123456")
173 174 175
        response = self.client.get(request_url)
        self.assertEqual(response.status_code, 404)

176
    @unittest.skip
177 178 179 180
    def test_unicode_handling_in_url(self):
        url_parts = [
            '/courses',
            self.course.id.to_deprecated_string(),
181
            'courseware',
182 183 184 185
            self.chapter.location.name,
            self.section.location.name,
            '1'
        ]
186
        self.client.login(username=self.user.username, password="123456")
187 188 189 190 191 192 193
        for idx, val in enumerate(url_parts):
            url_parts_copy = url_parts[:]
            url_parts_copy[idx] = val + u'χ'
            request_url = '/'.join(url_parts_copy)
            response = self.client.get(request_url)
            self.assertEqual(response.status_code, 404)

Deena Wang committed
194 195 196 197 198 199
    def test_registered_for_course(self):
        self.assertFalse(views.registered_for_course('Basketweaving', None))
        mock_user = MagicMock()
        mock_user.is_authenticated.return_value = False
        self.assertFalse(views.registered_for_course('dummy', mock_user))
        mock_course = MagicMock()
200
        mock_course.id = self.course_key
Deena Wang committed
201
        self.assertTrue(views.registered_for_course(mock_course, self.user))
Deena Wang committed
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    @override_settings(PAID_COURSE_REGISTRATION_CURRENCY=["USD", "$"])
    def test_get_cosmetic_display_price(self):
        """
        Check that get_cosmetic_display_price() returns the correct price given its inputs.
        """
        registration_price = 99
        self.course.cosmetic_display_price = 10
        # Since registration_price is set, it overrides the cosmetic_display_price and should be returned
        self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$99")

        registration_price = 0
        # Since registration_price is not set, cosmetic_display_price should be returned
        self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "$10")

        self.course.cosmetic_display_price = 0
        # Since both prices are not set, there is no price, thus "Free"
        self.assertEqual(views.get_cosmetic_display_price(self.course, registration_price), "Free")

221
    def test_jump_to_invalid(self):
222 223
        # TODO add a test for invalid location
        # TODO add a test for no data *
Deena Wang committed
224
        request = self.request_factory.get(self.chapter_url)
225
        self.assertRaisesRegexp(Http404, 'Invalid course_key or usage_key', views.jump_to,
226
                                request, 'bar', ())
227

228
    @unittest.skip
229 230
    def test_no_end_on_about_page(self):
        # Toy course has no course end date or about/end_date blob
231
        self.verify_end_date('edX/toy/TT_2012_Fall')
232

233
    @unittest.skip
234 235
    def test_no_end_about_blob(self):
        # test_end has a course end date, no end_date HTML blob
236
        self.verify_end_date("edX/test_end/2012_Fall", "Sep 17, 2015")
237

238
    @unittest.skip
239
    def test_about_blob_end_date(self):
240
        # test_about_blob_end_date has both a course end date and an end_date HTML blob.
241
        # HTML blob wins
242
        self.verify_end_date("edX/test_about_blob_end_date/2012_Fall", "Learning never ends")
243 244

    def verify_end_date(self, course_id, expected_end_text=None):
245 246 247 248 249 250 251
        """
        Visits the about page for `course_id` and tests that both the text "Classes End", as well
        as the specified `expected_end_text`, is present on the page.

        If `expected_end_text` is None, verifies that the about page *does not* contain the text
        "Classes End".
        """
252
        request = self.request_factory.get("foo")
253
        request.user = self.user
254 255 256 257

        # TODO: Remove the dependency on MakoMiddleware (by making the views explicitly supply a RequestContext)
        MakoMiddleware().process_request(request)

258 259 260 261 262 263
        result = views.course_about(request, course_id)
        if expected_end_text is not None:
            self.assertContains(result, "Classes End")
            self.assertContains(result, expected_end_text)
        else:
            self.assertNotContains(result, "Classes End")
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278

    def test_chat_settings(self):
        mock_user = MagicMock()
        mock_user.username = "johndoe"

        mock_course = MagicMock()
        mock_course.id = "a/b/c"

        # Stub this out in the case that it's not in the settings
        domain = "jabber.edx.org"
        settings.JABBER_DOMAIN = domain

        chat_settings = views.chat_settings(mock_course, mock_user)

        # Test the proper format of all chat settings
279 280 281
        self.assertEqual(chat_settings['domain'], domain)
        self.assertEqual(chat_settings['room'], "a-b-c_class")
        self.assertEqual(chat_settings['username'], "johndoe@%s" % domain)
282 283 284

        # TODO: this needs to be changed once we figure out how to
        #       generate/store a real password.
285
        self.assertEqual(chat_settings['password'], "johndoe@%s" % domain)
286

287
    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
288
    def test_course_mktg_about_coming_soon(self):
289
        # We should not be able to find this course
290
        url = reverse('mktg_about_course', kwargs={'course_id': 'no/course/here'})
291
        response = self.client.get(url, {'org': self.org})
292 293
        self.assertIn('Coming Soon', response.content)

294 295 296 297
        # Verify that the checkbox is not displayed
        self._email_opt_in_checkbox(response)

    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
298 299 300 301 302 303 304 305 306 307 308
    @ddt.data(
        # One organization name
        (u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ", u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"),
        # Two organization names
        (",".join([u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"] * 2), u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + " and " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"),
        # Three organization names
        (",".join([u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"] * 3), u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + ", " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + ", " + "and " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ")
    )
    @ddt.unpack
    def test_course_mktg_register(self, org, org_name_string):
        response = self._load_mktg_about(org=org)
309
        self.assertIn('Enroll in', response.content)
310 311
        self.assertNotIn('and choose your student track', response.content)

312
        # Verify that the checkbox is displayed
313
        self._email_opt_in_checkbox(response, org_name_string)
314 315

    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
316 317 318 319 320 321 322 323 324 325
    @ddt.data(
        # One organization name
        (u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ", u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"),
        # Two organization names
        (",".join([u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"] * 2), u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + " and " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"),
        # Three organization names
        (",".join([u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ"] * 3), u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + ", " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ" + ", " + "and " + u"ꜱᴛᴀʀᴋ ɪɴᴅᴜꜱᴛʀɪᴇꜱ")
    )
    @ddt.unpack
    def test_course_mktg_register_multiple_modes(self, org, org_name_string):
326 327 328 329 330 331 332 333 334 335 336
        CourseMode.objects.get_or_create(
            mode_slug='honor',
            mode_display_name='Honor Code Certificate',
            course_id=self.course_key
        )
        CourseMode.objects.get_or_create(
            mode_slug='verified',
            mode_display_name='Verified Certificate',
            course_id=self.course_key
        )

337
        response = self._load_mktg_about(org=org)
338
        self.assertIn('Enroll in', response.content)
339
        self.assertIn('and choose your student track', response.content)
340 341

        # Verify that the checkbox is displayed
342
        self._email_opt_in_checkbox(response, org_name_string)
343

344 345 346
        # clean up course modes
        CourseMode.objects.all().delete()

347 348 349 350 351 352 353 354 355 356 357 358 359
    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
    def test_course_mktg_no_organization_name(self):
        # Don't pass an organization name as a GET parameter, even though the email
        # opt-in feature is enabled.
        response = response = self._load_mktg_about()

        # Verify that the checkbox is not displayed
        self._email_opt_in_checkbox(response)

    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': False})
    def test_course_mktg_opt_in_disabled(self):
        # Pass an organization name as a GET parameter, even though the email
        # opt-in feature is disabled.
360
        response = self._load_mktg_about(org=self.org)
361 362 363 364 365 366

        # Verify that the checkbox is not displayed
        self._email_opt_in_checkbox(response)

    @patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
    def test_course_mktg_organization_html(self):
367
        response = self._load_mktg_about(org=self.org_html)
368 369 370

        # Verify that the checkbox is displayed with the organization name
        # in the label escaped as expected.
371
        self._email_opt_in_checkbox(response, cgi.escape(self.org_html))
372

373 374 375 376 377 378
    @patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': True})
    def test_mktg_about_language_edx_domain(self):
        # Since we're in an edx-controlled domain, and our marketing site
        # supports only English, override the language setting
        # and use English.
        response = self._load_mktg_about(language='eo')
379
        self.assertContains(response, "Enroll in")
380 381 382 383 384 385 386

    @patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': False})
    def test_mktg_about_language_openedx(self):
        # If we're in an OpenEdX installation,
        # may want to support languages other than English,
        # so respect the language code.
        response = self._load_mktg_about(language='eo')
387
        self.assertContains(response, u"Énröll ïn".encode('utf-8'))
388

389 390 391 392 393 394 395 396 397
    def test_submission_history_accepts_valid_ids(self):
        # log into a staff account
        admin = AdminFactory()

        self.client.login(username=admin.username, password='test')

        url = reverse('submission_history', kwargs={
            'course_id': self.course_key.to_deprecated_string(),
            'student_username': 'dummy',
398
            'location': self.component.location.to_deprecated_string(),
399 400 401 402 403
        })
        response = self.client.get(url)
        # Tests that we do not get an "Invalid x" response when passing correct arguments to view
        self.assertFalse('Invalid' in response.content)

404 405 406 407 408 409 410 411
    def test_submission_history_xss(self):
        # log into a staff account
        admin = AdminFactory()

        self.client.login(username=admin.username, password='test')

        # try it with an existing user and a malicious location
        url = reverse('submission_history', kwargs={
412
            'course_id': self.course_key.to_deprecated_string(),
413 414 415 416 417 418 419 420
            'student_username': 'dummy',
            'location': '<script>alert("hello");</script>'
        })
        response = self.client.get(url)
        self.assertFalse('<script>' in response.content)

        # try it with a malicious user and a non-existent location
        url = reverse('submission_history', kwargs={
421
            'course_id': self.course_key.to_deprecated_string(),
422 423 424 425 426
            'student_username': '<script>alert("hello");</script>',
            'location': 'dummy'
        })
        response = self.client.get(url)
        self.assertFalse('<script>' in response.content)
427

428
    def _load_mktg_about(self, language=None, org=None):
429
        """Retrieve the marketing about button (iframed into the marketing site)
430 431 432 433
        and return the HTTP response.

        Keyword Args:
            language (string): If provided, send this in the 'Accept-Language' HTTP header.
434
            org (string): If provided, send the string as a GET parameter.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449

        Returns:
            Response

        """
        # Log in as an administrator to guarantee that we can access the button
        admin = AdminFactory()
        self.client.login(username=admin.username, password='test')

        # If provided, set the language header
        headers = {}
        if language is not None:
            headers['HTTP_ACCEPT_LANGUAGE'] = language

        url = reverse('mktg_about_course', kwargs={'course_id': unicode(self.course_key)})
450 451
        if org:
            return self.client.get(url, {'org': org}, **headers)
452 453 454
        else:
            return self.client.get(url, **headers)

455
    def _email_opt_in_checkbox(self, response, org_name_string=None):
456 457
        """Check if the email opt-in checkbox appears in the response content."""
        checkbox_html = '<input id="email-opt-in" type="checkbox" name="opt-in" class="email-opt-in" value="true" checked>'
458
        if org_name_string:
459 460 461
            # Verify that the email opt-in checkbox appears, and that the expected
            # organization name is displayed.
            self.assertContains(response, checkbox_html, html=True)
462
            self.assertContains(response, org_name_string)
463 464 465
        else:
            # Verify that the email opt-in checkbox does not appear
            self.assertNotContains(response, checkbox_html, html=True)
466

467

468
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
469
@override_settings(TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC")
470 471 472 473 474
class BaseDueDateTests(ModuleStoreTestCase):
    """
    Base class that verifies that due dates are rendered correctly on a page
    """
    __test__ = False
475

476 477 478
    def get_text(self, course):  # pylint: disable=unused-argument
        """Return the rendered text for the page to be verified"""
        raise NotImplementedError
479

480
    def set_up_course(self, **course_kwargs):
481
        """
482 483 484
        Create a stock course with a specific due date.

        :param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
485
        """
Don Mitchell committed
486 487 488 489 490
        course = CourseFactory.create(**course_kwargs)
        chapter = ItemFactory.create(category='chapter', parent_location=course.location)  # pylint: disable=no-member
        section = ItemFactory.create(category='sequential', parent_location=chapter.location, due=datetime(2013, 9, 18, 11, 30, 00))
        vertical = ItemFactory.create(category='vertical', parent_location=section.location)
        ItemFactory.create(category='problem', parent_location=vertical.location)
491

492
        course = modulestore().get_course(course.id)  # pylint: disable=no-member
493 494 495 496
        self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
        return course

    def setUp(self):
497
        super(BaseDueDateTests, self).setUp()
498 499 500 501
        self.request_factory = RequestFactory()
        self.user = UserFactory.create()
        self.request = self.request_factory.get("foo")
        self.request.user = self.user
502

503 504
        self.time_with_tz = "due Sep 18, 2013 at 11:30 UTC"
        self.time_without_tz = "due Sep 18, 2013 at 11:30"
505

506
    def test_backwards_compatability(self):
507 508 509 510
        # The test course being used has show_timezone = False in the policy file
        # (and no due_date_display_format set). This is to test our backwards compatibility--
        # in course_module's init method, the date_display_format will be set accordingly to
        # remove the timezone.
511 512
        course = self.set_up_course(due_date_display_format=None, show_timezone=False)
        text = self.get_text(course)
513 514
        self.assertIn(self.time_without_tz, text)
        self.assertNotIn(self.time_with_tz, text)
515 516 517
        # Test that show_timezone has been cleared (which means you get the default value of True).
        self.assertTrue(course.show_timezone)

518 519 520
    def test_defaults(self):
        course = self.set_up_course()
        text = self.get_text(course)
521
        self.assertIn(self.time_with_tz, text)
522

523
    def test_format_none(self):
524
        # Same for setting the due date to None
525 526
        course = self.set_up_course(due_date_display_format=None)
        text = self.get_text(course)
527
        self.assertIn(self.time_with_tz, text)
528

529
    def test_format_plain_text(self):
530
        # plain text due date
531 532
        course = self.set_up_course(due_date_display_format="foobar")
        text = self.get_text(course)
533
        self.assertNotIn(self.time_with_tz, text)
534 535
        self.assertIn("due foobar", text)

536
    def test_format_date(self):
537
        # due date with no time
538 539
        course = self.set_up_course(due_date_display_format=u"%b %d %y")
        text = self.get_text(course)
540
        self.assertNotIn(self.time_with_tz, text)
541
        self.assertIn("due Sep 18 13", text)
542

543
    def test_format_hidden(self):
544
        # hide due date completely
545 546
        course = self.set_up_course(due_date_display_format=u"")
        text = self.get_text(course)
547 548
        self.assertNotIn("due ", text)

549
    def test_format_invalid(self):
550 551
        # improperly formatted due_date_display_format falls through to default
        # (value of show_timezone does not matter-- setting to False to make that clear).
552 553
        course = self.set_up_course(due_date_display_format=u"%%%", show_timezone=False)
        text = self.get_text(course)
554
        self.assertNotIn("%%%", text)
555
        self.assertIn(self.time_with_tz, text)
556 557 558 559 560 561 562 563 564 565


class TestProgressDueDate(BaseDueDateTests):
    """
    Test that the progress page displays due dates correctly
    """
    __test__ = True

    def get_text(self, course):
        """ Returns the HTML for the progress page """
566 567

        mako_middleware_process_request(self.request)
568
        return views.progress(self.request, course_id=course.id.to_deprecated_string(), student_id=self.user.id).content
569 570 571 572 573 574 575 576 577 578 579


class TestAccordionDueDate(BaseDueDateTests):
    """
    Test that the accordion page displays due dates correctly
    """
    __test__ = True

    def get_text(self, course):
        """ Returns the HTML for the accordion """
        return views.render_accordion(
580 581
            self.request, course, course.get_children()[0].scope_ids.usage_id.to_deprecated_string(),
            None, None
582
        )
583 584 585 586 587 588 589 590 591


class StartDateTests(ModuleStoreTestCase):
    """
    Test that start dates are properly localized and displayed on the student
    dashboard.
    """

    def setUp(self):
592
        super(StartDateTests, self).setUp()
593 594 595 596 597 598 599 600 601 602 603
        self.request_factory = RequestFactory()
        self.user = UserFactory.create()
        self.request = self.request_factory.get("foo")
        self.request.user = self.user

    def set_up_course(self):
        """
        Create a stock course with a specific due date.

        :param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
        """
Don Mitchell committed
604
        course = CourseFactory.create(start=datetime(2013, 9, 16, 7, 17, 28))
605
        course = modulestore().get_course(course.id)  # pylint: disable=no-member
606 607
        return course

608
    def get_about_text(self, course_key):
609 610 611
        """
        Get the text of the /about page for the course.
        """
612
        text = views.course_about(self.request, course_key.to_deprecated_string()).content
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
        return text

    @patch('util.date_utils.pgettext', fake_pgettext(translations={
        ("abbreviated month name", "Sep"): "SEPTEMBER",
    }))
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "SHORT_DATE_FORMAT": "%Y-%b-%d",
    }))
    def test_format_localized_in_studio_course(self):
        course = self.set_up_course()
        text = self.get_about_text(course.id)
        # The start date is set in the set_up_course function above.
        self.assertIn("2013-SEPTEMBER-16", text)

    @patch('util.date_utils.pgettext', fake_pgettext(translations={
        ("abbreviated month name", "Jul"): "JULY",
    }))
    @patch('util.date_utils.ugettext', fake_ugettext(translations={
        "SHORT_DATE_FORMAT": "%Y-%b-%d",
    }))
633
    @unittest.skip
634
    def test_format_localized_in_xml_course(self):
635
        text = self.get_about_text(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall'))
636 637
        # The start date is set in common/test/data/two_toys/policies/TT_2012_Fall/policy.json
        self.assertIn("2015-JULY-17", text)
638 639 640 641 642 643 644 645


class ProgressPageTests(ModuleStoreTestCase):
    """
    Tests that verify that the progress page works correctly.
    """

    def setUp(self):
646
        super(ProgressPageTests, self).setUp()
647 648 649 650 651 652 653
        self.request_factory = RequestFactory()
        self.user = UserFactory.create()
        self.request = self.request_factory.get("foo")
        self.request.user = self.user

        MakoMiddleware().process_request(self.request)

Don Mitchell committed
654
        course = CourseFactory.create(
655 656 657
            start=datetime(2013, 9, 16, 7, 17, 28),
            grade_cutoffs={u'çü†øƒƒ': 0.75, 'Pass': 0.5},
        )
658
        self.course = modulestore().get_course(course.id)  # pylint: disable=no-member
659

Don Mitchell committed
660 661 662
        self.chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)  # pylint: disable=no-member
        self.section = ItemFactory.create(category='sequential', parent_location=self.chapter.location)
        self.vertical = ItemFactory.create(category='vertical', parent_location=self.section.location)
663 664

    def test_pure_ungraded_xblock(self):
Don Mitchell committed
665
        ItemFactory.create(category='acid', parent_location=self.vertical.location)
666

667
        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string())
668 669 670
        self.assertEqual(resp.status_code, 200)

    def test_non_asci_grade_cutoffs(self):
671
        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string())
672
        self.assertEqual(resp.status_code, 200)
673 674


675
class VerifyCourseKeyDecoratorTests(TestCase):
676
    """
677
    Tests for the ensure_valid_course_key decorator.
678 679 680
    """

    def setUp(self):
681 682
        super(VerifyCourseKeyDecoratorTests, self).setUp()

683 684 685 686 687 688
        self.request = RequestFactory().get("foo")
        self.valid_course_id = "edX/test/1"
        self.invalid_course_id = "edX/"

    def test_decorator_with_valid_course_id(self):
        mocked_view = create_autospec(views.course_about)
689
        view_function = ensure_valid_course_key(mocked_view)
690
        view_function(self.request, course_id=self.valid_course_id)
691 692 693 694
        self.assertTrue(mocked_view.called)

    def test_decorator_with_invalid_course_id(self):
        mocked_view = create_autospec(views.course_about)
695
        view_function = ensure_valid_course_key(mocked_view)
696
        self.assertRaises(Http404, view_function, self.request, course_id=self.invalid_course_id)
697
        self.assertFalse(mocked_view.called)