Example #1
0
    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)
Example #2
0
    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)
Example #5
0
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')
Example #6
0
 def _get_updater(self):
     """
     Return a fresh instance of an AggregationUpdater.
     """
     return AggregationUpdater(self.user, self.course_key, mock.MagicMock())
 def aggregate_course(self, user, course_key):
     updater = AggregationUpdater(user=user, course_key=course_key, modulestore=self.store)
     updater.update(force=True)