def test_teammate_review_statuses(self): user_id = TestConstants.Users.USER1_ID reviews = { TestConstants.Users.USER2_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, peer=TestConstants.Users.USER2_ID, answer='not empty'), ], TestConstants.Users.USER3_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, peer=TestConstants.Users.USER3_ID, answer='not empty'), mri(user_id, self.REQUIRED_QUESTION_ID2, peer=TestConstants.Users.USER3_ID, answer='other') ], } self._setup_review_items_store(reviews) stage_element = self.get_stage(self.go_to_view(student_id=user_id)) expected_statuses = { TestConstants.Users.USER2_ID: ReviewState.INCOMPLETE, TestConstants.Users.USER3_ID: ReviewState.COMPLETED } self._assert_teammate_statuses(stage_element, expected_statuses)
def test_group_statuses(self): user_id = TestConstants.Users.USER1_ID reviews = { TestConstants.Groups.GROUP2_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, group=TestConstants.Groups.GROUP2_ID, answer='not empty'), ], TestConstants.Groups.GROUP3_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, group=TestConstants.Groups.GROUP3_ID, answer='not empty'), mri(user_id, self.REQUIRED_QUESTION_ID2, group=TestConstants.Groups.GROUP3_ID, answer='other') ], } self._setup_review_items_store(reviews) self.project_api_mock.get_workgroup_reviewers = mock.Mock( return_value=[{ "id": user_id }]) stage_element = self.get_stage(self.go_to_view(student_id=user_id)) expected_statuses = { TestConstants.Groups.GROUP2_ID: ReviewState.INCOMPLETE, TestConstants.Groups.GROUP3_ID: ReviewState.COMPLETED } self._assert_group_statuses(stage_element, expected_statuses)
class TestEvaluationDisplayStage(EvaluationStagesBaseTestMixin, BaseStageTest): block_to_test = EvaluationDisplayStage def setUp(self): super(TestEvaluationDisplayStage, self).setUp() self.team_members_mock = self.make_patch( self.block_to_test, 'team_members', mock.PropertyMock(return_value=[])) self.get_reviews_mock = self.make_patch(self.block, 'get_reviews') self.get_reviewer_ids_mock = self.make_patch(self.block, 'get_reviewer_ids') def assert_proceeds_calculation(self, should_perform_expensive_part): if should_perform_expensive_part: self.assertTrue(self.get_reviews_mock.called) self.assertTrue(self.get_reviewer_ids_mock.called) else: self.assertFalse(self.get_reviews_mock.called) self.assertFalse(self.get_reviewer_ids_mock.called) def test_can_mark_complete_no_reviewers_returns_true(self): self.team_members_mock.return_value = [] self.assertTrue(self.block.can_mark_complete) def test_can_mark_complete_no_questions_returns_true(self): with patch_obj(self.block_to_test, 'required_questions') as patched_required_questions: patched_required_questions.return_value = [] self.assertTrue(self.block.can_mark_complete) @ddt.data( ([10], ["q1"], [], False), ([10], ["q1"], [mri(10, "q1")], True), ([10], ["q1"], [mri(10, "q1"), mri(10, "q2")], True), ([10], ["q1"], [mri(11, "q1")], False), ([10], ["q1"], [mri(10, "q2"), mri(11, "q1")], False), ([10, 11], ["q1"], [mri(10, "q1"), mri(11, "q1")], True), ([10, 11], ["q1", "q2"], [mri(10, "q1"), mri(11, "q1")], False), ) @ddt.unpack def test_can_mark_compete_suite(self, reviewers, questions, reviews, expected_result): self.get_reviewer_ids_mock.return_value = reviewers self.get_reviews_mock.return_value = reviews with patch_obj(self.block_to_test, 'required_questions', mock.PropertyMock()) as patched_required_questions: patched_required_questions.return_value = questions self.assertEqual(self.block.can_mark_complete, expected_result)
def submit_review_items(reviewer_id, group_id, content_id, data): new_items = [ mri(reviewer_id, question_id, content_id=content_id, answer=answer, group=group_id) for question_id, answer in data.iteritems() if len(answer) > 0 ] store[group_id].extend(new_items)
def _parse_review_item_string(review_item_string): splitted = review_item_string.split(':') reviewer, question, group = splitted[:3] if len(splitted) > 3: answer = splitted[3] else: answer = None return mri(int(reviewer), question, group=group, answer=answer)
def test_group_statuses(self): user_id = TestConstants.Users.USER1_ID reviews = { TestConstants.Groups.GROUP2_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, group=TestConstants.Groups.GROUP2_ID, answer='not empty'), ], TestConstants.Groups.GROUP3_ID: [ mri(user_id, self.REQUIRED_QUESTION_ID1, group=TestConstants.Groups.GROUP3_ID, answer='not empty'), mri(user_id, self.REQUIRED_QUESTION_ID2, group=TestConstants.Groups.GROUP3_ID, answer='other') ], } self._setup_review_items_store(reviews) self.project_api_mock.get_workgroup_reviewers = mock.Mock(return_value=[{"id": user_id}]) stage_element = self.get_stage(self.go_to_view(student_id=user_id)) expected_statuses = { TestConstants.Groups.GROUP2_ID: ReviewState.INCOMPLETE, TestConstants.Groups.GROUP3_ID: ReviewState.COMPLETED } self._assert_group_statuses(stage_element, expected_statuses)
class TestProjectApi(TestCase, TestWithPatchesMixin): api_server_address = 'http://localhost' def setUp(self): self.project_api = TypedProjectAPI(self.api_server_address, dry_run=False) def _patch_send_request(self, calls_and_results, missing_callback=None): # pylint: disable=unused-argument def side_effect(method, url_parts, data=None, query_params=None, no_trailing_slash=False): if url_parts in calls_and_results: return calls_and_results[url_parts] if 'default' in calls_and_results: return calls_and_results['default'] if missing_callback: return missing_callback(url_parts) return None return mock.patch.object(self.project_api, 'send_request', mock.Mock(side_effect=side_effect)) def _patch_do_send_request(self, urls_and_results, missing_callback=None): # pylint: disable=unused-argument def side_effect(method, url, data=None): if url in urls_and_results: return urls_and_results[url] if 'default' in urls_and_results: return urls_and_results['default'] if missing_callback: return missing_callback(url) raise Exception("Response not found") return mock.patch.object(self.project_api, '_do_send_request', mock.Mock(side_effect=side_effect)) @ddt.data( (["part1", "part2" ], None, False, api_server_address + "/part1/part2/", { 'error': True }), (["part1", "part2"], None, True, api_server_address + "/part1/part2", { 'success': True }), (["part1", 1234, "part2" ], None, True, api_server_address + "/part1/1234/part2", { 'error': True }), (["part1", "part2", 1234 ], None, True, api_server_address + "/part1/part2/1234", { 'success': True }), ([api_server_address, "part1", "part2" ], None, False, api_server_address + "/part1/part2/", { 'success': True }), (["part1", "part2", "part3" ], None, False, api_server_address + "/part1/part2/part3/", { 'error': True }), (["part1"], { 'qwe': 'rty' }, False, api_server_address + "/part1/?qwe=rty", { 'success': True, 'data': [1, 2, 3] }), ([api_server_address, "part1"], { 'qwe': 'rty' }, False, api_server_address + "/part1/?qwe=rty", {}), (["part1"], { 'qwe': 'rty', 'asd': 'zxc' }, False, api_server_address + "/part1/?qwe=rty&asd=zxc", {}), (["part1"], { 'qwe': 'rty', 'asd': 'zxc' }, True, api_server_address + "/part1?qwe=rty&asd=zxc", {}), ) @ddt.unpack def test_send_request_no_data(self, url_parts, query_params, no_trailing_slash, expected_url, expected_response): response = mock.Mock() response.read.return_value = json.dumps(expected_response) method = mock.Mock(return_value=response) result = self.project_api.send_request( method, url_parts, query_params=query_params, no_trailing_slash=no_trailing_slash) method.assert_called_once_with(expected_url) self.assertEqual(result, expected_response) # pylint: disable=too-many-arguments @ddt.data( (["part1", "part2" ], None, [123], False, api_server_address + "/part1/part2/", { 'error': True }), (["part1", "part2" ], None, 'qwerty', True, api_server_address + "/part1/part2", { 'success': True }), (["part1", "part2", "part3"], None, { 'data': 11 }, False, api_server_address + "/part1/part2/part3/", { 'error': True }), (["part1"], { 'qwe': 'rty' }, { 'var1': 1, 'var2': 2 }, False, api_server_address + "/part1/?qwe=rty", { 'success': True, 'data': [1, 2, 3] }), (["part1"], { 'qwe': 'rty', 'asd': 'zxc' }, { 'stage': 1, 'activity': 2 }, False, api_server_address + "/part1/?qwe=rty&asd=zxc", {}), (["part1"], { 'qwe': 'rty', 'asd': 'zxc' }, { 'data': None }, True, api_server_address + "/part1?qwe=rty&asd=zxc", {}), ) @ddt.unpack def test_send_request_with_data(self, url_parts, query_params, data, no_trailing_slash, expected_url, expected_response): response = mock.Mock() response.read.return_value = json.dumps(expected_response) method = mock.Mock(return_value=response) result = self.project_api.send_request( method, url_parts, data=data, query_params=query_params, no_trailing_slash=no_trailing_slash) method.assert_called_once_with(expected_url, data) self.assertEqual(result, expected_response) def test_dry_run_does_not_send_request(self): method = mock.Mock() proj_api = TypedProjectAPI(self.api_server_address, True) result = proj_api.send_request(method, ('123', '34')) method.assert_not_called() self.assertEqual(result, {}) def test_send_delete_request_returns_none(self): with mock.patch( 'group_project_v2.project_api.api_implementation.DELETE' ) as patched_delete: result = self.project_api.send_request(patched_delete, ('123', '456')) self.assertEqual(result, None) patched_delete.assert_called_once_with(self.api_server_address + '/123/456/') @ddt.data( ('user1', 'course1', 'xblock:block-1', []), ('user1', 'course1', 'xblock:block-1', [1, 5]), ('user2', 'course-2', 'xblock:html-block-1', [1, 5, 6]), ('user7', 'course-15', 'xblock:construction-block-743', [6, 10, 15]), ) @ddt.unpack def test_get_workgroups_to_review(self, user_id, course_id, xblock_id, assignment_ids): def assignment_data_by_id(a_id): return {"id": a_id, 'data': 'data' + str(a_id)} with mock.patch.object(self.project_api, 'get_review_assignment_groups') as review_assignment_groups, \ mock.patch.object(self.project_api, 'get_workgroups_for_assignment') as workgroups_for_assignment: review_assignment_groups.return_value = [{ "id": assignment_id } for assignment_id in assignment_ids] workgroups_for_assignment.side_effect = lambda a_id: [ assignment_data_by_id(a_id) ] response = self.project_api.get_workgroups_to_review( user_id, course_id, xblock_id) review_assignment_groups.assert_called_once_with( user_id, course_id, xblock_id) self.assertEqual( workgroups_for_assignment.mock_calls, [mock.call(assignment_id) for assignment_id in assignment_ids]) self.assertEqual(response, [ assignment_data_by_id(assignment_id) for assignment_id in assignment_ids ]) @ddt.data( (1, 'content1', [], []), (2, 'content2', [{ 'data': { 'xblock_id': 'content2' }, 'url': 'url1' }], ['url1']), (3, 'content3', [{ 'data': { 'xblock_id': 'content2' }, 'url': 'url1' }, { 'data': { 'xblock_id': 'content3' }, 'url': 'url2' }, { 'data': { 'xblock_id': 'content3' }, 'url': 'url3' }], ['url2', 'url3']), ) @ddt.unpack def test_workgroup_reviewers(self, group_id, content_id, review_assignments, expected_urls): calls_and_results = { (WORKGROUP_API, group_id, 'groups'): review_assignments } def missing_callback(url_parts): # pylint: disable=unused-argument return {'users': [1, 2, 3]} with self._patch_send_request( calls_and_results, missing_callback) as patched_send_request: response = self.project_api.get_workgroup_reviewers( group_id, content_id) self.assertEqual(patched_send_request.mock_calls, [ mock.call(GET, (WORKGROUP_API, group_id, 'groups'), no_trailing_slash=True) ] + [ mock.call(GET, (expected_url, 'users')) for expected_url in expected_urls ]) self.assertEqual(response, [1, 2, 3] * len(expected_urls)) @ddt.data( (1, 2, [mri(1, 'qwe', peer=2), mri(1, 'asd', peer=3)], [mri(1, 'qwe', peer=2)]), (5, 3, [mri(5, 'qwe', peer=3), mri(5, 'asd', peer=3)], [mri(5, 'qwe', peer=3), mri(5, 'asd', peer=3)]), (11, 12, [mri(11, 'qwe', peer=3), mri(11, 'asd', peer=4)], []), (11, 12, [mri(15, 'qwe', peer=12), mri(18, 'asd', peer=12)], []), ) @ddt.unpack def test_get_peer_review_items(self, reviewer_id, peer_id, review_items, expected_result): with mock.patch.object( self.project_api, 'get_peer_review_items_for_group') as patched_get_review_items: patched_get_review_items.return_value = review_items result = self.project_api.get_peer_review_items( reviewer_id, peer_id, 'group_id', 'content_id') self.assertEqual(result, expected_result) patched_get_review_items.assert_called_once_with( 'group_id', 'content_id') @ddt.data( (1, [mri(2, 'qwe', peer=1), mri(5, 'asd', peer=1)], [mri(2, 'qwe', peer=1), mri(5, 'asd', peer=1)]), (5, [mri(7, 'qwe', peer=5), mri(7, 'asd', peer=5)], [mri(7, 'qwe', peer=5), mri(7, 'asd', peer=5)]), (11, [mri(16, 'qwe', peer=3), mri(18, 'asd', peer=4)], []), (11, [mri(16, 'qwe', peer=3), mri(18, 'question1', peer=11)], [mri(18, 'question1', peer=11)]), ) @ddt.unpack def test_get_user_peer_review_items(self, user_id, review_items, expected_result): with mock.patch.object( self.project_api, 'get_peer_review_items_for_group') as patched_get_review_items: patched_get_review_items.return_value = review_items result = self.project_api.get_user_peer_review_items( user_id, 'group_id', 'content_id') self.assertEqual(result, expected_result) patched_get_review_items.assert_called_once_with( 'group_id', 'content_id') # pylint: disable=too-many-function-args @ddt.data( (1, 'content_1', [ mri(1, 'qwe', peer=7, content_id='content_1'), mri(1, 'asd', peer=7, content_id='content_1') ], [ mri(1, 'qwe', peer=7, content_id='content_1'), mri(1, 'asd', peer=7, content_id='content_1') ]), (5, 'content_2', [ mri(5, 'qwe', peer=14, content_id='content_2'), mri(5, 'asd', peer=19, content_id='content_2') ], [ mri(5, 'qwe', peer=14, content_id='content_2'), mri(5, 'asd', peer=19, content_id='content_2') ]), (11, 'content_3', [ mri(11, 'qwe', peer=3, content_id='content_2'), mri(16, 'asd', peer=4, content_id='content_3') ], []), (11, 'content_4', [ mri(12, 'qwe', peer=18, content_id='content_4'), mri(11, 'question1', peer=18, content_id='content_4') ], [mri(11, 'question1', peer=18, content_id='content_4')]), ) @ddt.unpack def test_get_workgroup_review_items(self, reviewer_id, content_id, review_items, expected_result): with mock.patch.object(self.project_api, 'get_workgroup_review_items_for_group' ) as patched_get_review_items: patched_get_review_items.return_value = review_items result = self.project_api.get_workgroup_review_items( reviewer_id, 'group_id', content_id) self.assertEqual(result, expected_result) patched_get_review_items.assert_called_once_with( 'group_id', content_id) def assert_project_data(self, project_data, expected_values): attrs_to_test = [ "id", "url", "created", "modified", "course_id", "content_id", "organization", "workgroups" ] for attr in attrs_to_test: self.assertEqual(getattr(project_data, attr), expected_values[attr]) def test_get_project_details(self): calls_and_results = { (PROJECTS_API, 1): canned_responses.Projects.project1['results'][0], (PROJECTS_API, 2): canned_responses.Projects.project2['results'][0] } expected_calls = [ mock.call(GET, (PROJECTS_API, 1), no_trailing_slash=True), mock.call(GET, (PROJECTS_API, 2), no_trailing_slash=True) ] with self._patch_send_request( calls_and_results) as patched_send_request: project1 = self.project_api.get_project_details(1) project2 = self.project_api.get_project_details(2) self.assertEqual(patched_send_request.mock_calls, expected_calls) self.assert_project_data( project1, canned_responses.Projects.project1['results'][0]) self.assert_project_data( project2, canned_responses.Projects.project2['results'][0]) @ddt.data( ('course1', 'content1'), ('course2', 'content2'), ) @ddt.unpack def test_get_project_by_content_id(self, course_id, content_id): expected_parameters = { 'course_id': course_id, 'content_id': content_id } calls_and_results = { (PROJECTS_API, ): canned_responses.Projects.project1 } with self._patch_send_request( calls_and_results) as patched_send_request: project = self.project_api.get_project_by_content_id( course_id, content_id) self.assert_project_data( project, canned_responses.Projects.project1['results'][0]) patched_send_request.assert_called_once_with( GET, (PROJECTS_API, ), query_params=expected_parameters) def test_get_project_by_content_id_fail_if_more_than_one(self): calls_and_results = { (PROJECTS_API, ): canned_responses.Projects.two_projects } with self._patch_send_request(calls_and_results), \ self.assertRaises(AssertionError): self.project_api.get_project_by_content_id('irrelevant', 'irrelevant') def test_get_project_by_content_id_return_none_if_not_found(self): calls_and_results = { (PROJECTS_API, ): canned_responses.Projects.zero_projects } with self._patch_send_request(calls_and_results): project = self.project_api.get_project_by_content_id( 'irrelevant', 'irrelevant') self.assertIsNone(project) @ddt.data( (1, canned_responses.Workgroups.workgroup1), (2, canned_responses.Workgroups.workgroup2), ) @ddt.unpack def test_get_workgroup_by_id(self, group_id, expected_result): calls_and_results = { (WORKGROUP_API, 1): canned_responses.Workgroups.workgroup1, (WORKGROUP_API, 2): canned_responses.Workgroups.workgroup2 } with self._patch_send_request( calls_and_results) as patched_send_request: workgroup = self.project_api.get_workgroup_by_id(group_id) patched_send_request.assert_called_once_with( GET, (WORKGROUP_API, group_id)) self.assertEqual(workgroup.id, expected_result['id']) self.assertEqual(workgroup.project, expected_result['project']) self.assertEqual(len(workgroup.users), len(expected_result['users'])) self.assertEqual([user.id for user in workgroup.users], [user['id'] for user in expected_result['users']]) @ddt.data(('course1', 'content1'), ('course1', 'content2'), ('course2', 'content3')) @ddt.unpack def test_get_completions_by_content_id(self, course_id, content_id): def build_url(course_id, content_id): return self.project_api.build_url( (COURSES_API, course_id, 'completions'), query_params={'content_id': content_id}) urls_and_results = { build_url('course1', 'content1'): canned_responses.Completions.non_paged1, build_url('course1', 'content2'): canned_responses.Completions.non_paged2, build_url('course2', 'content3'): canned_responses.Completions.empty, } expected_url = build_url(course_id, content_id) expected_data = urls_and_results.get(expected_url) with self._patch_do_send_request( urls_and_results) as patched_do_send_request: completions = list( self.project_api.get_completions_by_content_id( course_id, content_id)) patched_do_send_request.assert_called_once_with( GET, expected_url, None) self.assertEqual(len(completions), len(expected_data['results'])) self.assertEqual([comp.id for comp in completions], [data['id'] for data in expected_data['results']]) def test_get_completions_by_content_id_paged(self): def build_url(course_id, content_id, page_num=None): query_params = {'content_id': content_id} if page_num: query_params['page'] = page_num return self.project_api.build_url( (COURSES_API, course_id, 'completions'), query_params=query_params) course, content = 'course1', 'content1' urls_and_results = { build_url(course, content): canned_responses.Completions.paged_page1, build_url(course, content, 1): canned_responses.Completions.paged_page1, build_url(course, content, 2): canned_responses.Completions.paged_page2, build_url(course, content, 3): canned_responses.Completions.paged_page3, } all_responses = [] pages = [ canned_responses.Completions.paged_page1, canned_responses.Completions.paged_page2, canned_responses.Completions.paged_page3 ] for page in pages: all_responses.extend(page['results']) expected_calls = [ mock.call(GET, build_url(course, content), None), mock.call(GET, canned_responses.Completions.paged_page1['next'], None), mock.call(GET, canned_responses.Completions.paged_page2['next'], None), ] with self._patch_do_send_request( urls_and_results) as patched_do_send_request: completions = list( self.project_api.get_completions_by_content_id( course, content)) self.assertEqual(patched_do_send_request.mock_calls, expected_calls) self.assertEqual(len(completions), len(all_responses)) self.assertEqual([comp.id for comp in completions], [data['id'] for data in all_responses]) self.assertEqual([comp.user_id for comp in completions], [data['user_id'] for data in all_responses]) @ddt.data( ({'foo', 'bar', 'baz'}, 1234, 4321), ({'foo', 'bar'}, 1, 2), ({'foo'}, 2, 1), (set(), 4321, 1234), ) @ddt.unpack def test_get_user_roles_for_course(self, roles, user_id, course_id): self.project_api.send_request = mock.Mock(return_value=[{ 'role': role, 'id': user_id } for role in roles]) response = self.project_api.get_user_roles_for_course( user_id, course_id) self.assertEqual(response, roles) self.project_api.send_request.assert_called_once_with( GET, ('api/server/courses', course_id, 'roles'), query_params={'user_id': user_id})
class TestPeerReviewStageReviewStatus(ReviewStageBaseTest, ReviewStageUserCompletionStatsMixin, BaseStageTest): block_to_test = PeerReviewStage def setUp(self): super(TestPeerReviewStageReviewStatus, self).setUp() self.activity_mock.is_ta_graded = False def _set_project_api_responses(self, workgroups, review_items): def workgroups_side_effect(user_id, _course_id, _content_id): return workgroups.get(user_id, None) def review_items_side_effect(workgroup_id, _content_id): return review_items.get(workgroup_id, []) self.project_api_mock.get_workgroups_to_review.side_effect = workgroups_side_effect self.project_api_mock.get_workgroup_review_items_for_group.side_effect = review_items_side_effect @staticmethod def _parse_review_item_string(review_item_string): splitted = review_item_string.split(':') reviewer, question, group = splitted[:3] if len(splitted) > 3: answer = splitted[3] else: answer = None return mri(int(reviewer), question, group=group, answer=answer) @ddt.data( ([GROUP_ID], ["q1"], { GROUP_ID: [] }, ReviewState.NOT_STARTED), ([GROUP_ID], ["q1"], { GROUP_ID: [mri(USER_ID, "q1", group=GROUP_ID, answer='1')] }, ReviewState.COMPLETED), ([GROUP_ID], ["q1"], { GROUP_ID: [mri(OTHER_USER_ID, "q1", group=GROUP_ID, answer='1')] }, ReviewState.NOT_STARTED), ([GROUP_ID], ["q1", "q2"], { GROUP_ID: [ mri(USER_ID, "q1", group=GROUP_ID, answer='1'), mri(OTHER_USER_ID, "q1", group=GROUP_ID, answer='1') ] }, ReviewState.INCOMPLETE), ([GROUP_ID], ["q1"], { GROUP_ID: [ mri(USER_ID, "q1", group=GROUP_ID, answer='2'), mri(USER_ID, "q2", group=GROUP_ID, answer="1") ] }, ReviewState.COMPLETED), ([GROUP_ID], ["q1", "q2"], { GROUP_ID: [mri(USER_ID, "q1", group=GROUP_ID, answer='3')] }, ReviewState.INCOMPLETE), ([GROUP_ID], ["q1"], { GROUP_ID: [ mri(USER_ID, "q2", group=GROUP_ID, answer='4'), mri(USER_ID, "q1", group=OTHER_GROUP_ID, answer='5') ] }, ReviewState.NOT_STARTED), ([GROUP_ID, OTHER_GROUP_ID], ["q1"], { GROUP_ID: [mri(USER_ID, "q1", group=GROUP_ID, answer='6')], OTHER_GROUP_ID: [mri(USER_ID, "q1", group=OTHER_GROUP_ID, answer='7')] }, ReviewState.COMPLETED), ([GROUP_ID, OTHER_GROUP_ID], ["q1", "q2"], { GROUP_ID: [mri(USER_ID, "q1", group=GROUP_ID, answer='7')], OTHER_GROUP_ID: [mri(USER_ID, "q1", group=OTHER_GROUP_ID, answer='8')] }, ReviewState.INCOMPLETE), ([GROUP_ID, OTHER_GROUP_ID], ["q1", "q2"], { GROUP_ID: [ mri(USER_ID, "q1", group=GROUP_ID, answer='7'), mri(USER_ID, "q2", group=GROUP_ID, answer='7') ], OTHER_GROUP_ID: [ mri(USER_ID, "q1", group=OTHER_GROUP_ID, answer='8'), mri(USER_ID, "q2", group=GROUP_ID, answer='7') ] }, ReviewState.INCOMPLETE), ) @ddt.unpack def test_review_status(self, groups, questions, reviews, expected_result): def get_reviews(group_id, _component_id): return reviews.get(group_id, []) expected_calls = [ mock.call(group_id, self.block.activity_content_id) for group_id in groups ] self.project_api_mock.get_workgroup_review_items_for_group.side_effect = get_reviews with patch_obj(self.block_to_test, 'review_subjects', mock.PropertyMock()) as patched_review_subjects, \ patch_obj(self.block_to_test, 'required_questions', mock.PropertyMock()) as patched_questions: patched_review_subjects.return_value = [ WorkgroupDetails(id=rev_id) for rev_id in groups ] patched_questions.return_value = [ make_question(q_id, 'irrelevant') for q_id in questions ] self.assertEqual(self.block.review_status(), expected_result) self.assertEqual( self.project_api_mock.get_workgroup_review_items_for_group. mock_calls, expected_calls) @ddt.data( # no reviews - not started ([GROUP_ID], ["q1"], [], (set(), set())), # some reviews - partially completed ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a"], (set(), {1})), # some reviews - partially completed; other reviewers reviews doe not affect the state ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "2:q1:10:a", "2:q2:10:a"], (set(), {1})), # all reviews - completed ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "1:q2:10:a"], ({1}, set())), # no reviews - not started ([GROUP_ID, OTHER_GROUP_ID], ["q1", "q2"], [], (set(), set())), # some reviews - partially completed ([GROUP_ID, OTHER_GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "1:q2:10:b"], (set(), {1})), # all reviews, but some answers are None - partially completed ([GROUP_ID, OTHER_GROUP_ID], ["q1"], ["1:q1:10:a", "1:q1:11"], (set(), {1})), # all reviews, but some answers are empty - partially completed ([GROUP_ID, OTHER_GROUP_ID], ["q1"], ["1:q1:10:a", "1:q1:11:"], (set(), {1})), # all reviews - completed ([GROUP_ID, OTHER_GROUP_ID], ["q1"], ["1:q1:10:a", "1:q1:11:b"], ({1}, set())), ) @ddt.unpack def test_users_completion_single_user(self, groups_to_review, questions, review_items, expected_result): user_id = 1 review_items = [ self._parse_review_item_string(review_item_str) for review_item_str in review_items ] self._set_project_api_responses( {user_id: [mk_wg(group_id) for group_id in groups_to_review]}, {GROUP_ID: review_items}) self.assert_users_completion(expected_result, questions, [user_id]) # checks if caching is ok expected_calls = [ mock.call(group_id, self.block.activity_content_id) for group_id in groups_to_review ] self.assertEqual( self.project_api_mock.get_workgroup_review_items_for_group. mock_calls, expected_calls) @ddt.data( # no reviews - both not started ([GROUP_ID], ["q1", "q2"], [], (set(), set())), # u1 some reviews - u1 partially completed ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a"], (set(), {1})), # u1 all reviews - u1 completed, u2 - not started ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "1:q2:10:b"], ({1}, set())), # u1 some reviews, u2 some reviews - both partially completed ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "2:q1:10:b"], (set(), {1, 2})), # u1 all reviews, u2 some reviews - u1 completed, u2 partially completed ([GROUP_ID], ["q1", "q2"], ["1:q1:10:a", "1:q2:10:b", "2:q1:10:c"], ({1}, {2})), # both all reviews - both completed ([GROUP_ID], ["q1", "q2" ], ["1:q1:10:a", "1:q2:10:b", "2:q1:10:c", "2:q2:10:d"], ({1, 2}, set())), ) @ddt.unpack def test_users_completion_same_groups(self, groups_to_review, questions, review_items, expected_result): target_users = [1, 2] review_items = [ self._parse_review_item_string(review_item_str) for review_item_str in review_items ] self._set_project_api_responses( { user_id: [mk_wg(group_id) for group_id in groups_to_review] for user_id in target_users }, {GROUP_ID: review_items}) self.assert_users_completion(expected_result, questions, target_users) # checks if caching is ok self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with( GROUP_ID, self.block.activity_content_id) @ddt.data( # no reviews - both not started ([1, 3], { GROUP_ID: [1, 3] }, ['q1'], {}, (set(), set())), # u1 some reviews - u1 partially, u4 - not started ([1, 4], { GROUP_ID: [1, 4] }, ['q1', 'q2'], { GROUP_ID: ['1:q1:10:b'] }, (set(), {1})), # u2 all reviews - u2 completed, u3 - not started ([2, 3], { GROUP_ID: [2, 3] }, ['q1'], { GROUP_ID: ['2:q1:10:a'] }, ({2}, set())), # u3 all reviews - u3 completed, u1, u2 not started ([1, 2, 3], { OTHER_GROUP_ID: [1, 2, 3] }, ['q1'], { OTHER_GROUP_ID: ['3:q1:11:a'] }, ({3}, set())), # u1, u2, u3 all reviews - u1, u2, u3 completed ([1, 2, 3], { GROUP_ID: [1, 2], OTHER_GROUP_ID: [3] }, ['q1'], { GROUP_ID: ['1:q1:10:a', '2:q1:10:b'], OTHER_GROUP_ID: ['3:q1:11:c'] }, ({1, 2, 3}, set())), # u1, u2, u3 all reviews, u4 no reviews - u1, u2, u3 completed, u4 not started ([1, 2, 3, 4], { GROUP_ID: [1, 2], OTHER_GROUP_ID: [3, 4] }, ['q1'], { GROUP_ID: ['1:q1:10:a', '2:q1:10:b'], OTHER_GROUP_ID: ['3:q1:11:c'] }, ({1, 2, 3}, set())), # u1 all reviews, u3 some reviews - u1 completed, u3 partially completed ([1, 3], { GROUP_ID: [1, 3], OTHER_GROUP_ID: [3] }, ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '3:q1:10:c', '3:q2:10:d'], OTHER_GROUP_ID: ['3:q1:11:e'] }, ({1}, {3})), # u1 all reviews, u3 some reviews - u1 completed, u3 partially completed ([1, 3], { GROUP_ID: [1, 3], OTHER_GROUP_ID: [3] }, ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '3:q1:10:c'], OTHER_GROUP_ID: ['3:q1:11:e', '3:q2:11:d'] }, ({1}, {3})), ) @ddt.unpack # pylint:disable=too-many-locals def test_users_completion_multiple_groups(self, target_users, group_reviewers, questions, review_items, expected_result): groups_to_review = defaultdict(list) for group_id, reviewers in group_reviewers.iteritems(): for reviewer in reviewers: groups_to_review[reviewer].append(mk_wg(group_id)) self._set_project_api_responses( groups_to_review, { group_id: [self._parse_review_item_string(item) for item in items] for group_id, items in review_items.iteritems() }) self.assert_users_completion(expected_result, questions, target_users) # checks if caching is ok expected_calls = [ mock.call(group_id, self.block.activity_content_id) for group_id in group_reviewers.keys() ] self.assertEqual( self.project_api_mock.get_workgroup_review_items_for_group. mock_calls, expected_calls) @ddt.data( # no reviewers - not started ([], ['q1'], {}, StageState.NOT_STARTED), # no reviews - not started ([1], ['q1'], {}, StageState.NOT_STARTED), # complete review for one group - completed ([1], ['q1'], { GROUP_ID: ['1:q1:10:a'] }, StageState.COMPLETED), # partial review for one group - partially complete ([1], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a'] }, StageState.INCOMPLETE), # complete review for one group with multiple questions - completed ([1], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b'] }, StageState.COMPLETED), # multiple reviewers - no reviews - not started ([1, 2], ['q1'], {}, StageState.NOT_STARTED), # multiple reviewers - one partial, other not started - partially complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a'] }, StageState.INCOMPLETE), # multiple reviewers - both partial - partially complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '2:q2:10:b'] }, StageState.INCOMPLETE), # multiple reviewers - one complete, one partial - partially complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '2:q2:10:b'] }, StageState.INCOMPLETE), # multiple reviewers - both complete - complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '2:q1:10:c', '2:q2:10:d'] }, StageState.COMPLETED), ) @ddt.unpack def test_get_external_group_status(self, reviewers, questions, review_items, expected_result): group = mk_wg(GROUP_ID, [{"id": 1}]) self.project_api_mock.get_workgroup_reviewers.return_value = [{ 'id': user_id } for user_id in reviewers] self._set_project_api_responses( group, { group.id: [self._parse_review_item_string(item) for item in items] for group_id, items in review_items.iteritems() }) self.assert_group_completion(group, questions, expected_result) self.project_api_mock.get_workgroup_reviewers.assert_called_once_with( group.id, self.block.activity_content_id) @ddt.data( # no ta reviewers - not started ([], ['q1'], {}, StageState.NOT_STARTED), # no reviews - not started ([1], ['q1'], {}, StageState.NOT_STARTED), # complete review for one group - completed ([1], ['q1'], { GROUP_ID: ['1:q1:10:a'] }, StageState.COMPLETED), # partial review for one group - partially complete ([1], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a'] }, StageState.INCOMPLETE), # complete review for one group with multiple questions - completed ([1], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b'] }, StageState.COMPLETED), # multiple TAs - no reviews - not started ([1, 2], ['q1'], {}, StageState.NOT_STARTED), # multiple TAs - one partial, other not started - partially complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a'] }, StageState.INCOMPLETE), # multiple TAs - both partial - partially complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '2:q2:10:b'] }, StageState.INCOMPLETE), # multiple TAs - one complete, other partial - complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '2:q2:10:c'] }, StageState.COMPLETED), # multiple reviewers - both complete - complete ([1, 2], ['q1', 'q2'], { GROUP_ID: ['1:q1:10:a', '1:q2:10:b', '2:q2:10:c', '2:q2:10:d'] }, StageState.COMPLETED), ) @ddt.unpack def test_ta_get_external_group_status(self, ta_reviewers, questions, review_items, expected_result): group_to_review = mk_wg(GROUP_ID, [{"id": 1}]) self.activity_mock.is_ta_graded = True self._set_project_api_responses( group_to_review, { group_to_review.id: [self._parse_review_item_string(item) for item in items] for group_id, items in review_items.iteritems() }) with patch_obj(self.block, 'is_user_ta') as patched_outsider_allowed: patched_outsider_allowed.side_effect = lambda user_id, _course_id: user_id in ta_reviewers self.assert_group_completion(group_to_review, questions, expected_result)
class TestGroupProjectGradeEvaluationDisplayXBlock( CommonFeedbackDisplayStageTests, StageComponentXBlockTestBase): block_to_test = GroupProjectGradeEvaluationDisplayXBlock @ddt.data( (2, 'content-1', 'q1', [mri(1, "q1"), mri(2, "q1") ], [mri(1, "q1"), mri(2, "q1")]), (9, 'content-2', 'q2', [mri(1, "q1"), mri(2, "q1")], []), (15, 'content-3', 'q1', [mri(1, "q1"), mri(1, "q2")], [mri(1, "q1")]), (15, 'content-3', 'q2', [mri(1, "q1"), mri(1, "q2")], [mri(1, "q2")]), ) @ddt.unpack def test_get_feedback(self, group_id, content_id, question_id, feedback_items, expected_result): self.project_api_mock.get_workgroup_review_items_for_group = mock.Mock( return_value=feedback_items) self.stage_mock.activity_content_id = content_id self.block.question_id = question_id with mock.patch.object(self.block_to_test, 'group_id', mock.PropertyMock(return_value=group_id)): result = self.block.get_feedback() self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with( group_id, content_id) self.assertEqual(result, expected_result) def test_activity_questions(self): self.activity_mock.team_evaluation_questions = [1, 2, 3] self.activity_mock.peer_review_questions = [4, 5, 6] self.assertEqual(self.block.activity_questions, [4, 5, 6])
class TestGroupProjectTeamEvaluationDisplayXBlock( CommonFeedbackDisplayStageTests, StageComponentXBlockTestBase): block_to_test = GroupProjectTeamEvaluationDisplayXBlock # pylint: disable=too-many-arguments @ddt.data( (1, 2, 'content-1', 'q1', [mri(1, "q1"), mri( 2, "q1")], [mri(1, "q1"), mri(2, "q1")]), (3, 9, 'content-2', 'q2', [mri(1, "q1"), mri(2, "q1")], []), (7, 15, 'content-3', 'q1', [mri(1, "q1"), mri(1, "q2") ], [mri(1, "q1")]), (7, 15, 'content-3', 'q2', [mri(1, "q1"), mri(1, "q2") ], [mri(1, "q2")]), ) @ddt.unpack def test_get_feedback(self, user_id, group_id, content_id, question_id, feedback_items, expected_result): self.project_api_mock.get_user_peer_review_items = mock.Mock( return_value=feedback_items) self.stage_mock.activity_content_id = content_id self.block.question_id = question_id with mock.patch.object(self.block_to_test, 'user_id', mock.PropertyMock(return_value=user_id)), \ mock.patch.object(self.block_to_test, 'group_id', mock.PropertyMock(return_value=group_id)): result = self.block.get_feedback() self.project_api_mock.get_user_peer_review_items.assert_called_once_with( user_id, group_id, content_id) self.assertEqual(result, expected_result) def test_activity_questions(self): self.activity_mock.team_evaluation_questions = [1, 2, 3] self.activity_mock.peer_review_questions = [4, 5, 6] self.assertEqual(self.block.activity_questions, [1, 2, 3])
class TestTeamEvaluationStageStageStatus(ReviewStageUserCompletionStatsMixin, BaseStageTest): block_to_test = TeamEvaluationStage @ddt.data( ([10], ["q1"], [], ReviewState.NOT_STARTED), ([10], ["q1"], [mri(USER_ID, "q1", peer=10, answer='1')], ReviewState.COMPLETED), ([10], ["q1"], [mri(OTHER_USER_ID, "q1", peer=10, answer='1')], ReviewState.NOT_STARTED), ( [10], ["q1", "q2"], [mri(USER_ID, "q1", peer=10, answer='1'), mri(OTHER_USER_ID, "q1", peer=10, answer='1')], ReviewState.INCOMPLETE ), ( [10], ["q1"], [mri(USER_ID, "q1", peer=10, answer='2'), mri(USER_ID, "q2", peer=10, answer="1")], ReviewState.COMPLETED ), ( [10], ["q1", "q2"], [mri(USER_ID, "q1", peer=10, answer='3')], ReviewState.INCOMPLETE ), ( [10], ["q1"], [mri(USER_ID, "q2", peer=10, answer='4'), mri(USER_ID, "q1", peer=11, answer='5')], ReviewState.NOT_STARTED ), ( [10, 11], ["q1"], [mri(USER_ID, "q1", peer=10, answer='6'), mri(USER_ID, "q1", peer=11, answer='7')], ReviewState.COMPLETED ), ( [10, 11], ["q1", "q2"], [mri(USER_ID, "q1", peer=10, answer='7'), mri(USER_ID, "q1", peer=11, answer='8')], ReviewState.INCOMPLETE ), ) @ddt.unpack def test_review_status(self, peers_to_review, questions, reviews, expected_result): self.project_api_mock.get_peer_review_items_for_group.return_value = reviews with patch_obj(self.block_to_test, 'review_subjects', mock.PropertyMock()) as patched_review_subjects, \ patch_obj(self.block_to_test, 'required_questions', mock.PropertyMock()) as patched_questions: patched_review_subjects.return_value = [ReducedUserDetails(id=rev_id) for rev_id in peers_to_review] patched_questions.return_value = [make_question(q_id, 'irrelevant') for q_id in questions] self.assertEqual(self.block.review_status(), expected_result) self.project_api_mock.get_peer_review_items_for_group.assert_called_once_with( self.workgroup_data.id, self.activity_mock.content_id ) def _set_project_api_responses(self, workgroups, review_items): def workgroups_side_effect(user_id, _course_id): return workgroups.get(user_id, None) def review_items_side_effect(workgroup_id, _content_id): return review_items.get(workgroup_id, []) self.project_api_mock.get_user_workgroup_for_course.side_effect = workgroups_side_effect self.project_api_mock.get_peer_review_items_for_group.side_effect = review_items_side_effect @staticmethod def _parse_review_item_string(review_item_string): splitted = review_item_string.split(':') reviewer, question, peer = splitted[:3] if len(splitted) > 3: answer = splitted[3] else: answer = None return mri(int(reviewer), question, peer=peer, answer=answer) @ddt.data( # no reviews - not started ([1, 2], ["q1", "q2"], [], (set(), set())), # some reviews - partially completed ([1, 2], ["q1", "q2"], ["1:q1:2:a"], (set(), {1})), # all reviews - completed ([1, 2], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b"], ({1}, set())), # no reviews - not started ([1, 2, 3], ["q1", "q2"], [], (set(), set())), # some reviews - partially completed ([1, 2, 3], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b"], (set(), {1})), # all reviews, but some answers are None - partially completed ([1, 2, 3], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b", "1:q1:3", "1:q2:3:d"], (set(), {1})), # all reviews, but some answers are empty - partially completed ([1, 2, 3], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b", "1:q1:3:", "1:q2:3:d"], (set(), {1})), # all reviews - completed ([1, 2, 3], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b", "1:q1:3:c", "1:q2:3:d"], ({1}, set())), ) @ddt.unpack def test_users_completion_single_user(self, users_in_group, questions, review_items, expected_result): user_id = 1 workgroup_id = 1 review_items = [self._parse_review_item_string(review_item_str) for review_item_str in review_items] self._set_project_api_responses( {user_id: mk_wg(workgroup_id, users=[{"id": uid} for uid in users_in_group])}, {workgroup_id: review_items} ) self.assert_users_completion(expected_result, questions, [user_id]) # checks if caching is ok self.project_api_mock.get_peer_review_items_for_group.assert_called_once_with( workgroup_id, self.block.activity_content_id ) @ddt.data( # no reviews - both not started ([1, 2], ["q1", "q2"], [], (set(), set())), # u1 some reviews - u1 partially completed ([1, 2], ["q1", "q2"], ["1:q1:2:a"], (set(), {1})), # u1 all reviews - u1 completed, u2 - not started ([1, 2], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b"], ({1}, set())), # u1 some reviews, u2 some reviews - both partially completed ([1, 2], ["q1", "q2"], ["1:q1:2:a", "2:q1:1:b"], (set(), {1, 2})), # u1 all reviews, u2 some reviews - u1 completed, u2 partially completed ([1, 2], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b", "2:q1:1:c"], ({1}, {2})), # both all reviews - both completed ([1, 2], ["q1", "q2"], ["1:q1:2:a", "1:q2:2:b", "2:q1:1:c", "2:q2:1:d"], ({1, 2}, set())), ) @ddt.unpack def test_users_completion_same_group_users(self, users_in_group, questions, review_items, expected_result): workgroup_id = 1 workgroup_data = mk_wg(workgroup_id, users=[{"id": uid} for uid in users_in_group]) review_items = [self._parse_review_item_string(review_item_str) for review_item_str in review_items] self._set_project_api_responses( {uid: workgroup_data for uid in users_in_group}, {workgroup_id: review_items} ) self.assert_users_completion(expected_result, questions, users_in_group) # checks if caching is ok self.project_api_mock.get_peer_review_items_for_group.assert_called_once_with( workgroup_id, self.block.activity_content_id ) @ddt.data( # no reviews - both not started ([1, 3], ['q1'], {}, (set(), set())), # u1 some reviews - u1 partially, u4 - not started ([1, 4], ['q1', 'q2'], {GROUP_ID: ['1:q1:2:b']}, (set(), {1})), # u2 all reviews - u2 completed, u3 - not started ([2, 3], ['q1'], {GROUP_ID: ['2:q1:1:a']}, ({2}, set())), # u3 all reviews - u3 completed, u1, u2 not started ([1, 2, 3], ['q1'], {OTHER_GROUP_ID: ['3:q1:4:a']}, ({3}, set())), # u1, u2, u3 all reviews - u1, u2, u3 completed ( [1, 2, 3], ['q1'], {GROUP_ID: ['1:q1:2:a', '2:q1:1:b'], OTHER_GROUP_ID: ['3:q1:4:c']}, ({1, 2, 3}, set()) ), # u1, u2, u3 all reviews, u4 no reviews - u1, u2, u3 completed, u4 not started ( [1, 2, 3, 4], ['q1'], {GROUP_ID: ['1:q1:2:a', '2:q1:1:b'], OTHER_GROUP_ID: ['3:q1:4:c']}, ({1, 2, 3}, set()) ), # u1 all reviews, u3 some reviews - u1 completed, u3 partially completed ( [1, 3], ['q1', 'q2'], {GROUP_ID: ['1:q1:2:a', '1:q2:2:b'], OTHER_GROUP_ID: ['3:q1:4:c']}, ({1}, {3}) ), ) @ddt.unpack def test_users_completion_multiple_groups(self, target_users, questions, review_items, expected_result): workgroups = [ mk_wg(GROUP_ID, users=[{"id": 1}, {"id": 2}]), mk_wg(OTHER_GROUP_ID, users=[{"id": 3}, {"id": 4}]), ] self._set_project_api_responses( {1: workgroups[0], 2: workgroups[0], 3: workgroups[1], 4: workgroups[1]}, { group_id: [self._parse_review_item_string(item) for item in items] for group_id, items in review_items.items() } ) self.assert_users_completion(expected_result, questions, target_users) # checks if caching is ok expected_calls = [ mock.call(GROUP_ID, self.block.activity_content_id), mock.call(OTHER_GROUP_ID, self.block.activity_content_id) ] self.assertEqual(self.project_api_mock.get_peer_review_items_for_group.mock_calls, expected_calls)