* chore: Updating Python Requirements * fix: fix completion test query count --------- Co-authored-by: UsamaSadiq <usama7274@gmail.com>
228 lines
9.0 KiB
Python
228 lines
9.0 KiB
Python
"""
|
|
Test models, managers, and validators.
|
|
"""
|
|
|
|
import pytest
|
|
from completion import models
|
|
from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing
|
|
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
|
|
from django.core.exceptions import ValidationError
|
|
from django.test import TestCase
|
|
from edx_toggles.toggles.testutils import override_waffle_switch
|
|
from opaque_keys.edx.keys import CourseKey, UsageKey
|
|
|
|
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
|
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
|
|
|
SELECT = 1
|
|
UPDATE = 1
|
|
SAVEPOINT = 1
|
|
OTHER = 1
|
|
|
|
|
|
@skip_unless_lms
|
|
class PercentValidatorTestCase(TestCase):
|
|
"""
|
|
Test that validate_percent only allows floats (and ints) between 0.0 and 1.0.
|
|
"""
|
|
def test_valid_percents(self):
|
|
for value in [1.0, 0.0, 1, 0, 0.5, 0.333081348071397813987230871]:
|
|
models.validate_percent(value)
|
|
|
|
def test_invalid_percent(self):
|
|
for value in [-0.00000000001, 1.0000000001, 47.1, 1000, None, float('inf'), float('nan')]:
|
|
self.assertRaises(ValidationError, models.validate_percent, value)
|
|
|
|
|
|
class CompletionSetUpMixin(CompletionWaffleTestMixin):
|
|
"""
|
|
Mixin that provides helper to create test BlockCompletion object.
|
|
"""
|
|
def set_up_completion(self):
|
|
self.user = UserFactory()
|
|
self.block_key = UsageKey.from_string('block-v1:edx+test+run+type@video+block@doggos')
|
|
self.completion = models.BlockCompletion.objects.create(
|
|
user=self.user,
|
|
context_key=self.block_key.context_key,
|
|
block_type=self.block_key.block_type,
|
|
block_key=self.block_key,
|
|
completion=0.5,
|
|
)
|
|
|
|
|
|
@skip_unless_lms
|
|
class SubmitCompletionTestCase(CompletionSetUpMixin, TestCase):
|
|
"""
|
|
Test that BlockCompletion.objects.submit_completion has the desired
|
|
semantics.
|
|
"""
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.override_waffle_switch(True)
|
|
self.set_up_completion()
|
|
|
|
def test_changed_value(self):
|
|
with self.assertNumQueries(2 * SELECT + UPDATE + 2 * SAVEPOINT + 2 * OTHER):
|
|
# OTHER = user exists, completion exists
|
|
completion, isnew = models.BlockCompletion.objects.submit_completion(
|
|
user=self.user,
|
|
block_key=self.block_key,
|
|
completion=0.9,
|
|
)
|
|
completion.refresh_from_db()
|
|
assert completion.completion == 0.9
|
|
assert not isnew
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
|
|
def test_unchanged_value(self):
|
|
with self.assertNumQueries(2 * SELECT + 2 * SAVEPOINT):
|
|
completion, isnew = models.BlockCompletion.objects.submit_completion(
|
|
user=self.user,
|
|
block_key=self.block_key,
|
|
completion=0.5,
|
|
)
|
|
completion.refresh_from_db()
|
|
assert completion.completion == 0.5
|
|
assert not isnew
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
|
|
def test_new_user(self):
|
|
newuser = UserFactory()
|
|
with self.assertNumQueries(SELECT + UPDATE + 4 * SAVEPOINT):
|
|
_, isnew = models.BlockCompletion.objects.submit_completion(
|
|
user=newuser,
|
|
block_key=self.block_key,
|
|
completion=0.0,
|
|
)
|
|
assert isnew
|
|
assert models.BlockCompletion.objects.count() == 2
|
|
|
|
def test_new_block(self):
|
|
newblock = UsageKey.from_string('block-v1:edx+test+run+type@video+block@puppers')
|
|
with self.assertNumQueries(SELECT + UPDATE + 4 * SAVEPOINT):
|
|
_, isnew = models.BlockCompletion.objects.submit_completion(
|
|
user=self.user,
|
|
block_key=newblock,
|
|
completion=1.0,
|
|
)
|
|
assert isnew
|
|
assert models.BlockCompletion.objects.count() == 2
|
|
|
|
def test_invalid_completion(self):
|
|
with pytest.raises(ValidationError):
|
|
models.BlockCompletion.objects.submit_completion(
|
|
user=self.user,
|
|
block_key=self.block_key,
|
|
completion=1.2
|
|
)
|
|
completion = models.BlockCompletion.objects.get(user=self.user, block_key=self.block_key)
|
|
assert completion.completion == 0.5
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
|
|
|
|
@skip_unless_lms
|
|
class CompletionDisabledTestCase(CompletionSetUpMixin, TestCase):
|
|
"""
|
|
Tests that completion API is not called when the feature is disabled.
|
|
"""
|
|
def setUp(self):
|
|
super().setUp()
|
|
# insert one completion record...
|
|
self.set_up_completion()
|
|
# ...then disable the feature.
|
|
self.override_waffle_switch(False)
|
|
|
|
def test_cannot_call_submit_completion(self):
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
with pytest.raises(RuntimeError):
|
|
models.BlockCompletion.objects.submit_completion(
|
|
user=self.user,
|
|
block_key=self.block_key,
|
|
completion=0.9,
|
|
)
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
|
|
|
|
@skip_unless_lms
|
|
class SubmitBatchCompletionTestCase(CompletionWaffleTestMixin, TestCase):
|
|
"""
|
|
Test that BlockCompletion.objects.submit_batch_completion has the desired
|
|
semantics.
|
|
"""
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.override_waffle_switch(True)
|
|
|
|
self.block_key = UsageKey.from_string('block-v1:edx+test+run+type@video+block@doggos')
|
|
self.course_key_obj = CourseKey.from_string('course-v1:edx+test+run')
|
|
self.user = UserFactory()
|
|
CourseEnrollmentFactory.create(user=self.user, course_id=str(self.course_key_obj))
|
|
|
|
def test_submit_batch_completion(self):
|
|
blocks = [(self.block_key, 1.0)]
|
|
models.BlockCompletion.objects.submit_batch_completion(self.user, blocks)
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
assert models.BlockCompletion.objects.last().completion == 1.0
|
|
|
|
def test_submit_batch_completion_without_waffle(self):
|
|
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, False):
|
|
with pytest.raises(RuntimeError):
|
|
blocks = [(self.block_key, 1.0)]
|
|
models.BlockCompletion.objects.submit_batch_completion(self.user, blocks)
|
|
|
|
def test_submit_batch_completion_with_same_block_new_completion_value(self):
|
|
blocks = [(self.block_key, 0.0)]
|
|
assert models.BlockCompletion.objects.count() == 0
|
|
models.BlockCompletion.objects.submit_batch_completion(self.user, blocks)
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
model = models.BlockCompletion.objects.first()
|
|
assert model.completion == 0.0
|
|
blocks = [
|
|
(UsageKey.from_string('block-v1:edx+test+run+type@video+block@doggos'), 1.0),
|
|
]
|
|
models.BlockCompletion.objects.submit_batch_completion(self.user, blocks)
|
|
assert models.BlockCompletion.objects.count() == 1
|
|
model = models.BlockCompletion.objects.first()
|
|
assert model.completion == 1.0
|
|
|
|
|
|
@skip_unless_lms
|
|
class BatchCompletionMethodTests(CompletionWaffleTestMixin, TestCase):
|
|
"""
|
|
Tests for the classmethods that retrieve course/block completion data.
|
|
"""
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.override_waffle_switch(True)
|
|
|
|
self.user = UserFactory.create()
|
|
self.other_user = UserFactory.create()
|
|
self.course_key = CourseKey.from_string("edX/MOOC101/2049_T2")
|
|
self.other_course_key = CourseKey.from_string("course-v1:ReedX+Hum110+1904")
|
|
self.block_keys = [UsageKey.from_string(f"i4x://edX/MOOC101/video/{number}") for number in range(5)]
|
|
self.block_keys_with_runs = [key.replace(course_key=self.course_key) for key in self.block_keys]
|
|
self.other_course_block_keys = [self.other_course_key.make_usage_key('html', '1')]
|
|
|
|
# Submit completions for the main course:
|
|
submit_completions_for_testing(self.user, self.block_keys_with_runs[:3])
|
|
# Different user:
|
|
submit_completions_for_testing(self.other_user, self.block_keys_with_runs[2:])
|
|
# Different course:
|
|
submit_completions_for_testing(self.user, self.other_course_block_keys)
|
|
|
|
def test_get_learning_context_completions_missing_runs(self):
|
|
actual_completions = models.BlockCompletion.get_learning_context_completions(self.user, self.course_key)
|
|
expected_block_keys = self.block_keys_with_runs[:3]
|
|
expected_completions = dict(list(zip(expected_block_keys, [1.0, 0.8, 0.6])))
|
|
assert expected_completions == actual_completions
|
|
|
|
def test_get_learning_context_completions_empty_result_set(self):
|
|
assert models.BlockCompletion.get_learning_context_completions(self.other_user, self.other_course_key) == {}
|
|
|
|
def test_get_latest_block_completed(self):
|
|
assert models.BlockCompletion.get_latest_block_completed(self.user, self.course_key).block_key == \
|
|
self.block_keys[2]
|
|
|
|
def test_get_latest_completed_none_exist(self):
|
|
assert models.BlockCompletion.get_latest_block_completed(self.other_user, self.other_course_key) is None
|