* refactor: moved remaining feature dicts settings into top-level settings. * refactor: moved remaining feature dicts settings into top-level settings. * fix: fixed the test files * fix: fixed tehe pylint errors * fix: fixation of the cms ci failure * fix: fixed remaining feature settings for cms * fix: added fix for requirements * fix: added fix for lms tests * fix: resolved the test views issue * fix: configured views file and test_views * fix: fixed lint errors and assertion issues * fix: added fix for base url issue in test view * fix: added fix for base_url and assertion issue * fix: added configurations for base utl fix * fix: handled none issue for mfe config * fix: corrected override settings in test views * fix: added getattr defensive technique for view settings * fix: reverted views and test_views file * fix: added settings in views file * fix: added with patch within functions in test view * fix: rearranged the features in default_legacy_config * fix: fixing the tests with clearing cache * fix: reverted test views to verify the CI check * fix: added cache clear in mfe config test * fix: fixed the patch toggles to override settings * fix: fixed the lint errors * fix: changed patch toggle to override settings
596 lines
23 KiB
Python
596 lines
23 KiB
Python
"""Tests the course blocks and their functions"""
|
|
|
|
|
|
import itertools
|
|
import unittest
|
|
from datetime import datetime, timedelta
|
|
import sys
|
|
from unittest.mock import Mock, patch
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import ddt
|
|
from dateutil import parser
|
|
from django.conf import settings
|
|
from django.test import override_settings
|
|
from fs.memoryfs import MemoryFS
|
|
from opaque_keys.edx.keys import CourseKey
|
|
import pytest
|
|
from xblock.runtime import DictKeyValueStore, KvsFieldData
|
|
|
|
from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TEAM_SIZE
|
|
import xmodule.course_block
|
|
from xmodule.course_metadata_utils import DEFAULT_START_DATE
|
|
from xmodule.data import CertificatesDisplayBehaviors
|
|
from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime, XMLModuleStore
|
|
from xmodule.modulestore.exceptions import InvalidProctoringProvider
|
|
|
|
ORG = 'test_org'
|
|
COURSE = 'test_course'
|
|
|
|
NOW = datetime.strptime('2013-01-01T01:00:00', '%Y-%m-%dT%H:%M:00').replace(tzinfo=ZoneInfo("UTC"))
|
|
|
|
_TODAY = datetime.now(ZoneInfo("UTC"))
|
|
_LAST_WEEK = _TODAY - timedelta(days=7)
|
|
_NEXT_WEEK = _TODAY + timedelta(days=7)
|
|
|
|
|
|
@ddt.ddt()
|
|
class CourseFieldsTestCase(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
|
|
|
def test_default_start_date(self):
|
|
assert xmodule.course_block.CourseFields.start.default == DEFAULT_START_DATE
|
|
|
|
@ddt.data(True, False)
|
|
def test_default_enrollment_start_date(self, should_have_default_enroll_start):
|
|
with override_settings(CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE=should_have_default_enroll_start):
|
|
# reimport, so settings override could take effect
|
|
del sys.modules['xmodule.course_block']
|
|
import xmodule.course_block # lint-amnesty, pylint: disable=redefined-outer-name, reimported
|
|
expected = DEFAULT_START_DATE if should_have_default_enroll_start else None
|
|
assert xmodule.course_block.CourseFields.enrollment_start.default == expected
|
|
|
|
|
|
class DummyModuleStoreRuntime(XMLImportingModuleStoreRuntime): # pylint: disable=abstract-method
|
|
"""
|
|
Minimal modulestore runtime for tests.
|
|
"""
|
|
@patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS())
|
|
def __init__(self, load_error_blocks, course_id=None):
|
|
|
|
xmlstore = XMLModuleStore("data_dir", source_dirs=[],
|
|
load_error_blocks=load_error_blocks)
|
|
if course_id is None:
|
|
course_id = CourseKey.from_string('/'.join([ORG, COURSE, 'test_run']))
|
|
course_dir = "test_dir"
|
|
error_tracker = Mock()
|
|
|
|
super().__init__(
|
|
xmlstore=xmlstore,
|
|
course_id=course_id,
|
|
course_dir=course_dir,
|
|
error_tracker=error_tracker,
|
|
load_error_blocks=load_error_blocks,
|
|
services={'field-data': KvsFieldData(DictKeyValueStore())},
|
|
)
|
|
|
|
|
|
def get_dummy_course(
|
|
start,
|
|
announcement=None,
|
|
is_new=None,
|
|
advertised_start=None,
|
|
end=None,
|
|
certs='end',
|
|
):
|
|
"""Get a dummy course"""
|
|
|
|
system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
|
|
def to_attrb(n, v):
|
|
return '' if v is None else f'{n}="{v}"'.lower()
|
|
|
|
is_new = to_attrb('is_new', is_new)
|
|
announcement = to_attrb('announcement', announcement)
|
|
advertised_start = to_attrb('advertised_start', advertised_start)
|
|
end = to_attrb('end', end)
|
|
|
|
start_xml = '''
|
|
<course org="{org}" course="{course}" display_organization="{org}_display" display_coursenumber="{course}_display"
|
|
graceperiod="1 day" url_name="test"
|
|
start="{start}"
|
|
{announcement}
|
|
{is_new}
|
|
{advertised_start}
|
|
{end}
|
|
certificates_display_behavior="{certs}">
|
|
<chapter url="hi" url_name="ch" display_name="CH">
|
|
<html url_name="h" display_name="H">Two houses, ...</html>
|
|
</chapter>
|
|
</course>
|
|
'''.format(
|
|
org=ORG,
|
|
course=COURSE,
|
|
start=start,
|
|
is_new=is_new,
|
|
announcement=announcement,
|
|
advertised_start=advertised_start,
|
|
end=end,
|
|
certs=certs
|
|
)
|
|
|
|
return system.process_xml(start_xml)
|
|
|
|
|
|
class HasEndedMayCertifyTestCase(unittest.TestCase):
|
|
"""Double check the semantics around when to finalize courses."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
system = DummyModuleStoreRuntime(load_error_blocks=True) # lint-amnesty, pylint: disable=unused-variable
|
|
|
|
past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
|
|
future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
|
|
self.past_show_certs = get_dummy_course(
|
|
"2012-01-01T12:00",
|
|
end=past_end,
|
|
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
|
)
|
|
self.past_show_certs_no_info = get_dummy_course(
|
|
"2012-01-01T12:00",
|
|
end=past_end,
|
|
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
|
)
|
|
self.past_noshow_certs = get_dummy_course(
|
|
"2012-01-01T12:00",
|
|
end=past_end,
|
|
certs=CertificatesDisplayBehaviors.END
|
|
)
|
|
|
|
self.future_show_certs_no_info = get_dummy_course(
|
|
"2012-01-01T12:00",
|
|
end=future_end,
|
|
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
|
)
|
|
self.future_noshow_certs = get_dummy_course(
|
|
"2012-01-01T12:00",
|
|
end=future_end,
|
|
certs=CertificatesDisplayBehaviors.END
|
|
)
|
|
|
|
def test_has_ended(self):
|
|
"""Check that has_ended correctly tells us when a course is over."""
|
|
assert self.past_show_certs.has_ended()
|
|
assert self.past_show_certs_no_info.has_ended()
|
|
assert self.past_noshow_certs.has_ended()
|
|
assert not self.future_show_certs_no_info.has_ended()
|
|
assert not self.future_noshow_certs.has_ended()
|
|
|
|
|
|
class CourseSummaryHasEnded(unittest.TestCase):
|
|
""" Test for has_ended method when end date is missing timezone information. """
|
|
|
|
def test_course_end(self):
|
|
test_course = get_dummy_course("2012-01-01T12:00")
|
|
bad_end_date = parser.parse("2012-02-21 10:28:45")
|
|
summary = xmodule.course_block.CourseSummary(test_course.id, end=bad_end_date)
|
|
assert summary.has_ended()
|
|
|
|
|
|
@ddt.ddt
|
|
class IsNewCourseTestCase(unittest.TestCase):
|
|
"""Make sure the property is_new works on courses"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
# Needed for test_is_newish
|
|
datetime_patcher = patch.object(
|
|
xmodule.course_metadata_utils, 'datetime',
|
|
Mock(wraps=datetime)
|
|
)
|
|
mocked_datetime = datetime_patcher.start()
|
|
mocked_datetime.now.return_value = NOW
|
|
self.addCleanup(datetime_patcher.stop)
|
|
|
|
@patch('xmodule.course_metadata_utils.datetime.now')
|
|
def test_sorting_score(self, gmtime_mock):
|
|
gmtime_mock.return_value = NOW
|
|
|
|
day1 = '2012-01-01T12:00'
|
|
day2 = '2012-01-02T12:00'
|
|
|
|
dates = [
|
|
# Announce date takes priority over actual start
|
|
# and courses announced on a later date are newer
|
|
# than courses announced for an earlier date
|
|
((day1, day2, None), (day1, day1, None), self.assertLess),
|
|
((day1, day1, None), (day2, day1, None), self.assertEqual),
|
|
|
|
# Announce dates take priority over advertised starts
|
|
((day1, day2, day1), (day1, day1, day1), self.assertLess),
|
|
((day1, day1, day2), (day2, day1, day2), self.assertEqual),
|
|
|
|
# Later start == newer course
|
|
((day2, None, None), (day1, None, None), self.assertLess),
|
|
((day1, None, None), (day1, None, None), self.assertEqual),
|
|
|
|
# Non-parseable advertised starts are ignored in preference to actual starts
|
|
((day2, None, "Spring"), (day1, None, "Fall"), self.assertLess),
|
|
((day1, None, "Spring"), (day1, None, "Fall"), self.assertEqual),
|
|
|
|
# Partially parsable advertised starts should take priority over start dates
|
|
((day2, None, "October 2013"), (day2, None, "October 2012"), self.assertLess),
|
|
((day2, None, "October 2013"), (day1, None, "October 2013"), self.assertEqual),
|
|
|
|
# Parseable advertised starts take priority over start dates
|
|
((day1, None, day2), (day1, None, day1), self.assertLess),
|
|
((day2, None, day2), (day1, None, day2), self.assertEqual),
|
|
]
|
|
|
|
for a, b, assertion in dates:
|
|
a_score = get_dummy_course(start=a[0], announcement=a[1], advertised_start=a[2]).sorting_score
|
|
b_score = get_dummy_course(start=b[0], announcement=b[1], advertised_start=b[2]).sorting_score
|
|
print(f"Comparing {a} to {b}")
|
|
assertion(a_score, b_score)
|
|
|
|
start_advertised_settings = [
|
|
# start, advertised, result, is_still_default, date_time_result
|
|
('2012-12-02T12:00', None, 'Dec 02, 2012', False, 'Dec 02, 2012 at 12:00 UTC'),
|
|
('2012-12-02T12:00', '2011-11-01T12:00', 'Nov 01, 2011', False, 'Nov 01, 2011 at 12:00 UTC'),
|
|
('2012-12-02T12:00', 'Spring 2012', 'Spring 2012', False, 'Spring 2012'),
|
|
('2012-12-02T12:00', 'November, 2011', 'November, 2011', False, 'November, 2011'),
|
|
(xmodule.course_block.CourseFields.start.default, None, 'TBD', True, 'TBD'),
|
|
(xmodule.course_block.CourseFields.start.default, 'January 2014', 'January 2014', False, 'January 2014'),
|
|
]
|
|
|
|
def test_start_date_is_default(self):
|
|
for s in self.start_advertised_settings:
|
|
d = get_dummy_course(start=s[0], advertised_start=s[1])
|
|
assert d.start_date_is_still_default == s[3]
|
|
|
|
def test_display_organization(self):
|
|
block = get_dummy_course(start='2012-12-02T12:00', is_new=True)
|
|
assert block.location.org != block.display_org_with_default
|
|
assert block.display_org_with_default == f'{ORG}_display'
|
|
|
|
def test_display_coursenumber(self):
|
|
block = get_dummy_course(start='2012-12-02T12:00', is_new=True)
|
|
assert block.location.course != block.display_number_with_default
|
|
assert block.display_number_with_default == f'{COURSE}_display'
|
|
|
|
def test_is_newish(self):
|
|
block = get_dummy_course(start='2012-12-02T12:00', is_new=True)
|
|
assert block.is_newish is True
|
|
|
|
block = get_dummy_course(start='2013-02-02T12:00', is_new=False)
|
|
assert block.is_newish is False
|
|
|
|
block = get_dummy_course(start='2013-02-02T12:00', is_new=True)
|
|
assert block.is_newish is True
|
|
|
|
block = get_dummy_course(start='2013-01-15T12:00')
|
|
assert block.is_newish is True
|
|
|
|
block = get_dummy_course(start='2013-03-01T12:00')
|
|
assert block.is_newish is True
|
|
|
|
block = get_dummy_course(start='2012-10-15T12:00')
|
|
assert block.is_newish is False
|
|
|
|
block = get_dummy_course(start='2012-12-31T12:00')
|
|
assert block.is_newish is True
|
|
|
|
|
|
class DiscussionTopicsTestCase(unittest.TestCase):
|
|
|
|
def test_default_discussion_topics(self):
|
|
d = get_dummy_course('2012-12-02T12:00')
|
|
assert {'General': {'id': 'i4x-test_org-test_course-course-test'}} == d.discussion_topics
|
|
|
|
|
|
class TeamsConfigurationTestCase(unittest.TestCase):
|
|
"""
|
|
Tests for the configuration of teams and the helper methods for accessing them.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.course = get_dummy_course('2012-12-02T12:00')
|
|
self.course.teams_configuration = TeamsConfig(None)
|
|
self.count = itertools.count()
|
|
|
|
def add_team_configuration(self, max_team_size=3, topics=None, enabled=None):
|
|
""" Add a team configuration to the course. """
|
|
teams_config_data = {}
|
|
teams_config_data["topics"] = [] if topics is None else topics
|
|
if max_team_size is not None:
|
|
teams_config_data["max_team_size"] = max_team_size
|
|
if enabled is not None:
|
|
teams_config_data["enabled"] = enabled
|
|
self.course.teams_configuration = TeamsConfig(teams_config_data)
|
|
|
|
def make_topic(self):
|
|
""" Make a sample topic dictionary. """
|
|
next_num = next(self.count)
|
|
topic_id = f"topic_id_{next_num}"
|
|
name = f"Name {next_num}"
|
|
description = f"Description {next_num}"
|
|
return {
|
|
"name": name,
|
|
"description": description,
|
|
"id": topic_id,
|
|
"type": "open",
|
|
"max_team_size": None,
|
|
"user_partition_id": None,
|
|
}
|
|
|
|
def test_teams_enabled_new_course(self):
|
|
"""
|
|
Tests that teams are not enabled by default as no teamsets exist.
|
|
"""
|
|
# Make sure we can detect when no teams exist.
|
|
assert not self.course.teams_enabled
|
|
assert not self.course.teams_configuration.is_enabled
|
|
|
|
def test_teams_enabled_with_default(self):
|
|
"""
|
|
Test that teams are automatically enabled if a teamset is added, but it can be disabled via the `enabled` field.
|
|
"""
|
|
# Test that teams is enabled if topic are created
|
|
self.add_team_configuration(max_team_size=4, topics=[self.make_topic()])
|
|
assert self.course.teams_enabled
|
|
assert self.course.teams_configuration.is_enabled
|
|
|
|
# Test that teams are disabled if topic exists, but enabled is False
|
|
self.add_team_configuration(max_team_size=4, topics=[self.make_topic()], enabled=False)
|
|
assert not self.course.teams_enabled
|
|
assert not self.course.teams_configuration.is_enabled
|
|
|
|
def test_teams_enabled_no_teamsets(self):
|
|
"""
|
|
Test that teams can be enabled / disabled with only the flag, even if no teamsets exist
|
|
"""
|
|
self.add_team_configuration(max_team_size=4, topics=[], enabled=True)
|
|
assert self.course.teams_enabled
|
|
assert self.course.teams_configuration.is_enabled
|
|
self.add_team_configuration(max_team_size=4, topics=[], enabled=False)
|
|
assert not self.course.teams_enabled
|
|
assert not self.course.teams_configuration.is_enabled
|
|
|
|
def test_teams_enabled_max_size_only(self):
|
|
"""
|
|
Test that teams isn't enabled if only a max team size is configured.
|
|
"""
|
|
self.add_team_configuration(max_team_size=4)
|
|
assert not self.course.teams_enabled
|
|
|
|
def test_teams_enabled_no_max_size(self):
|
|
"""
|
|
Test that teams is enabled if a max team size is missing but teamsets are created.s
|
|
"""
|
|
self.add_team_configuration(max_team_size=None, topics=[self.make_topic()])
|
|
assert self.course.teams_enabled
|
|
|
|
def test_teams_max_size_no_teams_configuration(self):
|
|
"""
|
|
Test that the default maximum team size matches the configured maximum
|
|
"""
|
|
assert self.course.teams_configuration.default_max_team_size == DEFAULT_COURSE_RUN_MAX_TEAM_SIZE
|
|
|
|
def test_teams_max_size_with_teams_configured(self):
|
|
"""
|
|
Test that if you provide a custom global max_team_size, it reflects in the config.
|
|
"""
|
|
size = 4
|
|
self.add_team_configuration(max_team_size=size, topics=[self.make_topic(), self.make_topic()])
|
|
assert self.course.teams_enabled
|
|
assert size == self.course.teams_configuration.default_max_team_size
|
|
|
|
def test_teamsets_no_config(self):
|
|
"""
|
|
Tests that no teamsets are configured by default.
|
|
"""
|
|
assert self.course.teamsets == []
|
|
|
|
def test_teamsets_empty(self):
|
|
"""
|
|
Test that if only the max team size is configured then there are no teamsets
|
|
"""
|
|
self.add_team_configuration(max_team_size=4)
|
|
assert self.course.teamsets == []
|
|
|
|
def test_teamsets_present(self):
|
|
"""
|
|
Tests that if valid teamsets are added they show up in the config
|
|
"""
|
|
topics = [self.make_topic(), self.make_topic()]
|
|
self.add_team_configuration(max_team_size=4, topics=topics)
|
|
assert self.course.teams_enabled
|
|
expected_teamsets_data = [
|
|
teamset.cleaned_data
|
|
for teamset in self.course.teamsets
|
|
]
|
|
assert expected_teamsets_data == topics
|
|
|
|
def test_teams_conf_cached_by_xblock_field(self):
|
|
"""
|
|
Test that the teamsets are cached in the field so repeated queries don't perform re-computation
|
|
"""
|
|
self.add_team_configuration(max_team_size=5, topics=[self.make_topic()])
|
|
cold_cache_conf = self.course.teams_configuration
|
|
warm_cache_conf = self.course.teams_configuration
|
|
self.add_team_configuration(max_team_size=5, topics=[self.make_topic(), self.make_topic()])
|
|
new_cold_cache_conf = self.course.teams_configuration
|
|
new_warm_cache_conf = self.course.teams_configuration
|
|
assert cold_cache_conf is warm_cache_conf
|
|
assert new_cold_cache_conf is new_warm_cache_conf
|
|
assert cold_cache_conf is not new_cold_cache_conf
|
|
|
|
|
|
class SelfPacedTestCase(unittest.TestCase):
|
|
"""Tests for self-paced courses."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.course = get_dummy_course('2012-12-02T12:00')
|
|
|
|
def test_default(self):
|
|
assert not self.course.self_paced
|
|
|
|
|
|
@ddt.ddt
|
|
class CourseBlockTestCase(unittest.TestCase):
|
|
"""
|
|
Tests for a select few functions from CourseBlock.
|
|
|
|
I wrote these test functions in order to satisfy the coverage checker for
|
|
PR #8484, which modified some code within CourseBlock. However, this
|
|
class definitely isn't a comprehensive test case for CourseBlock, as
|
|
writing a such a test case was out of the scope of the PR.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Initialize dummy testing course.
|
|
"""
|
|
super().setUp()
|
|
self.course = get_dummy_course(start=_TODAY, end=_NEXT_WEEK)
|
|
|
|
def test_clean_id(self):
|
|
"""
|
|
Test CourseBlock.clean_id.
|
|
"""
|
|
assert self.course.clean_id() == 'course_ORSXG5C7N5ZGOL3UMVZXIX3DN52XE43FF52GK43UL5ZHK3Q='
|
|
assert self.course.clean_id(padding_char='$') == 'course_ORSXG5C7N5ZGOL3UMVZXIX3DN52XE43FF52GK43UL5ZHK3Q$'
|
|
|
|
def test_has_started(self):
|
|
"""
|
|
Test CourseBlock.has_started.
|
|
"""
|
|
self.course.start = _LAST_WEEK
|
|
assert self.course.has_started()
|
|
self.course.start = _NEXT_WEEK
|
|
assert not self.course.has_started()
|
|
|
|
def test_number(self):
|
|
"""
|
|
Test CourseBlock.number.
|
|
"""
|
|
assert self.course.number == COURSE
|
|
|
|
@ddt.data(
|
|
(_LAST_WEEK, None, True),
|
|
(None, _NEXT_WEEK, True),
|
|
(_LAST_WEEK, _NEXT_WEEK, True),
|
|
(_LAST_WEEK, _LAST_WEEK, False),
|
|
(_NEXT_WEEK, _NEXT_WEEK, False)
|
|
)
|
|
@ddt.unpack
|
|
def test_is_enrollment_open(self, enrollment_start_date, enrollment_end_date, enrollment_open):
|
|
"""
|
|
Test CourseBlock.is_enrollment_open.
|
|
"""
|
|
self.course.enrollment_start = enrollment_start_date
|
|
self.course.enrollment_end = enrollment_end_date
|
|
|
|
assert self.course.is_enrollment_open() is enrollment_open
|
|
|
|
|
|
@ddt.ddt
|
|
class ProctoringProviderTestCase(unittest.TestCase):
|
|
"""
|
|
Tests for ProctoringProvider, including the default value, validation, and inheritance behavior.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Initialize dummy testing course.
|
|
"""
|
|
super().setUp()
|
|
self.proctoring_provider = xmodule.course_block.ProctoringProvider()
|
|
|
|
def test_from_json_with_platform_default(self):
|
|
"""
|
|
Test that a proctoring provider value equivalent to the platform
|
|
default will pass validation.
|
|
"""
|
|
default_provider = settings.PROCTORING_BACKENDS.get('DEFAULT')
|
|
|
|
# we expect the validated value to be equivalent to the value passed in,
|
|
# since there are no validation errors or missing data
|
|
assert self.proctoring_provider.from_json(default_provider) == default_provider
|
|
|
|
@override_settings(
|
|
PROCTORING_BACKENDS={
|
|
'DEFAULT': 'mock',
|
|
'mock': {},
|
|
'mock_proctoring_without_rules': {}
|
|
}
|
|
)
|
|
@ddt.data(True, False)
|
|
def test_from_json_with_invalid_provider(self, proctored_exams_setting_enabled):
|
|
"""
|
|
Test that an invalid provider (i.e. not one configured at the platform level)
|
|
throws a ValueError with the correct error message.
|
|
"""
|
|
provider = 'invalid-provider'
|
|
allowed_proctoring_providers = xmodule.course_block.get_available_providers()
|
|
|
|
FEATURES_WITH_PROCTORED_EXAMS = settings.FEATURES.copy()
|
|
FEATURES_WITH_PROCTORED_EXAMS['ENABLE_PROCTORED_EXAMS'] = proctored_exams_setting_enabled
|
|
|
|
with override_settings(FEATURES=FEATURES_WITH_PROCTORED_EXAMS):
|
|
if proctored_exams_setting_enabled:
|
|
with pytest.raises(InvalidProctoringProvider) as context_manager:
|
|
self.proctoring_provider.from_json(provider, validate_providers=True)
|
|
expected_error = f'The selected proctoring provider, {provider}, is not a valid provider. ' \
|
|
f'Please select from one of {allowed_proctoring_providers}.'
|
|
assert str(context_manager.value) == expected_error
|
|
else:
|
|
provider_value = self.proctoring_provider.from_json(provider, validate_providers=True)
|
|
assert provider_value == self.proctoring_provider.default
|
|
|
|
def test_from_json_validate_providers(self):
|
|
"""
|
|
Test that an invalid provider is ignored if validate providers is set to false
|
|
"""
|
|
provider = 'invalid-provider'
|
|
|
|
FEATURES_WITH_PROCTORED_EXAMS = settings.FEATURES.copy()
|
|
FEATURES_WITH_PROCTORED_EXAMS['ENABLE_PROCTORED_EXAMS'] = True
|
|
|
|
with override_settings(FEATURES=FEATURES_WITH_PROCTORED_EXAMS):
|
|
provider_value = self.proctoring_provider.from_json(provider, validate_providers=False)
|
|
assert provider_value == provider
|
|
|
|
def test_from_json_adds_platform_default_for_missing_provider(self):
|
|
"""
|
|
Test that a value with no provider will inherit the default provider
|
|
from the platform defaults.
|
|
"""
|
|
default_provider = settings.PROCTORING_BACKENDS.get('DEFAULT')
|
|
assert default_provider is not None
|
|
|
|
assert self.proctoring_provider.from_json(None) == default_provider
|
|
|
|
@override_settings(
|
|
PROCTORING_BACKENDS={
|
|
'mock': {},
|
|
'mock_proctoring_without_rules': {}
|
|
}
|
|
)
|
|
def test_default_with_no_platform_default(self):
|
|
"""
|
|
Test that, when the platform defaults are not set, the default is correct.
|
|
"""
|
|
assert self.proctoring_provider.default is None
|
|
|
|
@override_settings(PROCTORING_BACKENDS=None)
|
|
def test_default_with_no_platform_configuration(self):
|
|
"""
|
|
Test that, when the platform default is not specified, the default is correct.
|
|
"""
|
|
default = self.proctoring_provider.default
|
|
assert default is None
|