Skip to content
test_grades.py 97.1 KiB
Newer Older
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertEqual(
                models.FlowPageVisit.objects.filter(answer__isnull=False).count(),
                1)
            last_answered_visit = (
                models.FlowPageVisit.objects.filter(answer__isnull=False).first())
            last_answered_visit.visit_time = now() - timedelta(hours=1)
            last_answered_visit.save()
            self.assertEqual(models.GradeChange.objects.count(), 0)

            with mock.patch("course.flow.get_session_grading_rule") as \
                    mock_get_grading_rule:
                mock_get_grading_rule.side_effect = (
                    get_session_grading_rule_use_last_activity_as_cmplt_time_side_effect)  # noqa
                resp = self.end_flow()
                self.assertEqual(resp.status_code, 200)

            self.assertEqual(models.GradeChange.objects.count(), 1)
            latest_gchange = models.GradeChange.objects.last()
            latest_flow_session = models.FlowSession.objects.last()
            self.assertIsNotNone(latest_flow_session.last_activity())
            self.assertEqual(latest_flow_session.completion_time,
                             latest_flow_session.last_activity())
            self.assertEqual(latest_gchange.effective_time,
                             latest_flow_session.last_activity())

    # {{{ Fixed issue #263 and #417

    def test_update_latest_gc_of_ealier_finished_session(self):
        self.use_default_setup()
        self.assertGradeChangeMachineReadableStateEqual(6)

        # Issue #263 and #417
        # gc_session1 is the GradeChange object of session 1, update it's
        # value won't change the consumed state.
        self.update_gc(self.gc_session1, points=10)
        self.assertGradeChangeMachineReadableStateEqual(6)
        self.assertGradeChangeStateEqual("6.00% (/3)")

    def test_special_case(self):
        # https://github.com/inducer/relate/pull/423#discussion_r162121467
        gc2015 = factories.GradeChangeFactory.create(**(self.gc(points=5)))

        session1 = factories.FlowSessionFactory.create(
            participation=self.student_participation,
            start_time=self.time-timedelta(days=17),
            completion_time=self.time-timedelta(days=14))

        self.time_increment()

        gc2016 = factories.GradeChangeFactory.create(
            **(self.gc(points=0, flow_session=session1, grade_time=self.time)))

        gc2017 = factories.GradeChangeFactory.create(**(self.gc(points=7)))

        session2 = factories.FlowSessionFactory.create(
            participation=self.student_participation,
            start_time=self.time-timedelta(days=17),
            completion_time=self.time-timedelta(days=15))

        self.time_increment()

        gc2018 = factories.GradeChangeFactory.create(
            **(self.gc(points=6, flow_session=session2)))

        assert models.GradingOpportunity.objects.count() == 1
        assert models.GradeChange.objects.count() == 4
        assert models.FlowSession.objects.count() == 2

        self.assertTrue(session2.completion_time < session1.completion_time)
        self.assertTrue(
            gc2015.grade_time < gc2016.grade_time < gc2017.grade_time
            < gc2018.grade_time)

        self.assertGradeChangeMachineReadableStateEqual(gc2017.percentage())

    # {{{ When two grade changes have the same grade_time
Dong Zhuang's avatar
Dong Zhuang committed
    # The expected behavior is GradeChange object with the larger pk
Dong Zhuang's avatar
Dong Zhuang committed
    # dominate. Fixed with #263 and #417

    def test_gcs_have_same_grade_time1(self):
        gc1 = factories.GradeChangeFactory.create(**(self.gc(points=0)))
        session = factories.FlowSessionFactory.create(
            participation=self.student_participation,
            completion_time=gc1.grade_time-timedelta(days=1))
        factories.GradeChangeFactory.create(
            **(self.gc(points=5, flow_session=session,
                       grade_time=gc1.grade_time)))
        self.assertGradeChangeMachineReadableStateEqual(5)
        self.assertGradeChangeStateEqual("5.0% (/2)")

    def test_gc_have_same_grade_time2(self):
        session = factories.FlowSessionFactory.create(
            participation=self.student_participation,
            start_time=self.time-timedelta(days=1),
            completion_time=self.time)
        self.time_increment()
        gc1 = factories.GradeChangeFactory.create(
            **(self.gc(points=5, flow_session=session)))
        factories.GradeChangeFactory.create(
            **(self.gc(points=0, grade_time=gc1.grade_time)))
        self.assertGradeChangeMachineReadableStateEqual(0)
        self.assertGradeChangeStateEqual("0.00% (/2)")
    # }}}

    # {{{ Fix #430

    def test_reopen_session2(self):
        self.use_default_setup()

        # original state
        self.assertGradeChangeMachineReadableStateEqual("6")

        n_gc = models.GradeChange.objects.count()
        self.reopen_session2()

        # A new GradeChange object is created, with state "do_over"
        expected_n_gc = models.GradeChange.objects.count()
        self.assertEqual(expected_n_gc, n_gc + 1)
        self.assertEqual(
            models.GradeChange.objects.order_by("grade_time").last().state,
            g_state.do_over)

        self.assertGradeChangeMachineReadableStateEqual("NONE")
        self.assertGradeChangeStateEqual("- ∅ - (/3)")

    def test_reopen_session_without_existing_gc(self):
        # This is rare, because a completed_session should had created
        # a GradeChange object.
        session_temp = factories.FlowSessionFactory.create(
            participation=self.student_participation, completion_time=self.time)

        existing_gc_count = models.GradeChange.objects.count()
        reopen_session(now_datetime=local_now(), session=session_temp,
                       generate_grade_change=True,
                       suppress_log=True)
        self.assertEqual(models.GradeChange.objects.count(), existing_gc_count)

    def test_reopen_session1(self):
        self.use_default_setup()
        self.assertGradeChangeMachineReadableStateEqual("6")

        n_gc = models.GradeChange.objects.count()
        self.reopen_session1()

        # A new GradeChange object is created, with state "do_over"
        expected_n_gc = models.GradeChange.objects.count()
        self.assertEqual(expected_n_gc, n_gc + 1)
        self.assertEqual(
            models.GradeChange.objects.order_by("grade_time").last().state,
            g_state.do_over)

        # session 1 is not the latest session
        self.assertGradeChangeMachineReadableStateEqual("6")
        self.assertGradeChangeStateEqual("6.00% (/3)")

    def _get_admin_flow_session_delete_url(self, args):
        return reverse("admin:course_flowsession_delete", args=args)

    def _delete_flow_session_admin(self, flow_session):
        exist_flow_session_count = models.FlowSession.objects.count()
        flow_session_delete_url = self._get_admin_flow_session_delete_url(
            args=(flow_session.id,))
        delete_dict = {'post': 'yes'}
        with self.temporarily_switch_to_user(self.superuser):
            resp = self.c.get(flow_session_delete_url)
            self.assertEqual(resp.status_code, 200)
            resp = self.c.post(flow_session_delete_url, data=delete_dict)
        self.assertEqual(resp.status_code, 302)
        self.assertEqual(exist_flow_session_count,
                         models.FlowSession.objects.count() + 1)

    def test_delete_flow_session_admin_new_exempt_gradechange_created(self):
        self.use_default_setup()
        exist_grade_change_count = models.GradeChange.objects.count()

        # session1 has related grade changes, so a new grade change with 'exempt' is
        # created
        self._delete_flow_session_admin(self.session1)

        self.assertEqual(exist_grade_change_count + 1,
                         models.GradeChange.objects.count())

        last_gchange = (
            models.GradeChange.objects
            .order_by("-grade_time").first())
        self.assertIsNone(last_gchange.flow_session)
        self.assertEqual(last_gchange.state, g_state.exempt)

    def test_delete_flow_session_admin_no_new_gradechange_created(self):
        session_temp = factories.FlowSessionFactory.create(
            participation=self.student_participation, completion_time=self.time)

        exist_grade_change_count = models.GradeChange.objects.count()
        last_gchange_of_session_temp = (
            models.GradeChange.objects
            .filter(flow_session=session_temp)
            .order_by("-grade_time")[:1])
        self.assertEqual(last_gchange_of_session_temp.count(), 0)

        # session_temp has no related grade changes, so no new grade change
        # is created after deleted
        self._delete_flow_session_admin(session_temp)

        self.assertEqual(exist_grade_change_count,
                         models.GradeChange.objects.count())

Dong Zhuang's avatar
Dong Zhuang committed
    def test_backward_compatibility_merging_466(self):
        # this make sure after merging https://github.com/inducer/relate/pull/466
        # gchanges are consumed without issue
        self.use_default_setup()
        self.gc_session2.effective_time = None
        self.gc_session2.save()
        self.gc_session2.refresh_from_db()

        # We are not using reopen_session(), because that will create new
        # gchange, which only happen after #466 was merged.
        self.session2.in_progress = True
        self.session2.save()
        self.session2.refresh_from_db()

        machine = self.get_gc_machine()

        # session2's gchange is excluded
        self.assertGradeChangeMachineReadableStateEqual(7)
        self.assertEqual(machine.valid_percentages, [0, 7])
        self.assertGradeChangeStateEqual("7.00% (/2)")

    # {{{ test new gchange created when finishing flow

    def test_new_gchange_created_when_finish_flow_use_last_no_activity(self):
        # With use_last_activity_as_completion_time = True, if a flow session has
        # no last_activity, the expected effective_time of the new gchange should
        # be the completion time of the related flow_session.
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.start_flow(QUIZ_FLOW_ID)
            self.assertEqual(models.GradeChange.objects.count(), 0)

            with mock.patch("course.flow.get_session_grading_rule") as \
                    mock_get_grading_rule:
                mock_get_grading_rule.side_effect = (
                    get_session_grading_rule_use_last_activity_as_cmplt_time_side_effect)  # noqa
                resp = self.end_flow()
                self.assertEqual(resp.status_code, 200)

            self.assertEqual(models.GradeChange.objects.count(), 1)
            latest_gchange = models.GradeChange.objects.last()
            latest_flow_session = models.FlowSession.objects.last()
            self.assertIsNone(latest_flow_session.last_activity())
            self.assertEqual(latest_gchange.effective_time,
                             latest_flow_session.completion_time)

    def test_new_gchange_created_when_finish_flow_not_use_last_no_activity(self):
        # With use_last_activity_as_completion_time = False, if a flow session has
        # no last_activity, the expected effective_time of the new gchange should
        # be the completion time of the related flow_session.
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.start_flow(QUIZ_FLOW_ID)
            self.assertEqual(models.GradeChange.objects.count(), 0)

            resp = self.end_flow()
            self.assertEqual(resp.status_code, 200)

            self.assertEqual(models.GradeChange.objects.count(), 1)
            latest_gchange = models.GradeChange.objects.last()
            latest_flow_session = models.FlowSession.objects.last()
            self.assertIsNone(latest_flow_session.last_activity())
            self.assertEqual(latest_gchange.effective_time,
                             latest_flow_session.completion_time)

    def test_new_gchange_created_when_finish_flow_not_use_last_has_activity(self):
        # With use_last_activity_as_completion_time = False, even if a flow session
        # HAS last_activity, the expected effective_time of the new gchange should
        # be the completion_time of the related flow_session.
        with self.temporarily_switch_to_user(self.instructor_participation.user):
            self.start_flow(QUIZ_FLOW_ID)

            # create a flow page visit, then there should be last_activity() for
            # the session.
            self.post_answer_by_ordinal(1, {"answer": ['0.5']})
            self.assertEqual(
                models.FlowPageVisit.objects.filter(answer__isnull=False).count(),
                1)
            last_answered_visit = (
                models.FlowPageVisit.objects.filter(answer__isnull=False).first())
            last_answered_visit.visit_time = now() - timedelta(hours=1)
            last_answered_visit.save()
            self.assertEqual(models.GradeChange.objects.count(), 0)

            resp = self.end_flow()
            self.assertEqual(resp.status_code, 200)

            self.assertEqual(models.GradeChange.objects.count(), 1)
            latest_gchange = models.GradeChange.objects.last()
            latest_flow_session = models.FlowSession.objects.last()
            self.assertIsNotNone(latest_flow_session.last_activity())
            self.assertNotEqual(latest_flow_session.completion_time,
                             latest_flow_session.last_activity())
            self.assertEqual(latest_gchange.effective_time,
                             latest_flow_session.completion_time)
Dong Zhuang's avatar
Dong Zhuang committed

# vim: fdm=marker