def test_validating_completions(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     other_course_key = CourseKey.from_string('course-v1:ihg+fed+cba')
     other_user = User.objects.create(username='******')
     aggregators = [
         models.Aggregator.objects.submit_completion(
             user=other_user,
             course_key=course_key,
             block_key=course_key.make_usage_key(block_type='course',
                                                 block_id='course'),
             aggregation_name='course',
             earned=4.0,
             possible=4.0,
             last_modified=timezone.now(),
         )[0],
         models.Aggregator.objects.submit_completion(
             user=self.test_user,
             course_key=other_course_key,
             block_key=other_course_key.make_usage_key(block_type='course',
                                                       block_id='course'),
             aggregation_name='course',
             earned=1.0,
             possible=3.0,
             last_modified=timezone.now(),
         )[0],
     ]
     for agg in aggregators:
         with pytest.raises(ValueError):
             serializers.AggregatorAdapter(
                 user=self.test_user,
                 course_key=course_key,
                 aggregators=[agg],
             )
    def test_simple_aggregation_structure(self):
        course_completion, _ = models.Aggregator.objects.submit_completion(
            user=self.test_user,
            course_key=self.course_key,
            block_key=self.course_key.make_usage_key(block_type='course',
                                                     block_id='crs'),
            aggregation_name='course',
            earned=4.2,
            possible=9.6,
            last_modified=timezone.now(),
        )
        sequential_completion, _ = models.Aggregator.objects.submit_completion(
            user=self.test_user,
            course_key=self.course_key,
            block_key=self.course_key.make_usage_key(block_type='sequential',
                                                     block_id='chap1'),
            aggregation_name='sequential',
            earned=1.8,
            possible=3.4,
            last_modified=timezone.now(),
        )
        agstruct = serializers.AggregatorAdapter(
            user=self.test_user,
            course_key=self.course_key,
        )
        agstruct.add_aggregator(course_completion)
        agstruct.update_aggregators([sequential_completion])

        self.assertEqual(agstruct.course, course_completion)
        self.assertEqual(agstruct.sequential, [sequential_completion])
 def test_mean(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     data = [
         # earned, possible
         (5., 20.),
         (7., 14.),
         (3., 9.),
         (8., 14.),
     ]
     expected_mean = sum(item[0] / item[1] for item in data) / 5.
     completions = [
         models.Aggregator.objects.submit_completion(
             user=self.test_user,
             course_key=course_key,
             block_key=course_key.make_usage_key(
                 block_type='course', block_id='course{}'.format(idx)),
             aggregation_name='course',
             earned=data[idx][0],
             possible=data[idx][1],
             last_modified=timezone.now(),
         )[0] for idx in range(4)
     ]
     serial = _course_completion_serializer_factory(['mean'])(
         serializers.AggregatorAdapter(user=self.test_user,
                                       course_key=course_key,
                                       aggregators=completions),
         requested_fields={'mean'})
     self.assertAlmostEqual(serial.data['mean'], expected_mean)
 def test_mean_with_no_completions(self):
     # A course that has no aggregators recorded.
     course_key = CourseKey.from_string('course-v1:abc+def+ghj')
     serial = _course_completion_serializer_factory(['mean'])(
         serializers.AggregatorAdapter(
             user=self.test_user,
             course_key=course_key,
             aggregators=[],
         ),
         requested_fields={'mean'})
     self.assertAlmostEqual(serial.data['mean'], 0)
 def test_aggregation_without_completions(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     serial = _course_completion_serializer_factory([])(
         serializers.AggregatorAdapter(user=self.test_user,
                                       course_key=course_key,
                                       aggregators=[]))
     self.assertEqual(
         serial.data['completion'],
         {
             'earned': 0.0,
             'possible': None,
             'percent': 0.0,
         },
     )
 def test_filtering_completions(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     aggregator = models.Aggregator.objects.submit_completion(
         user=self.test_user,
         course_key=course_key,
         block_key=course_key.make_usage_key(block_type='video',
                                             block_id='neatvideo'),
         aggregation_name='video',
         earned=1.0,
         possible=2.0,
         last_modified=timezone.now(),
     )[0]
     adapted = serializers.AggregatorAdapter(
         user=self.test_user,
         course_key=course_key,
         aggregators=[aggregator],
     )
     assert not adapted.aggregators
 def test_invalid_aggregator(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     completion, _ = models.Aggregator.objects.submit_completion(
         user=self.test_user,
         course_key=course_key,
         block_key=course_key.make_usage_key(block_type='course',
                                             block_id='course'),
         aggregation_name='course',
         earned=0.0,
         possible=0.0,
         last_modified=timezone.now(),
     )
     agg = serializers.AggregatorAdapter(user=self.test_user,
                                         course_key=course_key,
                                         aggregators=[completion])
     # coverage demands this, because we have __getattr__ overridden
     with self.assertRaises(AttributeError):
         agg.something_that_doesnt_exist  # pylint: disable=pointless-statement
 def test_zero_possible(self):
     course_key = CourseKey.from_string('course-v1:abc+def+ghi')
     completion, _ = models.Aggregator.objects.submit_completion(
         user=self.test_user,
         course_key=course_key,
         block_key=course_key.make_usage_key(block_type='course',
                                             block_id='course'),
         aggregation_name='course',
         earned=0.0,
         possible=0.0,
         last_modified=timezone.now(),
     )
     serial = _course_completion_serializer_factory([])(
         serializers.AggregatorAdapter(user=self.test_user,
                                       course_key=course_key,
                                       aggregators=[completion]))
     self.assertEqual(
         serial.data['completion'],
         {
             'earned': 0.0,
             'possible': 0.0,
             'percent': 1.0,
         },
     )
    def assert_serialized_completions(self, serializer_cls_args, extra_body,
                                      recalc_stale):
        """
        Ensures that the course completion serializer returns the expected results
        for this set of submitted completions.
        """
        serializer_cls = _course_completion_serializer_factory(
            serializer_cls_args)
        aggregators = [
            models.Aggregator.objects.submit_completion(
                user=self.test_user,
                course_key=self.course_key,
                aggregation_name='course',
                block_key=self.course_key.make_usage_key(block_type='course',
                                                         block_id='course'),
                earned=16.0,
                possible=19.0,
                last_modified=timezone.now(),
            )[0],
            models.Aggregator.objects.submit_completion(
                user=self.test_user,
                course_key=self.course_key,
                aggregation_name='sequential',
                block_key=self.course_key.make_usage_key(
                    block_type='sequential', block_id='seq1'),
                earned=6.0,
                possible=7.0,
                last_modified=timezone.now(),
            )[0],
            models.Aggregator.objects.submit_completion(
                user=self.test_user,
                course_key=self.course_key,
                aggregation_name='sequential',
                block_key=self.course_key.make_usage_key(
                    block_type='sequential', block_id='seq2'),
                earned=10.0,
                possible=12.0,
                last_modified=timezone.now(),
            )[0],
        ]
        is_stale = recalc_stale and models.StaleCompletion.objects.filter(
            username=self.test_user.username,
            course_key=self.course_key,
            resolved=False)
        completion = serializers.AggregatorAdapter(
            user=self.test_user,
            course_key=self.course_key,
            aggregators=aggregators,
            recalculate_stale=recalc_stale,
        )
        serial = serializer_cls(completion)
        expected = {
            'course_key': str(self.course_key),
            'completion': {
                'earned': 0.0 if is_stale else 16.0,
                'possible': None if is_stale else 19.0,
                'percent': 0.0 if is_stale else 16 / 19,
            },
        }

        expected.update(extra_body)
        # Need to allow for rounding error when retrieving the percent from the test database
        self.assertEqual(serial.data['course_key'], expected['course_key'])
        self.assertEqual(serial.data['completion']['earned'],
                         expected['completion']['earned'])
        self.assertEqual(serial.data['completion']['possible'],
                         expected['completion']['possible'])
        self.assertAlmostEqual(serial.data['completion']['possible'],
                               expected['completion']['possible'],
                               places=14)