def setUp(self): cache.clear() self.course_id = PERFORMER_PRESENTER_COURSE_ID self.problem_id = 'i4x://edX/DemoX.1/problem/05d289c5ad3d47d48a77622c4a81ec36' self.presenter = CoursePerformancePresenter(settings.COURSE_API_KEY, self.course_id) self.factory = CoursePerformanceDataFactory()
def setUp(self): super(CoursePerformanceViewTestMixin, self).setUp() self.toggle_switch('enable_course_api', True) self.factory = CoursePerformanceDataFactory() # Ensure patches from previous test failures are removed and de-referenced self.clear_patches() self._patch( 'courses.presenters.performance.CoursePerformancePresenter.assignments', return_value=self.factory.present_assignments()) self._patch( 'courses.presenters.performance.CoursePerformancePresenter.grading_policy', return_value=self.factory.grading_policy) self.start_patching()
def setUp(self): super(CoursePerformanceViewTestMixin, self).setUp() self.toggle_switch('enable_course_api', True) self.factory = CoursePerformanceDataFactory() # Ensure patches from previous test failures are removed and de-referenced self.clear_patches()
def setUp(self): cache.clear() self.course_id = PERFORMER_PRESENTER_COURSE_ID self.problem_id = 'i4x://edX/DemoX.1/problem/05d289c5ad3d47d48a77622c4a81ec36' self.presenter = CoursePerformancePresenter(None, self.course_id) self.factory = CoursePerformanceDataFactory()
class CoursePerformancePresenterTests(TestCase): def setUp(self): cache.clear() self.course_id = PERFORMER_PRESENTER_COURSE_ID self.problem_id = 'i4x://edX/DemoX.1/problem/05d289c5ad3d47d48a77622c4a81ec36' self.presenter = CoursePerformancePresenter(None, self.course_id) self.factory = CoursePerformanceDataFactory() # First and last response counts were added, insights can handle both types of API responses at the moment. @data( annotated( utils.get_mock_api_answer_distribution_multiple_questions_data(PERFORMER_PRESENTER_COURSE_ID), 'count' ), annotated( utils.get_mock_api_answer_distribution_multiple_questions_first_last_data(PERFORMER_PRESENTER_COURSE_ID), 'first_last' ), ) @mock.patch('analyticsclient.module.Module.answer_distribution') def test_multiple_answer_distribution(self, mock_data, mock_answer_distribution): mock_answer_distribution.reset_mock() mock_answer_distribution.return_value = mock_data problem_parts = [ { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_2_1', 'expected': { 'active_question': 'Submissions for Part 1: Is this a text problem?', 'problem_part_description': 'Part 1: Is this a text problem?', 'is_random': False, 'answer_type': 'text' } }, { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_3_1', 'expected': { 'active_question': 'Submissions for Part 2: Is this a numeric problem?', 'problem_part_description': 'Part 2: Is this a numeric problem?', 'is_random': False, 'answer_type': 'numeric' } }, { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_4_1', 'expected': { 'active_question': 'Submissions for Part 3: Is this a randomized problem?', 'problem_part_description': 'Part 3: Is this a ' 'randomized problem?', 'is_random': True, 'answer_type': 'numeric' } } ] questions = utils.get_presenter_performance_answer_distribution_multiple_questions() self.assertAnswerDistribution(problem_parts, questions, mock_data) @mock.patch('analyticsclient.module.Module.answer_distribution') def test_single_answer_distribution(self, mock_answer_distribution): mock_data = utils.get_mock_api_answer_distribution_single_question_data(self.course_id) mock_answer_distribution.return_value = mock_data problem_parts = [ { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_2_1', 'expected': { 'active_question': 'Submissions: Is this a text problem?', 'problem_part_description': 'Is this a text problem?', 'is_random': False, 'answer_type': 'text' } } ] questions = utils.get_presenter_performance_answer_distribution_single_question() self.assertAnswerDistribution(problem_parts, questions, mock_data) def assertAnswerDistribution(self, expected_problem_parts, expected_questions, answer_distribution_data): for part in expected_problem_parts: expected = part['expected'] answer_distribution_entry = self.presenter.get_answer_distribution(self.problem_id, part['part_id']) self.assertEqual(answer_distribution_entry.last_updated, utils.CREATED_DATETIME) self.assertListEqual(answer_distribution_entry.questions, expected_questions) self.assertEqual(answer_distribution_entry.problem_part_description, expected['problem_part_description']) self.assertEqual(answer_distribution_entry.active_question, expected['active_question']) self.assertEqual(answer_distribution_entry.answer_type, expected['answer_type']) self.assertEqual(answer_distribution_entry.is_random, expected['is_random']) expected_answer_distribution = [d for d in answer_distribution_data if d['part_id'] == part['part_id']] self.assertListEqual(answer_distribution_entry.answer_distribution, expected_answer_distribution) if answer_distribution_entry.is_random: self.assertIsNone(answer_distribution_entry.answer_distribution_limited) else: self.assertListEqual(answer_distribution_entry.answer_distribution_limited, expected_answer_distribution[:12]) @mock.patch('slumber.Resource.get', mock.Mock(return_value=CoursePerformanceDataFactory.grading_policy)) def test_grading_policy(self): """ Verify the presenter returns the correct grading policy. Empty (non-truthy) assignment types should be dropped. """ grading_policy = self.presenter.grading_policy() self.assertListEqual(grading_policy, self.factory.presented_grading_policy) percent = self.presenter.get_max_policy_display_percent(grading_policy) self.assertEqual(100, percent) percent = self.presenter.get_max_policy_display_percent([{'weight': 0.0}, {'weight': 1.0}, {'weight': 0.04}]) self.assertEqual(90, percent) def test_assignment_types(self): """ Verify the presenter returns the correct assignment types. """ with mock.patch('courses.presenters.performance.CoursePerformancePresenter.grading_policy', mock.Mock(return_value=self.factory.presented_grading_policy)): self.assertListEqual(self.presenter.assignment_types(), self.factory.presented_assignment_types) def test_assignments(self): """ Verify the presenter returns the correct assignments and sets the last updated date. """ self.assertIsNone(self.presenter.last_updated) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', self.factory.problems): # With no assignment type set, the method should return all assignment types. assignments = self.presenter.assignments() expected_assignments = self.factory.presented_assignments self.assertListEqual(assignments, expected_assignments) self.assertEqual(self.presenter.last_updated, utils.CREATED_DATETIME) # With an assignment type set, the presenter should return only the assignments of the specified type. for assignment_type in self.factory.presented_assignment_types: cache.clear() expected = [assignment for assignment in expected_assignments if assignment[u'assignment_type'] == assignment_type['name']] for index, assignment in enumerate(expected): assignment[u'index'] = index + 1 self.assertListEqual(self.presenter.assignments(assignment_type), expected) def test_assignment(self): """ Verify the presenter returns a specific assignment. """ with mock.patch('courses.presenters.performance.CoursePerformancePresenter.assignments', mock.Mock(return_value=self.factory.presented_assignments)): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.assignment(None)) self.assertIsNone(self.presenter.assignment('non-existent-id')) # The method should return an individual assignment if the ID exists. assignment = self.factory.presented_assignments[0] self.assertDictEqual(self.presenter.assignment(assignment[u'id']), assignment) def test_problem(self): """ Verify the presenter returns a specific problem. """ problem = self.factory.presented_assignments[0]['children'][0] _id = problem['id'] with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): actual = self.presenter.block(_id) expected = { 'id': _id, 'name': problem['name'], 'children': [] } self.assertDictContainsSubset(expected, actual) def test_sections(self): """ Verify the presenter returns a specific assignment. """ ungraded_problems = self.factory.problems(False) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', mock.Mock(return_value=ungraded_problems)): expected = self.factory.presented_sections self.assertListEqual(self.presenter.sections(), expected) def test_section(self): """ Verify the presenter returns a specific assignment. """ ungraded_problems = self.factory.problems(False) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', mock.Mock(return_value=ungraded_problems)): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.section(None)) self.assertIsNone(self.presenter.section('non-existent-id')) expected = self.factory.presented_sections[0] self.assertEqual(self.presenter.section(expected['id']), expected) def test_subsections(self): """ Verify the presenter returns a specific assignment. """ ungraded_problems = self.factory.problems(False) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', mock.Mock(return_value=ungraded_problems)): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.subsections(None)) self.assertIsNone(self.presenter.subsections('non-existent-id')) section = self.factory.presented_sections[0] expected = section['children'] self.assertListEqual(self.presenter.subsections(section['id']), expected) def test_subsection(self): """ Verify the presenter returns a specific assignment. """ ungraded_problems = self.factory.problems(False) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', mock.Mock(return_value=ungraded_problems)): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.subsection(None, None)) self.assertIsNone(self.presenter.subsection('non-existent-id', 'nope')) section = self.factory.presented_sections[0] expected_subsection = section['children'][0] self.assertEqual(self.presenter.subsection(section['id'], expected_subsection['id']), expected_subsection) def test_subsection_problems(self): """ Verify the presenter returns a specific assignment. """ ungraded_problems = self.factory.problems(False) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', mock.Mock(return_value=ungraded_problems)): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.subsection_children(None, None)) self.assertIsNone(self.presenter.subsection_children('non-existent-id', 'nope')) section = self.factory.presented_sections[0] subsection = section['children'][0] expected_problems = subsection['children'] self.assertListEqual( self.presenter.subsection_children(section['id'], subsection['id']), expected_problems)
def setUp(self): cache.clear() self.course_id = 'edX/DemoX/Demo_Course' self.problem_id = 'i4x://edX/DemoX.1/problem/05d289c5ad3d47d48a77622c4a81ec36' self.presenter = CoursePerformancePresenter(None, self.course_id) self.factory = CoursePerformanceDataFactory()
class CoursePerformancePresenterTests(TestCase): def setUp(self): cache.clear() self.course_id = 'edX/DemoX/Demo_Course' self.problem_id = 'i4x://edX/DemoX.1/problem/05d289c5ad3d47d48a77622c4a81ec36' self.presenter = CoursePerformancePresenter(None, self.course_id) self.factory = CoursePerformanceDataFactory() @mock.patch('analyticsclient.module.Module.answer_distribution') def test_multiple_answer_distribution(self, mock_answer_distribution): mock_data = utils.get_mock_api_answer_distribution_multiple_questions_data(self.course_id) mock_answer_distribution.return_value = mock_data problem_parts = [ { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_2_1', 'expected': { 'active_question': 'Submissions for Part 1: Is this a text problem?', 'problem_part_description': 'Part 1: Is this a text problem?', 'is_random': False, 'answer_type': 'text' } }, { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_3_1', 'expected': { 'active_question': 'Submissions for Part 2: Is this a numeric problem?', 'problem_part_description': 'Part 2: Is this a numeric problem?', 'is_random': False, 'answer_type': 'numeric' } }, { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_4_1', 'expected': { 'active_question': 'Submissions for Part 3: Is this a randomized problem?', 'problem_part_description': 'Part 3: Is this a ' 'randomized problem?', 'is_random': True, 'answer_type': 'numeric' } } ] questions = utils.get_presenter_performance_answer_distribution_multiple_questions() self.assertAnswerDistribution(problem_parts, questions) @mock.patch('analyticsclient.module.Module.answer_distribution') def test_single_answer_distribution(self, mock_answer_distribution): mock_data = utils.get_mock_api_answer_distribution_single_question_data(self.course_id) mock_answer_distribution.return_value = mock_data problem_parts = [ { 'part_id': 'i4x-edX-DemoX_1-problem-5e3c6d6934494d87b3a025676c7517c1_2_1', 'expected': { 'active_question': 'Submissions: Is this a text problem?', 'problem_part_description': 'Is this a text problem?', 'is_random': False, 'answer_type': 'text' } } ] questions = utils.get_presenter_performance_answer_distribution_single_question() self.assertAnswerDistribution(problem_parts, questions) def assertAnswerDistribution(self, expected_problem_parts, expected_questions): for part in expected_problem_parts: expected = part['expected'] answer_distribution_entry = self.presenter.get_answer_distribution(self.problem_id, part['part_id']) self.assertEqual(answer_distribution_entry.last_updated, utils.CREATED_DATETIME) self.assertListEqual(answer_distribution_entry.questions, expected_questions) self.assertEqual(answer_distribution_entry.problem_part_description, expected['problem_part_description']) self.assertEqual(answer_distribution_entry.active_question, expected['active_question']) self.assertEqual(answer_distribution_entry.answer_type, expected['answer_type']) self.assertEqual(answer_distribution_entry.is_random, expected['is_random']) expected_answer_distribution = utils.get_filtered_answer_distribution(self.course_id, part['part_id']) self.assertListEqual(answer_distribution_entry.answer_distribution, expected_answer_distribution) if answer_distribution_entry.is_random: self.assertIsNone(answer_distribution_entry.answer_distribution_limited) else: self.assertListEqual(answer_distribution_entry.answer_distribution_limited, expected_answer_distribution[:12]) @mock.patch('slumber.Resource.get', mock.Mock(return_value=CoursePerformanceDataFactory.grading_policy)) def test_grading_policy(self): """ Verify the presenter returns the correct grading policy. """ grading_policy = self.presenter.grading_policy() self.assertListEqual(grading_policy, CoursePerformanceDataFactory.grading_policy) percent = self.presenter.get_max_policy_display_percent(grading_policy) self.assertEqual(100, percent) percent = self.presenter.get_max_policy_display_percent([{'weight': 0.0}, {'weight': 1.0}, {'weight': 0.04}]) self.assertEqual(90, percent) @mock.patch('courses.presenters.performance.CoursePerformancePresenter.grading_policy', mock.Mock(return_value=CoursePerformanceDataFactory.grading_policy)) def test_assignment_types(self): """ Verify the presenter returns the correct assignment types. """ self.assertListEqual(self.presenter.assignment_types(), CoursePerformanceDataFactory.assignment_types) def test_assignments(self): """ Verify the presenter returns the correct assignments and sets the last updated date. """ self.assertIsNone(self.presenter.last_updated) with mock.patch('slumber.Resource.get', mock.Mock(return_value=self.factory.structure)): with mock.patch('analyticsclient.course.Course.problems', self.factory.problems): # With no assignment type set, the method should return all assignment types. assignments = self.presenter.assignments() expected_assignments = self.factory.present_assignments() self.assertListEqual(assignments, expected_assignments) self.assertEqual(self.presenter.last_updated, utils.CREATED_DATETIME) # With an assignment type set, the presenter should return only the assignments of the specified type. self.maxDiff = None for assignment_type in self.factory.assignment_types: cache.clear() expected = [assignment for assignment in expected_assignments if assignment[u'assignment_type'] == assignment_type] for index, assignment in enumerate(expected): assignment[u'index'] = index + 1 self.assertListEqual(self.presenter.assignments(assignment_type), expected) def test_assignment(self): """ Verify the presenter returns a specific assignment. """ with mock.patch('courses.presenters.performance.CoursePerformancePresenter.assignments', mock.Mock(return_value=self.factory.present_assignments())): # The method should return None if the assignment does not exist. self.assertIsNone(self.presenter.assignment(None)) self.assertIsNone(self.presenter.assignment('non-existent-id')) # The method should return an individual assignment if the ID exists. assignment = self.factory.present_assignments()[0] self.assertDictEqual(self.presenter.assignment(assignment[u'id']), assignment)
def setUp(self): super(CoursePerformanceViewTestMixin, self).setUp() self.factory = CoursePerformanceDataFactory() self.factory.course_id = DEMO_COURSE_ID
class CoursePerformanceViewTestMixin(CourseAPIMixin, NavAssertMixin, ViewTestMixin): patches = [] def _patch(self, target, **mock_kwargs): self.patches.append(patch(target, Mock(**mock_kwargs))) def start_patching(self): for _patch in self.patches: _patch.start() def stop_patching(self): for _patch in self.patches: if _is_started(_patch): _patch.stop() def clear_patches(self): self.stop_patching() self.patches = [] def setUp(self): super(CoursePerformanceViewTestMixin, self).setUp() self.toggle_switch('enable_course_api', True) self.factory = CoursePerformanceDataFactory() # Ensure patches from previous test failures are removed and de-referenced self.clear_patches() self._patch( 'courses.presenters.performance.CoursePerformancePresenter.assignments', return_value=self.factory.present_assignments()) self._patch( 'courses.presenters.performance.CoursePerformancePresenter.grading_policy', return_value=self.factory.grading_policy) self.start_patching() def tearDown(self): super(CoursePerformanceViewTestMixin, self).tearDown() self.clear_patches() def get_mock_data(self, course_id): # The subclasses don't need this. pass def assertPrimaryNav(self, context, course_id): """ Verifies that the Performance option is active in the main navigation bar. """ nav = context['primary_nav_item'] expected = { 'icon': 'fa-check-square-o', 'href': reverse('courses:performance:graded_content', kwargs={'course_id': course_id}), 'label': _('Performance'), 'name': 'performance' } self.assertDictEqual(nav, expected) def assertSecondaryNavs(self, context, course_id): """ Verifies that the Graded Content option is active in the secondary navigation bar. """ nav = context['secondary_nav_items'] expected = [{ 'active': True, 'name': 'graded_content', 'label': _('Graded Content'), 'href': '#' }] self.assertListEqual(nav, expected) @httpretty.activate def test_valid_course(self): """ The view should return HTTP 200 if the course is valid. Additional assertions should be added to validate page content. """ course_id = DEMO_COURSE_ID # Mock the course details self.mock_course_detail(course_id) # Retrieve the page. Validate the status code and context. response = self.client.get(self.path(course_id=course_id)) self.assertEqual(response.status_code, 200) self.assertValidContext(response.context) self.assertPrimaryNav(response.context, course_id) self.assertSecondaryNavs(response.context, course_id) @httpretty.activate def test_invalid_course(self): """ The view should return HTTP 404 if the course is invalid. """ self.stop_patching() course_id = 'fakeOrg/soFake/Fake_Course' self.grant_permission(self.user, course_id) self.mock_course_detail(course_id) path = self.path(course_id=course_id) # The course API would return a 404 for an invalid course. Simulate it to force an error in the view. api_path = 'grading_policies/{}/'.format(course_id) self.mock_course_api(api_path, status=404) response = self.client.get(path, follow=True) self.assertEqual(response.status_code, 404) def _test_api_error(self): # We need to break the methods that we normally patch. self.stop_patching() course_id = DEMO_COURSE_ID self.mock_course_detail(course_id) path = self.path(course_id=course_id) self.assertRaises(Exception, self.client.get, path, follow=True) @httpretty.activate @patch('analyticsclient.course.Course.problems', Mock(side_effect=ClientError)) @patch('analyticsclient.module.Module.answer_distribution', Mock(side_effect=ClientError)) def test_analytics_api_error(self): """ The view should return HTTP 500 if the Analytics Data API fails (e.g. timeout, HTTP 5XX). """ self._test_api_error() @httpretty.activate def test_course_api_error(self): """ The view should return HTTP 500 if the Course API fails (e.g. timeout, HTTP 5XX). """ # Nearly all course performance pages rely on retrieving the grading policy. # Break that endpoint to simulate an error. course_id = DEMO_COURSE_ID api_path = 'grading_policies/{}/'.format(course_id) self.mock_course_api(api_path, status=500) self._test_api_error() def assertValidContext(self, context): """ Validates the response context. This is intended to be validate the context of a VALID response returned by a view under normal conditions. """ expected = { 'assignment_types': self.factory.assignment_types, 'assignments': self.factory.present_assignments() } self.assertDictContainsSubset(expected, context)