def test_partial_updates(self): instant = now() completion = BlockCompletion.objects.create( user=self.user, context_key=self.course_key, block_key=self.blocks[4], completion=0.75, modified=instant, ) updater = AggregationUpdater(self.user, self.course_key, mock.MagicMock()) updater.update(changed_blocks={self.blocks[4]}) course_agg = Aggregator.objects.get(course_key=self.course_key, block_key=self.blocks[0]) chap1_agg = Aggregator.objects.get(course_key=self.course_key, block_key=self.blocks[1]) chap2_agg = Aggregator.objects.get(course_key=self.course_key, block_key=self.blocks[2]) self.assertEqual(chap1_agg.earned, 0.75) self.assertEqual(chap1_agg.last_modified, completion.modified) self.assertEqual(chap2_agg.earned, 0.0) self.assertEqual(chap2_agg.last_modified, OLD_DATETIME) self.assertEqual(course_agg.earned, 0.75) self.assertEqual(course_agg.last_modified, completion.modified)
def setUp(self): """ For the purpose of the tests, we will use the following course structure: course | +--+---+--^-+----+----+ / | | | | \\ html html html html other hidden / \\ html hidden where `course` and `other` are a completion_mode of AGGREGATOR (but only `course` is registered to store aggregations), `html` is COMPLETABLE, and `hidden` is EXCLUDED. """ super(AggregationUpdaterTestCase, self).setUp() self.agg_modified = now() - timedelta(days=1) course_key = CourseKey.from_string('course-v1:edx+course+test') stubcompat = StubCompat([ course_key.make_usage_key('course', 'course'), course_key.make_usage_key('html', 'course-html0'), course_key.make_usage_key('html', 'course-html1'), course_key.make_usage_key('html', 'course-html2'), course_key.make_usage_key('html', 'course-html3'), course_key.make_usage_key('other', 'course-other'), course_key.make_usage_key('hidden', 'course-hidden0'), course_key.make_usage_key('html', 'course-other-html4'), course_key.make_usage_key('hidden', 'course-other-hidden1'), ]) for compat_module in 'completion_aggregator.core.compat', 'completion_aggregator.core.compat': patch = mock.patch(compat_module, stubcompat) patch.start() self.addCleanup(patch.stop) user = get_user_model().objects.create(username='******') self.course_key = CourseKey.from_string('course-v1:edx+course+test') self.agg, _ = Aggregator.objects.submit_completion( user=user, course_key=self.course_key, block_key=self.course_key.make_usage_key('course', 'course'), aggregation_name='course', earned=0.0, possible=0.0, last_modified=self.agg_modified, ) BlockCompletion.objects.create( user=user, context_key=self.course_key, block_key=self.course_key.make_usage_key('html', 'course-other-html4'), completion=1.0, modified=now(), ) self.updater = AggregationUpdater(user, self.course_key, mock.MagicMock())
def test_blockstructure_caching(self): mock_modulestore = mock.MagicMock() updater = AggregationUpdater(self.user, self.course_key, mock_modulestore) updater.calculate_updated_aggregators() mock_modulestore.bulk_operations.assert_called_once() mock_modulestore.bulk_operations.reset_mock() updater.calculate_updated_aggregators() mock_modulestore.bulk_operations.assert_not_called()
def aggregate_course(self, user, course_key): updater = AggregationUpdater(user=user, course_key=course_key, modulestore=self.store) updater.update(force=True)
class AggregationUpdaterTestCase(TestCase): """ Test the AggregationUpdater. It should create Aggregator records for new completion objects. """ def setUp(self): """ For the purpose of the tests, we will use the following course structure: course | +--+---+--^-+----+----+ / | | | | \\ html html html html other hidden / \\ html hidden where `course` and `other` are a completion_mode of AGGREGATOR (but only `course` is registered to store aggregations), `html` is COMPLETABLE, and `hidden` is EXCLUDED. """ super(AggregationUpdaterTestCase, self).setUp() self.agg_modified = now() - timedelta(days=1) course_key = CourseKey.from_string('course-v1:edx+course+test') stubcompat = StubCompat([ course_key.make_usage_key('course', 'course'), course_key.make_usage_key('html', 'course-html0'), course_key.make_usage_key('html', 'course-html1'), course_key.make_usage_key('html', 'course-html2'), course_key.make_usage_key('html', 'course-html3'), course_key.make_usage_key('other', 'course-other'), course_key.make_usage_key('hidden', 'course-hidden0'), course_key.make_usage_key('html', 'course-other-html4'), course_key.make_usage_key('hidden', 'course-other-hidden1'), ]) for compat_module in 'completion_aggregator.core.compat', 'completion_aggregator.core.compat': patch = mock.patch(compat_module, stubcompat) patch.start() self.addCleanup(patch.stop) user = get_user_model().objects.create(username='******') self.course_key = CourseKey.from_string('course-v1:edx+course+test') self.agg, _ = Aggregator.objects.submit_completion( user=user, course_key=self.course_key, block_key=self.course_key.make_usage_key('course', 'course'), aggregation_name='course', earned=0.0, possible=0.0, last_modified=self.agg_modified, ) BlockCompletion.objects.create( user=user, context_key=self.course_key, block_key=self.course_key.make_usage_key('html', 'course-other-html4'), completion=1.0, modified=now(), ) self.updater = AggregationUpdater(user, self.course_key, mock.MagicMock()) @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(HTMLBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_aggregation_update(self): self.updater.update() self.agg.refresh_from_db() assert self.agg.last_modified > self.agg_modified assert self.agg.earned == 1.0 assert self.agg.possible == 5.0 @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(HTMLBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_end_to_end_task_calling(self): ''' Queries are for the following table * Select - auth_user (fetch user details) - completion_aggregator_aggregator (user specific for specific course) - completion_blockcompletion (user specific) - auth user (fetch user details) * Insert or Update Query - completion_aggregator_aggregator (insert aggregation data) * Update query - completion_aggregator_stalecompletion (user specific) ''' with self.assertNumQueries(6): aggregation_tasks.update_aggregators( username='******', course_key='course-v1:edx+course+test') self.agg.refresh_from_db() assert self.agg.last_modified > self.agg_modified assert self.agg.earned == 1.0 assert self.agg.possible == 5.0 def test_task_with_unknown_user(self): StaleCompletion.objects.create(username='******', course_key='course-v1:edx+course+test', resolved=False) with mock.patch('completion_aggregator.core.update_aggregators' ) as mock_update_handler: aggregation_tasks.update_aggregators( username='******', course_key='course-v1:edx+course+test') assert StaleCompletion.objects.get(username='******').resolved mock_update_handler.assert_not_called() @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(HTMLBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_unregistered_not_recorded(self): self.updater.update() assert not any(agg.block_key.block_type == 'other' for agg in Aggregator.objects.all()) @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(HTMLBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_with_no_initial_aggregator(self): self.agg.delete() self.updater.update() aggs = Aggregator.objects.filter(course_key=self.course_key) assert len(aggs) == 1 agg = aggs[0] assert agg.course_key == self.course_key assert agg.aggregation_name == 'course' assert agg.earned == 1.0 assert agg.possible == 5.0 @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(InvalidModeBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_invalid_completion_mode(self): with pytest.raises(ValueError): self.updater.update() @ddt.data(ValueError, TypeError) @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(InvalidModeBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_expected_updater_errors(self, exception_class): # Verify that no exception is bubbled up when the constructor errors, but that the update method is not called. with mock.patch.object(AggregationUpdater, '__init__') as mock_update_constructor: mock_update_constructor.side_effect = exception_class('test') with mock.patch.object(AggregationUpdater, 'update') as mock_update_action: aggregation_tasks.update_aggregators( username='******', course_key='course-v1:OpenCraft+Onboarding+2018') assert not mock_update_action.called @XBlock.register_temp_plugin(CourseBlock, 'course') @XBlock.register_temp_plugin(InvalidModeBlock, 'html') @XBlock.register_temp_plugin(HiddenBlock, 'hidden') @XBlock.register_temp_plugin(OtherAggBlock, 'other') def test_unexpected_updater_errors(self): # Verify that no exception is bubbled up when the constructor errors, but that the update method is not called. with mock.patch.object(AggregationUpdater, '__init__') as mock_update_constructor: mock_update_constructor.side_effect = RuntimeError('test') with pytest.raises(RuntimeError): aggregation_tasks.update_aggregators( username='******', course_key='course-v1:OpenCraft+Onboarding+2018')
def _get_updater(self): """ Return a fresh instance of an AggregationUpdater. """ return AggregationUpdater(self.user, self.course_key, mock.MagicMock())