From 488923fd5cd3fa48d72aaa0be3c175a46b30c68d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 7 Dec 2013 19:53:23 -0500 Subject: [PATCH] Add a test for Django-based file uploads to problems. --- .../capa/capa/tests/response_xml_factory.py | 40 +++++++++--- .../tests/test_submitting_problems.py | 61 ++++++++++++++++++- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 4c015d6699..c2dd5c4da2 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -314,24 +314,43 @@ class CodeResponseXMLFactory(ResponseXMLFactory): return super(CodeResponseXMLFactory, self).build_xml(**kwargs) def create_response_element(self, **kwargs): - """ Create a XML element: + """ + Create a XML element. - Uses **kwargs: + Uses **kwargs: + + *initial_display*: The code that initially appears in the textbox + [DEFAULT: "Enter code here"] + *answer_display*: The answer to display to the student + [DEFAULT: "This is the correct answer!"] + *grader_payload*: A JSON-encoded string sent to the grader + [DEFAULT: empty dict string] + *allowed_files*: A space-separated string of file names. + [DEFAULT: None] + *required_files*: A space-separated string of file names. + [DEFAULT: None] - *initial_display*: The code that initially appears in the textbox - [DEFAULT: "Enter code here"] - *answer_display*: The answer to display to the student - [DEFAULT: "This is the correct answer!"] - *grader_payload*: A JSON-encoded string sent to the grader - [DEFAULT: empty dict string] """ # Get **kwargs initial_display = kwargs.get("initial_display", "Enter code here") answer_display = kwargs.get("answer_display", "This is the correct answer!") grader_payload = kwargs.get("grader_payload", '{}') + allowed_files = kwargs.get("allowed_files", None) + required_files = kwargs.get("required_files", None) # Create the element response_element = etree.Element("coderesponse") + + # If files are involved, create the element. + has_files = allowed_files or required_files + if has_files: + filesubmission_element = etree.SubElement(response_element, "filesubmission") + if allowed_files: + filesubmission_element.set("allowed_files", allowed_files) + if required_files: + filesubmission_element.set("required_files", required_files) + + # Create the element. codeparam_element = etree.SubElement(response_element, "codeparam") # Set the initial display text @@ -347,8 +366,9 @@ class CodeResponseXMLFactory(ResponseXMLFactory): grader_element.text = str(grader_payload) # Create the input within the response - input_element = etree.SubElement(response_element, "textbox") - input_element.set("mode", "python") + if not has_files: + input_element = etree.SubElement(response_element, "textbox") + input_element.set("mode", "python") return response_element diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py index 854750f8d1..cd328b5c47 100644 --- a/lms/djangoapps/courseware/tests/test_submitting_problems.py +++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py @@ -2,8 +2,12 @@ # text processing dependancies import json +import os from textwrap import dedent +from mock import patch + +from django.conf import settings from django.contrib.auth.models import User from django.test.client import RequestFactory from django.core.urlresolvers import reverse @@ -18,7 +22,10 @@ from xmodule.modulestore.django import modulestore, editable_modulestore #import factories and parent testcase modules from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from capa.tests.response_xml_factory import OptionResponseXMLFactory, CustomResponseXMLFactory, SchematicResponseXMLFactory +from capa.tests.response_xml_factory import ( + OptionResponseXMLFactory, CustomResponseXMLFactory, SchematicResponseXMLFactory, + CodeResponseXMLFactory, +) from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from lms.lib.xblock.runtime import quote_slashes @@ -547,6 +554,58 @@ class TestCourseGrader(TestSubmittingProblems): self.assertEqual(self.score_for_hw('homework3'), [1.0, 1.0]) +class ProblemWithUploadedFilesTest(TestSubmittingProblems): + """Tests of problems with uploaded files.""" + + def setUp(self): + super(ProblemWithUploadedFilesTest, self).setUp() + self.section = self.add_graded_section_to_course('section') + + def problem_setup(self, name, files): + """ + Create a CodeResponse problem with files to upload. + """ + + xmldata = CodeResponseXMLFactory().build_xml( + allowed_files=files, required_files=files, + ) + ItemFactory.create( + parent_location=self.section.location, + category='problem', + display_name=name, + data=xmldata + ) + + # re-fetch the course from the database so the object is up to date + self.refresh_course() + + def test_three_files(self): + # Open the test files, and arrange to close them later. + filenames = "prog1.py prog2.py prog3.py" + fileobjs = [ + open(os.path.join(settings.COMMON_TEST_DATA_ROOT, "capa", filename)) + for filename in filenames.split() + ] + for fileobj in fileobjs: + self.addCleanup(fileobj.close) + + self.problem_setup("the_problem", filenames) + with patch('courseware.module_render.xqueue_interface.session') as mock_session: + resp = self.submit_question_answer("the_problem", {'2_1': fileobjs}) + + self.assertEqual(resp.status_code, 200) + json_resp = json.loads(resp.content) + self.assertEqual(json_resp['success'], "incorrect") + + # See how post got called. + name, args, kwargs = mock_session.mock_calls[0] + self.assertEqual(name, "post") + self.assertEqual(len(args), 1) + self.assertTrue(args[0].endswith("/submit/")) + self.assertItemsEqual(kwargs.keys(), ["files", "data"]) + self.assertItemsEqual(kwargs['files'].keys(), filenames.split()) + + class TestPythonGradedResponse(TestSubmittingProblems): """ Check that we can submit a schematic and custom response, and it answers properly.