From 313afd70ae9b7243cc55eaaf06165608ae088841 Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Tue, 21 Dec 2021 09:20:16 -0500 Subject: [PATCH] fix: update program enrollments to be case insensitive on external_user_key (#29646) UTAsutin is an example partner who would use mixed casing on their external_user_key references for program enrollment upload and matriculation. Update the system to be case insensitive on external_user_key Co-authored-by: Simon Chen --- .../program_enrollments/api/linking.py | 5 +- .../program_enrollments/api/reading.py | 25 ++++- .../api/tests/test_linking.py | 45 ++++++-- .../api/tests/test_reading.py | 45 +++++++- .../api/tests/test_writing.py | 106 ++++++++++++++++-- .../program_enrollments/api/writing.py | 21 ++-- .../program_enrollments/tests/test_signals.py | 15 ++- .../program_enrollments/tests/test_tasks.py | 1 + 8 files changed, 220 insertions(+), 43 deletions(-) diff --git a/lms/djangoapps/program_enrollments/api/linking.py b/lms/djangoapps/program_enrollments/api/linking.py index fd3fdd6feb..e4ef0ae66c 100644 --- a/lms/djangoapps/program_enrollments/api/linking.py +++ b/lms/djangoapps/program_enrollments/api/linking.py @@ -11,6 +11,7 @@ import logging from django.contrib.auth import get_user_model from django.db import IntegrityError, transaction +from requests.structures import CaseInsensitiveDict from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.api import get_access_role_by_role_name @@ -158,10 +159,10 @@ def _get_program_enrollments_by_ext_key(program_uuid, external_user_keys): ).prefetch_related( 'program_course_enrollments' ).select_related('user') - return { + return CaseInsensitiveDict({ program_enrollment.external_user_key: program_enrollment for program_enrollment in program_enrollments - } + }) def _get_lms_users(lms_usernames): diff --git a/lms/djangoapps/program_enrollments/api/reading.py b/lms/djangoapps/program_enrollments/api/reading.py index 39442ce6e0..2b2984e870 100644 --- a/lms/djangoapps/program_enrollments/api/reading.py +++ b/lms/djangoapps/program_enrollments/api/reading.py @@ -5,6 +5,7 @@ Outside of this subpackage, import these functions from `lms.djangoapps.program_enrollments.api`. """ +import re from functools import reduce from operator import or_ @@ -62,7 +63,7 @@ def get_program_enrollment( raise ValueError(_STUDENT_ARG_ERROR_MESSAGE) filters = { "user": user, - "external_user_key": external_user_key, + "external_user_key__iexact": external_user_key, "curriculum_uuid": curriculum_uuid, } return ProgramEnrollment.objects.get( @@ -100,7 +101,7 @@ def get_program_course_enrollment( raise ValueError(_STUDENT_ARG_ERROR_MESSAGE) filters = { "program_enrollment__user": user, - "program_enrollment__external_user_key": external_user_key, + "program_enrollment__external_user_key__iexact": external_user_key, "program_enrollment__curriculum_uuid": curriculum_uuid, } return ProgramCourseEnrollment.objects.get( @@ -142,10 +143,13 @@ def fetch_program_enrollments( raise ValueError( _REALIZED_FILTER_ERROR_TEMPLATE.format("realized_only", "waiting_only") ) + external_user_key_regex = None + if external_user_keys: + external_user_key_regex = r'^(' + '|'.join([re.escape(b) for b in external_user_keys]) + ')$' filters = { "curriculum_uuid__in": curriculum_uuids, "user__in": users, - "external_user_key__in": external_user_keys, + "external_user_key__iregex": external_user_key_regex, "status__in": program_enrollment_statuses, } if realized_only: @@ -202,10 +206,13 @@ def fetch_program_course_enrollments( raise ValueError( _REALIZED_FILTER_ERROR_TEMPLATE.format("realized_only", "waiting_only") ) + external_user_key_regex = None + if external_user_keys: + external_user_key_regex = r'^(' + '|'.join([re.escape(b) for b in external_user_keys]) + ')$' filters = { "program_enrollment__curriculum_uuid__in": curriculum_uuids, "program_enrollment__user__in": users, - "program_enrollment__external_user_key__in": external_user_keys, + "program_enrollment__external_user_key__iregex": external_user_key_regex, "program_enrollment__status__in": program_enrollment_statuses, "program_enrollment__in": program_enrollments, } @@ -301,9 +308,12 @@ def fetch_program_enrollments_by_students( raise ValueError( _REALIZED_FILTER_ERROR_TEMPLATE.format("realized_only", "waiting_only") ) + external_user_key_regex = None + if external_user_keys: + external_user_key_regex = r'^(' + '|'.join([re.escape(b) for b in external_user_keys]) + ')$' filters = { "user__in": users, - "external_user_key__in": external_user_keys, + "external_user_key__iregex": external_user_key_regex, "status__in": program_enrollment_statuses, } if realized_only: @@ -357,9 +367,12 @@ def fetch_program_course_enrollments_by_students( raise ValueError( _REALIZED_FILTER_ERROR_TEMPLATE.format("realized_only", "waiting_only") ) + external_user_key_regex = None + if external_user_keys: + external_user_key_regex = r'^(' + '|'.join([re.escape(b) for b in external_user_keys]) + ')$' filters = { "program_enrollment__user__in": users, - "program_enrollment__external_user_key__in": external_user_keys, + "program_enrollment__external_user_key__iregex": external_user_key_regex, "program_enrollment__program_uuid__in": program_uuids, "program_enrollment__curriculum_uuid__in": curriculum_uuids, "course_key__in": course_keys, diff --git a/lms/djangoapps/program_enrollments/api/tests/test_linking.py b/lms/djangoapps/program_enrollments/api/tests/test_linking.py index 3f6762a909..b60cbad7d1 100644 --- a/lms/djangoapps/program_enrollments/api/tests/test_linking.py +++ b/lms/djangoapps/program_enrollments/api/tests/test_linking.py @@ -2,10 +2,10 @@ Tests for account linking Python API. """ - from unittest.mock import patch from uuid import uuid4 +import ddt from django.test import TestCase from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey @@ -98,7 +98,7 @@ class TestLinkProgramEnrollmentsMixin: if refresh: user.refresh_from_db() enrollment = user.programenrollment_set.get( - program_uuid=program_uuid, external_user_key=external_user_key + program_uuid=program_uuid, external_user_key__iexact=external_user_key ) assert enrollment is not None @@ -128,6 +128,7 @@ class TestLinkProgramEnrollmentsMixin: ) +@ddt.ddt class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): """ Tests for linking behavior """ @@ -153,6 +154,22 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): self._assert_no_user(another_program_enrollment) + def test_link_mixed_case_external_user_key(self): + """ + Test that when linking the program enrollment with same external user key, + but the casing on external_user_key is mixed, the linking is still successful + """ + program_enrollment = self._create_waiting_enrollment(self.program, 'student-43') + self._create_waiting_course_enrollment(program_enrollment, self.fruit_course) + self._create_waiting_course_enrollment(program_enrollment, self.animal_course) + + link_program_enrollments(self.program, {'STUDEnt-43': self.user_1.username}) + + self._assert_program_enrollment(self.user_1, self.program, 'STUDEnt-43') + self._assert_user_enrolled_in_program_courses( + self.user_1, self.program, self.fruit_course, self.animal_course + ) + def test_inactive_waiting_course_enrollment(self): """ Test that when a waiting program enrollment has waiting program course enrollments with a @@ -272,14 +289,24 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): for course_enrollment in course_enrollments: assert course_enrollment.mode == course_keys_to_mode[course_enrollment.course.id] + @ddt.data( + ('001', '001'), + ('learner-2', 'LEArneR-2'), + ) + @ddt.unpack @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): + def test_update_linking_enrollment_to_another_user( + self, + linked_external_key, + updated_external_key, + 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') + program_enrollment = self._create_waiting_enrollment(self.program, linked_external_key) self._create_waiting_course_enrollment(program_enrollment, self.fruit_course) self._create_waiting_course_enrollment(program_enrollment, self.animal_course) @@ -296,9 +323,9 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): 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}) + link_program_enrollments(self.program, {linked_external_key: self.user_1.username}) - self._assert_program_enrollment(self.user_1, self.program, '0001', refresh=False) + self._assert_program_enrollment(self.user_1, self.program, linked_external_key, 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 @@ -310,12 +337,12 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): errors = link_program_enrollments( self.program, { - '0001': self.user_2.username, + updated_external_key: self.user_2.username, } ) assert not errors - self._assert_program_enrollment(self.user_2, self.program, '0001') + self._assert_program_enrollment(self.user_2, self.program, updated_external_key) 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 @@ -332,7 +359,7 @@ class TestLinkProgramEnrollments(TestLinkProgramEnrollmentsMixin, TestCase): 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_program_enrollment(self.user_2, self.program, updated_external_key) self._assert_user_enrolled_in_program_courses(self.user_2, self.program, self.fruit_course, self.animal_course) diff --git a/lms/djangoapps/program_enrollments/api/tests/test_reading.py b/lms/djangoapps/program_enrollments/api/tests/test_reading.py index 4bb5a7669f..f1fcbe87a1 100644 --- a/lms/djangoapps/program_enrollments/api/tests/test_reading.py +++ b/lms/djangoapps/program_enrollments/api/tests/test_reading.py @@ -4,8 +4,9 @@ Tests for program enrollment reading Python API. from uuid import UUID -import pytest + import ddt +import pytest from django.contrib.auth import get_user_model from django.core.cache import cache from django.test import TestCase @@ -157,6 +158,10 @@ class ProgramEnrollmentReadingTests(TestCase): # enrollments in one curriculum, so it's not ambiguous). (program_uuid_y, None, None, ext_6, 6), (program_uuid_y, None, username_2, None, 10), + # use mixed casing for external_user_id + (program_uuid_x, curriculum_uuid_b, None, 'STUDENT-4', 4), + (program_uuid_x, curriculum_uuid_b, None, 'STUDent-5', 5), + (program_uuid_y, None, None, 'STudENT-6', 6), ) @ddt.unpack def test_get_program_enrollment( @@ -193,6 +198,10 @@ class ProgramEnrollmentReadingTests(TestCase): # because each user-course pairing can only have one # program-course enrollment. (program_uuid_y, curriculum_uuid_c, course_key_r, None, ext_6, 10), + # Use mixed casing for external_user_key + (program_uuid_x, None, course_key_p, username_3, 'stuDENT-3', 5), + (program_uuid_y, None, course_key_p, None, 'STudenT-4', 7), + (program_uuid_x, None, course_key_p, None, 'STUDENT-5', 6), ) @ddt.unpack def test_get_program_course_enrollment( @@ -253,6 +262,15 @@ class ProgramEnrollmentReadingTests(TestCase): {'program_uuid': program_uuid_x, 'waiting_only': True}, {5}, ), + # Use mixed casing on external_user_key + ( + { + 'program_uuid': program_uuid_x, + 'usernames': {username_1, username_2, username_3, username_4}, + 'external_user_keys': {'studeNT-3', 'STUdent-4', 'STudenT-5'} + }, + {3, 4}, + ), ) @ddt.unpack def test_fetch_program_enrollments(self, kwargs, expected_enrollment_ids): @@ -312,6 +330,16 @@ class ProgramEnrollmentReadingTests(TestCase): }, {10}, ), + # Use mixed casing on external_user_key + ( + { + 'program_uuid': program_uuid_x, + 'course_key': course_key_p, + 'usernames': {username_2, username_3}, + 'external_user_keys': {'STudENt-3', 'stuDENt-5'} + }, + {5}, + ), ) @ddt.unpack def test_fetch_program_course_enrollments(self, kwargs, expected_enrollment_ids): @@ -363,6 +391,11 @@ class ProgramEnrollmentReadingTests(TestCase): {'external_user_key': ext_4, 'waiting_only': True}, {8}, ), + # Use mixed casing on external_user_key + ( + {'external_user_key': 'STudeNT-4', 'realized_only': True}, + {4}, + ), ) @ddt.unpack def test_fetch_program_enrollments_by_student(self, kwargs, expected_enrollment_ids): @@ -408,6 +441,11 @@ class ProgramEnrollmentReadingTests(TestCase): {'external_user_keys': [ext_4], 'waiting_only': True}, {8}, ), + # Use mixed casing on external_user_key + ( + {'external_user_keys': ['STUdenT-4'], 'waiting_only': True}, + {8}, + ), ) @ddt.unpack def test_fetch_program_enrollments_by_students(self, kwargs, expected_enrollment_ids): @@ -461,6 +499,11 @@ class ProgramEnrollmentReadingTests(TestCase): }, {8}, ), + # Use mixed casing on external_user_key + ( + {'external_user_keys': ['STUDENT-4'], 'realized_only': True}, + set(), + ), ) @ddt.unpack def test_fetch_program_course_enrollments_by_students(self, kwargs, expected_enrollment_ids): diff --git a/lms/djangoapps/program_enrollments/api/tests/test_writing.py b/lms/djangoapps/program_enrollments/api/tests/test_writing.py index f22315b7f3..bd722a1bb2 100644 --- a/lms/djangoapps/program_enrollments/api/tests/test_writing.py +++ b/lms/djangoapps/program_enrollments/api/tests/test_writing.py @@ -118,32 +118,44 @@ class EnrollmentTestMixin(CacheIsolationTestCase): return self.create_program_course_enrollment(program_enrollment, course_status=course_status) +@ddt.ddt class WritingProgramEnrollmentTest(EnrollmentTestMixin): """ Test cases for program enrollment writing functions. """ - def test_write_program_enrollments_status_ended(self): + @ddt.data( + ('learner-1', 'learner-1', PEStatuses.ENDED), + # Test mixing the external_user_key casing + ('learner-1', 'LEARNER-1', PEStatuses.ENROLLED), + ) + @ddt.unpack + def test_write_program_enrollments_status_ended( + self, + external_key_1, + external_key_2, + target_status + ): """ - Successfully updates program enrollment to status ended if requested. + Successfully updates program enrollment to status if requested. This also validates history records are created on both create and update. """ assert ProgramEnrollment.objects.count() == 0 assert ProgramEnrollment.historical_records.count() == 0 # pylint: disable=no-member write_program_enrollments(self.program_uuid, [{ - 'external_user_key': 'learner-1', + 'external_user_key': external_key_1, 'status': PEStatuses.PENDING, 'curriculum_uuid': self.curriculum_uuid_a, }], True, False) assert ProgramEnrollment.objects.count() == 1 assert ProgramEnrollment.historical_records.count() == 1 # pylint: disable=no-member - write_program_enrollments(self.program_uuid, [{ - 'external_user_key': 'learner-1', - 'status': PEStatuses.ENDED, + result = write_program_enrollments(self.program_uuid, [{ + 'external_user_key': external_key_2, + 'status': target_status, 'curriculum_uuid': self.curriculum_uuid_a, }], False, True) assert ProgramEnrollment.objects.count() == 1 assert ProgramEnrollment.historical_records.count() == 2 # pylint: disable=no-member - assert ProgramEnrollment.objects.filter(status=PEStatuses.ENDED).exists() + assert ProgramEnrollment.objects.filter(status=target_status).exists() @ddt.ddt @@ -166,7 +178,7 @@ class WriteProgramCourseEnrollmentTest(EnrollmentTestMixin): and potentially that a CourseEnrollment also exists """ enrollment = ProgramCourseEnrollment.objects.get( - program_enrollment__external_user_key=external_user_key, + program_enrollment__external_user_key__iexact=external_user_key, program_enrollment__program_uuid=self.program_uuid ) assert expected_status == enrollment.status @@ -306,6 +318,48 @@ class WriteProgramCourseEnrollmentTest(EnrollmentTestMixin): self.assert_program_course_enrollment('learner-5', CourseStatuses.ACTIVE, True) self.assert_program_course_enrollment('learner-6', CourseStatuses.ACTIVE, False) + def test_create_or_update_with_mixed_cased_external_user_key(self): + """ + Test writing enrollments with both create and update flags true. + However, this time, the external_user_keys are mixed cased. + Existing enrollments should be updated. If no matching enrollment is found, create one. + """ + # learners 1-4 are already enrolled in courses, 5-6 only have a program enrollment + self.setup_change_test_data([ + CourseStatuses.ACTIVE, CourseStatuses.ACTIVE, + CourseStatuses.ACTIVE, CourseStatuses.ACTIVE] + ) + self.create_program_enrollment('LEArneR-5') + self.create_program_enrollment('leARnER-6', user=None) + + course_enrollment_requests = [ + self.course_enrollment_request('leaRNER-1', CourseStatuses.INACTIVE), + self.course_enrollment_request('LEarner-2', CourseStatuses.ACTIVE), + self.course_enrollment_request('learner-5', CourseStatuses.ACTIVE), + self.course_enrollment_request('learner-6', CourseStatuses.ACTIVE), + ] + + result = write_program_course_enrollments( + self.program_uuid, + self.course_id, + course_enrollment_requests, + True, + True, + ) + self.assertDictEqual( + { + 'leaRNER-1': CourseStatuses.INACTIVE, + 'LEarner-2': CourseStatuses.ACTIVE, + 'learner-5': CourseStatuses.ACTIVE, + 'learner-6': CourseStatuses.ACTIVE, + }, + result, + ) + self.assert_program_course_enrollment('learner-1', CourseStatuses.INACTIVE, True) + self.assert_program_course_enrollment('learner-2', CourseStatuses.ACTIVE, True) + self.assert_program_course_enrollment('LEArneR-5', CourseStatuses.ACTIVE, True) + self.assert_program_course_enrollment('leARnER-6', CourseStatuses.ACTIVE, False) + def test_create_conflicting_enrollment(self): """ The program enrollments application already has a program_course_enrollment @@ -322,6 +376,22 @@ class WriteProgramCourseEnrollmentTest(EnrollmentTestMixin): ) self.assertDictEqual({'learner-1': CourseStatuses.CONFLICT}, result) + def test_create_conflicting_enrollment_mixed_case_external_user_key(self): + """ + The program enrollments application already has a program_course_enrollment + record for this user with a mixed cased external_user_key and course + """ + self.create_program_and_course_enrollments('learner-1') + course_enrollment_requests = [self.course_enrollment_request('LeArnER-1')] + result = write_program_course_enrollments( + self.program_uuid, + self.course_id, + course_enrollment_requests, + True, + False, + ) + self.assertDictEqual({'LeArnER-1': CourseStatuses.CONFLICT}, result) + def test_update_nonexistent_enrollment(self): self.create_program_enrollment('learner-1') result = write_program_course_enrollments( @@ -374,7 +444,13 @@ class WriteProgramCourseEnrollmentTest(EnrollmentTestMixin): ) self.assertDictEqual({'learner-1': CourseStatuses.NOT_IN_PROGRAM}, result) - def test_create_enrollments_and_assign_staff(self): + @ddt.data( + 'learner', + 'LEARNer', + 'learNER', + 'LEARNER', + ) + def test_create_enrollments_and_assign_staff(self, request_user_key_prefix): """ Successfully creates both waiting and linked program course enrollments with the course staff role. """ @@ -386,9 +462,15 @@ class WriteProgramCourseEnrollmentTest(EnrollmentTestMixin): self.create_program_enrollment('learner-3', user=self.student_2) course_enrollment_requests = [ - self.course_enrollment_request('learner-1', CourseStatuses.ACTIVE, True), - self.course_enrollment_request('learner-2', CourseStatuses.ACTIVE, True), - self.course_enrollment_request('learner-3', CourseStatuses.ACTIVE, True), + self.course_enrollment_request( + '{}-1'.format(request_user_key_prefix), CourseStatuses.ACTIVE, True + ), + self.course_enrollment_request( + '{}-2'.format(request_user_key_prefix), CourseStatuses.ACTIVE, True + ), + self.course_enrollment_request( + '{}-3'.format(request_user_key_prefix), CourseStatuses.ACTIVE, True + ), ] write_program_course_enrollments( self.program_uuid, diff --git a/lms/djangoapps/program_enrollments/api/writing.py b/lms/djangoapps/program_enrollments/api/writing.py index a9c1de4d78..837c319efe 100644 --- a/lms/djangoapps/program_enrollments/api/writing.py +++ b/lms/djangoapps/program_enrollments/api/writing.py @@ -8,6 +8,7 @@ from `lms.djangoapps.program_enrollments.api`. import logging +from requests.structures import CaseInsensitiveDict from simple_history.utils import bulk_create_with_history from common.djangoapps.course_modes.models import CourseMode @@ -61,7 +62,7 @@ def write_program_enrollments(program_uuid, enrollment_requests, create, update) existing_enrollments = fetch_program_enrollments( program_uuid=program_uuid, external_user_keys=external_keys ) - existing_enrollments_by_key = {key: None for key in external_keys} + existing_enrollments_by_key = CaseInsensitiveDict({key: None for key in external_keys}) existing_enrollments_by_key.update({ enrollment.external_user_key: enrollment for enrollment in existing_enrollments @@ -210,9 +211,9 @@ def write_program_course_enrollments( program_uuid=program_uuid, external_user_keys=processable_external_keys, ).prefetch_related('program_course_enrollments') - program_enrollments_by_key = { + program_enrollments_by_key = CaseInsensitiveDict({ enrollment.external_user_key: enrollment for enrollment in program_enrollments - } + }) # Fetch enrollments regardless of anchored Program Enrollments existing_course_enrollments = fetch_program_course_enrollments_by_students( @@ -239,7 +240,9 @@ def write_program_course_enrollments( program_enrollment__program_uuid=program_uuid ) - existing_course_enrollments_by_key = {key: None for key in processable_external_keys} + existing_course_enrollments_by_key = CaseInsensitiveDict({ + key: None for key in processable_external_keys + }) existing_course_enrollments_by_key.update({ enrollment.program_enrollment.external_user_key: enrollment for enrollment in existing_course_enrollments_of_program_enrollment @@ -252,7 +255,7 @@ def write_program_course_enrollments( enrollments_to_save = [] created_enrollments = [] updated_enrollments = [] - staff_assignments_by_user_key = {} + staff_assignments_by_user_key = CaseInsensitiveDict() for external_key, request in requests_by_key.items(): course_staff = request['course_staff'] status = request['status'] @@ -498,7 +501,7 @@ def _organize_requests_by_external_key(enrollment_requests): where requests_by_key is dict[str: dict] and duplicated_keys is set[str]. """ - requests_by_key = {} + requests_by_key = CaseInsensitiveDict() duplicated_keys = set() for request in enrollment_requests: key = request['external_user_key'] @@ -534,11 +537,11 @@ def _get_conflicting_active_course_enrollments( Returns: results (dict) with detected conflict entry, or empty dict. """ - conflicted_by_user_key = {} + conflicted_by_user_key = CaseInsensitiveDict() - requested_statuses_by_user_key = { + requested_statuses_by_user_key = CaseInsensitiveDict({ key: request.get('status') for key, request in requests_by_key.items() - } + }) for existing_enrollment in existing_course_enrollments: external_user_key = existing_enrollment.program_enrollment.external_user_key diff --git a/lms/djangoapps/program_enrollments/tests/test_signals.py b/lms/djangoapps/program_enrollments/tests/test_signals.py index 4e995b5d66..c7332bb062 100644 --- a/lms/djangoapps/program_enrollments/tests/test_signals.py +++ b/lms/djangoapps/program_enrollments/tests/test_signals.py @@ -5,6 +5,7 @@ Test signal handlers for program_enrollments from unittest import mock +import ddt import pytest from django.core.cache import cache from edx_django_utils.cache import RequestCache @@ -12,6 +13,7 @@ from opaque_keys.edx.keys import CourseKey from organizations.tests.factories import OrganizationFactory from social_django.models import UserSocialAuth from testfixtures import LogCapture +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollmentException @@ -27,7 +29,6 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_completed_retirement from openedx.core.djangolib.testing.utils import CacheIsolationTestCase -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order class ProgramEnrollmentRetireSignalTests(ModuleStoreTestCase): @@ -100,6 +101,7 @@ class ProgramEnrollmentRetireSignalTests(ModuleStoreTestCase): self.assert_enrollment_and_history_retired(enrollment) +@ddt.ddt class SocialAuthEnrollmentCompletionSignalTest(CacheIsolationTestCase): """ Test post-save handler on UserSocialAuth @@ -110,7 +112,7 @@ class SocialAuthEnrollmentCompletionSignalTest(CacheIsolationTestCase): def setUpClass(cls): super().setUpClass() - cls.external_id = '0000' + cls.external_id = 'learner1a' cls.provider_slug = 'uox' cls.course_keys = [ CourseKey.from_string('course-v1:edX+DemoX+Test_Course'), @@ -197,13 +199,18 @@ class SocialAuthEnrollmentCompletionSignalTest(CacheIsolationTestCase): for program_course_enrollment in program_course_enrollments: self._assert_program_course_enrollment(program_course_enrollment) - def test_waiting_course_enrollments_completed(self): + @ddt.data( + 'learner1a', + 'LEarnER1A', + 'LEARNER1A', + ) + def test_waiting_course_enrollments_completed(self, auth_user_key): program_enrollment = self._create_waiting_program_enrollment() program_course_enrollments = self._create_waiting_course_enrollments(program_enrollment) UserSocialAuth.objects.create( user=self.user, - uid=f'{self.provider_slug}:{self.external_id}' + uid=f'{self.provider_slug}:{auth_user_key}' ) self._assert_program_enrollment_user(program_enrollment, self.user) diff --git a/lms/djangoapps/program_enrollments/tests/test_tasks.py b/lms/djangoapps/program_enrollments/tests/test_tasks.py index b1b79aa52f..89c3527bfe 100644 --- a/lms/djangoapps/program_enrollments/tests/test_tasks.py +++ b/lms/djangoapps/program_enrollments/tests/test_tasks.py @@ -4,6 +4,7 @@ Unit tests for program_course_enrollments tasks from datetime import timedelta + import pytest from django.db.models.base import ObjectDoesNotExist from django.test import TestCase