* MST-682 Add external_user_key to the student profile CSV This is a request from some Masters school partners. They would like to download the student enrolled list with the Masters external_user_key data referenced. This way, the schools can properly match the students enrolled in the course with the students enrolled through Masters enrollment system
748 lines
28 KiB
Python
748 lines
28 KiB
Python
"""
|
|
Tests for program enrollment reading Python API.
|
|
"""
|
|
|
|
|
|
from uuid import UUID
|
|
import pytest
|
|
import ddt
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.cache import cache
|
|
from django.test import TestCase
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from organizations.tests.factories import OrganizationFactory
|
|
from social_django.models import UserSocialAuth
|
|
|
|
from common.djangoapps.course_modes.models import CourseMode
|
|
from common.djangoapps.student.roles import CourseStaffRole
|
|
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
|
from common.djangoapps.third_party_auth.tests.factories import SAMLProviderConfigFactory
|
|
from lms.djangoapps.program_enrollments.constants import ProgramCourseEnrollmentStatuses as PCEStatuses
|
|
from lms.djangoapps.program_enrollments.constants import ProgramEnrollmentStatuses as PEStatuses
|
|
from lms.djangoapps.program_enrollments.exceptions import (
|
|
OrganizationDoesNotExistException,
|
|
ProgramDoesNotExistException,
|
|
ProviderConfigurationException,
|
|
ProviderDoesNotExistException
|
|
)
|
|
from lms.djangoapps.program_enrollments.models import ProgramCourseEnrollment, ProgramEnrollment
|
|
from lms.djangoapps.program_enrollments.tests.factories import (
|
|
CourseAccessRoleAssignmentFactory,
|
|
ProgramCourseEnrollmentFactory,
|
|
ProgramEnrollmentFactory
|
|
)
|
|
from openedx.core.djangoapps.catalog.cache import PROGRAM_CACHE_KEY_TPL
|
|
from openedx.core.djangoapps.catalog.tests.factories import OrganizationFactory as CatalogOrganizationFactory
|
|
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory
|
|
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
|
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
|
|
|
from ..reading import (
|
|
fetch_program_course_enrollments,
|
|
fetch_program_course_enrollments_by_students,
|
|
fetch_program_enrollments,
|
|
fetch_program_enrollments_by_student,
|
|
fetch_program_enrollments_by_students,
|
|
get_external_key_by_user_and_course,
|
|
get_program_course_enrollment,
|
|
get_program_enrollment,
|
|
get_users_by_external_keys,
|
|
is_course_staff_enrollment
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
@ddt.ddt
|
|
class ProgramEnrollmentReadingTests(TestCase):
|
|
"""
|
|
Tests for program enrollment reading functions.
|
|
"""
|
|
program_uuid_x = UUID('dddddddd-5f48-493d-9410-84e1d36c657f')
|
|
program_uuid_y = UUID('eeeeeeee-f803-43f6-bbf3-5ae15d393649')
|
|
program_uuid_z = UUID('ffffffff-89eb-43df-a6b9-c144e7204fd7') # No enrollments
|
|
curriculum_uuid_a = UUID('aaaaaaaa-bd26-43d0-94b8-b0063858210b')
|
|
curriculum_uuid_b = UUID('bbbbbbbb-145f-43db-ad05-f9ad65eec285')
|
|
curriculum_uuid_c = UUID('cccccccc-4577-4559-85f0-4a83e8160a4d')
|
|
course_key_p = CourseKey.from_string('course-v1:TestX+ProEnroll+P')
|
|
course_key_q = CourseKey.from_string('course-v1:TestX+ProEnroll+Q')
|
|
course_key_r = CourseKey.from_string('course-v1:TestX+ProEnroll+R')
|
|
username_0 = 'user-0'
|
|
username_1 = 'user-1'
|
|
username_2 = 'user-2'
|
|
username_3 = 'user-3'
|
|
username_4 = 'user-4'
|
|
ext_3 = 'student-3'
|
|
ext_4 = 'student-4'
|
|
ext_5 = 'student-5'
|
|
ext_6 = 'student-6'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
super().setUpTestData()
|
|
cls.user_0 = UserFactory(username=cls.username_0) # No enrollments
|
|
cls.user_1 = UserFactory(username=cls.username_1)
|
|
cls.user_2 = UserFactory(username=cls.username_2)
|
|
cls.user_3 = UserFactory(username=cls.username_3)
|
|
cls.user_4 = UserFactory(username=cls.username_4)
|
|
CourseOverviewFactory(id=cls.course_key_p)
|
|
CourseOverviewFactory(id=cls.course_key_q)
|
|
CourseOverviewFactory(id=cls.course_key_r)
|
|
enrollment_test_data = [ # ID
|
|
(cls.user_1, None, cls.program_uuid_x, cls.curriculum_uuid_a, PEStatuses.ENROLLED), # 1
|
|
(cls.user_2, None, cls.program_uuid_x, cls.curriculum_uuid_a, PEStatuses.PENDING), # 2
|
|
(cls.user_3, cls.ext_3, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.ENROLLED), # 3
|
|
(cls.user_4, cls.ext_4, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.PENDING), # 4
|
|
(None, cls.ext_5, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.SUSPENDED), # 5
|
|
(None, cls.ext_6, cls.program_uuid_y, cls.curriculum_uuid_c, PEStatuses.CANCELED), # 6
|
|
(cls.user_3, cls.ext_3, cls.program_uuid_y, cls.curriculum_uuid_c, PEStatuses.CANCELED), # 7
|
|
(None, cls.ext_4, cls.program_uuid_y, cls.curriculum_uuid_c, PEStatuses.ENROLLED), # 8
|
|
(cls.user_1, None, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.SUSPENDED), # 9
|
|
(cls.user_2, None, cls.program_uuid_y, cls.curriculum_uuid_c, PEStatuses.ENDED), # 10
|
|
]
|
|
for user, external_user_key, program_uuid, curriculum_uuid, status in enrollment_test_data:
|
|
ProgramEnrollmentFactory(
|
|
user=user,
|
|
external_user_key=external_user_key,
|
|
program_uuid=program_uuid,
|
|
curriculum_uuid=curriculum_uuid,
|
|
status=status,
|
|
)
|
|
course_enrollment_test_data = [ # ID
|
|
(1, cls.course_key_p, PCEStatuses.ACTIVE), # 1
|
|
(1, cls.course_key_q, PCEStatuses.ACTIVE), # 2
|
|
(9, cls.course_key_r, PCEStatuses.ACTIVE), # 3
|
|
(2, cls.course_key_p, PCEStatuses.INACTIVE), # 4
|
|
(3, cls.course_key_p, PCEStatuses.ACTIVE), # 5
|
|
(5, cls.course_key_p, PCEStatuses.INACTIVE), # 6
|
|
(8, cls.course_key_p, PCEStatuses.ACTIVE), # 7
|
|
(8, cls.course_key_q, PCEStatuses.INACTIVE), # 8
|
|
(2, cls.course_key_r, PCEStatuses.INACTIVE), # 9
|
|
(6, cls.course_key_r, PCEStatuses.INACTIVE), # 10
|
|
(8, cls.course_key_r, PCEStatuses.ACTIVE), # 11
|
|
(7, cls.course_key_q, PCEStatuses.ACTIVE), # 12
|
|
|
|
]
|
|
for program_enrollment_id, course_key, status in course_enrollment_test_data:
|
|
program_enrollment = ProgramEnrollment.objects.get(id=program_enrollment_id)
|
|
course_enrollment = (
|
|
CourseEnrollmentFactory(
|
|
course_id=course_key,
|
|
user=program_enrollment.user,
|
|
mode=CourseMode.MASTERS,
|
|
)
|
|
if program_enrollment.user
|
|
else None
|
|
)
|
|
ProgramCourseEnrollmentFactory(
|
|
program_enrollment=program_enrollment,
|
|
course_enrollment=course_enrollment,
|
|
course_key=course_key,
|
|
status=status,
|
|
)
|
|
|
|
@ddt.data(
|
|
# Realized enrollment, specifying only user.
|
|
(program_uuid_x, curriculum_uuid_a, username_1, None, 1),
|
|
|
|
# Realized enrollment, specifiying both user and external key.
|
|
(program_uuid_x, curriculum_uuid_b, username_3, ext_3, 3),
|
|
|
|
# Realized enrollment, specifiying only external key.
|
|
(program_uuid_x, curriculum_uuid_b, None, ext_4, 4),
|
|
|
|
# Waiting enrollment, specifying external key
|
|
(program_uuid_x, curriculum_uuid_b, None, ext_5, 5),
|
|
|
|
# Specifying no curriculum (because ext_6 only has Program Y
|
|
# 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),
|
|
)
|
|
@ddt.unpack
|
|
def test_get_program_enrollment(
|
|
self,
|
|
program_uuid,
|
|
curriculum_uuid,
|
|
username,
|
|
external_user_key,
|
|
expected_enrollment_id,
|
|
):
|
|
user = User.objects.get(username=username) if username else None
|
|
actual_enrollment = get_program_enrollment(
|
|
program_uuid=program_uuid,
|
|
curriculum_uuid=curriculum_uuid,
|
|
user=user,
|
|
external_user_key=external_user_key,
|
|
)
|
|
assert actual_enrollment.id == expected_enrollment_id
|
|
|
|
@ddt.data(
|
|
# Realized enrollment, specifying only user.
|
|
(program_uuid_x, None, course_key_p, username_1, None, 1),
|
|
|
|
# Realized enrollment, specifiying both user and external key.
|
|
(program_uuid_x, None, course_key_p, username_3, ext_3, 5),
|
|
|
|
# Realized enrollment, specifiying only external key.
|
|
(program_uuid_y, None, course_key_p, None, ext_4, 7),
|
|
|
|
# Waiting enrollment, specifying external key
|
|
(program_uuid_x, None, course_key_p, None, ext_5, 6),
|
|
|
|
# We can specify curriculum, but it shouldn't affect anything,
|
|
# 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),
|
|
)
|
|
@ddt.unpack
|
|
def test_get_program_course_enrollment(
|
|
self,
|
|
program_uuid,
|
|
curriculum_uuid,
|
|
course_key,
|
|
username,
|
|
external_user_key,
|
|
expected_enrollment_id,
|
|
):
|
|
user = User.objects.get(username=username) if username else None
|
|
actual_enrollment = get_program_course_enrollment(
|
|
program_uuid=program_uuid,
|
|
curriculum_uuid=curriculum_uuid,
|
|
course_key=course_key,
|
|
user=user,
|
|
external_user_key=external_user_key,
|
|
)
|
|
assert actual_enrollment.id == expected_enrollment_id
|
|
|
|
@ddt.data(
|
|
|
|
# Program with no enrollments
|
|
(
|
|
{'program_uuid': program_uuid_z},
|
|
set(),
|
|
),
|
|
|
|
# Curriculum & status filters
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_x,
|
|
'curriculum_uuids': {curriculum_uuid_a, curriculum_uuid_c},
|
|
'program_enrollment_statuses': {PEStatuses.PENDING, PEStatuses.CANCELED},
|
|
},
|
|
{2},
|
|
),
|
|
|
|
# User & external key filters
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_x,
|
|
'usernames': {username_1, username_2, username_3, username_4},
|
|
'external_user_keys': {ext_3, ext_4, ext_5}
|
|
},
|
|
{3, 4},
|
|
),
|
|
|
|
# Realized-only filter
|
|
(
|
|
{'program_uuid': program_uuid_x, 'realized_only': True},
|
|
{1, 2, 3, 4, 9},
|
|
),
|
|
|
|
# Waiting-only filter
|
|
(
|
|
{'program_uuid': program_uuid_x, 'waiting_only': True},
|
|
{5},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_fetch_program_enrollments(self, kwargs, expected_enrollment_ids):
|
|
kwargs = self._usernames_to_users(kwargs)
|
|
actual_enrollments = fetch_program_enrollments(**kwargs)
|
|
actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
|
|
assert actual_enrollment_ids == expected_enrollment_ids
|
|
|
|
@ddt.data(
|
|
|
|
# Program with no enrollments
|
|
(
|
|
{'program_uuid': program_uuid_z, 'course_key': course_key_p},
|
|
set(),
|
|
),
|
|
|
|
# Curriculum, status, active-only filters
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_x,
|
|
'course_key': course_key_p,
|
|
'curriculum_uuids': {curriculum_uuid_a, curriculum_uuid_c},
|
|
'program_enrollment_statuses': {PEStatuses.ENROLLED},
|
|
'active_only': True,
|
|
},
|
|
{1},
|
|
),
|
|
|
|
# User and external key filters
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_x,
|
|
'course_key': course_key_p,
|
|
'usernames': {username_2, username_3},
|
|
'external_user_keys': {ext_3, ext_5}
|
|
},
|
|
{5},
|
|
),
|
|
|
|
# Realized-only filter
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_x,
|
|
'course_key': course_key_p,
|
|
'realized_only': True,
|
|
},
|
|
{1, 4, 5},
|
|
),
|
|
|
|
# Waiting-only and inactive-only filters
|
|
(
|
|
{
|
|
'program_uuid': program_uuid_y,
|
|
'course_key': course_key_r,
|
|
'waiting_only': True,
|
|
'inactive_only': True,
|
|
},
|
|
{10},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_fetch_program_course_enrollments(self, kwargs, expected_enrollment_ids):
|
|
kwargs = self._usernames_to_users(kwargs)
|
|
actual_enrollments = fetch_program_course_enrollments(**kwargs)
|
|
actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
|
|
assert actual_enrollment_ids == expected_enrollment_ids
|
|
|
|
@ddt.data(
|
|
|
|
# User with no enrollments
|
|
(
|
|
{'username': username_0},
|
|
set(),
|
|
),
|
|
|
|
# Filters
|
|
(
|
|
{
|
|
'username': username_3,
|
|
'external_user_key': ext_3,
|
|
'program_uuids': {program_uuid_x},
|
|
'curriculum_uuids': {curriculum_uuid_b, curriculum_uuid_c},
|
|
'program_enrollment_statuses': {PEStatuses.ENROLLED, PEStatuses.CANCELED},
|
|
},
|
|
{3},
|
|
),
|
|
|
|
# More filters
|
|
(
|
|
{
|
|
'username': username_3,
|
|
'external_user_key': ext_3,
|
|
'program_uuids': {program_uuid_x, program_uuid_y},
|
|
'curriculum_uuids': {curriculum_uuid_b, curriculum_uuid_c},
|
|
'program_enrollment_statuses': {PEStatuses.SUSPENDED, PEStatuses.CANCELED},
|
|
},
|
|
{7},
|
|
),
|
|
|
|
# Realized-only filter
|
|
(
|
|
{'external_user_key': ext_4, 'realized_only': True},
|
|
{4},
|
|
),
|
|
|
|
# Waiting-only filter
|
|
(
|
|
{'external_user_key': ext_4, 'waiting_only': True},
|
|
{8},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_fetch_program_enrollments_by_student(self, kwargs, expected_enrollment_ids):
|
|
kwargs = self._username_to_user(kwargs)
|
|
actual_enrollments = fetch_program_enrollments_by_student(**kwargs)
|
|
actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
|
|
assert actual_enrollment_ids == expected_enrollment_ids
|
|
|
|
@ddt.data(
|
|
|
|
# User with no enrollments
|
|
(
|
|
{'usernames': [username_0]},
|
|
set(),
|
|
),
|
|
|
|
# Filters
|
|
(
|
|
{
|
|
'usernames': [username_3],
|
|
},
|
|
{3, 7},
|
|
),
|
|
|
|
# More filters
|
|
(
|
|
{
|
|
'usernames': [username_3],
|
|
'external_user_keys': [ext_3],
|
|
'program_enrollment_statuses': {PEStatuses.SUSPENDED, PEStatuses.CANCELED},
|
|
},
|
|
{7},
|
|
),
|
|
|
|
# Realized-only filter
|
|
(
|
|
{'usernames': [username_4], 'realized_only': True},
|
|
{4},
|
|
),
|
|
|
|
# Waiting-only filter
|
|
(
|
|
{'external_user_keys': [ext_4], 'waiting_only': True},
|
|
{8},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_fetch_program_enrollments_by_students(self, kwargs, expected_enrollment_ids):
|
|
kwargs = self._usernames_to_users(kwargs)
|
|
actual_enrollments = fetch_program_enrollments_by_students(**kwargs)
|
|
actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
|
|
assert actual_enrollment_ids == expected_enrollment_ids
|
|
|
|
@ddt.data(
|
|
|
|
# User with no program enrollments
|
|
(
|
|
{'usernames': [username_0]},
|
|
set(),
|
|
),
|
|
|
|
# Course keys and active-only filters
|
|
(
|
|
{
|
|
'external_user_keys': [ext_4],
|
|
'course_keys': {course_key_p, course_key_q},
|
|
'active_only': True,
|
|
},
|
|
{7},
|
|
),
|
|
|
|
# Curriculum filter
|
|
(
|
|
{'usernames': [username_3], 'curriculum_uuids': {curriculum_uuid_b}},
|
|
{5},
|
|
),
|
|
|
|
# Program filter
|
|
(
|
|
{'usernames': [username_3], 'program_uuids': {program_uuid_y}},
|
|
{12},
|
|
),
|
|
|
|
# Realized-only filter
|
|
(
|
|
{'external_user_keys': [ext_4], 'realized_only': True},
|
|
set(),
|
|
),
|
|
|
|
# Waiting-only and inactive-only filter
|
|
(
|
|
{
|
|
'external_user_keys': [ext_4],
|
|
'waiting_only': True,
|
|
'inactive_only': True,
|
|
},
|
|
{8},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_fetch_program_course_enrollments_by_students(self, kwargs, expected_enrollment_ids):
|
|
kwargs = self._usernames_to_users(kwargs)
|
|
actual_enrollments = fetch_program_course_enrollments_by_students(**kwargs)
|
|
actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
|
|
assert actual_enrollment_ids == expected_enrollment_ids
|
|
|
|
@staticmethod
|
|
def _username_to_user(dictionary):
|
|
"""
|
|
We can't access the user instances when building `ddt.data`,
|
|
so return a dict with the username swapped out for the user themself.
|
|
"""
|
|
result = dictionary.copy()
|
|
if 'username' in result:
|
|
result['user'] = User.objects.get(username=result['username'])
|
|
del result['username']
|
|
return result
|
|
|
|
@staticmethod
|
|
def _usernames_to_users(dictionary):
|
|
"""
|
|
We can't access the user instances when building `ddt.data`,
|
|
so return a dict with the usernames swapped out for the users themselves.
|
|
"""
|
|
result = dictionary.copy()
|
|
if 'usernames' in result:
|
|
result['users'] = set(
|
|
User.objects.filter(username__in=result['usernames'])
|
|
)
|
|
del result['usernames']
|
|
return result
|
|
|
|
@ddt.data(
|
|
(
|
|
{'username': username_0, 'course_key': course_key_p},
|
|
None
|
|
),
|
|
(
|
|
{'username': username_1, 'course_key': course_key_p},
|
|
None
|
|
),
|
|
(
|
|
{'username': username_1, 'course_key': course_key_r},
|
|
None
|
|
),
|
|
(
|
|
{'username': username_2, 'course_key': course_key_p},
|
|
None
|
|
),
|
|
(
|
|
{'username': username_3, 'course_key': course_key_p},
|
|
ext_3
|
|
),
|
|
(
|
|
{'username': username_3, 'course_key': course_key_r},
|
|
None
|
|
),
|
|
(
|
|
{'username': username_4, 'course_key': course_key_p},
|
|
None
|
|
)
|
|
)
|
|
@ddt.unpack
|
|
def test_get_external_key_by_user_and_course(self, kwargs, expected_external_user_key):
|
|
kwarg = self._username_to_user(kwargs)
|
|
external_user_key = get_external_key_by_user_and_course(**kwarg)
|
|
assert expected_external_user_key == external_user_key
|
|
|
|
|
|
class GetUsersByExternalKeysTests(CacheIsolationTestCase):
|
|
"""
|
|
Tests for the get_users_by_external_keys function
|
|
"""
|
|
ENABLED_CACHES = ['default']
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
super().setUpTestData()
|
|
cls.program_uuid = UUID('e7a82f8d-d485-486b-b733-a28222af92bf')
|
|
cls.organization_key = 'ufo'
|
|
cls.external_user_id = '1234'
|
|
cls.user_0 = UserFactory(username='user-0')
|
|
cls.user_1 = UserFactory(username='user-1')
|
|
cls.user_2 = UserFactory(username='user-2')
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
catalog_org = CatalogOrganizationFactory.create(key=self.organization_key)
|
|
program = ProgramFactory.create(
|
|
uuid=self.program_uuid,
|
|
authoring_organizations=[catalog_org]
|
|
)
|
|
cache.set(PROGRAM_CACHE_KEY_TPL.format(uuid=self.program_uuid), program, None)
|
|
|
|
def create_social_auth_entry(self, user, provider, external_id):
|
|
"""
|
|
helper functio to create a user social auth entry
|
|
"""
|
|
UserSocialAuth.objects.create(
|
|
user=user,
|
|
uid=f'{provider.slug}:{external_id}',
|
|
provider=provider.backend_name,
|
|
)
|
|
|
|
def test_happy_path(self):
|
|
"""
|
|
Test that get_users_by_external_keys returns the expected
|
|
mapping of external keys to users.
|
|
"""
|
|
organization = OrganizationFactory.create(short_name=self.organization_key)
|
|
provider = SAMLProviderConfigFactory.create(organization=organization)
|
|
self.create_social_auth_entry(self.user_0, provider, 'ext-user-0')
|
|
self.create_social_auth_entry(self.user_1, provider, 'ext-user-1')
|
|
self.create_social_auth_entry(self.user_2, provider, 'ext-user-2')
|
|
requested_keys = {'ext-user-1', 'ext-user-2', 'ext-user-3'}
|
|
actual = get_users_by_external_keys(self.program_uuid, requested_keys)
|
|
# ext-user-0 not requested, ext-user-3 doesn't exist
|
|
expected = {
|
|
'ext-user-1': self.user_1,
|
|
'ext-user-2': self.user_2,
|
|
'ext-user-3': None,
|
|
}
|
|
assert actual == expected
|
|
|
|
def test_empty_request(self):
|
|
"""
|
|
Test that requesting no external keys does not cause an exception.
|
|
"""
|
|
organization = OrganizationFactory.create(short_name=self.organization_key)
|
|
SAMLProviderConfigFactory.create(organization=organization)
|
|
actual = get_users_by_external_keys(self.program_uuid, set())
|
|
assert actual == {}
|
|
|
|
def test_catalog_program_does_not_exist(self):
|
|
"""
|
|
Test ProgramDoesNotExistException is thrown if the program cache does
|
|
not include the requested program uuid.
|
|
"""
|
|
fake_program_uuid = UUID('80cc59e5-003e-4664-a582-48da44bc7e12')
|
|
with pytest.raises(ProgramDoesNotExistException):
|
|
get_users_by_external_keys(fake_program_uuid, [])
|
|
|
|
def test_catalog_program_missing_org(self):
|
|
"""
|
|
Test OrganizationDoesNotExistException is thrown if the cached program does not
|
|
have an authoring organization.
|
|
"""
|
|
program = ProgramFactory.create(
|
|
uuid=self.program_uuid,
|
|
authoring_organizations=[]
|
|
)
|
|
cache.set(PROGRAM_CACHE_KEY_TPL.format(uuid=self.program_uuid), program, None)
|
|
with pytest.raises(OrganizationDoesNotExistException):
|
|
get_users_by_external_keys(self.program_uuid, [])
|
|
|
|
def test_lms_organization_not_found(self):
|
|
"""
|
|
Test an OrganizationDoesNotExistException is thrown if the LMS has no organization
|
|
matching the catalog program's authoring_organization
|
|
"""
|
|
organization = OrganizationFactory.create(short_name='some_other_org')
|
|
SAMLProviderConfigFactory.create(organization=organization)
|
|
with pytest.raises(OrganizationDoesNotExistException):
|
|
get_users_by_external_keys(self.program_uuid, [])
|
|
|
|
def test_saml_provider_not_found(self):
|
|
"""
|
|
Test that Prov exception is thrown if no SAML provider exists for this
|
|
program's organization.
|
|
"""
|
|
OrganizationFactory.create(short_name=self.organization_key)
|
|
with pytest.raises(ProviderDoesNotExistException):
|
|
get_users_by_external_keys(self.program_uuid, [])
|
|
|
|
def test_extra_saml_provider_disabled(self):
|
|
"""
|
|
If multiple samlprovider records exist with the same organization,
|
|
but the extra record is disabled, no exception is raised.
|
|
"""
|
|
organization = OrganizationFactory.create(short_name=self.organization_key)
|
|
SAMLProviderConfigFactory.create(organization=organization)
|
|
# create a second active config for the same organization, NOT enabled
|
|
SAMLProviderConfigFactory.create(
|
|
organization=organization, slug='foox', enabled=False
|
|
)
|
|
get_users_by_external_keys(self.program_uuid, [])
|
|
|
|
def test_extra_saml_provider_enabled(self):
|
|
"""
|
|
If multiple enabled samlprovider records exist with the same organization
|
|
an exception is raised.
|
|
"""
|
|
organization = OrganizationFactory.create(short_name=self.organization_key)
|
|
SAMLProviderConfigFactory.create(organization=organization)
|
|
# create a second active config for the same organizationm, IS enabled
|
|
SAMLProviderConfigFactory.create(
|
|
organization=organization, slug='foox', enabled=True
|
|
)
|
|
with pytest.raises(ProviderConfigurationException):
|
|
get_users_by_external_keys(self.program_uuid, [])
|
|
|
|
|
|
@ddt.ddt
|
|
class IsCourseStaffEnrollmentTest(TestCase):
|
|
"""
|
|
Tests for the is_course_staff_enrollment function
|
|
"""
|
|
program_uuid_x = UUID('dddddddd-5f48-493d-9410-84e1d36c657f')
|
|
program_uuid_y = UUID('eeeeeeee-f803-43f6-bbf3-5ae15d393649')
|
|
curriculum_uuid_a = UUID('aaaaaaaa-bd26-43d0-94b8-b0063858210b')
|
|
curriculum_uuid_b = UUID('bbbbbbbb-145f-43db-ad05-f9ad65eec285')
|
|
course_key_p = CourseKey.from_string('course-v1:TestX+ProEnroll+P')
|
|
course_key_q = CourseKey.from_string('course-v1:TestX+ProEnroll+Q')
|
|
username_0 = 'user-0'
|
|
ext_3 = 'student-3'
|
|
ext_4 = 'student-4'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
super().setUpTestData()
|
|
cls.user_0 = UserFactory(username=cls.username_0) # No enrollments
|
|
CourseOverviewFactory(id=cls.course_key_p)
|
|
CourseOverviewFactory(id=cls.course_key_q)
|
|
enrollment_test_data = [ # ID
|
|
(cls.user_0, None, cls.program_uuid_x, cls.curriculum_uuid_a, PEStatuses.ENROLLED), # 1
|
|
(None, cls.ext_3, cls.program_uuid_x, cls.curriculum_uuid_b, PEStatuses.PENDING), # 2
|
|
(None, cls.ext_4, cls.program_uuid_y, cls.curriculum_uuid_a, PEStatuses.ENROLLED), # 3
|
|
(cls.user_0, None, cls.program_uuid_y, cls.curriculum_uuid_b, PEStatuses.SUSPENDED), # 4
|
|
]
|
|
for user, external_user_key, program_uuid, curriculum_uuid, status in enrollment_test_data:
|
|
ProgramEnrollmentFactory(
|
|
user=user,
|
|
external_user_key=external_user_key,
|
|
program_uuid=program_uuid,
|
|
curriculum_uuid=curriculum_uuid,
|
|
status=status,
|
|
)
|
|
course_enrollment_test_data = [ # ID
|
|
(1, cls.course_key_p, PCEStatuses.ACTIVE, True), # 1
|
|
(2, cls.course_key_q, PCEStatuses.ACTIVE, False), # 2
|
|
(3, cls.course_key_p, PCEStatuses.ACTIVE, True), # 3
|
|
(4, cls.course_key_q, PCEStatuses.ACTIVE, False), # 4
|
|
]
|
|
for program_enrollment_id, course_key, status, course_staff in course_enrollment_test_data:
|
|
program_enrollment = ProgramEnrollment.objects.get(id=program_enrollment_id)
|
|
course_enrollment = (
|
|
CourseEnrollmentFactory(
|
|
course_id=course_key,
|
|
user=program_enrollment.user,
|
|
mode=CourseMode.MASTERS,
|
|
)
|
|
if program_enrollment.user
|
|
else None
|
|
)
|
|
|
|
program_course_enrollment = ProgramCourseEnrollmentFactory(
|
|
program_enrollment=program_enrollment,
|
|
course_enrollment=course_enrollment,
|
|
course_key=course_key,
|
|
status=status,
|
|
)
|
|
if course_staff:
|
|
if program_enrollment.user:
|
|
CourseStaffRole(course_key).add_users(program_enrollment.user)
|
|
else:
|
|
CourseAccessRoleAssignmentFactory(
|
|
enrollment=program_course_enrollment
|
|
)
|
|
|
|
@ddt.data(
|
|
(1, True),
|
|
(2, False),
|
|
(3, True),
|
|
(4, False),
|
|
)
|
|
@ddt.unpack
|
|
def test_is_course_staff_enrollment(self, program_course_enrollment_id, is_course_staff):
|
|
program_course_enrollment = ProgramCourseEnrollment.objects.get(
|
|
id=program_course_enrollment_id
|
|
)
|
|
assert is_course_staff == is_course_staff_enrollment(program_course_enrollment)
|