diff --git a/common/djangoapps/pipeline_mako/tests/test_render.py b/common/djangoapps/pipeline_mako/tests/test_render.py index f49f0500d0..e3a1d784ec 100644 --- a/common/djangoapps/pipeline_mako/tests/test_render.py +++ b/common/djangoapps/pipeline_mako/tests/test_render.py @@ -39,7 +39,7 @@ class RequireJSPathOverridesTest(TestCase): def test_requirejs_path_overrides(self): result = render_require_js_path_overrides(self.OVERRIDES) # To make the string comparision easy remove the whitespaces - self.assertEqual(list(map(str.strip, result.splitlines())), self.OVERRIDES_JS) + self.assertCountEqual(list(map(str.strip, result.splitlines())), self.OVERRIDES_JS) @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS') diff --git a/common/djangoapps/student/management/commands/bulk_unenroll.py b/common/djangoapps/student/management/commands/bulk_unenroll.py index e9932afe85..1412a0ba63 100644 --- a/common/djangoapps/student/management/commands/bulk_unenroll.py +++ b/common/djangoapps/student/management/commands/bulk_unenroll.py @@ -32,7 +32,7 @@ class Command(BaseCommand): csv_path = options['csv_path'] if csv_path: - with open(csv_path) as csv_file: + with open(csv_path, 'rb') as csv_file: self.unenroll_users(csv_file) else: csv_file = BulkUnenrollConfiguration.current().csv_file diff --git a/common/djangoapps/student/management/tests/test_bulk_unenroll.py b/common/djangoapps/student/management/tests/test_bulk_unenroll.py index 3417b7c1a2..02ecc0da25 100644 --- a/common/djangoapps/student/management/tests/test_bulk_unenroll.py +++ b/common/djangoapps/student/management/tests/test_bulk_unenroll.py @@ -96,7 +96,7 @@ class BulkUnenrollTests(SharedModuleStoreTestCase): lines += str(enrollment.user.id) + "," + enrollment.user.username + "," + \ enrollment.user.email + "," + str(enrollment.course.id) + "\n" - csv_file = SimpleUploadedFile(name='test.csv', content=lines, content_type='text/csv') + csv_file = SimpleUploadedFile(name='test.csv', content=lines.encode('utf-8'), content_type='text/csv') BulkUnenrollConfiguration.objects.create(enabled=True, csv_file=csv_file) call_command("bulk_unenroll") @@ -110,14 +110,14 @@ class BulkUnenrollTests(SharedModuleStoreTestCase): for enrollment in self.enrollments: username = enrollment.user.username if username in users_unenrolled: - users_unenrolled[username].append(str(enrollment.course.id)) + users_unenrolled[username].append(str(enrollment.course.id).encode('utf-8')) else: - users_unenrolled[username] = [str(enrollment.course.id)] + users_unenrolled[username] = [str(enrollment.course.id).encode('utf-8')] lines += str(enrollment.user.id) + "," + username + "," + \ enrollment.user.email + "," + str(enrollment.course.id) + "\n" - csv_file = SimpleUploadedFile(name='test.csv', content=lines, content_type='text/csv') + csv_file = SimpleUploadedFile(name='test.csv', content=lines.encode('utf-8'), content_type='text/csv') BulkUnenrollConfiguration.objects.create(enabled=True, csv_file=csv_file) with LogCapture(LOGGER_NAME) as log: diff --git a/common/djangoapps/terrain/stubs/http.py b/common/djangoapps/terrain/stubs/http.py index 1213a1ef22..24ed0e7194 100644 --- a/common/djangoapps/terrain/stubs/http.py +++ b/common/djangoapps/terrain/stubs/http.py @@ -16,7 +16,6 @@ import six.moves.urllib.request # pylint: disable=import-error from lazy import lazy from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer # pylint: disable=import-error from six.moves.socketserver import ThreadingMixIn # pylint: disable=import-error - LOGGER = getLogger(__name__) @@ -105,13 +104,13 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object): Retrieve the request POST parameters from the client as a dictionary. If no POST parameters can be interpreted, return an empty dict. """ - contents = self.request_content + contents = self.request_content.decode() # The POST dict will contain a list of values for each key. # None of our parameters are lists, however, so we map [val] --> val # If the list contains multiple entries, we pick the first one try: - post_dict = six.moves.urllib.parse.parse_qs(contents, keep_blank_values=True) + post_dict = six.moves.urllib.parse.parse_qs(contents.decode('utf-8'), keep_blank_values=True) return { key: list_val[0] for key, list_val in post_dict.items() @@ -159,13 +158,6 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object): if len(self.post_dict) > 0: for key, value in six.iteritems(self.post_dict): - # Decode the params as UTF-8 - try: - key = six.text_type(key, 'utf-8') - value = six.text_type(value, 'utf-8') - except UnicodeDecodeError: - self.log_message("Could not decode request params as UTF-8") - self.log_message(u"Set config '{0}' to '{1}'".format(key, value)) try: @@ -209,6 +201,8 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object): self.end_headers() if content is not None: + if not six.PY2 and isinstance(content, six.text_type): + content = content.encode('utf-8') self.wfile.write(content) def send_json_response(self, content): diff --git a/common/djangoapps/terrain/stubs/lti.py b/common/djangoapps/terrain/stubs/lti.py index fd4f2cfed2..ff0f430c34 100644 --- a/common/djangoapps/terrain/stubs/lti.py +++ b/common/djangoapps/terrain/stubs/lti.py @@ -266,7 +266,7 @@ class StubLtiHandler(StubHttpRequestHandler): # Calculate and encode body hash. See http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html sha1 = hashlib.sha1() - sha1.update(body) + sha1.update(body.encode('utf-8')) oauth_body_hash = six.text_type(base64.b64encode(sha1.digest())) mock_request = mock.Mock( uri=six.text_type(six.moves.urllib.parse.unquote(url)), diff --git a/common/djangoapps/terrain/stubs/tests/test_lti_stub.py b/common/djangoapps/terrain/stubs/tests/test_lti_stub.py index 43e3dc0044..90c2fa2c55 100644 --- a/common/djangoapps/terrain/stubs/tests/test_lti_stub.py +++ b/common/djangoapps/terrain/stubs/tests/test_lti_stub.py @@ -49,7 +49,7 @@ class StubLtiServiceTest(unittest.TestCase): """ self.launch_uri = self.uri + 'wrong_lti_endpoint' response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('Invalid request URL', response.content) + self.assertIn(b'Invalid request URL', response.content) def test_wrong_signature(self): """ @@ -57,7 +57,7 @@ class StubLtiServiceTest(unittest.TestCase): path and responses with incorrect signature. """ response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('Wrong LTI signature', response.content) + self.assertIn(b'Wrong LTI signature', response.content) @patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True) def test_success_response_launch_lti(self, check_oauth): @@ -65,34 +65,34 @@ class StubLtiServiceTest(unittest.TestCase): Success lti launch. """ response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('This is LTI tool. Success.', response.content) + self.assertIn(b'This is LTI tool. Success.', response.content) @patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True) def test_send_graded_result(self, verify_hmac): # pylint: disable=unused-argument response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('This is LTI tool. Success.', response.content) + self.assertIn(b'This is LTI tool. Success.', response.content) grade_uri = self.uri + 'grade' with patch('terrain.stubs.lti.requests.post') as mocked_post: mocked_post.return_value = Mock(content='Test response', status_code=200) - response = six.moves.urllib.request.urlopen(grade_uri, data='') - self.assertIn('Test response', response.read()) + response = six.moves.urllib.request.urlopen(grade_uri, data=b'') + self.assertIn(b'Test response', response.read()) @patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True) def test_lti20_outcomes_put(self, verify_hmac): # pylint: disable=unused-argument response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('This is LTI tool. Success.', response.content) + self.assertIn(b'This is LTI tool. Success.', response.content) grade_uri = self.uri + 'lti2_outcome' with patch('terrain.stubs.lti.requests.put') as mocked_put: mocked_put.return_value = Mock(status_code=200) - response = six.moves.urllib.request.urlopen(grade_uri, data='') - self.assertIn('LTI consumer (edX) responded with HTTP 200', response.read()) + response = six.moves.urllib.request.urlopen(grade_uri, data=b'') + self.assertIn(b'LTI consumer (edX) responded with HTTP 200', response.read()) @patch('terrain.stubs.lti.signature.verify_hmac_sha1', return_value=True) def test_lti20_outcomes_put_like_delete(self, verify_hmac): # pylint: disable=unused-argument response = requests.post(self.launch_uri, data=self.payload) - self.assertIn('This is LTI tool. Success.', response.content) + self.assertIn(b'This is LTI tool. Success.', response.content) grade_uri = self.uri + 'lti2_delete' with patch('terrain.stubs.lti.requests.put') as mocked_put: mocked_put.return_value = Mock(status_code=200) - response = six.moves.urllib.request.urlopen(grade_uri, data='') - self.assertIn('LTI consumer (edX) responded with HTTP 200', response.read()) + response = six.moves.urllib.request.urlopen(grade_uri, data=b'') + self.assertIn(b'LTI consumer (edX) responded with HTTP 200', response.read()) diff --git a/common/lib/capa/capa/tests/test_targeted_feedback.py b/common/lib/capa/capa/tests/test_targeted_feedback.py index 24d05124ff..d3268ee2b2 100644 --- a/common/lib/capa/capa/tests/test_targeted_feedback.py +++ b/common/lib/capa/capa/tests/test_targeted_feedback.py @@ -100,7 +100,7 @@ class CapaTargetedFeedbackTest(unittest.TestCase): problem.student_answers = {'1_2_1': 'choice_3'} the_html = problem.get_html() - without_new_lines = the_html.replace("\n", "") + without_new_lines = the_html.replace("\\n", "").replace("\n", "") # pylint: disable=line-too-long self.assertRegexpMatches(without_new_lines, r"\s*Incorrect.*3rd WRONG solution") self.assertNotRegexpMatches(without_new_lines, r"feedback1|feedback2|feedbackC") @@ -114,7 +114,7 @@ class CapaTargetedFeedbackTest(unittest.TestCase): problem.student_answers = {'1_2_1': 'choice_0'} the_html = problem.get_html() - without_new_lines = the_html.replace("\n", "") + without_new_lines = the_html.replace("\\n", "").replace("\n", "") # pylint: disable=line-too-long self.assertRegexpMatches(without_new_lines, r"\s*Incorrect.*1st WRONG solution") self.assertRegexpMatches(without_new_lines, r"
\{.*'1_solution_1'.*\}
") @@ -127,7 +127,7 @@ class CapaTargetedFeedbackTest(unittest.TestCase): problem.student_answers = {'1_2_1': 'choice_2'} the_html = problem.get_html() - without_new_lines = the_html.replace("\n", "") + without_new_lines = the_html.replace("\\n", "").replace("\n", "") # pylint: disable=line-too-long self.assertRegexpMatches(without_new_lines, r"\s*Correct.*Feedback on your correct solution...") diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py index 9a0f27c26a..bd9e387865 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py @@ -1553,7 +1553,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): next_versions = [struct for struct in next_entries] for course_structure in next_versions: result.setdefault(course_structure['previous_version'], []).append( - CourseLocator(version_guid=struct['_id'])) + CourseLocator(version_guid=next_entries[-1]['_id'])) return VersionTree(course_locator, result) def get_block_generations(self, block_locator): diff --git a/common/lib/xmodule/xmodule/randomize_module.py b/common/lib/xmodule/xmodule/randomize_module.py index 2a5fdf9203..41d716dc3a 100644 --- a/common/lib/xmodule/xmodule/randomize_module.py +++ b/common/lib/xmodule/xmodule/randomize_module.py @@ -44,7 +44,7 @@ class RandomizeModule(RandomizeFields, XModule): # NOTE: calling self.get_children() doesn't work until we've picked a choice num_choices = len(self.descriptor.get_children()) - if self.choice > num_choices: + if self.choice is not None and self.choice > num_choices: # Oops. Children changed. Reset. self.choice = None diff --git a/common/lib/xmodule/xmodule/tests/test_sequence.py b/common/lib/xmodule/xmodule/tests/test_sequence.py index c7ee64376b..3dc8eda361 100644 --- a/common/lib/xmodule/xmodule/tests/test_sequence.py +++ b/common/lib/xmodule/xmodule/tests/test_sequence.py @@ -4,6 +4,7 @@ Tests for sequence module. # pylint: disable=no-member from __future__ import absolute_import +import ast import json from datetime import timedelta @@ -195,10 +196,10 @@ class SequenceBlockTestCase(XModuleXmlImportTest): extra_context=dict(specific_masquerade=True), ) self.assertIn("seq_module.html", html) - self.assertIn( - "'banner_text': u'Because the due date has passed, " - "this assignment is hidden from the learner.'", - html + html = self.get_context_dict_from_string(html) + self.assertEqual( + 'Because the due date has passed, this assignment is hidden from the learner.', + html['banner_text'] ) def test_hidden_content_self_paced_past_due_before_end(self): @@ -223,31 +224,35 @@ class SequenceBlockTestCase(XModuleXmlImportTest): Assert sequence content is gated """ self.assertIn("seq_module.html", html) - self.assertIn("'banner_text': None", html) - self.assertIn("'items': []", html) - self.assertIn("'gated': True", html) - self.assertIn("'prereq_url': 'PrereqUrl'", html) - self.assertIn("'prereq_section_name': 'PrereqSectionName'", html) - self.assertIn("'gated_section_name': u'{}'".format(six.text_type(sequence.display_name)), html) - self.assertIn("'next_url': 'NextSequential'", html) - self.assertIn("'prev_url': 'PrevSequential'", html) + html = self.get_context_dict_from_string(html) + self.assertIsNone(html['banner_text']) + self.assertEqual([], html['items']) + self.assertTrue(html['gated_content']['gated']) + self.assertEqual('PrereqUrl', html['gated_content']['prereq_url']) + self.assertEqual('PrereqSectionName', html['gated_content']['prereq_section_name']) + self.assertIn( + six.text_type(sequence.display_name), + html['gated_content']['gated_section_name'] + ) + self.assertEqual('NextSequential', html['next_url']) + self.assertEqual('PrevSequential', html['prev_url']) def _assert_prereq(self, html, sequence): """ Assert sequence is a prerequisite with unfulfilled gates """ self.assertIn("seq_module.html", html) - self.assertIn( - "'banner_text': u'This section is a prerequisite. " - "You must complete this section in order to unlock additional content.'", - html + html = self.get_context_dict_from_string(html) + self.assertEqual( + "This section is a prerequisite. You must complete this section in order to unlock additional content.", + html['banner_text'] ) - self.assertIn("'gated': False", html) - self.assertIn(six.text_type(sequence.location), html) - self.assertIn("'prereq_url': None", html) - self.assertIn("'prereq_section_name': None", html) - self.assertIn("'next_url': 'NextSequential'", html) - self.assertIn("'prev_url': 'PrevSequential'", html) + self.assertFalse(html['gated_content']['gated']) + self.assertEqual(six.text_type(sequence.location), html['item_id']) + self.assertIsNone(html['gated_content']['prereq_url']) + self.assertIsNone(html['gated_content']['prereq_section_name']) + self.assertEqual('NextSequential', html['next_url']) + self.assertEqual('PrevSequential', html['prev_url']) def _assert_ungated(self, html, sequence): """ @@ -295,7 +300,6 @@ class SequenceBlockTestCase(XModuleXmlImportTest): self.sequence_1_2, extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'), ) - # assert that content and preq banner is shown self._assert_prereq(html, self.sequence_1_2) @@ -338,3 +342,11 @@ class SequenceBlockTestCase(XModuleXmlImportTest): {'usage_key': usage_key} ) self.assertIs(completion_return, None) + + def get_context_dict_from_string(self, data): + """ + Retrieve dictionary from string. + """ + # Replace tuple and un-necessary info from inside string and get the dictionary. + cleaned_data = data.replace("(('seq_module.html',\n", '').replace("),\n {})", '').strip() + return ast.literal_eval(cleaned_data) diff --git a/common/lib/xmodule/xmodule/tests/test_video.py b/common/lib/xmodule/xmodule/tests/test_video.py index 261d1c526a..707235af62 100644 --- a/common/lib/xmodule/xmodule/tests/test_video.py +++ b/common/lib/xmodule/xmodule/tests/test_video.py @@ -827,7 +827,7 @@ class VideoExportTestCase(VideoBlockTestBase): self.descriptor.transcripts = None xml = self.descriptor.definition_to_xml(self.file_system) expected = '