Enhance the Link Program Enrollments Support Tool to handle the case of linking a learner to a ProgramEnrollment that is already linked to a different edX account.
In this case, * unenroll the currently linked learner from the courses associated with related ProgramCourseEnrollments * for each course the is enrolled in as part of a ProgramCourseEnrollment, if the coures has an audit track, then move the enrollment into the audit track. Otherwise, keep the learner's enrollment in the existing track. * link the new user to the ProgramEnrollment * enroll the new user in the Master's track in the courses associated with related ProgramCourseEnrollments
This commit is contained in:
@@ -5,6 +5,7 @@ Tests for account linking Python API.
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -103,7 +104,7 @@ class TestLinkProgramEnrollmentsMixin(object):
|
||||
|
||||
def _assert_user_enrolled_in_program_courses(self, user, program_uuid, *course_keys):
|
||||
"""
|
||||
Assert that the given user is has active enrollments in the given courses
|
||||
Assert that the given user has active enrollments in the given courses
|
||||
through the given program.
|
||||
"""
|
||||
user.refresh_from_db()
|
||||
@@ -256,6 +257,86 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase):
|
||||
# assert that all CourseAccessRoleAssignment objects are deleted
|
||||
assert not active_enrollment_1.courseaccessroleassignment_set.all().exists()
|
||||
|
||||
@staticmethod
|
||||
def _assert_course_enrollments_in_mode(course_enrollments, course_keys_to_mode):
|
||||
"""
|
||||
Assert that all program course enrollments are in the correct modes as
|
||||
described by course_keys_to_mode.
|
||||
|
||||
Arguments:
|
||||
user: the user whose course enrollments we are checking
|
||||
program_uuid: the UUID of the program in which the user is enrolled
|
||||
course_keys_to_mode: a mapping from course keys to the the mode
|
||||
slug the user's enrollment should be in
|
||||
"""
|
||||
assert len(course_enrollments) == len(course_keys_to_mode)
|
||||
|
||||
for course_enrollment in course_enrollments:
|
||||
assert course_enrollment.mode == course_keys_to_mode[course_enrollment.course.id]
|
||||
|
||||
@patch('lms.djangoapps.program_enrollments.api.linking.CourseMode.modes_for_course_dict')
|
||||
def test_update_linking_enrollment_to_another_user(self, mock_modes_for_course_dict):
|
||||
"""
|
||||
Test that when link_program_enrollments is called with a program and an external_user_key,
|
||||
user pair and that program is already linked to a different user with the same external_user_key
|
||||
that the original user's link is removed and replaced by a link with the new user.
|
||||
"""
|
||||
program_enrollment = self._create_waiting_enrollment(self.program, '0001')
|
||||
|
||||
self._create_waiting_course_enrollment(program_enrollment, self.fruit_course)
|
||||
self._create_waiting_course_enrollment(program_enrollment, self.animal_course)
|
||||
|
||||
# in order to test what happens to a learner's enrollment in a course without an audit mode
|
||||
# (e.g. Master's only course), we need to mock out the course modes that exist for our courses;
|
||||
# doing it this way helps to avoid needing to use the modulestore when using the CourseModeFactory
|
||||
def mocked_modes_for_course_dict(course_key):
|
||||
if course_key == self.animal_course:
|
||||
return {'masters': 'masters'}
|
||||
else:
|
||||
return {'audit': 'audit'}
|
||||
|
||||
mock_modes_for_course_dict.side_effect = mocked_modes_for_course_dict
|
||||
|
||||
# do the initial link of user_1 to the program enrollment
|
||||
link_program_enrollments(self.program, {'0001': self.user_1.username})
|
||||
|
||||
self._assert_program_enrollment(self.user_1, self.program, '0001', refresh=False)
|
||||
self._assert_no_program_enrollment(self.user_2, self.program, refresh=False)
|
||||
|
||||
# grab the user's original course enrollment before the link between the program
|
||||
# and the course enrollments is severed
|
||||
course_enrollments_for_user_1 = [pce.course_enrollment
|
||||
for pce
|
||||
in program_enrollment.program_course_enrollments.all()]
|
||||
|
||||
errors = link_program_enrollments(
|
||||
self.program,
|
||||
{
|
||||
'0001': self.user_2.username,
|
||||
}
|
||||
)
|
||||
|
||||
assert errors == {}
|
||||
self._assert_program_enrollment(self.user_2, self.program, '0001')
|
||||
self._assert_no_program_enrollment(self.user_1, self.program)
|
||||
# assert that all of user_1's course enrollments as part of the program
|
||||
# are inactive
|
||||
for course_enrollment in course_enrollments_for_user_1:
|
||||
course_enrollment.refresh_from_db()
|
||||
assert not course_enrollment.is_active
|
||||
|
||||
# assert that user_1's course enrollments are in the expected mode
|
||||
# after unlinking
|
||||
course_keys_to_mode = {
|
||||
self.fruit_course: 'audit',
|
||||
self.animal_course: 'masters',
|
||||
}
|
||||
self._assert_course_enrollments_in_mode(course_enrollments_for_user_1, course_keys_to_mode)
|
||||
|
||||
# assert that user_2 has been successfully linked to the program
|
||||
self._assert_program_enrollment(self.user_2, self.program, '0001')
|
||||
self._assert_user_enrolled_in_program_courses(self.user_2, self.program, self.fruit_course, self.animal_course)
|
||||
|
||||
|
||||
class TestLinkProgramEnrollmentsErrors(TestLinkProgramEnrollmentsMixin, TestCase):
|
||||
""" Tests for linking error behavior """
|
||||
@@ -285,7 +366,7 @@ class TestLinkProgramEnrollmentsErrors(TestLinkProgramEnrollmentsMixin, TestCase
|
||||
)
|
||||
expected_error_msg = NO_PROGRAM_ENROLLMENT_TEMPLATE.format(
|
||||
program_uuid=self.program,
|
||||
external_student_key='0002'
|
||||
external_user_key='0002'
|
||||
)
|
||||
logger.check_present((LOG_PATH, 'WARNING', expected_error_msg))
|
||||
|
||||
@@ -337,34 +418,6 @@ class TestLinkProgramEnrollmentsErrors(TestLinkProgramEnrollmentsMixin, TestCase
|
||||
self._assert_program_enrollment(self.user_1, self.program, '0001')
|
||||
self._assert_program_enrollment(self.user_2, self.program, '0002')
|
||||
|
||||
def test_enrollment_already_linked_to_different_user(self):
|
||||
self._create_waiting_enrollment(self.program, '0001')
|
||||
enrollment = ProgramEnrollmentFactory.create(
|
||||
program_uuid=self.program,
|
||||
external_user_key='0003',
|
||||
)
|
||||
user_3 = enrollment.user
|
||||
|
||||
self._assert_no_program_enrollment(self.user_1, self.program, refresh=False)
|
||||
self._assert_no_program_enrollment(self.user_2, self.program, refresh=False)
|
||||
self._assert_program_enrollment(user_3, self.program, '0003', refresh=False)
|
||||
|
||||
with LogCapture() as logger:
|
||||
errors = link_program_enrollments(
|
||||
self.program,
|
||||
{
|
||||
'0001': self.user_1.username,
|
||||
'0003': self.user_2.username,
|
||||
}
|
||||
)
|
||||
expected_error_msg = _user_already_linked_message(enrollment, self.user_2)
|
||||
logger.check_present((LOG_PATH, 'WARNING', expected_error_msg))
|
||||
|
||||
self.assertDictEqual(errors, {'0003': expected_error_msg})
|
||||
self._assert_program_enrollment(self.user_1, self.program, '0001')
|
||||
self._assert_no_program_enrollment(self.user_2, self.program)
|
||||
self._assert_program_enrollment(user_3, self.program, '0003')
|
||||
|
||||
def test_error_enrolling_in_course(self):
|
||||
nonexistant_course = CourseKey.from_string('course-v1:edX+Zilch+Bupkis')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user