Skip to content
test_flow.py 205 KiB
Newer Older
Dong Zhuang's avatar
Dong Zhuang committed
        self.addCleanup(fake_post_start_flow.stop)

        fake_get_session_start_rule = mock.patch(
            "course.flow.get_session_start_rule")
        self.mock_get_session_start_rule = fake_get_session_start_rule.start()
        self.addCleanup(fake_get_session_start_rule.stop)

        fake_get_session_access_rule = mock.patch(
            "course.flow.get_session_access_rule")
        self.mock_get_session_access_rule = fake_get_session_access_rule.start()
        self.addCleanup(fake_get_session_access_rule.stop)

        fake_get_session_grading_rule = mock.patch(
            "course.flow.get_session_grading_rule")
        self.mock_get_session_grading_rule = fake_get_session_grading_rule.start()
        self.addCleanup(fake_get_session_grading_rule.stop)

Dong Zhuang's avatar
Dong Zhuang committed
    default_session_access_rule = {"permissions": []}
Dong Zhuang's avatar
Dong Zhuang committed

    def get_test_flow_session(self, **kwargs):
        defaults = {"course": self.course,
                    "participation": self.student_participation,
                    "points": 5,
                    "max_points": 10,
                    "completion_time": now() - timedelta(days=1),
                    "in_progress": False
                    }
        defaults.update(kwargs)
        return factories.FlowSessionFactory(**defaults)

    def test_post(self):
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.client.post(self.get_view_start_flow_url(self.flow_id))
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertEqual(self.mock_post_start_flow.call_count, 1)

            self.assertEqual(self.mock_get_login_exam_ticket.call_count, 0)
            self.assertEqual(self.mock_get_session_start_rule.call_count, 0)
            self.assertEqual(self.mock_get_session_access_rule.call_count, 0)
            self.assertEqual(self.mock_get_session_grading_rule.call_count, 0)

    def test_get_may_list_existing_sessions_but_no_session(self):
        session_start_rule = self.get_hacked_session_start_rule()

        self.mock_get_session_grading_rule.return_value = (
            self.get_hacked_session_grading_rule(
                due=now() + timedelta(hours=2), max_points=8))

        self.mock_get_session_start_rule.return_value = session_start_rule

        with self.temporarily_switch_to_user(self.student_participation.user):
            resp = self.client.get(self.get_view_start_flow_url(self.flow_id))
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertResponseContextEqual(resp, "may_start", True)
            self.assertResponseContextEqual(
                resp, "start_may_decrease_grade", False)

            past_sessions_and_properties = resp.context[
                "past_sessions_and_properties"]
            self.assertEqual(len(past_sessions_and_properties), 0)

    def test_get_may_list_existing_sessions(self):
        session_start_rule = self.get_hacked_session_start_rule()

        # create 2 session with different access_rule and grading_rule
        fs1 = self.get_test_flow_session(in_progress=False,
Dong Zhuang's avatar
Dong Zhuang committed
                                         start_time=now() - timedelta(days=3))
Dong Zhuang's avatar
Dong Zhuang committed
        fs2 = self.get_test_flow_session(in_progress=True,
Dong Zhuang's avatar
Dong Zhuang committed
                                         start_time=now() - timedelta(days=2),
                                         completion_time=None)
Dong Zhuang's avatar
Dong Zhuang committed

        access_rule_for_session1 = self.get_hacked_session_access_rule(
Dong Zhuang's avatar
Dong Zhuang committed
            permissions=[fperm.cannot_see_flow_result]
Dong Zhuang's avatar
Dong Zhuang committed
        )

        access_rule_for_session2 = self.get_hacked_session_access_rule(
            permissions=[
Dong Zhuang's avatar
Dong Zhuang committed
                fperm.view,
                fperm.submit_answer,
                fperm.end_session,
                fperm.see_answer_after_submission]
Dong Zhuang's avatar
Dong Zhuang committed
        )

        grading_rule_for_session1 = self.get_hacked_session_grading_rule(
            due=None, max_points=10
        )
        grading_rule_for_session2 = self.get_hacked_session_grading_rule(
            due=now() - timedelta(days=1), max_points=9
        )

        new_session_grading_rule = self.get_hacked_session_grading_rule(
            due=now() + timedelta(hours=2), max_points=8
        )

        self.mock_get_session_start_rule.return_value = session_start_rule

        self.mock_get_session_access_rule.side_effect = [
            access_rule_for_session1, access_rule_for_session2
        ]

        self.mock_get_session_grading_rule.side_effect = [
            grading_rule_for_session1, grading_rule_for_session2,
            new_session_grading_rule
        ]

        with self.temporarily_switch_to_user(self.student_participation.user):
            resp = self.client.get(self.get_view_start_flow_url(self.flow_id))
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertResponseContextEqual(resp, "may_start", True)
            self.assertResponseContextEqual(
                resp, "start_may_decrease_grade", True)
            past_sessions_and_properties = resp.context[
                "past_sessions_and_properties"]
            self.assertEqual(len(past_sessions_and_properties), 2)

            psap_session1, psap_property1 = past_sessions_and_properties[0]
            self.assertEqual(psap_session1, fs1)
            self.assertEqual(psap_property1.may_view, False)
            self.assertEqual(psap_property1.may_modify, False)
            self.assertEqual(psap_property1.due, None)
            self.assertEqual(psap_property1.grade_shown, False)

            psap_session2, psap_property2 = past_sessions_and_properties[1]
            self.assertEqual(psap_session2, fs2)
            self.assertEqual(psap_property2.may_view, True)
            self.assertEqual(psap_property2.may_modify, True)
            self.assertIsNotNone(psap_property2.due)
            self.assertEqual(psap_property2.grade_shown, True)

    def test_get_not_may_list_existing_sessions(self):
        session_start_rule = self.get_hacked_session_start_rule(
            may_start_new_session=False,
            may_list_existing_sessions=False,
        )

        # create 2 session with different access_rule and grading_rule
        self.get_test_flow_session(in_progress=False,
                                   start_time=now() - timedelta(days=3))
        self.get_test_flow_session(in_progress=True,
                                   start_time=now() - timedelta(days=2),
                                   completion_time=None)

        self.mock_get_session_start_rule.return_value = session_start_rule

        self.mock_get_session_grading_rule.return_value = (
            self.get_hacked_session_grading_rule(
                due=now() + timedelta(hours=2), max_points=8))

        with self.temporarily_switch_to_user(self.student_participation.user):
            resp = self.client.get(self.get_view_start_flow_url(self.flow_id))
Dong Zhuang's avatar
Dong Zhuang committed
            self.assertResponseContextEqual(resp, "may_start", False)
            self.assertResponseContextEqual(
                resp, "start_may_decrease_grade", False)

            past_sessions_and_properties = resp.context[
                "past_sessions_and_properties"]

            self.assertEqual(len(past_sessions_and_properties), 0)

        self.assertEqual(self.mock_get_session_grading_rule.call_count, 0)
        self.assertEqual(self.mock_get_session_access_rule.call_count, 0)


class PostStartFlowTest(SingleCourseTestMixin, TestCase):
    # test flow.post_start_flow

    flow_id = QUIZ_FLOW_ID

    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed

        fake_get_login_exam_ticket = mock.patch("course.flow.get_login_exam_ticket")
        self.mock_get_login_exam_ticket = fake_get_login_exam_ticket.start()
        self.addCleanup(fake_get_login_exam_ticket.stop)

        fake_flow_context = mock.patch("course.flow.FlowContext")
        self.mock_flow_context = fake_flow_context.start()
        self.fctx = mock.MagicMock()
        self.fctx.flow_id = self.flow_id
        self.fctx.flow_desc = dict_to_struct(
            {"title": "test page title", "description_html": "foo bar"})
        self.mock_flow_context.return_value = self.fctx
        self.addCleanup(fake_flow_context.stop)

        fake_get_session_start_rule = mock.patch(
            "course.flow.get_session_start_rule")
        self.mock_get_session_start_rule = fake_get_session_start_rule.start()
        self.addCleanup(fake_get_session_start_rule.stop)

        fake_get_session_access_rule = mock.patch(
            "course.flow.get_session_access_rule")
        self.mock_get_session_access_rule = fake_get_session_access_rule.start()
        self.addCleanup(fake_get_session_access_rule.stop)

        fake_lock_down_if_needed = mock.patch(
            "course.flow.lock_down_if_needed")
        self.mock_lock_down_if_needed = fake_lock_down_if_needed.start()
        self.addCleanup(fake_lock_down_if_needed.stop)

        fake_start_flow = mock.patch("course.flow.start_flow")
        self.mock_start_flow = fake_start_flow.start()
        self.addCleanup(fake_start_flow.stop)

Dong Zhuang's avatar
Dong Zhuang committed
    default_session_access_rule = {"permissions": []}
Dong Zhuang's avatar
Dong Zhuang committed

    def test_cooldown_seconds_worked(self):
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.mock_get_session_start_rule.return_value = \
                self.get_hacked_session_start_rule()

            mock_session = mock.MagicMock()
            mock_session.id = 0
            mock_session.pk = 0
            self.mock_start_flow.return_value = mock_session

            # create an existing session started recently
Dong Zhuang's avatar
Dong Zhuang committed
            factories.FlowSessionFactory(participation=self.student_participation)

            self.start_flow(self.flow_id, ignore_cool_down=False,
                            assume_success=False)
            self.assertEqual(self.mock_start_flow.call_count, 0)
            self.assertEqual(self.mock_get_session_access_rule.call_count, 0)
            self.assertEqual(self.mock_get_login_exam_ticket.call_count, 1)
            self.assertEqual(self.mock_lock_down_if_needed.call_count, 0)

    def test_cooldown_seconds_dued(self):
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.mock_get_session_start_rule.return_value = (
                self.get_hacked_session_start_rule())

            mock_session = mock.MagicMock()
            mock_session.id = 0
            mock_session.pk = 0
            self.mock_start_flow.return_value = mock_session

            # create an existing session started recently
Dong Zhuang's avatar
Dong Zhuang committed
            factories.FlowSessionFactory(
                participation=self.student_participation,
                start_time=now() - timedelta(seconds=11))

            self.start_flow(self.flow_id, ignore_cool_down=False,
                            assume_success=False)
            self.assertEqual(self.mock_start_flow.call_count, 1)
            self.assertEqual(self.mock_get_session_access_rule.call_count, 1)
            self.assertEqual(self.mock_get_login_exam_ticket.call_count, 1)
            self.assertEqual(self.mock_lock_down_if_needed.call_count, 1)

    def test_not_may_start(self):
        with self.temporarily_switch_to_user(self.student_participation.user):
            self.mock_get_session_start_rule.return_value = \
                self.get_hacked_session_start_rule(
                    may_start_new_session=False,
                )
            resp = self.start_flow(self.flow_id, ignore_cool_down=True,
                                   assume_success=False)
            self.assertEqual(resp.status_code, 403)

            self.assertEqual(self.mock_start_flow.call_count, 0)
            self.assertEqual(self.mock_get_session_access_rule.call_count, 0)
            self.assertEqual(self.mock_get_login_exam_ticket.call_count, 1)
            self.assertEqual(self.mock_lock_down_if_needed.call_count, 0)

    def test_start_session_for_anonymous(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.mock_get_session_start_rule.return_value = \
            self.get_hacked_session_start_rule(
                may_start_new_session=True,
            )

        mock_session = mock.MagicMock()
        mock_session.id = 0
        mock_session.pk = 0
        self.mock_start_flow.return_value = mock_session

        resp = self.start_flow(self.flow_id, ignore_cool_down=True,
                               assume_success=False)
        self.assertEqual(resp.status_code, 302)

        self.assertEqual(self.mock_start_flow.call_count, 1)
        self.assertEqual(self.mock_get_session_access_rule.call_count, 1)
        self.assertEqual(self.mock_get_login_exam_ticket.call_count, 1)
        self.assertEqual(self.mock_lock_down_if_needed.call_count, 1)


Dong Zhuang's avatar
Dong Zhuang committed
class ViewResumeFlowTest(SingleCourseTestMixin, TestCase):
    # test flow.view_resume_flow
Dong Zhuang's avatar
Dong Zhuang committed

    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed

        fake_get_now_or_fake_time = mock.patch(
            "course.flow.get_now_or_fake_time")
        self.mock_get_now_or_fake_time = fake_get_now_or_fake_time.start()
        self.addCleanup(fake_get_now_or_fake_time.stop)

        fake_get_and_check_flow_session = mock.patch(
            "course.flow.get_and_check_flow_session")
        self.mock_get_and_check_flow_session = (
            fake_get_and_check_flow_session.start())
        self.addCleanup(fake_get_and_check_flow_session.stop)

        fake_get_login_exam_ticket = mock.patch("course.flow.get_login_exam_ticket")
        self.mock_get_login_exam_ticket = fake_get_login_exam_ticket.start()
        self.addCleanup(fake_get_login_exam_ticket.stop)

        fake_lock_down_if_needed = mock.patch(
            "course.flow.lock_down_if_needed")
        self.mock_lock_down_if_needed = fake_lock_down_if_needed.start()
        self.addCleanup(fake_lock_down_if_needed.stop)

        fake_get_session_access_rule = mock.patch(
            "course.flow.get_session_access_rule")
        self.mock_get_session_access_rule = fake_get_session_access_rule.start()
        self.addCleanup(fake_get_session_access_rule.stop)

Dong Zhuang's avatar
Dong Zhuang committed
    def test(self):
        fs = factories.FlowSessionFactory(participation=self.student_participation)
Dong Zhuang's avatar
Dong Zhuang committed
        faked_now_datetime = mock.MagicMock()
        self.mock_get_now_or_fake_time.return_value = faked_now_datetime
Dong Zhuang's avatar
Dong Zhuang committed
        faked_ticket = mock.MagicMock()
        self.mock_get_login_exam_ticket.return_value = faked_ticket

        self.mock_get_and_check_flow_session.return_value = fs

        resp = self.client.get(self.get_resume_flow_url(
Dong Zhuang's avatar
Dong Zhuang committed
            course_identifier=self.course.identifier, flow_session_id=fs.pk))

        self.assertRedirects(
            resp, self.get_page_url_by_ordinal(
                page_ordinal=0, flow_session_id=fs.pk),
            fetch_redirect_response=False)

        self.assertEqual(self.mock_get_and_check_flow_session.call_count, 1)
        self.assertEqual(self.mock_get_login_exam_ticket.call_count, 1)
        self.assertEqual(self.mock_get_session_access_rule.call_count, 1)
        self.assertIn(
            faked_now_datetime, self.mock_get_session_access_rule.call_args[0])
        self.assertIn(
            "facilities", self.mock_get_session_access_rule.call_args[1])
        self.assertEqual(
            self.mock_get_session_access_rule.call_args[1]["login_exam_ticket"],
            faked_ticket)

        self.assertEqual(self.mock_lock_down_if_needed.call_count, 1)
Dong Zhuang's avatar
Dong Zhuang committed


class GetAndCheckFlowSessionTest(SingleCourseTestMixin, TestCase):
    # test flow.get_and_check_flow_session

    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.rf = RequestFactory()

    def get_pctx(self, request_user):
        request = self.rf.get(self.get_course_page_url())
        request.user = request_user

        from course.utils import CoursePageContext
        pctx = CoursePageContext(request, self.course.identifier)
        return pctx

    def test_object_does_not_exist(self):
        pctx = self.get_pctx(self.student_participation.user)

        with self.assertRaises(http.Http404):
            flow.get_and_check_flow_session(pctx, flow_session_id=100)

    def test_flow_session_course_not_match(self):
        another_course = factories.CourseFactory(identifier="another-course")
        another_course_fs = factories.FlowSessionFactory(
            participation=factories.ParticipationFactory(course=another_course)
        )

        pctx = self.get_pctx(self.student_participation.user)

        with self.assertRaises(http.Http404):
            flow.get_and_check_flow_session(
                pctx, flow_session_id=another_course_fs.pk)

    def test_anonymous_session(self):
        anonymous_session = factories.FlowSessionFactory(
            course=self.course, participation=None, user=None
        )

        pctx = self.get_pctx(request_user=AnonymousUser())

        self.assertEqual(
            flow.get_and_check_flow_session(
                pctx, flow_session_id=anonymous_session.pk),
            anonymous_session)

    def test_my_session(self):
        my_session = factories.FlowSessionFactory(
            course=self.course, participation=self.student_participation
        )

        pctx = self.get_pctx(request_user=self.student_participation.user)

        self.assertEqual(
            flow.get_and_check_flow_session(
                pctx, flow_session_id=my_session.pk),
            my_session)

    def test_not_my_session_anonymous(self):
        my_session = factories.FlowSessionFactory(
            course=self.course, participation=self.student_participation
        )

        from django.contrib.auth.models import AnonymousUser
        pctx = self.get_pctx(request_user=AnonymousUser())

        with self.assertRaises(PermissionDenied) as cm:
            flow.get_and_check_flow_session(
                pctx, flow_session_id=my_session.pk)
        expected_error_msg = "may not view other people's sessions"
        self.assertIn(expected_error_msg, str(cm.exception))

    def test_view_student_session(self):
        student_session = factories.FlowSessionFactory(
            course=self.course, participation=self.student_participation
        )

        pctx = self.get_pctx(request_user=self.instructor_participation.user)

        self.assertEqual(
            flow.get_and_check_flow_session(
                pctx, flow_session_id=student_session.pk),
            student_session)


class WillReceiveFeedbackTest(unittest.TestCase):
    # test flow.will_receive_feedback
Dong Zhuang's avatar
Dong Zhuang committed
    def test_false(self):
        combinations = [(frozenset([fp]), False) for fp in
                        get_flow_permissions_list(
                            excluded=[fperm.see_correctness,
                                      fperm.see_answer_after_submission])]
        combinations.append(([], False))
Dong Zhuang's avatar
Dong Zhuang committed
        for permissions, will_receive in combinations:
            with self.subTest(permissions=permissions):
                self.assertEqual(
                    flow.will_receive_feedback(permissions),
                    will_receive)

    def test_true(self):
        combinations = [
            (frozenset([fp, fperm.see_correctness]), True)
            for fp in get_flow_permissions_list(
                excluded=[fperm.see_correctness])]
Dong Zhuang's avatar
Dong Zhuang committed
        combinations2 = [
            (frozenset([fp, fperm.see_answer_after_submission]), True)
            for fp in get_flow_permissions_list(
                excluded=[fperm.see_answer_after_submission])]
        combinations.extend(combinations2)
Dong Zhuang's avatar
Dong Zhuang committed
        for permissions, will_receive in combinations:
            with self.subTest(permissions=permissions):
                self.assertEqual(
                    flow.will_receive_feedback(permissions),
                    will_receive)
Josh Asplund's avatar
Josh Asplund committed
@pytest.mark.django_db
class MaySendEmailAboutFlowPageTest(TestCase):
Dong Zhuang's avatar
Dong Zhuang committed
    # test flow.may_send_email_about_flow_page
Dong Zhuang's avatar
Dong Zhuang committed
    @classmethod
    def setUpClass(cls):
        cls.course = course = factories.CourseFactory()
        participation = factories.ParticipationFactory(course=course)
        cls.fs = factories.FlowSessionFactory(
            course=course, participation=participation)
        cls.fs_no_participation_no_user = factories.FlowSessionFactory(
            course=course, participation=None, user=None)
        cls.fs_no_user = factories.FlowSessionFactory(
            course=course, participation=participation, user=None)
Dong Zhuang's avatar
Dong Zhuang committed
    @classmethod
    def tearDownClass(cls):
        cls.course.delete()
Dong Zhuang's avatar
Dong Zhuang committed
    def test_false_has_no_send_email_about_flow_page_fperm(self):
        combinations = [(frozenset([fp]), False) for fp in
                        get_flow_permissions_list(
                            excluded=[fperm.send_email_about_flow_page])]
        combinations.append((frozenset([]), False))

        for session in [self.fs, self.fs_no_user, self.fs_no_participation_no_user]:
            for permissions, may_send in combinations:
                with self.subTest(session=session, permissions=permissions):
                    self.assertEqual(
                        flow.may_send_email_about_flow_page(session, permissions),
                        may_send)

    def test_false_flow_session_has_no_participation_or_no_user(self):
        combinations = [
            (frozenset([fp, fperm.send_email_about_flow_page]), False)
            for fp in get_flow_permissions_list(
                excluded=[fperm.send_email_about_flow_page])]

        for session in [self.fs_no_user, self.fs_no_participation_no_user]:
            for permissions, may_send in combinations:
                with self.subTest(session=session, permissions=permissions):
                    self.assertEqual(
                        flow.may_send_email_about_flow_page(session, permissions),
                        may_send)

    def test_true(self):
        combinations = [
            (frozenset([fp, fperm.send_email_about_flow_page]), True)
            for fp in get_flow_permissions_list(
                excluded=[fperm.send_email_about_flow_page])]
        combinations.append((frozenset([fperm.send_email_about_flow_page]), True))
Dong Zhuang's avatar
Dong Zhuang committed
        for permissions, may_send in combinations:
            with self.subTest(session=self.fs, permissions=permissions):
                self.assertEqual(
                    flow.may_send_email_about_flow_page(self.fs, permissions),
                    may_send)
Dong Zhuang's avatar
Dong Zhuang committed


class GetPageBehaviorTest(unittest.TestCase):
    # test flow.get_page_behavior
    def setUp(self):
        self.page = mock.MagicMock()

    def assertShowCorrectness(self, behavior, perms, show=True):  # noqa
        self.assertEqual(
            behavior.show_correctness, show,
            "behavior.show_correctness unexpected to be {} with {}"
            .format(not show, ", ".join(perms)))
Dong Zhuang's avatar
Dong Zhuang committed

    def assertShowAnswer(self, behavior, perms, show=True):  # noqa
        self.assertEqual(behavior.show_answer, show,
                         "behavior.show_answer unexpected to be {} with {}"
                         .format(not show, ", ".join(perms)))
Dong Zhuang's avatar
Dong Zhuang committed

    def assertMayChangeAnswer(self, behavior, perms, may_change=True):  # noqa
        self.assertEqual(behavior.may_change_answer, may_change,
                         "behavior.may_change_answer unexpected to be {} with {}"
                         .format(not may_change, ", ".join(perms)))
Dong Zhuang's avatar
Dong Zhuang committed

    def test_show_correctness1(self):
        # not expects_answer
        self.page.expects_answer.return_value = False

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list()]
Dong Zhuang's avatar
Dong Zhuang committed
        combinations.append(([], False))

        params = list(itertools.product([True, False], repeat=5))

        for p in params:
            (session_in_progress, answer_was_graded, generate_grade,
             is_unenrooled_session, viewing_prior_version) = p
            for permissions, show in combinations:
                with self.subTest(
                        permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=answer_was_graded,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                self.assertShowCorrectness(behavior, permissions, show)

    def test_show_correctness2(self):
        # expects_answer, answer_was_graded
        self.page.expects_answer.return_value = True

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                            excluded=fperm.see_correctness)]
        combinations.append(([], False))
        combinations.append((frozenset([fperm.see_correctness]), True))

        params = list(itertools.product([True, False], repeat=4))

        for p in params:
            (session_in_progress, generate_grade,
             is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=True,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                self.assertShowCorrectness(behavior, permissions, show)

    def test_show_correctness3(self):
        # expects_answer, answer was NOT graded
        self.page.expects_answer.return_value = True

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list()]
Dong Zhuang's avatar
Dong Zhuang committed
        combinations.append(([], False))

        params = list(itertools.product([True, False], repeat=4))

        for p in params:
            (session_in_progress, generate_grade,
             is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=False,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                self.assertShowCorrectness(behavior, permissions, show)

    def test_show_answer1(self):
        # not expects_answer
        self.page.expects_answer.return_value = False

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                            excluded=[
                                fperm.see_answer_before_submission,
                                fperm.see_answer_after_submission])]
        combinations.append(([], False))
        combinations.append(
            (frozenset([fperm.see_answer_before_submission]), True))
        combinations.append(
            (frozenset([fperm.see_answer_after_submission]), True))
        combinations.append(
            (frozenset([fperm.see_answer_after_submission,
                        fperm.see_answer_before_submission]), True))

        params = list(itertools.product([True, False], repeat=5))

        for p in params:
            (session_in_progress, generate_grade, answer_was_graded,
             is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=answer_was_graded,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                    self.assertShowAnswer(behavior, permissions, show)

    def test_show_answer2(self):
        # expects_answer, answer was NOT graded
        self.page.expects_answer.return_value = True

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                            excluded=[
                                fperm.see_answer_before_submission])]
        combinations.append(([], False))
        combinations.append(
            (frozenset([fperm.see_answer_before_submission]), True))

        params = list(itertools.product([True, False], repeat=4))

        for p in params:
            (session_in_progress, generate_grade,
             is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=False,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                    self.assertShowAnswer(behavior, permissions, show)

    def test_show_answer3(self):
        # expects_answer, answer was graded, session not in progress

        self.page.expects_answer.return_value = True

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                            excluded=[
                                fperm.see_answer_before_submission,
                                fperm.see_answer_after_submission])]
        combinations.append(([], False))

        # when session not in_progress, see_answer_after_submission dominates
        combinations.extend(
            [(frozenset([fp, fperm.see_answer_after_submission]), True)
Dong Zhuang's avatar
Dong Zhuang committed
             for fp in get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                excluded=fperm.see_answer_after_submission)])

        combinations.append(
            (frozenset([fperm.see_answer_before_submission]), True))
        combinations.append(
            (frozenset([fperm.see_answer_after_submission]), True))

        # see_answer_before_submission also dominates
        combinations.extend(
            [(frozenset([fp, fperm.see_answer_before_submission]), True)
Dong Zhuang's avatar
Dong Zhuang committed
             for fp in get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                excluded=fperm.see_answer_before_submission)])

        params = list(itertools.product([True, False], repeat=3))

        for p in params:
            (generate_grade, is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=False,
                        answer_was_graded=True,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                    self.assertShowAnswer(behavior, permissions, show)

    def test_show_answer4(self):
        # expects_answer, answer was graded, session in progress

        self.page.expects_answer.return_value = True

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                            excluded=[
                                fperm.see_answer_before_submission,
                                fperm.see_answer_after_submission])]
        combinations.append(([], False))
        combinations.append(
            (frozenset([fperm.see_answer_before_submission]), True))

        combinations.append(
            (frozenset([fperm.see_answer_after_submission]), True))

        # if see_answer_before_submission dominate not present,
        # change_answer dominates
        combinations.extend(
            [(frozenset([fp, fperm.change_answer]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
             get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                 excluded=fperm.see_answer_before_submission)])

        # see_answer_before_submission dominates
        combinations.extend(
            [(frozenset([fp, fperm.see_answer_before_submission]), True)
Dong Zhuang's avatar
Dong Zhuang committed
             for fp in get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                excluded=fperm.see_answer_before_submission)])

        params = list(itertools.product([True, False], repeat=3))

        for p in params:
            (generate_grade, is_unenrooled_session, viewing_prior_version) = p

            for permissions, show in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=True,
                        answer_was_graded=True,
                        generates_grade=generate_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=viewing_prior_version,
                    )
                    self.assertShowAnswer(behavior, permissions, show)

    def test_may_change_answer1(self):
        # viewing_prior_version dominates

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list()]
Dong Zhuang's avatar
Dong Zhuang committed

        params = list(itertools.product([True, False], repeat=5))

        for p in params:
            (self.page.expects_answer.return_value,
             session_in_progress, answer_was_graded,
             generates_grade,
             is_unenrooled_session) = p

            for permissions, may_change in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=answer_was_graded,
                        generates_grade=generates_grade,
                        is_unenrolled_session=is_unenrooled_session,
                        viewing_prior_version=True,
                    )
                    self.assertMayChangeAnswer(behavior, permissions, may_change)

    def test_may_change_answer2(self):
        # session_in_progress dominates

        combinations = [(frozenset([fp]), False) for fp in
Dong Zhuang's avatar
Dong Zhuang committed
                        get_flow_permissions_list()]
Dong Zhuang's avatar
Dong Zhuang committed

        params = list(itertools.product([True, False], repeat=4))

        for p in params:
            (self.page.expects_answer.return_value,
             answer_was_graded, generates_grade,
             is_unenrooled_session) = p

            for permissions, may_change in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=False,
                        answer_was_graded=answer_was_graded,
                        generates_grade=generates_grade,
                        is_unenrolled_session=is_unenrooled_session,
                    )
                    self.assertMayChangeAnswer(behavior, permissions, may_change)

    def test_may_change_answer3(self):
        # no submit_answer dominates

        combinations = [(frozenset([fp]), False)
Dong Zhuang's avatar
Dong Zhuang committed
                        for fp in get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                excluded=fperm.submit_answer)]

        params = list(itertools.product([True, False], repeat=5))

        for p in params:
            (self.page.expects_answer.return_value,
             session_in_progress, answer_was_graded,
             generates_grade,
             is_unenrooled_session) = p

            for permissions, may_change in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=answer_was_graded,
                        generates_grade=generates_grade,
                        is_unenrolled_session=is_unenrooled_session,
                    )
                    self.assertMayChangeAnswer(behavior, permissions, may_change)

    def test_may_change_answer4(self):
        # not answer_was_graded or (flow_permission.change_answer in permissions)

        combinations = [(frozenset([fp, fperm.submit_answer]), False)
Dong Zhuang's avatar
Dong Zhuang committed
                        for fp in get_flow_permissions_list(
Dong Zhuang's avatar
Dong Zhuang committed
                excluded=fperm.change_answer)]

        params = list(itertools.product([True, False], repeat=4))

        for p in params:
            (self.page.expects_answer.return_value,
             session_in_progress, generates_grade, is_unenrooled_session) = p

            for permissions, may_change in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=session_in_progress,
                        answer_was_graded=True,
                        generates_grade=generates_grade,
                        is_unenrolled_session=is_unenrooled_session,
                    )
                    self.assertMayChangeAnswer(behavior, permissions, may_change)

    def test_may_change_answer6(self):
        # generates_grade and not is_unenrolled_session or (not generates_grade)

        combinations = [
            (frozenset([fp, fperm.submit_answer, fperm.change_answer]), False)
Dong Zhuang's avatar
Dong Zhuang committed
            for fp in get_flow_permissions_list()]
Dong Zhuang's avatar
Dong Zhuang committed

        params = list(itertools.product([True, False], repeat=1))

        for p in params:
            (self.page.expects_answer.return_value) = p

            for permissions, may_change in combinations:
                with self.subTest(permissions=permissions):
                    behavior = flow.get_page_behavior(
                        self.page,
                        permissions=permissions,
                        session_in_progress=True,
                        answer_was_graded=False,
                        generates_grade=True,
                        is_unenrolled_session=True,
                    )
                    self.assertMayChangeAnswer(behavior, permissions, may_change)

    def test_may_change_answer7(self):
        # cases that may_change_answer
        from collections import namedtuple
        Conf = namedtuple(
            "Conf", [
Andreas Klöckner's avatar
Andreas Klöckner committed
                "extra_fperms",
                "answer_was_graded",
                "generates_grade",
                "is_unenrolled_session",
Dong Zhuang's avatar
Dong Zhuang committed
            ]
        )

        confs = (
            Conf([], False, False, True),
            Conf([], False, False, False),
            Conf([fperm.change_answer], True, False, True),
            Conf([fperm.change_answer], True, False, False),
            Conf([fperm.change_answer], False, False, True),
            Conf([fperm.change_answer], False, False, False),
        )

        params = list(itertools.product([True, False], repeat=1))

        for conf in confs:
            combinations = []
Dong Zhuang's avatar
Dong Zhuang committed
            for fp in get_flow_permissions_list():
Dong Zhuang's avatar
Dong Zhuang committed
                fperms = [fp, fperm.submit_answer]
                if conf.extra_fperms:
                    fperms.extend(conf.extra_fperms)

                combinations.append((frozenset(fperms), True))

                for p in params:
                    (self.page.expects_answer.return_value) = p

                for permissions, may_change in combinations:
                    with self.subTest(
                            permissions=permissions,
                            answer_was_graded=conf.answer_was_graded,
                            generates_grade=conf.generates_grade,
                            is_unenrolled_session=conf.is_unenrolled_session):
                        behavior = flow.get_page_behavior(
                            self.page,
                            permissions=permissions,
                            session_in_progress=True,
                            answer_was_graded=conf.answer_was_graded,
                            generates_grade=conf.generates_grade,
                            is_unenrolled_session=conf.is_unenrolled_session,
                        )
                        self.assertMayChangeAnswer(behavior, permissions, may_change)


class AddButtonsToFormTest(unittest.TestCase):
    # test flow.add_buttons_to_form
    def setUp(self):
Dong Zhuang's avatar
Dong Zhuang committed
        self.flow_session = mock.MagicMock()
        self.fpctx = mock.MagicMock()

        fake_will_receive_feedback = mock.patch("course.flow.will_receive_feedback")
        self.mock_will_receive_feedback = fake_will_receive_feedback.start()
        self.addCleanup(fake_will_receive_feedback.stop)

    def fake_flow_session_page_count(self, count):
        self.flow_session.page_count = count

    def fake_fpctx_page_data_page_ordinal(self, ordinal):
        self.fpctx.page_data.page_ordinal = ordinal

    def get_form_submit_inputs(self, form):
        from crispy_forms.layout import Submit
        inputs = [
            (input.name, input.value) for input in form.helper.inputs
            if isinstance(input, Submit)
        ]
        names = list(dict(inputs).keys())
        values = list(dict(inputs).values())
        return names, values

    def test_not_add_save_button(self):
        class MyForm(StyledForm):
            show_save_button = False

        form = MyForm()