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)