def setUp(self): """ Test setup """ super(CommandTest, self).setUp() self.command = ExportDiscussionCommand() self.command.stdout = mock.Mock() self.command.stderr = mock.Mock()
class CommandTest(TestCase): """ Tests that command correctly parses arguments, creates helper class instances and invokes correct methods on them """ def setUp(self): """ Test setup """ super(CommandTest, self).setUp() self.command = ExportDiscussionCommand() self.command.stdout = mock.Mock() self.command.stderr = mock.Mock() def set_up_default_mocks(self, patched_get_course, patched_get_settings, cohorted_thread_ids=None): """ Sets up default mocks passed via class decorator """ patched_get_course.return_value = mock.Mock(spec=CourseLocator) patched_get_settings.return_value = mock.Mock(spec=CourseCohortsSettings) patched_get_settings.return_value.cohorted_discussions = cohorted_thread_ids or [] # pylint:disable=unused-argument def test_handle_given_no_arguments_raises_command_error(self, patched_get_course, patched_get_settings): """ Tests that raises error if invoked with no arguments """ with self.assertRaises(CommandError): self.command.handle() # pylint:disable=unused-argument def test_handle_given_more_than_two_args_raises_command_error(self, patched_get_course, patched_get_setting): """ Tests that raises error if invoked with too many arguments """ with self.assertRaises(CommandError): self.command.handle(1, 2, 3) def test_handle_given_invalid_course_key_raises_invalid_key_error(self, patched_get_course, patched_get_settings): """ Tests that invalid key errors are propagated """ patched_get_course.return_value = None with self.assertRaises(InvalidKeyError): self.command.handle("I'm invalid key") def test_handle_given_missing_course_raises_command_error(self, patched_get_course, patched_get_settings): """ Tests that raises command error if missing course key was provided """ patched_get_course.return_value = None with self.assertRaises(CommandError): self.command.handle("edX/demoX/now") # pylint: disable=unused-argument def test_all_option(self, patched_get_course, patched_get_settings): """ Tests that the 'all' option does run the dump command for all courses """ self.command.dump_one = mock.Mock() self.command.get_all_courses = mock.Mock() course_list = [mock.Mock() for __ in range(0, 3)] locator_list = [ CourseLocator(org="edX", course="demoX", run="now"), CourseLocator(org="Sandbox", course="Sandbox", run="Sandbox"), CourseLocator(org="Test", course="Testy", run="Testify"), ] for index, course in enumerate(course_list): course.location.course_key = locator_list[index] self.command.get_all_courses.return_value = course_list self.command.handle("test_dir", all=True, dummy='test') calls = self.command.dump_one.call_args_list self.assertEqual(len(calls), 3) self.assertEqual(calls[0][0][0], 'course-v1:edX+demoX+now') self.assertEqual(calls[1][0][0], 'course-v1:Sandbox+Sandbox+Sandbox') self.assertEqual(calls[2][0][0], 'course-v1:Test+Testy+Testify') self.assertIn('test_dir/social_stats_course-v1edXdemoXnow', calls[0][0][1]) self.assertIn('test_dir/social_stats_course-v1SandboxSandboxSandbox', calls[1][0][1]) self.assertIn('test_dir/social_stats_course-v1TestTestyTestify', calls[2][0][1]) def test_all_cohortedonly_options_together(self, patched_get_course, patched_get_settings): """ Ensure the 'all' option doesn't stop when one of the course doesn't have cohorted discussions """ self.set_up_default_mocks(patched_get_course, patched_get_settings) self.command.get_all_courses = mock.Mock() course_list = [mock.Mock() for __ in range(0, 3)] locator_list = [ CourseLocator(org="edX", course="demoX", run="now"), CourseLocator(org="Sandbox", course="Sandbox", run="Sandbox"), CourseLocator(org="Test", course="Testy", run="Testify"), ] for index, course in enumerate(course_list): course.location.course_key = locator_list[index] self.command.get_all_courses.return_value = course_list self.command.handle("test_dir", all=True, cohorted_only=True, dummy='test') calls = patched_get_course.call_args_list self.assertEqual(len(calls), 3) self.assertEqual(calls[0][0][0], locator_list[0]) self.assertEqual(calls[1][0][0], locator_list[1]) self.assertEqual(calls[2][0][0], locator_list[2]) @ddt.data("edX/demoX/now", "otherX/CourseX/later") def test_handle_writes_to_correct_location_when_output_file_not_specified( self, course_key, patched_get_course, patched_get_settings ): """ Tests that when no explicit filename is given data is exported to default location """ self.set_up_default_mocks(patched_get_course, patched_get_settings) expected_filename = utils.format_filename( "social_stats_{course}_{date:%Y_%m_%d_%H_%M_%S}.csv".format(course=course_key, date=datetime.utcnow()) ) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle(course_key) patched_open.assert_called_with(expected_filename, 'wb') @ddt.data("test.csv", "other_file.csv") def test_handle_writes_to_correct_location_when_output_file_is_specified( self, location, patched_get_course, patched_get_settings ): """ Tests that when explicit filename is given data is exported to chosen location """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle("irrelevant/course/key", location) patched_open.assert_called_with(location, 'wb') def test_handle_creates_correct_exporter(self, patched_get_course, patched_get_settings): """ Tests that creates correct exporter """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor, \ mock.patch(_target_module + ".Exporter") as patched_exporter: open_retval = patched_open() patched_extractor.return_value = [] self.command.handle("irrelevant/course/key", "irrelevant_location.csv") patched_exporter.assert_called_with(open_retval) @ddt.data( {}, {"1": {"num_threads": 12}}, {"1": {"num_threads": 14, "num_comments": 7}} ) def test_handle_exports_correct_data(self, extracted, patched_get_course, patched_get_settings): """ Tests that invokes export with correct data """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor, \ mock.patch(_target_module + ".Exporter.export") as patched_exporter: patched_extractor.return_value = extracted self.command.handle("irrelevant/course/key", "irrelevant_location.csv") patched_exporter.assert_called_with(extracted) @ddt.unpack @ddt.data(*_std_parameters_list) def test_handle_passes_correct_parameters_to_extractor( self, course_key, end_date, thread_type, cohorted_thread_ids, patched_get_course, patched_get_settings): """ Tests that when no explicit filename is given data is exported to default location """ self.set_up_default_mocks(patched_get_course, patched_get_settings, cohorted_thread_ids) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle( str(course_key), end_date=end_date.isoformat() if end_date else end_date, thread_type=thread_type, cohorted_only=True if cohorted_thread_ids else False ) patched_extractor.assert_called_with( course_key, end_date=end_date, thread_type=thread_type, thread_ids=cohorted_thread_ids )
class CommandTest(TestCase): """ Tests that command correctly parses arguments, creates helper class instances and invokes correct methods on them """ def setUp(self): """ Test setup """ super(CommandTest, self).setUp() self.command = ExportDiscussionCommand() self.command.stdout = mock.Mock() self.command.stderr = mock.Mock() def set_up_default_mocks(self, patched_get_course, patched_get_settings, cohorted_thread_ids=None): """ Sets up default mocks passed via class decorator """ patched_get_course.return_value = mock.Mock(spec=CourseLocator) patched_get_settings.return_value = mock.Mock( spec=CourseCohortsSettings) patched_get_settings.return_value.cohorted_discussions = cohorted_thread_ids or [] # pylint:disable=unused-argument def test_handle_given_no_arguments_raises_command_error( self, patched_get_course, patched_get_settings): """ Tests that raises error if invoked with no arguments """ with self.assertRaises(CommandError): self.command.handle() # pylint:disable=unused-argument def test_handle_given_more_than_two_args_raises_command_error( self, patched_get_course, patched_get_setting): """ Tests that raises error if invoked with too many arguments """ with self.assertRaises(CommandError): self.command.handle(1, 2, 3) def test_handle_given_invalid_course_key_raises_invalid_key_error( self, patched_get_course, patched_get_settings): """ Tests that invalid key errors are propagated """ patched_get_course.return_value = None with self.assertRaises(InvalidKeyError): self.command.handle("I'm invalid key") def test_handle_given_missing_course_raises_command_error( self, patched_get_course, patched_get_settings): """ Tests that raises command error if missing course key was provided """ patched_get_course.return_value = None with self.assertRaises(CommandError): self.command.handle("edX/demoX/now") # pylint: disable=unused-argument def test_all_option(self, patched_get_course, patched_get_settings): """ Tests that the 'all' option does run the dump command for all courses """ self.command.dump_one = mock.Mock() self.command.get_all_courses = mock.Mock() course_list = [mock.Mock() for __ in range(0, 3)] locator_list = [ CourseLocator(org="edX", course="demoX", run="now"), CourseLocator(org="Sandbox", course="Sandbox", run="Sandbox"), CourseLocator(org="Test", course="Testy", run="Testify"), ] for index, course in enumerate(course_list): course.location.course_key = locator_list[index] self.command.get_all_courses.return_value = course_list self.command.handle("test_dir", all=True, dummy='test') calls = self.command.dump_one.call_args_list self.assertEqual(len(calls), 3) self.assertEqual(calls[0][0][0], 'course-v1:edX+demoX+now') self.assertEqual(calls[1][0][0], 'course-v1:Sandbox+Sandbox+Sandbox') self.assertEqual(calls[2][0][0], 'course-v1:Test+Testy+Testify') self.assertIn('test_dir/social_stats_course-v1edXdemoXnow', calls[0][0][1]) self.assertIn('test_dir/social_stats_course-v1SandboxSandboxSandbox', calls[1][0][1]) self.assertIn('test_dir/social_stats_course-v1TestTestyTestify', calls[2][0][1]) def test_all_cohortedonly_options_together(self, patched_get_course, patched_get_settings): """ Ensure the 'all' option doesn't stop when one of the course doesn't have cohorted discussions """ self.set_up_default_mocks(patched_get_course, patched_get_settings) self.command.get_all_courses = mock.Mock() course_list = [mock.Mock() for __ in range(0, 3)] locator_list = [ CourseLocator(org="edX", course="demoX", run="now"), CourseLocator(org="Sandbox", course="Sandbox", run="Sandbox"), CourseLocator(org="Test", course="Testy", run="Testify"), ] for index, course in enumerate(course_list): course.location.course_key = locator_list[index] self.command.get_all_courses.return_value = course_list self.command.handle("test_dir", all=True, cohorted_only=True, dummy='test') calls = patched_get_course.call_args_list self.assertEqual(len(calls), 3) self.assertEqual(calls[0][0][0], locator_list[0]) self.assertEqual(calls[1][0][0], locator_list[1]) self.assertEqual(calls[2][0][0], locator_list[2]) @ddt.data("edX/demoX/now", "otherX/CourseX/later") def test_handle_writes_to_correct_location_when_output_file_not_specified( self, course_key, patched_get_course, patched_get_settings): """ Tests that when no explicit filename is given data is exported to default location """ self.set_up_default_mocks(patched_get_course, patched_get_settings) expected_filename = utils.format_filename( "social_stats_{course}_{date:%Y_%m_%d_%H_%M_%S}.csv".format( course=course_key, date=datetime.utcnow())) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle(course_key) patched_open.assert_called_with(expected_filename, 'wb') @ddt.data("test.csv", "other_file.csv") def test_handle_writes_to_correct_location_when_output_file_is_specified( self, location, patched_get_course, patched_get_settings): """ Tests that when explicit filename is given data is exported to chosen location """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle("irrelevant/course/key", location) patched_open.assert_called_with(location, 'wb') def test_handle_creates_correct_exporter(self, patched_get_course, patched_get_settings): """ Tests that creates correct exporter """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor, \ mock.patch(_target_module + ".Exporter") as patched_exporter: open_retval = patched_open() patched_extractor.return_value = [] self.command.handle("irrelevant/course/key", "irrelevant_location.csv") patched_exporter.assert_called_with(open_retval) @ddt.data({}, {"1": { "num_threads": 12 }}, {"1": { "num_threads": 14, "num_comments": 7 }}) def test_handle_exports_correct_data(self, extracted, patched_get_course, patched_get_settings): """ Tests that invokes export with correct data """ self.set_up_default_mocks(patched_get_course, patched_get_settings) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor, \ mock.patch(_target_module + ".Exporter.export") as patched_exporter: patched_extractor.return_value = extracted self.command.handle("irrelevant/course/key", "irrelevant_location.csv") patched_exporter.assert_called_with(extracted) @ddt.unpack @ddt.data(*_std_parameters_list) def test_handle_passes_correct_parameters_to_extractor( self, course_key, end_date, thread_type, cohorted_thread_ids, patched_get_course, patched_get_settings): """ Tests that when no explicit filename is given data is exported to default location """ self.set_up_default_mocks(patched_get_course, patched_get_settings, cohorted_thread_ids) patched_open = mock.mock_open() with mock.patch("{}.open".format(_target_module), patched_open, create=True), \ mock.patch(_target_module + ".Extractor.extract") as patched_extractor: patched_extractor.return_value = [] self.command.handle( str(course_key), end_date=end_date.isoformat() if end_date else end_date, thread_type=thread_type, cohorted_only=True if cohorted_thread_ids else False) patched_extractor.assert_called_with( course_key, end_date=end_date, thread_type=thread_type, thread_ids=cohorted_thread_ids)