# coding=UTF-8
"""
Tests courseware views.py
"""

from urllib import urlencode, quote
import ddt
import json
import itertools
import unittest
from datetime import datetime, timedelta
from HTMLParser import HTMLParser
from nose.plugins.attrib import attr
from freezegun import freeze_time

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseBadRequest
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.client import Client
from django.test.utils import override_settings
from mock import MagicMock, patch, create_autospec, PropertyMock
from opaque_keys.edx.locations import Location, SlashSeparatedCourseKey
from pytz import UTC
from xblock.core import XBlock
from xblock.fields import String, Scope
from xblock.fragment import Fragment

import courseware.views.views as views
import shoppingcart
from certificates import api as certs_api
from certificates.models import CertificateStatuses, CertificateGenerationConfiguration
from certificates.tests.factories import (
    CertificateInvalidationFactory,
    GeneratedCertificateFactory
)
from commerce.models import CommerceConfiguration
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from courseware.model_data import set_score
from courseware.module_render import toc_for_course
from courseware.testutils import RenderXBlockTestMixin
from courseware.tests.factories import StudentModuleFactory, GlobalStaffFactory
from courseware.url_helpers import get_redirect_url
from courseware.user_state_client import DjangoXBlockUserStateClient
from courseware.views.index import render_accordion
from lms.djangoapps.commerce.utils import EcommerceService  # pylint: disable=import-error
from milestones.tests.utils import MilestonesTestCaseMixin
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.lib.gating import api as gating_api
from student.models import CourseEnrollment
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
from util.tests.test_date_utils import fake_ugettext, fake_pgettext
from util.url import reload_django_url_config
from util.views import ensure_valid_course_key
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
from openedx.core.djangoapps.credit.api import set_credit_requirements
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider


@attr(shard=1)
class TestJumpTo(ModuleStoreTestCase):
    """
    Check the jumpto link for a course.
    """
    MODULESTORE = TEST_DATA_MIXED_MODULESTORE

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

    def test_jumpto_invalid_location(self):
        location = self.course_key.make_usage_key(None, 'NoSuchPlace')
        # 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', unicode(self.course_key), unicode(location))
        response = self.client.get(jumpto_url)
        self.assertEqual(response.status_code, 404)

    @unittest.skip
    def test_jumpto_from_chapter(self):
        location = self.course_key.make_usage_key('chapter', 'Overview')
        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', unicode(self.course_key), unicode(location))
        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)

    @unittest.skip
    def test_jumpto_id(self):
        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', unicode(self.course_key), 'Overview')
        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_from_section(self):
        course = CourseFactory.create()
        chapter = ItemFactory.create(category='chapter', parent_location=course.location)
        section = ItemFactory.create(category='sequential', parent_location=chapter.location)
        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/?{activate_block_id}'.format(
            course_id=unicode(course.id),
            chapter_id=chapter.url_name,
            section_id=section.url_name,
            activate_block_id=urlencode({'activate_block_id': unicode(section.location)})
        )
        jumpto_url = '{0}/{1}/jump_to/{2}'.format(
            '/courses',
            unicode(course.id),
            unicode(section.location),
        )
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)

    def test_jumpto_from_module(self):
        course = CourseFactory.create()
        chapter = ItemFactory.create(category='chapter', parent_location=course.location)
        section = ItemFactory.create(category='sequential', parent_location=chapter.location)
        vertical1 = ItemFactory.create(category='vertical', parent_location=section.location)
        vertical2 = ItemFactory.create(category='vertical', parent_location=section.location)
        module1 = ItemFactory.create(category='html', parent_location=vertical1.location)
        module2 = ItemFactory.create(category='html', parent_location=vertical2.location)

        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
            course_id=unicode(course.id),
            chapter_id=chapter.url_name,
            section_id=section.url_name,
            activate_block_id=urlencode({'activate_block_id': unicode(module1.location)})
        )
        jumpto_url = '{0}/{1}/jump_to/{2}'.format(
            '/courses',
            unicode(course.id),
            unicode(module1.location),
        )
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)

        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/2?{activate_block_id}'.format(
            course_id=unicode(course.id),
            chapter_id=chapter.url_name,
            section_id=section.url_name,
            activate_block_id=urlencode({'activate_block_id': unicode(module2.location)})
        )
        jumpto_url = '{0}/{1}/jump_to/{2}'.format(
            '/courses',
            unicode(course.id),
            unicode(module2.location),
        )
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)

    def test_jumpto_from_nested_module(self):
        course = CourseFactory.create()
        chapter = ItemFactory.create(category='chapter', parent_location=course.location)
        section = ItemFactory.create(category='sequential', parent_location=chapter.location)
        vertical = ItemFactory.create(category='vertical', parent_location=section.location)
        nested_section = ItemFactory.create(category='sequential', parent_location=vertical.location)
        nested_vertical1 = ItemFactory.create(category='vertical', parent_location=nested_section.location)
        # put a module into nested_vertical1 for completeness
        ItemFactory.create(category='html', parent_location=nested_vertical1.location)
        nested_vertical2 = ItemFactory.create(category='vertical', parent_location=nested_section.location)
        module2 = ItemFactory.create(category='html', parent_location=nested_vertical2.location)

        # internal position of module2 will be 1_2 (2nd item withing 1st item)

        expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format(
            course_id=unicode(course.id),
            chapter_id=chapter.url_name,
            section_id=section.url_name,
            activate_block_id=urlencode({'activate_block_id': unicode(module2.location)})
        )
        jumpto_url = '{0}/{1}/jump_to/{2}'.format(
            '/courses',
            unicode(course.id),
            unicode(module2.location),
        )
        response = self.client.get(jumpto_url)
        self.assertRedirects(response, expected, status_code=302, target_status_code=302)

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


@attr(shard=2)
@ddt.ddt
class ViewsTestCase(ModuleStoreTestCase):
    """
    Tests for views.py methods.
    """

    def setUp(self):
        super(ViewsTestCase, self).setUp()
        self.course = CourseFactory.create(display_name=u'teꜱᴛ course', run="Testing_course")
        self.chapter = ItemFactory.create(
            category='chapter',
            parent_location=self.course.location,
            display_name="Chapter 1",
        )
        self.section = ItemFactory.create(
            category='sequential',
            parent_location=self.chapter.location,
            due=datetime(2013, 9, 18, 11, 30, 00),
            display_name='Sequential 1',
            format='Homework'
        )
        self.vertical = ItemFactory.create(
            category='vertical',
            parent_location=self.section.location,
            display_name='Vertical 1',
        )
        self.problem = ItemFactory.create(
            category='problem',
            parent_location=self.vertical.location,
            display_name='Problem 1',
        )

        self.section2 = ItemFactory.create(
            category='sequential',
            parent_location=self.chapter.location,
            display_name='Sequential 2',
        )
        self.vertical2 = ItemFactory.create(
            category='vertical',
            parent_location=self.section2.location,
            display_name='Vertical 2',
        )
        self.problem2 = ItemFactory.create(
            category='problem',
            parent_location=self.vertical2.location,
            display_name='Problem 2',
        )

        self.course_key = self.course.id
        self.password = '123456'
        self.user = UserFactory(username='dummy', password=self.password, email='test@mit.edu')
        self.date = datetime(2013, 1, 22, tzinfo=UTC)
        self.enrollment = CourseEnrollment.enroll(self.user, self.course_key)
        self.enrollment.created = self.date
        self.enrollment.save()
        chapter = 'Overview'
        self.chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)

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

        self.assertTrue(self.client.login(username=self.user.username, password=self.password))

        # refresh the course from the modulestore so that it has children
        self.course = modulestore().get_course(self.course.id)

    def test_index_success(self):
        response = self._verify_index_response()
        self.assertIn(unicode(self.problem2.location), response.content.decode("utf-8"))

        # re-access to the main course page redirects to last accessed view.
        url = reverse('courseware', kwargs={'course_id': unicode(self.course_key)})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 302)
        response = self.client.get(response.url)  # pylint: disable=no-member
        self.assertNotIn(unicode(self.problem.location), response.content.decode("utf-8"))
        self.assertIn(unicode(self.problem2.location), response.content.decode("utf-8"))

    def test_index_nonexistent_chapter(self):
        self._verify_index_response(expected_response_code=404, chapter_name='non-existent')

    def test_index_nonexistent_chapter_masquerade(self):
        with patch('courseware.views.index.setup_masquerade') as patch_masquerade:
            masquerade = MagicMock(role='student')
            patch_masquerade.return_value = (masquerade, self.user)
            self._verify_index_response(expected_response_code=302, chapter_name='non-existent')

    def test_index_nonexistent_section(self):
        self._verify_index_response(expected_response_code=404, section_name='non-existent')

    def test_index_nonexistent_section_masquerade(self):
        with patch('courseware.views.index.setup_masquerade') as patch_masquerade:
            masquerade = MagicMock(role='student')
            patch_masquerade.return_value = (masquerade, self.user)
            self._verify_index_response(expected_response_code=302, section_name='non-existent')

    def _verify_index_response(self, expected_response_code=200, chapter_name=None, section_name=None):
        """
        Verifies the response when the courseware index page is accessed with
        the given chapter and section names.
        """
        url = reverse(
            'courseware_section',
            kwargs={
                'course_id': unicode(self.course_key),
                'chapter': unicode(self.chapter.location.name) if chapter_name is None else chapter_name,
                'section': unicode(self.section2.location.name) if section_name is None else section_name,
            }
        )
        response = self.client.get(url)
        self.assertEqual(response.status_code, expected_response_code)
        return response

    def test_index_no_visible_section_in_chapter(self):

        # reload the chapter from the store so its children information is updated
        self.chapter = self.store.get_item(self.chapter.location)

        # disable the visibility of the sections in the chapter
        for section in self.chapter.get_children():
            section.visible_to_staff_only = True
            self.store.update_item(section, ModuleStoreEnum.UserID.test)

        url = reverse(
            'courseware_chapter',
            kwargs={'course_id': unicode(self.course.id), 'chapter': unicode(self.chapter.location.name)},
        )
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertNotIn('Problem 1', response.content)
        self.assertNotIn('Problem 2', response.content)

    def _create_global_staff_user(self):
        """
        Create global staff user and log them in
        """
        self.global_staff = GlobalStaffFactory.create()  # pylint: disable=attribute-defined-outside-init
        self.assertTrue(self.client.login(username=self.global_staff.username, password='test'))

    def _create_url_for_enroll_staff(self):
        """
        creates the courseware url and enroll staff url
        """
        # create the _next parameter
        courseware_url = reverse(
            'courseware_section',
            kwargs={
                'course_id': unicode(self.course_key),
                'chapter': unicode(self.chapter.location.name),
                'section': unicode(self.section.location.name),
            }
        )
        # create the url for enroll_staff view
        enroll_url = "{enroll_url}?next={courseware_url}".format(
            enroll_url=reverse('enroll_staff', kwargs={'course_id': unicode(self.course.id)}),
            courseware_url=courseware_url
        )
        return courseware_url, enroll_url

    @ddt.data(
        ({'enroll': "Enroll"}, True),
        ({'dont_enroll': "Don't enroll"}, False))
    @ddt.unpack
    def test_enroll_staff_redirection(self, data, enrollment):
        """
        Verify unenrolled staff is redirected to correct url.
        """
        self._create_global_staff_user()
        courseware_url, enroll_url = self._create_url_for_enroll_staff()
        response = self.client.post(enroll_url, data=data, follow=True)
        self.assertEqual(response.status_code, 200)

        # we were redirected to our current location
        self.assertIn(302, response.redirect_chain[0])
        self.assertEqual(len(response.redirect_chain), 1)
        if enrollment:
            self.assertRedirects(response, courseware_url)
        else:
            self.assertRedirects(response, '/courses/{}/about'.format(unicode(self.course_key)))

    def test_enroll_staff_with_invalid_data(self):
        """
        If we try to post with an invalid data pattern, then we'll redirected to
        course about page.
        """
        self._create_global_staff_user()
        __, enroll_url = self._create_url_for_enroll_staff()
        response = self.client.post(enroll_url, data={'test': "test"})
        self.assertEqual(response.status_code, 302)
        self.assertRedirects(response, '/courses/{}/about'.format(unicode(self.course_key)))

    def test_courseware_redirection(self):
        """
        Tests that a global staff member is redirected to the staff enrollment page.

        Un-enrolled Staff user should be redirected to the staff enrollment page accessing courseware,
        user chooses to enroll in the course. User is enrolled and redirected to the requested url.

        Scenario:
            1. Un-enrolled staff tries to access any course vertical (courseware url).
            2. User is redirected to the staff enrollment page.
            3. User chooses to enroll in the course.
            4. User is enrolled in the course and redirected to the requested courseware url.
        """
        self._create_global_staff_user()
        courseware_url, enroll_url = self._create_url_for_enroll_staff()

        # Accessing the courseware url in which not enrolled & redirected to staff enrollment page
        response = self.client.get(courseware_url, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertIn(302, response.redirect_chain[0])
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertRedirects(response, enroll_url)

        # Accessing the enroll staff url and verify the correct url
        response = self.client.get(enroll_url)
        self.assertEqual(response.status_code, 200)
        response_content = response.content
        self.assertIn('Enroll', response_content)
        self.assertIn("dont_enroll", response_content)

        # Post the valid data to enroll the staff in the course
        response = self.client.post(enroll_url, data={'enroll': "Enroll"}, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertIn(302, response.redirect_chain[0])
        self.assertEqual(len(response.redirect_chain), 1)
        self.assertRedirects(response, courseware_url)

        # Verify staff has been enrolled to the given course
        self.assertTrue(CourseEnrollment.is_enrolled(self.global_staff, self.course.id))

    @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
    @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
    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")

        self.client.logout()
        response = self.client.get(reverse('about_course', args=[unicode(course.id)]))
        self.assertEqual(response.status_code, 200)
        self.assertNotIn(in_cart_span, response.content)

        # authenticated user with nothing in cart
        self.assertTrue(self.client.login(username=self.user.username, password=self.password))
        response = self.client.get(reverse('about_course', args=[unicode(course.id)]))
        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)
        response = self.client.get(reverse('about_course', args=[unicode(course.id)]))
        self.assertEqual(response.status_code, 200)
        self.assertIn(in_cart_span, response.content)

    def assert_enrollment_link_present(self, is_anonymous):
        """
        Prepare ecommerce checkout data and assert if the ecommerce link is contained in the response.

        Arguments:
            is_anonymous(bool): Tell the method to use an anonymous user or the logged in one.
            _id(bool): Tell the method to either expect an id in the href or not.

        """
        checkout_page = '/test_basket/'
        sku = 'TEST123'
        CommerceConfiguration.objects.create(
            checkout_on_ecommerce_service=True,
            single_course_checkout_page=checkout_page
        )
        course = CourseFactory.create()
        CourseModeFactory(mode_slug=CourseMode.PROFESSIONAL, course_id=course.id, sku=sku, min_price=1)

        if is_anonymous:
            self.client.logout()
        else:
            self.assertTrue(self.client.login(username=self.user.username, password=self.password))

        # Construct the link according the following scenarios and verify its presence in the response:
        #      (1) shopping cart is enabled and the user is not logged in
        #      (2) shopping cart is enabled and the user is logged in
        href = '<a href="{uri_stem}?sku={sku}" class="add-to-cart">'.format(uri_stem=checkout_page, sku=sku)

        # Generate the course about page content
        response = self.client.get(reverse('about_course', args=[unicode(course.id)]))
        self.assertEqual(response.status_code, 200)
        self.assertIn(href, response.content)

    @ddt.data(True, False)
    def test_ecommerce_checkout(self, is_anonymous):
        if not is_anonymous:
            self.assert_enrollment_link_present(is_anonymous=is_anonymous)
        else:
            self.assertEqual(EcommerceService().is_enabled(AnonymousUser()), False)

    @ddt.data(True, False)
    @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), 'Shopping Cart not enabled in settings')
    @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
    def test_ecommerce_checkout_shopping_cart_enabled(self, is_anonymous):
        """
        Two scenarios are being validated here -- authenticated/known user and unauthenticated/anonymous user
        For a known user we expect the checkout link to point to Otto in a scenario where the CommerceConfiguration
        is active and the course mode is PROFESSIONAL.
        """
        if not is_anonymous:
            self.assert_enrollment_link_present(is_anonymous=is_anonymous)
        else:
            self.assertEqual(EcommerceService().is_enabled(AnonymousUser()), False)

    def test_user_groups(self):
        # depreciated function
        mock_user = MagicMock()
        mock_user.is_authenticated.return_value = False
        self.assertEqual(views.user_groups(mock_user), [])

    def test_get_current_child(self):
        mock_xmodule = MagicMock()
        self.assertIsNone(views.get_current_child(mock_xmodule))

        mock_xmodule.position = -1
        mock_xmodule.get_display_items.return_value = ['one', 'two', 'three']
        self.assertEqual(views.get_current_child(mock_xmodule), 'one')

        mock_xmodule.position = 2
        self.assertEqual(views.get_current_child(mock_xmodule), 'two')
        self.assertEqual(views.get_current_child(mock_xmodule, requested_child='first'), 'one')
        self.assertEqual(views.get_current_child(mock_xmodule, requested_child='last'), 'three')

        mock_xmodule.position = 3
        mock_xmodule.get_display_items.return_value = []
        self.assertIsNone(views.get_current_child(mock_xmodule))

    def test_get_redirect_url(self):
        self.assertIn(
            'activate_block_id',
            get_redirect_url(self.course_key, self.section.location),
        )

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

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

    def test_index_invalid_position(self):
        request_url = '/'.join([
            '/courses',
            unicode(self.course.id),
            'courseware',
            self.chapter.location.name,
            self.section.location.name,
            'f'
        ])
        self.assertTrue(self.client.login(username=self.user.username, password=self.password))
        response = self.client.get(request_url)
        self.assertEqual(response.status_code, 404)

    def test_unicode_handling_in_url(self):
        url_parts = [
            '/courses',
            unicode(self.course.id),
            'courseware',
            self.chapter.location.name,
            self.section.location.name,
            '1'
        ]
        self.assertTrue(self.client.login(username=self.user.username, password=self.password))
        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)

    @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")

    def test_jump_to_invalid(self):
        # TODO add a test for invalid location
        # TODO add a test for no data *
        response = self.client.get(reverse('jump_to', args=['foo/bar/baz', 'baz']))
        self.assertEquals(response.status_code, 404)

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

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

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

    def verify_end_date(self, course_id, expected_end_text=None):
        """
        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".
        """
        result = self.client.get(reverse('about_course', args=[unicode(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")

    def test_submission_history_accepts_valid_ids(self):
        # log into a staff account
        admin = AdminFactory()

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

        url = reverse('submission_history', kwargs={
            'course_id': unicode(self.course_key),
            'student_username': 'dummy',
            'location': unicode(self.problem.location),
        })
        response = self.client.get(url)
        # Tests that we do not get an "Invalid x" response when passing correct arguments to view
        self.assertNotIn('Invalid', response.content)

    def test_submission_history_xss(self):
        # log into a staff account
        admin = AdminFactory()

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

        # try it with an existing user and a malicious location
        url = reverse('submission_history', kwargs={
            'course_id': unicode(self.course_key),
            'student_username': 'dummy',
            'location': '<script>alert("hello");</script>'
        })
        response = self.client.get(url)
        self.assertNotIn('<script>', response.content)

        # try it with a malicious user and a non-existent location
        url = reverse('submission_history', kwargs={
            'course_id': unicode(self.course_key),
            'student_username': '<script>alert("hello");</script>',
            'location': 'dummy'
        })
        response = self.client.get(url)
        self.assertNotIn('<script>', response.content)

    def test_submission_history_contents(self):
        # log into a staff account
        admin = AdminFactory.create()

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

        usage_key = self.course_key.make_usage_key('problem', 'test-history')
        state_client = DjangoXBlockUserStateClient(admin)

        # store state via the UserStateClient
        state_client.set(
            username=admin.username,
            block_key=usage_key,
            state={'field_a': 'x', 'field_b': 'y'}
        )

        set_score(admin.id, usage_key, 0, 3)

        state_client.set(
            username=admin.username,
            block_key=usage_key,
            state={'field_a': 'a', 'field_b': 'b'}
        )
        set_score(admin.id, usage_key, 3, 3)

        url = reverse('submission_history', kwargs={
            'course_id': unicode(self.course_key),
            'student_username': admin.username,
            'location': unicode(usage_key),
        })
        response = self.client.get(url)
        response_content = HTMLParser().unescape(response.content.decode('utf-8'))

        # We have update the state 4 times: twice to change content, and twice
        # to set the scores. We'll check that the identifying content from each is
        # displayed (but not the order), and also the indexes assigned in the output
        # #1 - #4

        self.assertIn('#1', response_content)
        self.assertIn(json.dumps({'field_a': 'a', 'field_b': 'b'}, sort_keys=True, indent=2), response_content)
        self.assertIn("Score: 0.0 / 3.0", response_content)
        self.assertIn(json.dumps({'field_a': 'x', 'field_b': 'y'}, sort_keys=True, indent=2), response_content)
        self.assertIn("Score: 3.0 / 3.0", response_content)
        self.assertIn('#4', response_content)

    @ddt.data(('America/New_York', -5),  # UTC - 5
              ('Asia/Pyongyang', 9),  # UTC + 9
              ('Europe/London', 0),  # UTC
              ('Canada/Yukon', -8),  # UTC - 8
              ('Europe/Moscow', 4))  # UTC + 3 + 1 for daylight savings
    @ddt.unpack
    @freeze_time('2012-01-01')
    def test_submission_history_timezone(self, timezone, hour_diff):
        with (override_settings(TIME_ZONE=timezone)):
            course = CourseFactory.create()
            course_key = course.id
            client = Client()
            admin = AdminFactory.create()
            self.assertTrue(client.login(username=admin.username, password='test'))
            state_client = DjangoXBlockUserStateClient(admin)
            usage_key = course_key.make_usage_key('problem', 'test-history')
            state_client.set(
                username=admin.username,
                block_key=usage_key,
                state={'field_a': 'x', 'field_b': 'y'}
            )
            url = reverse('submission_history', kwargs={
                'course_id': unicode(course_key),
                'student_username': admin.username,
                'location': unicode(usage_key),
            })
            response = client.get(url)
            response_content = HTMLParser().unescape(response.content)
            expected_time = datetime.now() + timedelta(hours=hour_diff)
            expected_tz = expected_time.strftime('%Z')
            self.assertIn(expected_tz, response_content)
            self.assertIn(str(expected_time), response_content)

    def _email_opt_in_checkbox(self, response, org_name_string=None):
        """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>'
        if org_name_string:
            # Verify that the email opt-in checkbox appears, and that the expected
            # organization name is displayed.
            self.assertContains(response, checkbox_html, html=True)
            self.assertContains(response, org_name_string)
        else:
            # Verify that the email opt-in checkbox does not appear
            self.assertNotContains(response, checkbox_html, html=True)

    def test_financial_assistance_page(self):
        url = reverse('financial_assistance')
        response = self.client.get(url)
        # This is a static page, so just assert that it is returned correctly
        self.assertEqual(response.status_code, 200)
        self.assertIn('Financial Assistance Application', response.content)

    def test_financial_assistance_form(self):
        non_verified_course = CourseFactory.create().id
        verified_course_verified_track = CourseFactory.create().id
        verified_course_audit_track = CourseFactory.create().id
        verified_course_deadline_passed = CourseFactory.create().id
        unenrolled_course = CourseFactory.create().id

        enrollments = (
            (non_verified_course, CourseMode.AUDIT, None),
            (verified_course_verified_track, CourseMode.VERIFIED, None),
            (verified_course_audit_track, CourseMode.AUDIT, None),
            (verified_course_deadline_passed, CourseMode.AUDIT, datetime.now(UTC) - timedelta(days=1))
        )
        for course, mode, expiration in enrollments:
            CourseModeFactory.create(mode_slug=CourseMode.AUDIT, course_id=course)
            if course != non_verified_course:
                CourseModeFactory.create(
                    mode_slug=CourseMode.VERIFIED,
                    course_id=course,
                    expiration_datetime=expiration
                )
            CourseEnrollmentFactory(course_id=course, user=self.user, mode=mode)

        url = reverse('financial_assistance_form')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        # Ensure that the user can only apply for assistance in
        # courses which have a verified mode which hasn't expired yet,
        # where the user is not already enrolled in verified mode
        self.assertIn(str(verified_course_audit_track), response.content)
        for course in (
                non_verified_course,
                verified_course_verified_track,
                verified_course_deadline_passed,
                unenrolled_course
        ):
            self.assertNotIn(str(course), response.content)

    def _submit_financial_assistance_form(self, data):
        """Submit a financial assistance request."""
        url = reverse('submit_financial_assistance_request')
        return self.client.post(url, json.dumps(data), content_type='application/json')

    @patch.object(views, '_record_feedback_in_zendesk')
    def test_submit_financial_assistance_request(self, mock_record_feedback):
        username = self.user.username
        course = unicode(self.course_key)
        legal_name = 'Jesse Pinkman'
        country = 'United States'
        income = '1234567890'
        reason_for_applying = "It's just basic chemistry, yo."
        goals = "I don't know if it even matters, but... work with my hands, I guess."
        effort = "I'm done, okay? You just give me my money, and you and I, we're done."
        data = {
            'username': username,
            'course': course,
            'name': legal_name,
            'email': self.user.email,
            'country': country,
            'income': income,
            'reason_for_applying': reason_for_applying,
            'goals': goals,
            'effort': effort,
            'mktg-permission': False,
        }
        response = self._submit_financial_assistance_form(data)
        self.assertEqual(response.status_code, 204)

        __, __, ticket_subject, __, tags, additional_info = mock_record_feedback.call_args[0]
        mocked_kwargs = mock_record_feedback.call_args[1]
        group_name = mocked_kwargs['group_name']
        require_update = mocked_kwargs['require_update']
        private_comment = '\n'.join(additional_info.values())
        for info in (country, income, reason_for_applying, goals, effort, username, legal_name, course):
            self.assertIn(info, private_comment)

        self.assertEqual(additional_info['Allowed for marketing purposes'], 'No')

        self.assertEqual(
            ticket_subject,
            u'Financial assistance request for learner {username} in course {course}'.format(
                username=username,
                course=self.course.display_name
            )
        )
        self.assertDictContainsSubset({'course_id': course}, tags)
        self.assertIn('Client IP', additional_info)
        self.assertEqual(group_name, 'Financial Assistance')
        self.assertTrue(require_update)

    @patch.object(views, '_record_feedback_in_zendesk', return_value=False)
    def test_zendesk_submission_failed(self, _mock_record_feedback):
        response = self._submit_financial_assistance_form({
            'username': self.user.username,
            'course': unicode(self.course.id),
            'name': '',
            'email': '',
            'country': '',
            'income': '',
            'reason_for_applying': '',
            'goals': '',
            'effort': '',
            'mktg-permission': False,
        })
        self.assertEqual(response.status_code, 500)

    @ddt.data(
        ({}, 400),
        ({'username': 'wwhite'}, 403),
        ({'username': 'dummy', 'course': 'bad course ID'}, 400)
    )
    @ddt.unpack
    def test_submit_financial_assistance_errors(self, data, status):
        response = self._submit_financial_assistance_form(data)
        self.assertEqual(response.status_code, status)

    def test_financial_assistance_login_required(self):
        for url in (
                reverse('financial_assistance'),
                reverse('financial_assistance_form'),
                reverse('submit_financial_assistance_request')
        ):
            self.client.logout()
            response = self.client.get(url)
            self.assertRedirects(response, reverse('signin_user') + '?next=' + url)

    def test_bypass_course_info(self):
        course_id = unicode(self.course_key)

        self.assertFalse(self.course.bypass_home)

        response = self.client.get(reverse('info', args=[course_id]))
        self.assertEqual(response.status_code, 200)

        response = self.client.get(reverse('info', args=[course_id]), HTTP_REFERER=reverse('dashboard'))
        self.assertEqual(response.status_code, 200)

        self.course.bypass_home = True
        self.store.update_item(self.course, self.user.id)  # pylint: disable=no-member
        self.assertTrue(self.course.bypass_home)

        response = self.client.get(reverse('info', args=[course_id]), HTTP_REFERER=reverse('dashboard'))

        self.assertRedirects(response, reverse('courseware', args=[course_id]), fetch_redirect_response=False)

        response = self.client.get(reverse('info', args=[course_id]), HTTP_REFERER='foo')
        self.assertEqual(response.status_code, 200)

    def test_accordion(self):
        """
        This needs a response_context, which is not included in the render_accordion's main method
        returning a render_to_string, so we will render via the courseware URL in order to include
        the needed context
        """
        course_id = quote(unicode(self.course.id).encode("utf-8"))
        response = self.client.get(
            reverse('courseware', args=[unicode(course_id)]),
            follow=True
        )
        test_responses = [
            '<p class="accordion-display-name">Sequential 1 <span class="sr">current section</span></p>',
            '<p class="accordion-display-name">Sequential 2 </p>'
        ]
        for test in test_responses:
            self.assertContains(response, test)


@attr(shard=1)
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
@override_settings(TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC")
class BaseDueDateTests(ModuleStoreTestCase):
    """
    Base class that verifies that due dates are rendered correctly on a page
    """
    __test__ = False

    def get_response(self, course):
        """Return the rendered text for the page to be verified"""
        raise NotImplementedError

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

        :param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
        """
        course = CourseFactory.create(**course_kwargs)
        chapter = ItemFactory.create(category='chapter', parent_location=course.location)
        section = ItemFactory.create(
            category='sequential',
            parent_location=chapter.location,
            due=datetime(2013, 9, 18, 11, 30, 00),
            format='homework'
        )
        vertical = ItemFactory.create(category='vertical', parent_location=section.location)
        ItemFactory.create(category='problem', parent_location=vertical.location)

        course = modulestore().get_course(course.id)
        self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
        CourseEnrollmentFactory(user=self.user, course_id=course.id)
        return course

    def setUp(self):
        super(BaseDueDateTests, self).setUp()
        self.user = UserFactory.create()
        self.assertTrue(self.client.login(username=self.user.username, password='test'))

        self.time_with_tz = "2013-09-18 11:30:00+00:00"

    def test_backwards_compatability(self):
        # 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.
        course = self.set_up_course(due_date_display_format=None, show_timezone=False)
        response = self.get_response(course)
        self.assertContains(response, self.time_with_tz)
        # Test that show_timezone has been cleared (which means you get the default value of True).
        self.assertTrue(course.show_timezone)

    def test_defaults(self):
        course = self.set_up_course()
        response = self.get_response(course)
        self.assertContains(response, self.time_with_tz)

    def test_format_none(self):
        # Same for setting the due date to None
        course = self.set_up_course(due_date_display_format=None)
        response = self.get_response(course)
        self.assertContains(response, self.time_with_tz)

    def test_format_date(self):
        # due date with no time
        course = self.set_up_course(due_date_display_format=u"%b %d %y")
        response = self.get_response(course)
        self.assertContains(response, self.time_with_tz)

    def test_format_invalid(self):
        # improperly formatted due_date_display_format falls through to default
        # (value of show_timezone does not matter-- setting to False to make that clear).
        course = self.set_up_course(due_date_display_format=u"%%%", show_timezone=False)
        response = self.get_response(course)
        self.assertNotContains(response, "%%%")
        self.assertContains(response, self.time_with_tz)


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

    def get_response(self, course):
        """ Returns the HTML for the progress page """
        return self.client.get(reverse('progress', args=[unicode(course.id)]))


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

    def get_response(self, course):
        """ Returns the HTML for the accordion """
        return self.client.get(
            reverse('courseware', args=[unicode(course.id)]),
            follow=True
        )


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

    def setUp(self):
        super(StartDateTests, self).setUp()
        self.user = UserFactory.create()

    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`
        """
        course = CourseFactory.create(start=datetime(2013, 9, 16, 7, 17, 28))
        course = modulestore().get_course(course.id)
        return course

    def get_about_response(self, course_key):
        """
        Get the text of the /about page for the course.
        """
        return self.client.get(reverse('about_course', args=[unicode(course_key)]))

    @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()
        response = self.get_about_response(course.id)
        # The start date is set in the set_up_course function above.
        # This should return in the format '%Y-%m-%dT%H:%M:%S%z'
        self.assertContains(response, "2013-09-16T07:17:28+0000")

    @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", })
    )
    @unittest.skip
    def test_format_localized_in_xml_course(self):
        response = self.get_about_response(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall'))
        # The start date is set in common/test/data/two_toys/policies/TT_2012_Fall/policy.json
        self.assertContains(response, "2015-JULY-17")


# pylint: disable=protected-access, no-member
@attr(shard=1)
@ddt.ddt
class ProgressPageTests(ModuleStoreTestCase):
    """
    Tests that verify that the progress page works correctly.
    """

    ENABLED_CACHES = ['default', 'mongo_modulestore_inheritance', 'loc_cache']

    def setUp(self):
        super(ProgressPageTests, self).setUp()
        self.user = UserFactory.create()
        self.assertTrue(self.client.login(username=self.user.username, password='test'))

        self.setup_course()

    def setup_course(self, **options):
        """Create the test course."""
        course = CourseFactory.create(
            start=datetime(2013, 9, 16, 7, 17, 28),
            grade_cutoffs={u'çü†øƒƒ': 0.75, 'Pass': 0.5},
            **options
        )

        self.course = modulestore().get_course(course.id)
        CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=CourseMode.HONOR)

        self.chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)
        self.section = ItemFactory.create(category='sequential', parent_location=self.chapter.location)
        self.vertical = ItemFactory.create(category='vertical', parent_location=self.section.location)

    def _get_progress_page(self, expected_status_code=200):
        """
        Gets the progress page for the user in the course.
        """
        resp = self.client.get(
            reverse('progress', args=[unicode(self.course.id)])
        )
        self.assertEqual(resp.status_code, expected_status_code)
        return resp

    def _get_student_progress_page(self, expected_status_code=200):
        """
        Gets the progress page for the user in the course.
        """
        resp = self.client.get(
            reverse('student_progress', args=[unicode(self.course.id), self.user.id])
        )
        self.assertEqual(resp.status_code, expected_status_code)
        return resp

    @ddt.data('"><script>alert(1)</script>', '<script>alert(1)</script>', '</script><script>alert(1)</script>')
    def test_progress_page_xss_prevent(self, malicious_code):
        """
        Test that XSS attack is prevented
        """
        resp = self._get_student_progress_page()
        # Test that malicious code does not appear in html
        self.assertNotIn(malicious_code, resp.content)

    def test_pure_ungraded_xblock(self):
        ItemFactory.create(category='acid', parent_location=self.vertical.location)
        self._get_progress_page()

    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_student_progress_with_valid_and_invalid_id(self, default_store):
        """
         Check that invalid 'student_id' raises Http404 for both old mongo and
         split mongo courses.
        """

        # Create new course with respect to 'default_store'
        # Enroll student into course
        self.course = CourseFactory.create(default_store=default_store)
        CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=CourseMode.HONOR)

        # Invalid Student Ids (Integer and Non-int)
        invalid_student_ids = [
            991021,
            'azU3N_8$',
        ]
        for invalid_id in invalid_student_ids:

            resp = self.client.get(
                reverse('student_progress', args=[unicode(self.course.id), invalid_id])
            )
            self.assertEquals(resp.status_code, 404)

        # Assert that valid 'student_id' returns 200 status
        self._get_student_progress_page()

    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_unenrolled_student_progress_for_credit_course(self, default_store):
        """
         Test that student progress page does not break while checking for an unenrolled student.

         Scenario: When instructor checks the progress of a student who is not enrolled in credit course.
         It should return 200 response.
        """
        # Create a new course, a user which will not be enrolled in course, admin user for staff access
        course = CourseFactory.create(default_store=default_store)
        not_enrolled_user = UserFactory.create()
        admin = AdminFactory.create()
        self.assertTrue(self.client.login(username=admin.username, password='test'))

        # Create and enable Credit course
        CreditCourse.objects.create(course_key=course.id, enabled=True)

        # Configure a credit provider for the course
        CreditProvider.objects.create(
            provider_id="ASU",
            enable_integration=True,
            provider_url="https://credit.example.com/request"
        )

        requirements = [{
            "namespace": "grade",
            "name": "grade",
            "display_name": "Grade",
            "criteria": {"min_grade": 0.52},
        }]
        # Add a single credit requirement (final grade)
        set_credit_requirements(course.id, requirements)

        self._get_student_progress_page()

    def test_non_ascii_grade_cutoffs(self):
        self._get_progress_page()

    def test_generate_cert_config(self):

        resp = self._get_progress_page()
        self.assertNotContains(resp, 'Request Certificate')

        # Enable the feature, but do not enable it for this course
        CertificateGenerationConfiguration(enabled=True).save()

        resp = self._get_progress_page()
        self.assertNotContains(resp, 'Request Certificate')

        # Enable certificate generation for this course
        certs_api.set_cert_generation_enabled(self.course.id, True)

        resp = self._get_progress_page()
        self.assertNotContains(resp, 'Request Certificate')

    @patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []}),
    )
    def test_view_certificate_link(self):
        """
        If certificate web view is enabled then certificate web view button should appear for user who certificate is
        available/generated
        """
        certificate = GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            download_url="http://www.example.com/certificate.pdf",
            mode='honor'
        )

        # Enable the feature, but do not enable it for this course
        CertificateGenerationConfiguration(enabled=True).save()

        # Enable certificate generation for this course
        certs_api.set_cert_generation_enabled(self.course.id, True)

        # Course certificate configurations
        certificates = [
            {
                'id': 1,
                'name': 'Name 1',
                'description': 'Description 1',
                'course_title': 'course_title_1',
                'signatories': [],
                'version': 1,
                'is_active': True
            }
        ]

        self.course.certificates = {'certificates': certificates}
        self.course.cert_html_view_enabled = True
        self.course.save()
        self.store.update_item(self.course, self.user.id)

        resp = self._get_progress_page()
        self.assertContains(resp, u"View Certificate")

        self.assertContains(resp, u"You can keep working for a higher grade")
        cert_url = certs_api.get_certificate_url(course_id=self.course.id, uuid=certificate.verify_uuid)
        self.assertContains(resp, cert_url)

        # when course certificate is not active
        certificates[0]['is_active'] = False
        self.store.update_item(self.course, self.user.id)

        resp = self._get_progress_page()
        self.assertNotContains(resp, u"View Your Certificate")
        self.assertNotContains(resp, u"You can now view your certificate")
        self.assertContains(resp, "working on it...")
        self.assertContains(resp, "creating your certificate")

    @patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': False})
    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []})
    )
    def test_view_certificate_link_hidden(self):
        """
        If certificate web view is disabled then certificate web view button should not appear for user who certificate
        is available/generated
        """
        GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            download_url="http://www.example.com/certificate.pdf",
            mode='honor'
        )

        # Enable the feature, but do not enable it for this course
        CertificateGenerationConfiguration(enabled=True).save()

        # Enable certificate generation for this course
        certs_api.set_cert_generation_enabled(self.course.id, True)

        resp = self._get_progress_page()
        self.assertContains(resp, u"Download Your Certificate")

    @ddt.data(
        *itertools.product((True, False), (True, False))
    )
    @ddt.unpack
    def test_progress_queries_paced_courses(self, self_paced, self_paced_enabled):
        """Test that query counts remain the same for self-paced and instructor-paced courses."""
        SelfPacedConfiguration(enabled=self_paced_enabled).save()
        self.setup_course(self_paced=self_paced)
        with self.assertNumQueries(38), check_mongo_calls(4):
            self._get_progress_page()

    def test_progress_queries(self):
        self.setup_course()
        with self.assertNumQueries(38), check_mongo_calls(4):
            self._get_progress_page()

        # subsequent accesses to the progress page require fewer queries.
        for _ in range(2):
            with self.assertNumQueries(24), check_mongo_calls(4):
                self._get_progress_page()

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []})
    )
    @ddt.data(
        *itertools.product(
            (
                CourseMode.AUDIT,
                CourseMode.HONOR,
                CourseMode.VERIFIED,
                CourseMode.PROFESSIONAL,
                CourseMode.NO_ID_PROFESSIONAL_MODE,
                CourseMode.CREDIT_MODE
            ),
            (True, False)
        )
    )
    @ddt.unpack
    def test_show_certificate_request_button(self, course_mode, user_verified):
        """Verify that the Request Certificate is not displayed in audit mode."""
        CertificateGenerationConfiguration(enabled=True).save()
        certs_api.set_cert_generation_enabled(self.course.id, True)
        CourseEnrollment.enroll(self.user, self.course.id, mode=course_mode)
        with patch(
            'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified'
        ) as user_verify:
            user_verify.return_value = user_verified
            resp = self.client.get(
                reverse('progress', args=[unicode(self.course.id)])
            )

            cert_button_hidden = course_mode is CourseMode.AUDIT or \
                course_mode in CourseMode.VERIFIED_MODES and not user_verified

            self.assertEqual(
                cert_button_hidden,
                'Request Certificate' not in resp.content
            )

    @patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []})
    )
    def test_page_with_invalidated_certificate_with_html_view(self):
        """
        Verify that for html certs if certificate is marked as invalidated than
        re-generate button should not appear on progress page.
        """
        generated_certificate = self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )

        # Course certificate configurations
        certificates = [
            {
                'id': 1,
                'name': 'dummy',
                'description': 'dummy description',
                'course_title': 'dummy title',
                'signatories': [],
                'version': 1,
                'is_active': True
            }
        ]
        self.course.certificates = {'certificates': certificates}
        self.course.cert_html_view_enabled = True
        self.course.save()
        self.store.update_item(self.course, self.user.id)

        resp = self._get_progress_page()
        self.assertContains(resp, u"View Certificate")
        self.assert_invalidate_certificate(generated_certificate)

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []})
    )
    def test_page_with_invalidated_certificate_with_pdf(self):
        """
        Verify that for pdf certs if certificate is marked as invalidated than
        re-generate button should not appear on progress page.
        """
        generated_certificate = self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )

        resp = self._get_progress_page()
        self.assertContains(resp, u'Download Your Certificate')
        self.assert_invalidate_certificate(generated_certificate)

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75, 'section_breakdown': [], 'grade_breakdown': []})
    )
    def test_message_for_audit_mode(self):
        """ Verify that message appears on progress page, if learner is enrolled
         in audit mode.
        """
        user = UserFactory.create()
        self.assertTrue(self.client.login(username=user.username, password='test'))
        CourseEnrollmentFactory(user=user, course_id=self.course.id, mode=CourseMode.AUDIT)
        response = self._get_progress_page()

        self.assertContains(
            response,
            u'You are enrolled in the audit track for this course. The audit track does not include a certificate.'
        )

    def test_invalidated_cert_data(self):
        """
        Verify that invalidated cert data is returned if cert is invalidated.
        """
        generated_certificate = self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )

        CertificateInvalidationFactory.create(
            generated_certificate=generated_certificate,
            invalidated_by=self.user
        )
        # Invalidate user certificate
        generated_certificate.invalidate()
        response = views._get_cert_data(self.user, self.course, self.course.id, True, CourseMode.HONOR)
        self.assertEqual(response.cert_status, 'invalidated')
        self.assertEqual(response.title, 'Your certificate has been invalidated')

    def test_downloadable_get_cert_data(self):
        """
        Verify that downloadable cert data is returned if cert is downloadable.
        """
        self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )
        with patch('certificates.api.certificate_downloadable_status',
                   return_value=self.mock_certificate_downloadable_status(is_downloadable=True)):
            response = views._get_cert_data(self.user, self.course, self.course.id, True, CourseMode.HONOR)

        self.assertEqual(response.cert_status, 'downloadable')
        self.assertEqual(response.title, 'Your certificate is available')

    def test_generating_get_cert_data(self):
        """
        Verify that generating cert data is returned if cert is generating.
        """
        self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )
        with patch('certificates.api.certificate_downloadable_status',
                   return_value=self.mock_certificate_downloadable_status(is_generating=True)):
            response = views._get_cert_data(self.user, self.course, self.course.id, True, CourseMode.HONOR)

        self.assertEqual(response.cert_status, 'generating')
        self.assertEqual(response.title, "We're working on it...")

    def test_unverified_get_cert_data(self):
        """
        Verify that unverified cert data is returned if cert is unverified.
        """
        self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )
        with patch('certificates.api.certificate_downloadable_status',
                   return_value=self.mock_certificate_downloadable_status(is_unverified=True)):
            response = views._get_cert_data(self.user, self.course, self.course.id, True, CourseMode.HONOR)

        self.assertEqual(response.cert_status, 'unverified')
        self.assertEqual(response.title, "Certificate unavailable")

    def test_request_get_cert_data(self):
        """
        Verify that requested cert data is returned if cert is to be requested.
        """
        self.generate_certificate(
            "http://www.example.com/certificate.pdf", "honor"
        )
        with patch('certificates.api.certificate_downloadable_status',
                   return_value=self.mock_certificate_downloadable_status()):
            response = views._get_cert_data(self.user, self.course, self.course.id, True, CourseMode.HONOR)

        self.assertEqual(response.cert_status, 'requesting')
        self.assertEqual(response.title, "Congratulations, you qualified for a certificate!")

    def assert_invalidate_certificate(self, certificate):
        """ Dry method to mark certificate as invalid. And assert the response. """
        CertificateInvalidationFactory.create(
            generated_certificate=certificate,
            invalidated_by=self.user
        )
        # Invalidate user certificate
        certificate.invalidate()
        resp = self._get_progress_page()

        self.assertNotContains(resp, u'Request Certificate')
        self.assertContains(resp, u'Your certificate has been invalidated')
        self.assertContains(resp, u'Please contact your course team if you have any questions.')
        self.assertNotContains(resp, u'View Your Certificate')
        self.assertNotContains(resp, u'Download Your Certificate')

    def generate_certificate(self, url, mode):
        """ Dry method to generate certificate. """

        generated_certificate = GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            download_url=url,
            mode=mode
        )
        CertificateGenerationConfiguration(enabled=True).save()
        certs_api.set_cert_generation_enabled(self.course.id, True)
        return generated_certificate

    def mock_certificate_downloadable_status(
            self, is_downloadable=False, is_generating=False, is_unverified=False, uuid=None, download_url=None
    ):
        """Dry method to mock certificate downloadable status response."""
        return {
            'is_downloadable': is_downloadable,
            'is_generating': is_generating,
            'is_unverified': is_unverified,
            'download_url': uuid,
            'uuid': download_url,
        }


@attr(shard=1)
class VerifyCourseKeyDecoratorTests(TestCase):
    """
    Tests for the ensure_valid_course_key decorator.
    """

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

        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)
        view_function = ensure_valid_course_key(mocked_view)
        view_function(self.request, course_id=self.valid_course_id)
        self.assertTrue(mocked_view.called)

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


@attr(shard=1)
class IsCoursePassedTests(ModuleStoreTestCase):
    """
    Tests for the is_course_passed helper function
    """

    SUCCESS_CUTOFF = 0.5

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

        self.student = UserFactory()
        self.course = CourseFactory.create(
            org='edx',
            number='verified',
            display_name='Verified Course',
            grade_cutoffs={'cutoff': 0.75, 'Pass': self.SUCCESS_CUTOFF}
        )
        self.request = RequestFactory()
        self.request.user = self.student

    def test_user_fails_if_not_clear_exam(self):
        # If user has not grade then false will return
        self.assertFalse(views.is_course_passed(self.course, None, self.student, self.request))

    @patch('lms.djangoapps.grades.new.course_grade.CourseGrade.summary', PropertyMock(return_value={'percent': 0.9}))
    def test_user_pass_if_percent_appears_above_passing_point(self):
        # Mocking the grades.grade
        # If user has above passing marks then True will return
        self.assertTrue(views.is_course_passed(self.course, None, self.student, self.request))

    @patch('lms.djangoapps.grades.new.course_grade.CourseGrade.summary', PropertyMock(return_value={'percent': 0.2}))
    def test_user_fail_if_percent_appears_below_passing_point(self):
        # Mocking the grades.grade
        # If user has below passing marks then False will return
        self.assertFalse(views.is_course_passed(self.course, None, self.student, self.request))

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'percent': SUCCESS_CUTOFF})
    )
    def test_user_with_passing_marks_and_achieved_marks_equal(self):
        # Mocking the grades.grade
        # If user's achieved passing marks are equal to the required passing
        # marks then it will return True
        self.assertTrue(views.is_course_passed(self.course, None, self.student, self.request))


@attr(shard=1)
class GenerateUserCertTests(ModuleStoreTestCase):
    """
    Tests for the view function Generated User Certs
    """

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

        self.student = UserFactory(username='dummy', password='123456', email='test@mit.edu')
        self.course = CourseFactory.create(
            org='edx',
            number='verified',
            display_name='Verified Course',
            grade_cutoffs={'cutoff': 0.75, 'Pass': 0.5}
        )
        self.enrollment = CourseEnrollment.enroll(self.student, self.course.id, mode='honor')
        self.assertTrue(self.client.login(username=self.student, password='123456'))
        self.url = reverse('generate_user_cert', kwargs={'course_id': unicode(self.course.id)})

    def test_user_with_out_passing_grades(self):
        # If user has no grading then json will return failed message and badrequest code
        resp = self.client.post(self.url)
        self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
        self.assertIn("Your certificate will be available when you pass the course.", resp.content)

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75})
    )
    @override_settings(CERT_QUEUE='certificates', LMS_SEGMENT_KEY="foobar")
    def test_user_with_passing_grade(self):
        # If user has above passing grading then json will return cert generating message and
        # status valid code
        # mocking xqueue and analytics

        analytics_patcher = patch('courseware.views.views.analytics')
        mock_tracker = analytics_patcher.start()
        self.addCleanup(analytics_patcher.stop)

        with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_send_to_queue:
            mock_send_to_queue.return_value = (0, "Successfully queued")
            resp = self.client.post(self.url)
            self.assertEqual(resp.status_code, 200)

            # Verify Google Analytics event fired after generating certificate
            mock_tracker.track.assert_called_once_with(  # pylint: disable=no-member
                self.student.id,  # pylint: disable=no-member
                'edx.bi.user.certificate.generate',
                {
                    'category': 'certificates',
                    'label': unicode(self.course.id)
                },

                context={
                    'ip': '127.0.0.1',
                    'Google Analytics': {'clientId': None}
                }
            )
            mock_tracker.reset_mock()

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75})
    )
    def test_user_with_passing_existing_generating_cert(self):
        # If user has passing grade but also has existing generating cert
        # then json will return cert generating message with bad request code
        GeneratedCertificateFactory.create(
            user=self.student,
            course_id=self.course.id,
            status=CertificateStatuses.generating,
            mode='verified'
        )
        resp = self.client.post(self.url)
        self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
        self.assertIn("Certificate is being created.", resp.content)

    @patch(
        'lms.djangoapps.grades.new.course_grade.CourseGrade.summary',
        PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75})
    )
    @override_settings(CERT_QUEUE='certificates', LMS_SEGMENT_KEY="foobar")
    def test_user_with_passing_existing_downloadable_cert(self):
        # If user has already downloadable certificate
        # then json will return cert generating message with bad request code

        GeneratedCertificateFactory.create(
            user=self.student,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            mode='verified'
        )

        resp = self.client.post(self.url)
        self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
        self.assertIn("Certificate has already been created.", resp.content)

    def test_user_with_non_existing_course(self):
        # If try to access a course with valid key pattern then it will return
        # bad request code with course is not valid message
        resp = self.client.post('/courses/def/abc/in_valid/generate_user_cert')
        self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
        self.assertIn("Course is not valid", resp.content)

    def test_user_with_invalid_course_id(self):
        # If try to access a course with invalid key pattern then 404 will return
        resp = self.client.post('/courses/def/generate_user_cert')
        self.assertEqual(resp.status_code, 404)

    def test_user_without_login_return_error(self):
        # If user try to access without login should see a bad request status code with message
        self.client.logout()
        resp = self.client.post(self.url)
        self.assertEqual(resp.status_code, HttpResponseBadRequest.status_code)
        self.assertIn(u"You must be signed in to {platform_name} to create a certificate.".format(
            platform_name=settings.PLATFORM_NAME
        ), resp.content.decode('utf-8'))


class ActivateIDCheckerBlock(XBlock):
    """
    XBlock for checking for an activate_block_id entry in the render context.
    """
    # We don't need actual children to test this.
    has_children = False

    def student_view(self, context):
        """
        A student view that displays the activate_block_id context variable.
        """
        result = Fragment()
        if 'activate_block_id' in context:
            result.add_content(u"Activate Block ID: {block_id}</p>".format(block_id=context['activate_block_id']))
        return result


class ViewCheckerBlock(XBlock):
    """
    XBlock for testing user state in views.
    """
    has_children = True
    state = String(scope=Scope.user_state)

    def student_view(self, context):  # pylint: disable=unused-argument
        """
        A student_view that asserts that the ``state`` field for this block
        matches the block's usage_id.
        """
        msg = "{} != {}".format(self.state, self.scope_ids.usage_id)
        assert self.state == unicode(self.scope_ids.usage_id), msg
        fragments = self.runtime.render_children(self)
        result = Fragment(
            content=u"<p>ViewCheckerPassed: {}</p>\n{}".format(
                unicode(self.scope_ids.usage_id),
                "\n".join(fragment.content for fragment in fragments),
            )
        )
        return result


@attr(shard=1)
@ddt.ddt
class TestIndexView(ModuleStoreTestCase):
    """
    Tests of the courseware.views.index view.
    """

    @XBlock.register_temp_plugin(ViewCheckerBlock, 'view_checker')
    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_student_state(self, default_store):
        """
        Verify that saved student state is loaded for xblocks rendered in the index view.
        """
        user = UserFactory()

        with modulestore().default_store(default_store):
            course = CourseFactory.create()
            chapter = ItemFactory.create(parent=course, category='chapter')
            section = ItemFactory.create(parent=chapter, category='view_checker', display_name="Sequence Checker")
            vertical = ItemFactory.create(parent=section, category='view_checker', display_name="Vertical Checker")
            block = ItemFactory.create(parent=vertical, category='view_checker', display_name="Block Checker")

        for item in (section, vertical, block):
            StudentModuleFactory.create(
                student=user,
                course_id=course.id,
                module_state_key=item.scope_ids.usage_id,
                state=json.dumps({'state': unicode(item.scope_ids.usage_id)})
            )

        CourseEnrollmentFactory(user=user, course_id=course.id)

        self.assertTrue(self.client.login(username=user.username, password='test'))
        response = self.client.get(
            reverse(
                'courseware_section',
                kwargs={
                    'course_id': unicode(course.id),
                    'chapter': chapter.url_name,
                    'section': section.url_name,
                }
            )
        )

        # Trigger the assertions embedded in the ViewCheckerBlocks
        self.assertEquals(response.content.count("ViewCheckerPassed"), 3)

    @XBlock.register_temp_plugin(ActivateIDCheckerBlock, 'id_checker')
    def test_activate_block_id(self):
        user = UserFactory()

        course = CourseFactory.create()
        chapter = ItemFactory.create(parent=course, category='chapter')
        section = ItemFactory.create(parent=chapter, category='sequential', display_name="Sequence")
        vertical = ItemFactory.create(parent=section, category='vertical', display_name="Vertical")
        ItemFactory.create(parent=vertical, category='id_checker', display_name="ID Checker")

        CourseEnrollmentFactory(user=user, course_id=course.id)

        self.assertTrue(self.client.login(username=user.username, password='test'))
        response = self.client.get(
            reverse(
                'courseware_section',
                kwargs={
                    'course_id': unicode(course.id),
                    'chapter': chapter.url_name,
                    'section': section.url_name,
                }
            ) + '?activate_block_id=test_block_id'
        )
        self.assertIn("Activate Block ID: test_block_id", response.content)


@ddt.ddt
class TestIndexViewWithVerticalPositions(ModuleStoreTestCase):
    """
    Test the index view to handle vertical positions. Confirms that first position is loaded
    if input position is non-positive or greater than number of positions available.
    """

    def setUp(self):
        """
        Set up initial test data
        """
        super(TestIndexViewWithVerticalPositions, self).setUp()

        self.user = UserFactory()

        # create course with 3 positions
        self.course = CourseFactory.create()
        self.chapter = ItemFactory.create(parent=self.course, category='chapter')
        self.section = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Sequence")
        ItemFactory.create(parent=self.section, category='vertical', display_name="Vertical1")
        ItemFactory.create(parent=self.section, category='vertical', display_name="Vertical2")
        ItemFactory.create(parent=self.section, category='vertical', display_name="Vertical3")

        self.client.login(username=self.user, password='test')
        CourseEnrollmentFactory(user=self.user, course_id=self.course.id)

    def _get_course_vertical_by_position(self, input_position):
        """
        Returns client response to input position.
        """
        return self.client.get(
            reverse(
                'courseware_position',
                kwargs={
                    'course_id': unicode(self.course.id),
                    'chapter': self.chapter.url_name,
                    'section': self.section.url_name,
                    'position': input_position,
                }
            )
        )

    def _assert_correct_position(self, response, expected_position):
        """
        Asserts that the expected position and the position in the response are the same
        """
        self.assertIn('data-position="{}"'.format(expected_position), response.content)

    @ddt.data(("-1", 1), ("0", 1), ("-0", 1), ("2", 2), ("5", 1))
    @ddt.unpack
    def test_vertical_positions(self, input_position, expected_position):
        """
        Tests the following cases:

        * Load first position when negative position inputted.
        * Load first position when 0/-0 position inputted.
        * Load given position when 0 < input_position <= num_positions_available.
        * Load first position when positive position > num_positions_available.
        """
        resp = self._get_course_vertical_by_position(input_position)
        self._assert_correct_position(resp, expected_position)


class TestIndexViewWithGating(ModuleStoreTestCase, MilestonesTestCaseMixin):
    """
    Test the index view for a course with gated content
    """

    def setUp(self):
        """
        Set up the initial test data
        """
        super(TestIndexViewWithGating, self).setUp()

        self.user = UserFactory()
        self.course = CourseFactory.create()
        self.course.enable_subsection_gating = True
        self.course.save()
        self.store.update_item(self.course, 0)
        self.chapter = ItemFactory.create(parent=self.course, category="chapter", display_name="Chapter")
        self.open_seq = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Open Sequential")
        ItemFactory.create(parent=self.open_seq, category='problem', display_name="Problem 1")
        self.gated_seq = ItemFactory.create(parent=self.chapter, category='sequential', display_name="Gated Sequential")
        ItemFactory.create(parent=self.gated_seq, category='problem', display_name="Problem 2")
        gating_api.add_prerequisite(self.course.id, self.open_seq.location)
        gating_api.set_required_content(self.course.id, self.gated_seq.location, self.open_seq.location, 100)

        CourseEnrollmentFactory(user=self.user, course_id=self.course.id)

    def test_index_with_gated_sequential(self):
        """
        Test index view with a gated sequential raises Http404
        """
        self.assertTrue(self.client.login(username=self.user.username, password='test'))
        response = self.client.get(
            reverse(
                'courseware_section',
                kwargs={
                    'course_id': unicode(self.course.id),
                    'chapter': self.chapter.url_name,
                    'section': self.gated_seq.url_name,
                }
            )
        )

        self.assertEquals(response.status_code, 404)


class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase):
    """
    Tests for the courseware.render_xblock endpoint.
    This class overrides the get_response method, which is used by
    the tests defined in RenderXBlockTestMixin.
    """

    def setUp(self):
        reload_django_url_config()
        super(TestRenderXBlock, self).setUp()

    def get_response(self, url_encoded_params=None):
        """
        Overridable method to get the response from the endpoint that is being tested.
        """
        url = reverse('render_xblock', kwargs={"usage_key_string": unicode(self.html_block.location)})
        if url_encoded_params:
            url += '?' + url_encoded_params
        return self.client.get(url)


class TestRenderXBlockSelfPaced(TestRenderXBlock):
    """
    Test rendering XBlocks for a self-paced course. Relies on the query
    count assertions in the tests defined by RenderXBlockMixin.
    """

    def setUp(self):
        super(TestRenderXBlockSelfPaced, self).setUp()
        SelfPacedConfiguration(enabled=True).save()

    def course_options(self):
        return {'self_paced': True}