Files
edx-platform/lms/djangoapps/courseware/tests/test_date_summary.py
2017-06-01 15:12:50 -04:00

479 lines
21 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for course home page date summary blocks."""
from datetime import datetime, timedelta
import ddt
from django.core.urlresolvers import reverse
from freezegun import freeze_time
from nose.plugins.attrib import attr
from pytz import utc
from commerce.models import CommerceConfiguration
from course_modes.tests.factories import CourseModeFactory
from course_modes.models import CourseMode
from courseware.courses import get_course_date_blocks
from courseware.date_summary import (
CourseEndDate,
CourseStartDate,
DateSummary,
TodaysDate,
VerificationDeadlineDate,
VerifiedUpgradeDeadlineDate,
)
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@attr(shard=1)
@ddt.ddt
class CourseDateSummaryTest(SharedModuleStoreTestCase):
"""Tests for course date summary blocks."""
def setUp(self):
SelfPacedConfiguration(enable_course_home_improvements=True).save()
super(CourseDateSummaryTest, self).setUp()
def setup_course_and_user(
self,
days_till_start=1,
days_till_end=14,
days_till_upgrade_deadline=4,
enroll_user=True,
enrollment_mode=CourseMode.VERIFIED,
user_enrollment_mode=None,
course_min_price=100,
days_till_verification_deadline=14,
verification_status=None,
sku=None,
create_user=True
):
"""Set up the course and user for this test."""
now = datetime.now(utc)
if create_user:
self.user = UserFactory.create(username='mrrobot', password='test') # pylint: disable=attribute-defined-outside-init
self.course = CourseFactory.create( # pylint: disable=attribute-defined-outside-init
start=now + timedelta(days=days_till_start)
)
if days_till_end is not None:
self.course.end = now + timedelta(days=days_till_end)
else:
self.course.end = None
if enrollment_mode is not None and days_till_upgrade_deadline is not None:
CourseModeFactory.create(
course_id=self.course.id,
mode_slug=enrollment_mode,
expiration_datetime=now + timedelta(days=days_till_upgrade_deadline),
min_price=course_min_price,
sku=sku
)
if enroll_user:
if user_enrollment_mode:
CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=user_enrollment_mode)
else:
enrollment_mode = enrollment_mode or CourseMode.DEFAULT_MODE_SLUG
CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=enrollment_mode)
if days_till_verification_deadline is not None:
VerificationDeadline.objects.create(
course_key=self.course.id,
deadline=now + timedelta(days=days_till_verification_deadline)
)
if verification_status is not None:
SoftwareSecurePhotoVerificationFactory.create(user=self.user, status=verification_status)
def test_course_info_feature_flag(self):
SelfPacedConfiguration(enable_course_home_improvements=False).save()
self.setup_course_and_user()
self.client.login(username='mrrobot', password='test')
url = reverse('info', args=(self.course.id,))
response = self.client.get(url)
self.assertNotIn('date-summary', response.content)
def test_course_info_logged_out(self):
self.setup_course_and_user()
url = reverse('info', args=(self.course.id,))
response = self.client.get(url)
self.assertEqual(200, response.status_code)
# Tests for which blocks are enabled
def assert_block_types(self, expected_blocks):
"""Assert that the enabled block types for this course are as expected."""
blocks = get_course_date_blocks(self.course, self.user)
self.assertEqual(len(blocks), len(expected_blocks))
self.assertEqual(set(type(b) for b in blocks), set(expected_blocks))
@ddt.data(
# Verified enrollment with no photo-verification before course start
({}, (CourseEndDate, CourseStartDate, TodaysDate, VerificationDeadlineDate)),
# Verified enrollment with `approved` photo-verification after course end
({'days_till_start': -10,
'days_till_end': -5,
'days_till_upgrade_deadline': -6,
'days_till_verification_deadline': -5,
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# Verified enrollment with `expired` photo-verification during course run
({'days_till_start': -10,
'verification_status': 'expired'},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Verified enrollment with `approved` photo-verification during course run
({'days_till_start': -10,
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# Audit enrollment and non-upsell course.
({'days_till_start': -10,
'days_till_upgrade_deadline': None,
'days_till_verification_deadline': None,
'course_min_price': 0,
'enrollment_mode': CourseMode.AUDIT},
(TodaysDate, CourseEndDate)),
# Verified enrollment with *NO* course end date
({'days_till_end': None},
(CourseStartDate, TodaysDate, VerificationDeadlineDate)),
# Verified enrollment with no photo-verification during course run
({'days_till_start': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Verification approved
({'days_till_start': -10,
'days_till_upgrade_deadline': -1,
'days_till_verification_deadline': 1,
'verification_status': 'approved'},
(TodaysDate, CourseEndDate)),
# After upgrade deadline
({'days_till_start': -10,
'days_till_upgrade_deadline': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# After verification deadline
({'days_till_start': -10,
'days_till_upgrade_deadline': -2,
'days_till_verification_deadline': -1},
(TodaysDate, CourseEndDate, VerificationDeadlineDate)),
# Un-enrolled user before course start
({'enroll_user': False},
(CourseStartDate, TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
# Un-enrolled user during course run
({'days_till_start': -1,
'enroll_user': False},
(TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
# Un-enrolled user after course end.
({'enroll_user': False,
'days_till_start': -10,
'days_till_end': -5},
(TodaysDate, CourseEndDate, VerifiedUpgradeDeadlineDate)),
)
@ddt.unpack
def test_enabled_block_types(self, course_options, expected_blocks):
self.setup_course_and_user(**course_options)
self.assert_block_types(expected_blocks)
def test_todays_date_block(self):
"""
Helper function to test that today's date block renders correctly
and displays the correct time, accounting for daylight savings
"""
with freeze_time('2015-01-02'):
self.setup_course_and_user()
block = TodaysDate(self.course, self.user)
self.assertTrue(block.is_enabled)
self.assertEqual(block.date, datetime.now(utc))
self.assertEqual(block.title, 'current_datetime')
@ddt.data(
'info',
'openedx.course_experience.course_home',
)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
def test_todays_date_no_timezone(self, url_name):
with freeze_time('2015-01-02'):
self.setup_course_and_user()
self.client.login(username='mrrobot', password='test')
html_elements = [
'<h3 class="hd hd-6 handouts-header">Important Course Dates</h3>',
'<div class="date-summary-container">',
'<div class="date-summary date-summary-todays-date">',
'<span class="hd hd-6 heading localized-datetime"',
'data-datetime="2015-01-02 00:00:00+00:00"',
'data-string="Today is {date}"',
'data-timezone="None"'
]
url = reverse(url_name, args=(self.course.id, ))
response = self.client.get(url, follow=True)
for html in html_elements:
self.assertContains(response, html)
@ddt.data(
'info',
'openedx.course_experience.course_home',
)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
def test_todays_date_timezone(self, url_name):
with freeze_time('2015-01-02'):
self.setup_course_and_user()
self.client.login(username='mrrobot', password='test')
set_user_preference(self.user, "time_zone", "America/Los_Angeles")
url = reverse(url_name, args=(self.course.id,))
response = self.client.get(url, follow=True)
html_elements = [
'<h3 class="hd hd-6 handouts-header">Important Course Dates</h3>',
'<div class="date-summary-container">',
'<div class="date-summary date-summary-todays-date">',
'<span class="hd hd-6 heading localized-datetime"',
'data-datetime="2015-01-02 00:00:00+00:00"',
'data-string="Today is {date}"',
'data-timezone="America/Los_Angeles"'
]
for html in html_elements:
self.assertContains(response, html)
## Tests Course Start Date
def test_course_start_date(self):
self.setup_course_and_user()
block = CourseStartDate(self.course, self.user)
self.assertEqual(block.date, self.course.start)
@ddt.data(
'info',
'openedx.course_experience.course_home',
)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
def test_start_date_render(self, url_name):
with freeze_time('2015-01-02'):
self.setup_course_and_user()
self.client.login(username='mrrobot', password='test')
url = reverse(url_name, args=(self.course.id,))
response = self.client.get(url, follow=True)
html_elements = [
'data-string="in 1 day - {date}"',
'data-datetime="2015-01-03 00:00:00+00:00"'
]
for html in html_elements:
self.assertContains(response, html)
@ddt.data(
'info',
'openedx.course_experience.course_home',
)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
def test_start_date_render_time_zone(self, url_name):
with freeze_time('2015-01-02'):
self.setup_course_and_user()
self.client.login(username='mrrobot', password='test')
set_user_preference(self.user, "time_zone", "America/Los_Angeles")
url = reverse(url_name, args=(self.course.id,))
response = self.client.get(url, follow=True)
html_elements = [
'data-string="in 1 day - {date}"',
'data-datetime="2015-01-03 00:00:00+00:00"',
'data-timezone="America/Los_Angeles"'
]
for html in html_elements:
self.assertContains(response, html)
## Tests Course End Date Block
def test_course_end_date_for_certificate_eligible_mode(self):
self.setup_course_and_user(days_till_start=-1)
block = CourseEndDate(self.course, self.user)
self.assertEqual(
block.description,
'To earn a certificate, you must complete all requirements before this date.'
)
def test_course_end_date_for_non_certificate_eligible_mode(self):
self.setup_course_and_user(days_till_start=-1, enrollment_mode=CourseMode.AUDIT)
block = CourseEndDate(self.course, self.user)
self.assertEqual(
block.description,
'After this date, course content will be archived.'
)
self.assertEqual(block.title, 'Course End')
def test_course_end_date_after_course(self):
self.setup_course_and_user(days_till_start=-2, days_till_end=-1)
block = CourseEndDate(self.course, self.user)
self.assertEqual(
block.description,
'This course is archived, which means you can review course content but it is no longer active.'
)
self.assertEqual(block.title, 'Course End')
# Tests Verified Upgrade Deadline Date Block
def check_upgrade_banner(
self,
banner_expected=True,
include_url_parameter=True,
expected_cookie_value=None
):
"""
Helper method to check for the presence of the Upgrade Banner
"""
url = reverse('info', args=[self.course.id.to_deprecated_string()])
if include_url_parameter:
url += '?upgrade=true'
resp = self.client.get(url)
upgrade_cookie_name = 'show_upgrade_notification'
expected_banner_text = "Give yourself an additional incentive to complete"
if banner_expected:
self.assertIn(expected_banner_text, resp.content)
self.assertIn(str(self.course.id), self.client.cookies[upgrade_cookie_name].value)
else:
self.assertNotIn(expected_banner_text, resp.content)
if upgrade_cookie_name in self.client.cookies:
self.assertNotIn(str(self.course.id), self.client.cookies[upgrade_cookie_name].value)
if expected_cookie_value is not None:
self.assertIn(str(expected_cookie_value), self.client.cookies[upgrade_cookie_name].value)
def test_verified_upgrade_deadline_date(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertEqual(block.date, datetime.now(utc) + timedelta(days=1))
self.assertTrue(block.is_enabled)
self.assertEqual(block.link, reverse('verify_student_upgrade_and_verify', args=(self.course.id,)))
self.check_upgrade_banner()
def test_without_upgrade_deadline(self):
self.setup_course_and_user(enrollment_mode=None)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
self.assertIsNone(block.date)
self.check_upgrade_banner(banner_expected=False)
def test_verified_upgrade_banner_not_present_past_deadline(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=-1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
self.check_upgrade_banner(banner_expected=False)
def test_verified_upgrade_banner_cookie(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT)
self.client.login(username='mrrobot', password='test')
# No URL parameter or cookie present, notification should not be shown.
self.check_upgrade_banner(include_url_parameter=False, banner_expected=False)
# Now pass URL parameter-- notification should be shown.
self.check_upgrade_banner(include_url_parameter=True)
# A cookie should be set in the previous call, so it is no longer necessary to pass
# the URL parameter in order to see the notification.
self.check_upgrade_banner(include_url_parameter=False)
# Store the current course_id for testing
old_course_id = self.course.id
# Change to another course
self.setup_course_and_user(days_till_upgrade_deadline=1,
user_enrollment_mode=CourseMode.AUDIT,
create_user=False)
# Banner should not be present in the newly created course
self.check_upgrade_banner(include_url_parameter=False,
banner_expected=False,
expected_cookie_value=old_course_id)
# Unfortunately (according to django doc), it is not possible to test expiration of the cookie.
def test_ecommerce_checkout_redirect(self):
"""Verify the block link redirects to ecommerce checkout if it's enabled."""
sku = 'TESTSKU'
checkout_page = '/test_basket/'
CommerceConfiguration.objects.create(
checkout_on_ecommerce_service=True,
single_course_checkout_page=checkout_page
)
self.setup_course_and_user(sku=sku)
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertEqual(block.link, '{}?sku={}'.format(checkout_page, sku))
## VerificationDeadlineDate
def test_no_verification_deadline(self):
self.setup_course_and_user(days_till_start=-1, days_till_verification_deadline=None)
block = VerificationDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
def test_no_verified_enrollment(self):
self.setup_course_and_user(days_till_start=-1, enrollment_mode=CourseMode.AUDIT)
block = VerificationDeadlineDate(self.course, self.user)
self.assertFalse(block.is_enabled)
def test_verification_deadline_date_upcoming(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_start=-1)
block = VerificationDeadlineDate(self.course, self.user)
self.assertEqual(block.css_class, 'verification-deadline-upcoming')
self.assertEqual(block.title, 'Verification Deadline')
self.assertEqual(block.date, datetime.now(utc) + timedelta(days=14))
self.assertEqual(
block.description,
'You must successfully complete verification before this date to qualify for a Verified Certificate.'
)
self.assertEqual(block.link_text, 'Verify My Identity')
self.assertEqual(block.link, reverse('verify_student_verify_now', args=(self.course.id,)))
def test_verification_deadline_date_retry(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(days_till_start=-1, verification_status='denied')
block = VerificationDeadlineDate(self.course, self.user)
self.assertEqual(block.css_class, 'verification-deadline-retry')
self.assertEqual(block.title, 'Verification Deadline')
self.assertEqual(block.date, datetime.now(utc) + timedelta(days=14))
self.assertEqual(
block.description,
'You must successfully complete verification before this date to qualify for a Verified Certificate.'
)
self.assertEqual(block.link_text, 'Retry Verification')
self.assertEqual(block.link, reverse('verify_student_reverify'))
def test_verification_deadline_date_denied(self):
with freeze_time('2015-01-02'):
self.setup_course_and_user(
days_till_start=-10,
verification_status='denied',
days_till_verification_deadline=-1,
)
block = VerificationDeadlineDate(self.course, self.user)
self.assertEqual(block.css_class, 'verification-deadline-passed')
self.assertEqual(block.title, 'Missed Verification Deadline')
self.assertEqual(block.date, datetime.now(utc) + timedelta(days=-1))
self.assertEqual(
block.description,
"Unfortunately you missed this course's deadline for a successful verification."
)
self.assertEqual(block.link_text, 'Learn More')
self.assertEqual(block.link, '')
@ddt.data(
(-1, '1 day ago - {date}'),
(1, 'in 1 day - {date}')
)
@ddt.unpack
def test_render_date_string_past(self, delta, expected_date_string):
with freeze_time('2015-01-02'):
self.setup_course_and_user(
days_till_start=-10,
verification_status='denied',
days_till_verification_deadline=delta,
)
block = VerificationDeadlineDate(self.course, self.user)
self.assertEqual(block.relative_datestring, expected_date_string)