Files
edx-platform/common/djangoapps/enrollment/tests/test_data.py
Michael Youngstrom 9d589f997d INCR-128
2019-04-02 12:40:26 -04:00

401 lines
16 KiB
Python

"""
Test the Data Aggregation Layer for Course Enrollments.
"""
from __future__ import absolute_import
import datetime
import unittest
import ddt
import pytest
import six
from django.conf import settings
from mock import patch
from pytz import UTC
from six.moves import range
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from enrollment import data
from enrollment.errors import (
CourseEnrollmentClosedError,
CourseEnrollmentExistsError,
CourseEnrollmentFullError,
UserNotFoundError
)
from enrollment.serializers import CourseEnrollmentSerializer
from openedx.core.lib.exceptions import CourseNotFoundError
from student.models import AlreadyEnrolledError, CourseEnrollment, CourseFullError, EnrollmentClosedError
from student.tests.factories import CourseAccessRoleFactory, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@ddt.ddt
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class EnrollmentDataTest(ModuleStoreTestCase):
"""
Test course enrollment data aggregation.
"""
USERNAME = "Bob"
EMAIL = "bob@example.com"
PASSWORD = "edx"
def setUp(self):
"""Create a course and user, then log in. """
super(EnrollmentDataTest, self).setUp()
self.course = CourseFactory.create()
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD)
@ddt.data(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([], 'honor'),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
(['honor', 'verified', 'audit'], 'honor'),
)
@ddt.unpack
def test_enroll(self, course_modes, enrollment_mode):
# Create the course modes (if any) required for this test case
self._create_course_modes(course_modes)
enrollment = data.create_course_enrollment(
self.user.username,
six.text_type(self.course.id),
enrollment_mode,
True
)
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
self.assertTrue(is_active)
self.assertEqual(course_mode, enrollment_mode)
# Confirm the returned enrollment and the data match up.
self.assertEqual(course_mode, enrollment['mode'])
self.assertEqual(is_active, enrollment['is_active'])
self.assertEqual(self.course.display_name_with_default, enrollment['course_details']['course_name'])
def test_unenroll(self):
# Enroll the user in the course
CourseEnrollment.enroll(self.user, self.course.id, mode="honor")
enrollment = data.update_course_enrollment(
self.user.username,
six.text_type(self.course.id),
is_active=False
)
# Determine that the returned enrollment is inactive.
self.assertFalse(enrollment['is_active'])
# Expect that we're no longer enrolled
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
@ddt.data(
# No course modes, no course enrollments.
([]),
# Audit / Verified / Honor course modes, with three course enrollments.
(['honor', 'verified', 'audit']),
)
def test_get_course_info(self, course_modes):
self._create_course_modes(course_modes, course=self.course)
result_course = data.get_course_enrollment_info(six.text_type(self.course.id))
result_slugs = [mode['slug'] for mode in result_course['course_modes']]
for course_mode in course_modes:
self.assertIn(course_mode, result_slugs)
@ddt.data(
# No course modes, no course enrollments.
([], []),
# Audit / Verified / Honor course modes, with three course enrollments.
(['honor', 'verified', 'audit'], ['1', '2', '3']),
)
@ddt.unpack
def test_get_course_enrollments(self, course_modes, course_numbers):
# Create all the courses
created_courses = []
for course_number in course_numbers:
created_courses.append(CourseFactory.create(number=course_number))
created_enrollments = []
for course in created_courses:
self._create_course_modes(course_modes, course=course)
# Create the original enrollment.
created_enrollments.append(data.create_course_enrollment(
self.user.username,
six.text_type(course.id),
'honor',
True
))
# Compare the created enrollments with the results
# from the get enrollments request.
results = data.get_course_enrollments(self.user.username)
self.assertEqual(results, created_enrollments)
# Now create a course enrollment with some invalid course (does
# not exist in database) for the user and check that the method
# 'get_course_enrollments' ignores course enrollments for invalid
# or deleted courses
CourseEnrollment.objects.create(
user=self.user,
course_id='InvalidOrg/InvalidCourse/InvalidRun',
mode='honor',
is_active=True
)
updated_results = data.get_course_enrollments(self.user.username)
self.assertEqual(results, updated_results)
def test_get_enrollments_including_inactive(self):
""" Verify that if 'include_inactive' is True, all enrollments
are returned including inactive.
"""
course_modes, course_numbers = ['honor', 'verified', 'audit'], ['1', '2', '3']
created_courses = []
for course_number in course_numbers:
created_courses.append(CourseFactory.create(number=course_number))
created_enrollments = []
for course in created_courses:
self._create_course_modes(course_modes, course=course)
# Create the original enrollment.
created_enrollments.append(data.create_course_enrollment(
self.user.username,
six.text_type(course.id),
'honor',
True
))
# deactivate one enrollment
data.update_course_enrollment(
self.user.username,
six.text_type(created_courses[0].id),
'honor',
False
)
# by default in-active enrollment will be excluded.
results = data.get_course_enrollments(self.user.username)
self.assertNotEqual(len(results), len(created_enrollments))
# we can get all enrollments including inactive by passing "include_inactive"
results = data.get_course_enrollments(self.user.username, include_inactive=True)
self.assertEqual(len(results), len(created_enrollments))
@ddt.data(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([], 'honor'),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
(['honor', 'verified', 'audit'], 'verified'),
)
@ddt.unpack
def test_get_course_enrollment(self, course_modes, enrollment_mode):
self._create_course_modes(course_modes)
# Try to get an enrollment before it exists.
result = data.get_course_enrollment(self.user.username, six.text_type(self.course.id))
self.assertIsNone(result)
# Create the original enrollment.
enrollment = data.create_course_enrollment(
self.user.username,
six.text_type(self.course.id),
enrollment_mode,
True
)
# Get the enrollment and compare it to the original.
result = data.get_course_enrollment(self.user.username, six.text_type(self.course.id))
self.assertEqual(self.user.username, result['user'])
self.assertEqual(enrollment, result)
@ddt.data(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([], 'honor'),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
(['honor', 'verified', 'audit'], 'verified'),
)
@ddt.unpack
def test_get_user_enrollments(self, course_modes, enrollment_mode):
self._create_course_modes(course_modes)
# Try to get enrollments before they exist.
result = data.get_user_enrollments(self.course.id)
self.assertFalse(result.exists())
# Create 10 test users to enroll in the course
users = []
for i in range(10):
users.append(UserFactory.create(
username=self.USERNAME + str(i),
email=self.EMAIL + str(i),
password=self.PASSWORD + str(i)
))
# Create the original enrollments.
created_enrollments = []
for user in users:
created_enrollments.append(data.create_course_enrollment(
user.username,
six.text_type(self.course.id),
enrollment_mode,
True
))
# Compare the created enrollments with the results
# from the get user enrollments request.
results = data.get_user_enrollments(
self.course.id
)
self.assertTrue(result.exists())
self.assertEqual(CourseEnrollmentSerializer(results, many=True).data, created_enrollments)
@ddt.data(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([], 'credit'),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
(['honor', 'verified', 'audit', 'credit'], 'credit'),
)
@ddt.unpack
def test_add_or_update_enrollment_attr(self, course_modes, enrollment_mode):
# Create the course modes (if any) required for this test case
self._create_course_modes(course_modes)
data.create_course_enrollment(self.user.username, six.text_type(self.course.id), enrollment_mode, True)
enrollment_attributes = [
{
"namespace": "credit",
"name": "provider_id",
"value": "hogwarts",
}
]
data.add_or_update_enrollment_attr(self.user.username, six.text_type(self.course.id), enrollment_attributes)
enrollment_attr = data.get_enrollment_attributes(self.user.username, six.text_type(self.course.id))
self.assertEqual(enrollment_attr[0], enrollment_attributes[0])
enrollment_attributes = [
{
"namespace": "credit",
"name": "provider_id",
"value": "ASU",
}
]
data.add_or_update_enrollment_attr(self.user.username, six.text_type(self.course.id), enrollment_attributes)
enrollment_attr = data.get_enrollment_attributes(self.user.username, six.text_type(self.course.id))
self.assertEqual(enrollment_attr[0], enrollment_attributes[0])
def test_non_existent_course(self):
with pytest.raises(CourseNotFoundError):
data.get_course_enrollment_info("this/is/bananas")
def _create_course_modes(self, course_modes, course=None):
"""Create the course modes required for a test. """
course_id = course.id if course else self.course.id
for mode_slug in course_modes:
CourseModeFactory.create(
course_id=course_id,
mode_slug=mode_slug,
mode_display_name=mode_slug,
)
def test_enrollment_for_non_existent_user(self):
with pytest.raises(UserNotFoundError):
data.create_course_enrollment("some_fake_user", six.text_type(self.course.id), 'honor', True)
def test_enrollment_for_non_existent_course(self):
with pytest.raises(CourseNotFoundError):
data.create_course_enrollment(self.user.username, "some/fake/course", 'honor', True)
@patch.object(CourseEnrollment, "enroll")
def test_enrollment_for_closed_course(self, mock_enroll):
mock_enroll.side_effect = EnrollmentClosedError("Bad things happened")
with pytest.raises(CourseEnrollmentClosedError):
data.create_course_enrollment(self.user.username, six.text_type(self.course.id), 'honor', True)
@patch.object(CourseEnrollment, "enroll")
def test_enrollment_for_full_course(self, mock_enroll):
mock_enroll.side_effect = CourseFullError("Bad things happened")
with pytest.raises(CourseEnrollmentFullError):
data.create_course_enrollment(self.user.username, six.text_type(self.course.id), 'honor', True)
@patch.object(CourseEnrollment, "enroll")
def test_enrollment_for_enrolled_course(self, mock_enroll):
mock_enroll.side_effect = AlreadyEnrolledError("Bad things happened")
with pytest.raises(CourseEnrollmentExistsError):
data.create_course_enrollment(self.user.username, six.text_type(self.course.id), 'honor', True)
def test_update_for_non_existent_user(self):
with pytest.raises(UserNotFoundError):
data.update_course_enrollment("some_fake_user", six.text_type(self.course.id), is_active=False)
def test_update_for_non_existent_course(self):
enrollment = data.update_course_enrollment(self.user.username, "some/fake/course", is_active=False)
self.assertIsNone(enrollment)
def test_get_course_with_expired_mode_included(self):
"""Verify that method returns expired modes if include_expired
is true."""
modes = ['honor', 'verified', 'audit']
self._create_course_modes(modes, course=self.course)
self._update_verified_mode_as_expired(self.course.id)
self.assert_enrollment_modes(modes, True)
def test_get_course_without_expired_mode_included(self):
"""Verify that method does not returns expired modes if include_expired
is false."""
self._create_course_modes(['honor', 'verified', 'audit'], course=self.course)
self._update_verified_mode_as_expired(self.course.id)
self.assert_enrollment_modes(['audit', 'honor'], False)
def _update_verified_mode_as_expired(self, course_id):
"""Dry method to change verified mode expiration."""
mode = CourseMode.objects.get(course_id=course_id, mode_slug=CourseMode.VERIFIED)
mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=UTC)
mode.save()
def assert_enrollment_modes(self, expected_modes, include_expired):
"""Get enrollment data and assert response with expected modes."""
result_course = data.get_course_enrollment_info(six.text_type(self.course.id), include_expired=include_expired)
result_slugs = [mode['slug'] for mode in result_course['course_modes']]
for course_mode in expected_modes:
self.assertIn(course_mode, result_slugs)
if not include_expired:
self.assertNotIn('verified', result_slugs)
def test_get_roles(self):
"""Create a role for a user, then get it"""
expected_role = CourseAccessRoleFactory.create(course_id=self.course.id, user=self.user, role="SuperCoolTestRole")
roles = data.get_user_roles(self.user.username)
self.assertEqual(roles, {expected_role})
def test_get_roles_no_roles(self):
"""Get roles for a user who has no roles"""
roles = data.get_user_roles(self.user.username)
self.assertEqual(roles, set())
def test_get_roles_invalid_user(self):
"""Get roles for a user that doesn't exist"""
with pytest.raises(UserNotFoundError):
data.get_user_roles("i_dont_exist_and_should_raise_an_error")