From eb7d0703b2d93c36c63b0ae51b17122969aa0a38 Mon Sep 17 00:00:00 2001 From: dzhuang Date: Wed, 31 Jan 2018 14:06:44 +0800 Subject: [PATCH] Refactor sandbox and some tests. --- course/sandbox.py | 25 +++-- tests/base_test_mixins.py | 49 ++++++++++ tests/test_grades.py | 19 ++-- tests/test_pages/__init__.py | 1 + .../test_generic.py} | 93 +++++++++++++++++-- tests/test_sandbox.py | 17 +++- 6 files changed, 173 insertions(+), 31 deletions(-) create mode 100644 tests/test_pages/__init__.py rename tests/{test_pages.py => test_pages/test_generic.py} (88%) diff --git a/course/sandbox.py b/course/sandbox.py index 18a7cdbd..41e4092a 100644 --- a/course/sandbox.py +++ b/course/sandbox.py @@ -49,6 +49,14 @@ if False: # }}} +# {{{ sandbox session key prefix + +PAGE_SESSION_KEY_PREFIX = "cf_validated_sandbox_page" +ANSWER_DATA_SESSION_KEY_PREFIX = "cf_page_sandbox_answer_data" +PAGE_DATA_SESSION_KEY_PREFIX = "cf_page_sandbox_page_data" + +# }}} + # {{{ sandbox form @@ -191,6 +199,11 @@ class PageSandboxForm(SandboxForm): # {{{ page sandbox +def make_sandbox_session_key(prefix, course_identifier): + # type: (Text, Text) -> Text + return "%s:%s" % (prefix, course_identifier) + + @course_view def view_page_sandbox(pctx): # type: (CoursePageContext) -> http.HttpResponse @@ -202,12 +215,12 @@ def view_page_sandbox(pctx): from relate.utils import dict_to_struct, Struct import yaml - PAGE_SESSION_KEY = ( # noqa - "cf_validated_sandbox_page:" + pctx.course.identifier) - ANSWER_DATA_SESSION_KEY = ( # noqa - "cf_page_sandbox_answer_data:" + pctx.course.identifier) - PAGE_DATA_SESSION_KEY = ( # noqa - "cf_page_sandbox_page_data:" + pctx.course.identifier) + PAGE_SESSION_KEY = make_sandbox_session_key( # noqa + PAGE_SESSION_KEY_PREFIX, pctx.course.identifier) + ANSWER_DATA_SESSION_KEY = make_sandbox_session_key( # noqa + ANSWER_DATA_SESSION_KEY_PREFIX, pctx.course.identifier) + PAGE_DATA_SESSION_KEY = make_sandbox_session_key( # noqa + PAGE_DATA_SESSION_KEY_PREFIX, pctx.course.identifier) request = pctx.request page_source = pctx.request.session.get(PAGE_SESSION_KEY) diff --git a/tests/base_test_mixins.py b/tests/base_test_mixins.py index 37b4eb30..df6e7f29 100644 --- a/tests/base_test_mixins.py +++ b/tests/base_test_mixins.py @@ -1053,6 +1053,55 @@ class CoursesTestMixinBase(SuperuserCreateMixin): self.assertIsNotNone(result, "The query returns None") return result + def download_all_submissions_url(self, flow_id, course_identifier): + params = {"course_identifier": course_identifier, + "flow_id": flow_id} + return reverse("relate-download_all_submissions", kwargs=params) + + def get_download_all_submissions(self, flow_id, course_identifier=None): + if course_identifier is None: + course_identifier = self.get_default_course_identifier() + + return self.c.get( + self.download_all_submissions_url(flow_id, course_identifier)) + + def post_download_all_submissions_by_group_page_id( + self, group_page_id, flow_id, course_identifier=None, **kwargs): + """ + :param group_page_id: format: group_id/page_id + :param flow_id: + :param course_identifier: + :param kwargs: for updating the default post_data + :return: response + """ + if course_identifier is None: + course_identifier = self.get_default_course_identifier() + + data = {'restrict_to_rules_tag': '<<>>', + 'which_attempt': 'last', + 'extra_file': '', 'download': 'Download', + 'page_id': group_page_id, + 'non_in_progress_only': 'on'} + + data.update(kwargs) + + return self.c.post( + self.download_all_submissions_url(flow_id, course_identifier), + data=data + ) + + def get_flow_page_analytics(self, flow_id, group_id, page_id, + course_identifier=None): + if course_identifier is None: + course_identifier = self.get_default_course_identifier() + + params = {"course_identifier": course_identifier, + "flow_id": flow_id, + "group_id": group_id, + "page_id": page_id} + + return self.c.get(reverse("relate-page_analytics", kwargs=params)) + class SingleCourseTestMixin(CoursesTestMixinBase): courses_setup_list = SINGLE_COURSE_SETUP_LIST diff --git a/tests/test_grades.py b/tests/test_grades.py index 4023eab6..692dce7d 100644 --- a/tests/test_grades.py +++ b/tests/test_grades.py @@ -183,23 +183,18 @@ class GradeTestMixin(SingleCoursePageTestMixin): self.assertEqual(resp.status_code, 200) def test_view_download_submissions(self): - params = {"course_identifier": self.course.identifier, - "flow_id": self.flow_id} - # Check download form first - resp = self.c.get(reverse("relate-download_all_submissions", - kwargs=params)) + resp = self.get_download_all_submissions(flow_id=self.flow_id) self.assertEqual(resp.status_code, 200) # Check download here, only test intro page # Maybe we should include an "all" option in the future? - data = {'restrict_to_rules_tag': ['<<>>'], - 'which_attempt': ['last'], - 'extra_file': [''], 'download': ['Download'], - 'page_id': ['intro/welcome'], - 'non_in_progress_only': ['on']} - resp = self.c.post(reverse("relate-download_all_submissions", - kwargs=params), data) + resp = ( + self.post_download_all_submissions_by_group_page_id( + flow_id=self.flow_id, + group_page_id="intro/welcome") + ) + self.assertEqual(resp.status_code, 200) prefix, zip_file = resp["Content-Disposition"].split('=') self.assertEqual(prefix, "attachment; filename") diff --git a/tests/test_pages/__init__.py b/tests/test_pages/__init__.py new file mode 100644 index 00000000..7d3b0dd5 --- /dev/null +++ b/tests/test_pages/__init__.py @@ -0,0 +1 @@ +from .test_generic import QUIZ_FLOW_ID # noqa diff --git a/tests/test_pages.py b/tests/test_pages/test_generic.py similarity index 88% rename from tests/test_pages.py rename to tests/test_pages/test_generic.py index f994d10c..6c5bd11f 100644 --- a/tests/test_pages.py +++ b/tests/test_pages/test_generic.py @@ -82,6 +82,9 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, resp = self.post_answer_by_ordinal(1, {"answer": ['0.5']}) self.assertEqual(resp.status_code, 200) self.assertResponseMessagesContains(resp, MESSAGE_ANSWER_SAVED_TEXT) + + # Make sure the page is rendered with max_points + self.assertResponseContextEqual(resp, "max_points", 5) self.assertEqual(self.end_flow().status_code, 200) self.assertSessionScoreEqual(5) @@ -201,6 +204,10 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, last_answer_visit = self.get_last_answer_visit() self.assertEqual(last_answer_visit.answer["choice"], 8) + # }}} + + # {{{ fileupload questions + def test_fileupload_any(self): page_id = "anyup" ordinal = self.get_page_ordinal_via_page_id(page_id) @@ -208,7 +215,7 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, expected_count=0) with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.txt'), 'rb') as fp: + '../fixtures', 'test_file.txt'), 'rb') as fp: resp = self.post_answer_by_page_id( page_id, {"uploaded_file": fp}) fp.seek(0) @@ -229,7 +236,7 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, expected_count=0) with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.txt'), 'rb') as fp: + '../fixtures', 'test_file.txt'), 'rb') as fp: resp = self.post_answer_by_page_id( page_id, {"uploaded_file": fp}) fp.seek(0) @@ -241,7 +248,7 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.pdf'), 'rb') as fp: + '../fixtures', 'test_file.pdf'), 'rb') as fp: resp = self.post_answer_by_page_id( page_id, {"uploaded_file": fp}) self.assertEqual(resp.status_code, 200) @@ -268,7 +275,7 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, # wrong MIME type with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.txt'), 'rb') as fp: + '../fixtures', 'test_file.txt'), 'rb') as fp: resp = self.post_answer_by_page_id( page_id, {"uploaded_file": fp}) self.assertEqual(resp.status_code, 200) @@ -280,7 +287,7 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, expected_count=0) with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.pdf'), 'rb') as fp: + '../fixtures', 'test_file.pdf'), 'rb') as fp: resp = self.post_answer_by_page_id( page_id, {"uploaded_file": fp}) self.assertEqual(resp.status_code, 200) @@ -294,6 +301,40 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, self.assertEqual(last_answer_visit.answer["base64_data"], expected_result) self.assertSessionScoreEqual(None) + # }}} + + # {{{ optional page + + def test_optional_page_with_correct_answer(self): + page_id = "quarter" + resp = self.post_answer_by_page_id(page_id, {"answer": ['0.25']}) + self.assertEqual(resp.status_code, 200) + self.assertResponseMessagesContains(resp, MESSAGE_ANSWER_SAVED_TEXT) + + # Make sure the page is rendered with 0 max_points + self.assertResponseContextEqual(resp, "max_points", 0) + self.assertEqual(self.end_flow().status_code, 200) + self.assertResponseMessagesContains(resp, MESSAGE_ANSWER_SAVED_TEXT) + + # Even the answer is correct, there should be zero score. + self.assertSessionScoreEqual(0) + + def test_optional_page_with_wrong_answer(self): + page_id = "quarter" + resp = self.post_answer_by_page_id(page_id, {"answer": ['0.15']}) + self.assertEqual(resp.status_code, 200) + self.assertResponseMessagesContains(resp, MESSAGE_ANSWER_SAVED_TEXT) + + # Make sure the page is rendered with 0 max_points + self.assertResponseContextEqual(resp, "max_points", 0) + self.assertEqual(self.end_flow().status_code, 200) + self.assertResponseMessagesContains(resp, MESSAGE_ANSWER_SAVED_TEXT) + + # The answer is wrong, there should also be zero score. + self.assertSessionScoreEqual(0) + + # }}} + # {{{ tests on submission history dropdown def test_submit_history_failure_not_ajax(self): self.post_answer_by_ordinal(1, {"answer": ['0.5']}) @@ -316,6 +357,8 @@ class SingleCourseQuizPageTest(SingleCoursePageTestMixin, self.get_page_submit_history_url_by_ordinal(page_ordinal=1)) self.assertEqual(resp.status_code, 403) + # }}} + class SingleCourseQuizPageTestExtra(SingleCoursePageTestMixin, FallBackStorageMessageTestMixin, TestCase): @@ -348,8 +391,6 @@ class SingleCourseQuizPageTestExtra(SingleCoursePageTestMixin, self.get_page_submit_history_url_by_ordinal(page_ordinal=1)) self.assertEqual(resp.status_code, 403) - # }}} - class SingleCourseQuizPageGradeInterfaceTest(LocmemBackendTestsMixin, SingleCoursePageTestMixin, @@ -378,7 +419,7 @@ class SingleCourseQuizPageGradeInterfaceTest(LocmemBackendTestsMixin, def submit_any_upload_question(cls): with open( os.path.join(os.path.dirname(__file__), - 'fixtures', 'test_file.txt'), 'rb') as fp: + '../fixtures', 'test_file.txt'), 'rb') as fp: answer_data = {"uploaded_file": fp} cls.post_answer_by_page_id_class( cls.any_up_page_id, answer_data, **cls.default_flow_params) @@ -656,4 +697,40 @@ class SingleCourseQuizPageCodeQuestionTest( resp, "The human grader assigned 2/2 points.") self.assertSessionScoreEqual(2) + def test_code_human_feedback_page_grade3(self): + page_id = "py_simple_list" + resp = self.post_answer_by_page_id( + page_id, {"answer": ['b = [a + 1] * 50\r']}) + + # this is testing feedback.finish(0.3, feedback_msg) + # 2 * 0.3 = 0.6 + self.assertResponseContextAnswerFeedbackContainsFeedback( + resp, "The autograder assigned 0.6/2 points.") + self.assertResponseContextAnswerFeedbackContainsFeedback( + resp, "The elements in b have wrong values") + self.assertEqual(self.end_flow().status_code, 200) + + # The page is not graded before human grading. + self.assertSessionScoreEqual(None) + + def test_code_human_feedback_page_grade4(self): + page_id = "py_simple_list" + resp = self.post_answer_by_page_id( + page_id, {"answer": ['b = [a] * 50\r']}) + self.assertResponseContextAnswerFeedbackContainsFeedback( + resp, "b looks good") + self.assertEqual(self.end_flow().status_code, 200) + + grade_data = { + "grade_percent": ["100"], + "released": ["on"] + } + + resp = self.post_grade_by_page_id(page_id, grade_data) + self.assertTrue(resp.status_code, 200) + self.assertResponseContextAnswerFeedbackContainsFeedback( + resp, "The human grader assigned 1/1 points.") + + self.assertSessionScoreEqual(3) + # vim: fdm=marker diff --git a/tests/test_sandbox.py b/tests/test_sandbox.py index 4139bb38..31ab7469 100644 --- a/tests/test_sandbox.py +++ b/tests/test_sandbox.py @@ -26,6 +26,10 @@ from django.test import TestCase from django.urls import reverse from tests.base_test_mixins import SingleCourseTestMixin +from course.sandbox import ( + PAGE_SESSION_KEY_PREFIX, PAGE_DATA_SESSION_KEY_PREFIX, + ANSWER_DATA_SESSION_KEY_PREFIX, make_sandbox_session_key) + QUESTION_MARKUP = """ type: TextQuestion id: half @@ -89,23 +93,26 @@ class SingleCoursePageSandboxTestBaseMixin(SingleCourseTestMixin): return cls.get_page_sandbox_post_response(data) def get_sandbox_data_by_key(self, key): - return self.c.session.get("%s:%s" % (key, self.course.identifier)) + return self.c.session.get( + make_sandbox_session_key(key, self.course.identifier)) def get_sandbox_page_data(self): - return self.get_sandbox_data_by_key("cf_page_sandbox_page_data") + return self.get_sandbox_data_by_key(PAGE_DATA_SESSION_KEY_PREFIX) def get_sandbox_answer_data(self): - return self.get_sandbox_data_by_key("cf_page_sandbox_answer_data") + return self.get_sandbox_data_by_key(ANSWER_DATA_SESSION_KEY_PREFIX) def get_sandbox_page_session(self): - return self.get_sandbox_data_by_key("cf_validated_sandbox_page") + return self.get_sandbox_data_by_key(PAGE_SESSION_KEY_PREFIX) def assertSandboxHaveValidPage(self, resp): # noqa self.assertResponseContextEqual(resp, HAVE_VALID_PAGE, True) - def assertSandboxWarningTextContain(self, resp, expected_text): # noqa + def assertSandboxWarningTextContain(self, resp, expected_text, loose=False): # noqa warnings = self.get_response_context_value_by_name(resp, PAGE_WARNINGS) warnings_text = [w.text for w in warnings] + if loose: + warnings_text = "".join(warnings_text) self.assertIn(expected_text, warnings_text) def assertSandboxNotHaveValidPage(self, resp): # noqa -- GitLab