def setUp(self):
        self.project_api_mock = mock.PropertyMock()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.grade_questions_mock = mock.PropertyMock()
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=mock.Mock())

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.grade_questions_mock = self.make_patch(GroupActivityXBlock, 'grade_questions', mock.PropertyMock())
        self.real_user_id_mock = self.make_patch(self.block, 'real_user_id')
        self.real_user_id_mock.side_effect = lambda u_id: u_id
    def setUp(self):
        super(TestGroupActivityXBlock, self).setUp()
        self.project_api_mock = mock.Mock(spec=TypedProjectAPI)
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))

        self.group_project_mock = mock.Mock(spec=GroupProjectXBlock)
        self.group_project_mock.content_id = 'test_project_content_id'
        self.make_patch(GroupActivityXBlock, 'project', mock.PropertyMock(return_value=self.group_project_mock))
    def setUp(self):
        self.project_api_mock = mock.Mock()
        self.project_api_mock.get_workgroup_by_id = mock.Mock(return_value=_make_workgroup([]))
        self.runtime_mock = mock.create_autospec(spec=Runtime)
        self.runtime_mock.service = mock.Mock(return_value=None)
        self.block = GroupActivityXBlock(
            self.runtime_mock, field_data=DictFieldData({'weight': 100}), scope_ids=mock.Mock()
        )

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.calculate_grade_mock = self.make_patch(self.block, 'calculate_grade')
        self.mark_complete = self.make_patch(self.block, 'mark_complete')
    def setUp(self):
        super(TestGetDashboardURL, self).setUp()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.runtime_mock.course_id = 'course_id'
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.user_pref_mock = self.make_patch(
            GroupActivityXBlock, 'user_preferences', mock.PropertyMock(return_value={})
        )
        self.project_mock = mock.Mock()
        self.project_mock.scope_ids.usage_id = 'project1'
        self.make_patch(GroupActivityXBlock, 'project', self.project_mock)

        self.settings_service_mock = mock.Mock()
        self.runtime_mock.service = mock.Mock(return_value=self.settings_service_mock)
    def setUp(self):
        self.project_api_mock = mock.PropertyMock()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.grade_questions_mock = mock.PropertyMock()
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=mock.Mock())

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.grade_questions_mock = self.make_patch(GroupActivityXBlock, 'grade_questions', mock.PropertyMock())
        self.real_user_id_mock = self.make_patch(self.block, 'real_user_id')
        self.real_user_id_mock.side_effect = lambda u_id: u_id
    def setUp(self):
        super(TestGroupActivityXBlock, self).setUp()
        self.project_api_mock = mock.Mock(spec=TypedProjectAPI)
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))

        self.group_project_mock = mock.Mock(spec=GroupProjectXBlock)
        self.group_project_mock.content_id = 'test_project_content_id'
        self.make_patch(GroupActivityXBlock, 'project', mock.PropertyMock(return_value=self.group_project_mock))
    def setUp(self):
        self.project_api_mock = mock.Mock()
        self.project_api_mock.get_workgroup_by_id = mock.Mock(return_value=_make_workgroup([]))
        self.runtime_mock = mock.create_autospec(spec=Runtime)
        self.runtime_mock.service = mock.Mock(return_value=None)
        self.block = GroupActivityXBlock(
            self.runtime_mock, field_data=DictFieldData({'weight': 100}), scope_ids=mock.Mock()
        )

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.calculate_grade_mock = self.make_patch(self.block, 'calculate_grade')
        self.mark_complete = self.make_patch(self.block, 'mark_complete')
    def setUp(self):
        super(TestGetDashboardURL, self).setUp()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.runtime_mock.course_id = 'course_id'
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.user_pref_mock = self.make_patch(
            GroupActivityXBlock, 'user_preferences', mock.PropertyMock(return_value={})
        )
        self.project_mock = mock.Mock()
        self.project_mock.scope_ids.usage_id = 'project1'
        self.make_patch(GroupActivityXBlock, 'project', self.project_mock)

        self.settings_service_mock = mock.Mock()
        self.runtime_mock.service = mock.Mock(return_value=self.settings_service_mock)
class TestEventsAndCompletionGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    STANDARD_DATA = (
        (1, [], 100),
        (2, [1, 2], 10),
        (3, [1, 2, 3], 92)
    )

    def setUp(self):
        self.project_api_mock = mock.Mock()
        self.project_api_mock.get_workgroup_by_id = mock.Mock(return_value=_make_workgroup([]))
        self.runtime_mock = mock.create_autospec(spec=Runtime)
        self.runtime_mock.service = mock.Mock(return_value=None)
        self.block = GroupActivityXBlock(
            self.runtime_mock, field_data=DictFieldData({'weight': 100}), scope_ids=mock.Mock()
        )

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.calculate_grade_mock = self.make_patch(self.block, 'calculate_grade')
        self.mark_complete = self.make_patch(self.block, 'mark_complete')

    @ddt.data(
        (1, [], 100),
        (2, [1, 2], 10),
        (3, [1, 2, 3], 92),
        (4, [1, 2, 3, 4], 0)
    )
    @ddt.unpack
    def test_marks_complete_for_workgroup(self, group_id, workgroup_users, grade):
        self.calculate_grade_mock.return_value = grade
        self.project_api_mock.get_workgroup_by_id.return_value = _make_workgroup(workgroup_users)

        self.block.calculate_and_send_grade(group_id)

        mark_complete_calls = self.block.mark_complete.call_args_list
        self.assertEqual(mark_complete_calls, [mock.call(user_id) for user_id in workgroup_users])

    @ddt.data(
        (1, 100, 'course1', 'content1', 100),
        (2, 10, 'course2', 'content2', 50),
        (3, 92, 'course3', 'content3', 150),
        (4, 0, 'course4', 'content4', 200)
    )
    @ddt.unpack
    def test_sets_group_grade(self, group_id, grade, course_id, content_id, weight):
        self.calculate_grade_mock.return_value = grade
        self.block.weight = weight

        with mock.patch.object(GroupActivityXBlock, 'course_id', mock.PropertyMock(return_value=course_id)), \
                mock.patch.object(GroupActivityXBlock, 'content_id', mock.PropertyMock(return_value=content_id)):

            self.block.calculate_and_send_grade(group_id)

            self.project_api_mock.set_group_grade.assert_called_once_with(
                group_id, course_id, content_id, grade, weight
            )

    @ddt.data(1, 2, 3)
    def test_sends_notifications_message(self, group_id):
        self.calculate_grade_mock.return_value = 100
        # self.runtime_mock.service.return_value = None
        stage_mock = mock.create_autospec(BaseGroupActivityStage)
        stage_mock.open_date = datetime.now().replace(tzinfo=pytz.UTC)  # pylint: disable=maybe-no-member

        with mock.patch.object(GroupActivityXBlock, 'get_grade_display_stage', return_value=stage_mock):
            with mock.patch.object(stage_mock, 'fire_grades_posted_notification') as grades_posted_mock:
                self.block.calculate_and_send_grade(group_id)
                self.runtime_mock.service.assert_called_with(self.block, 'notifications')
                grades_posted_mock.assert_not_called()

                notifications_service_mock = mock.Mock()
                self.runtime_mock.service.return_value = notifications_service_mock

                self.block.calculate_and_send_grade(group_id)

                self.runtime_mock.service.assert_called_with(self.block, 'notifications')
                grades_posted_mock.assert_called_once_with(
                    group_id, notifications_service_mock
                )
class TestCalculateGradeGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    def setUp(self):
        self.project_api_mock = mock.PropertyMock()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.grade_questions_mock = mock.PropertyMock()
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=mock.Mock())

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.grade_questions_mock = self.make_patch(GroupActivityXBlock, 'grade_questions', mock.PropertyMock())
        self.real_user_id_mock = self.make_patch(self.block, 'real_user_id')
        self.real_user_id_mock.side_effect = lambda u_id: u_id

    @ddt.data(
        (1, ["q1"], [1], [], None),
        (2, ["q1", "q2"], [1], [], None),
        (3, ["q1"], [1, 2], [], None),
        (4, ["q1"], [1], [(1, "q1", 100)], 100),
        (5, ["q1", "q2"], [1], [(1, "q1", 20), (1, "q2", 30)], 25),
        (6, ["q1", "q2"], [1], [(1, "q1", 1), (1, "q2", 2)], round((1.0 + 2.0) / 2.0)),  # rounding
        (7, ["q1", "q2"], [1, 2], [(1, "q1", 1), (1, "q2", 2)], None),
        (8, ["q1", "q2"], [1, 2], [(1, "q1", 1), (1, "q2", 2), (2, "q2", 0)], None),
        (9, ["q1", "q2"], [1, 2], [(1, "q1", 10), (1, "q2", 20), (2, "q1", 30), (2, "q2", 60)], 30.0),
    )
    @ddt.unpack
    def test_calculate_grade(self, group_id, question_ids, reviewer_ids, reviews, expected_grade):
        self.project_api_mock.get_workgroup_reviewers = mock.Mock(
            return_value=[{"id": rew_id} for rew_id in reviewer_ids]
        )
        self.project_api_mock.get_workgroup_review_items_for_group = mock.Mock(return_value=_make_reviews(reviews))

        self.grade_questions_mock.return_value = [_make_question(question_id) for question_id in question_ids]

        grade = self.block.calculate_grade(group_id)
        self.assertEqual(grade, expected_grade)
        self.project_api_mock.get_workgroup_reviewers.assert_called_once_with(group_id, self.block.content_id)
        self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with(
            group_id, self.block.content_id
        )

    # pylint: disable=too-many-arguments
    @ddt.data(
        (0, ["q1"], [], [], [], None),  # no reviews at all
        (1, ["q1"], [], [], [(10, "q1", 15)], 15),  # one admin review
        (1, ["q1"], [], [], [(10, "q1", 15), (20, "q1", 75)], 45),  # two admin reviews for same question - mean of two
        (2, ["q1", "q2"], [], [], [(10, "q1", 15)], None),  # incomplete admin review is ignored
        (2, ["q1", "q2"], [], [], [(10, "q1", 15), (10, "q2", 25)], 20),  # two questions
        (2, ["q1", "q2"], [], [], [(10, "q1", 15), (10, "q2", 25), (20, "q2", 100)], 20),  # incomplete second review
        (2, ["q1"], [1], [(1, "q1", 100)], [(10, "q1", 15)], 100),  # user reviews takes precedence
        (2, ["q1"], [1, 2], [(1, "q1", 100)], [(10, "q1", 20)], 60),  # if a user failed to review admin grade used
        (2, ["q1"], [1, 2, 3], [(1, "q1", 100)], [(10, "q1", 25)], 50),  # if a user failed to review admin grade used
    )
    @ddt.unpack
    def test_calculate_grade_with_admins(
            self, group_id, question_ids, reviewer_ids, reviews, admin_reviews, expected_grade
    ):
        self.project_api_mock.get_workgroup_reviewers = mock.Mock(
            return_value=[{"id": rew_id} for rew_id in reviewer_ids]
        )
        self.project_api_mock.get_workgroup_review_items_for_group = mock.Mock(
            return_value=_make_reviews(reviews) + _make_reviews(admin_reviews)
        )

        self.grade_questions_mock.return_value = [_make_question(question_id) for question_id in question_ids]
        self.real_user_id_mock.side_effect = lambda u_id: u_id

        grade = self.block.calculate_grade(group_id)
        self.assertEqual(grade, expected_grade)
        self.project_api_mock.get_workgroup_reviewers.assert_called_once_with(group_id, self.block.content_id)
        self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with(
            group_id, self.block.content_id
        )
class TestGetDashboardURL(TestWithPatchesMixin, TestCase):
    def setUp(self):
        super(TestGetDashboardURL, self).setUp()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.runtime_mock.course_id = 'course_id'
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.user_pref_mock = self.make_patch(
            GroupActivityXBlock, 'user_preferences', mock.PropertyMock(return_value={})
        )
        self.project_mock = mock.Mock()
        self.project_mock.scope_ids.usage_id = 'project1'
        self.make_patch(GroupActivityXBlock, 'project', self.project_mock)

        self.settings_service_mock = mock.Mock()
        self.runtime_mock.service = mock.Mock(return_value=self.settings_service_mock)

    @staticmethod
    def _get_dashboard_url(template, program_id=None, course_id=None, project_id=None, activity_id=None):
        return template.format(
            program_id=program_id, course_id=course_id, project_id=project_id, activity_id=activity_id
        )

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_service(self, block_id):
        self.runtime_mock.service.return_value = None
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.runtime_mock.service.assert_called_once_with(self.block, 'settings')

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_settings(self, block_id):
        self.settings_service_mock.get_settings_bucket = mock.Mock(return_value=None)
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_settings_key(self, block_id):
        self.settings_service_mock.get_settings_bucket = mock.Mock(return_value={})
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)

    @ddt.data(
        ('qwe', 'na', 'na', 'na', 'na', 'qwe'),
        ('zxc?activity_id={activity_id}', 'activity_2', 'na', 'na', 'na', 'zxc?activity_id=activity_2'),
        ('?activate_block_id={activity_id}', 'activity_92', 'na', 'na', 'na', '?activate_block_id=activity_92'),
        ('/part1/part2/{activity_id}', 'qweasdzxc', 'na', 'na', 'na', '/part1/part2/qweasdzxc'),
        ('/{program_id}/part2/{activity_id}', 'act_1', 'prog_1', 'na', 'na', '/prog_1/part2/act_1'),
        ('/{program_id}/{course_id}/{activity_id}', 'act_2', 'prog_2', 'c_2', 'na', '/prog_2/c_2/act_2'),
        (
            '/{program_id}/{course_id}/{project_id}?act={activity_id}',
            'act_3', 'prog_3', 'c_3', 'proj_3', '/prog_3/c_3/proj_3?act=act_3'
        ),
    )
    @ddt.unpack
    def test_dashboard_details_url_setting_present(  # pylint:disable=too-many-arguments
            self, setting_value, block_id, preferences_program, course_id, project_id, expected_url
    ):
        self.settings_service_mock.get_settings_bucket = mock.Mock(
            return_value={GroupActivityXBlock.DASHBOARD_DETAILS_URL_KEY: setting_value}
        )
        self.scope_ids_mock.usage_id = block_id
        self.project_mock.scope_ids.usage_id = project_id
        self.runtime_mock.course_id = course_id
        self.user_pref_mock.return_value = {self.block.DASHBOARD_PROGRAM_ID_KEY: preferences_program}

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)
class TestGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    def setUp(self):
        super(TestGroupActivityXBlock, self).setUp()
        self.project_api_mock = mock.Mock(spec=TypedProjectAPI)
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))

        self.group_project_mock = mock.Mock(spec=GroupProjectXBlock)
        self.group_project_mock.content_id = 'test_project_content_id'
        self.make_patch(GroupActivityXBlock, 'project', mock.PropertyMock(return_value=self.group_project_mock))

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_questions_property(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block.__class__, 'stages', mock.PropertyMock()) as stages_mock:
            stages_mock.return_value = stages
            self.assertEqual(self.block.questions, expected_result)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_grade_questions_property(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.grade_questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block.__class__, 'stages', mock.PropertyMock()) as stages_mock:
            stages_mock.return_value = stages
            self.assertEqual(self.block.grade_questions, expected_result)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_team_evaluation_questions(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(TeamEvaluationStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages

            result = self.block.team_evaluation_questions
            self.assertEqual(result, expected_result)

            get_stages.assert_called_with(TeamEvaluationStage.CATEGORY)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_peer_grade_questions(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(PeerReviewStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages

            result = self.block.peer_review_questions
            self.assertEqual(result, expected_result)

            get_stages.assert_called_with(PeerReviewStage.CATEGORY)

    @ddt.data(
        (None, []),
        (None, [None, None, None]),
        ('2016-02-27 08:00:00', ['2016-02-26 04:30:48', None, '2016-02-27 08:00:00']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', None, '2016-10-29 04:30:48', None]),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-10-29 04:30:48', '2016-02-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-10-29 04:30:48', '2016-02-26 04:30:48', '2016-02-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-02-29 04:30:48', '2016-10-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-10-29 04:30:48', '2016-10-29 04:30:48']),
        ('2016-11-30 04:30:48', ['2016-11-30 04:30:48', '2016-10-29 04:30:48', '2016-02-26 04:30:48']),
        ('2016-12-29 12:30:49', ['2016-12-29 12:30:47', '2016-12-29 12:30:48', '2016-12-29 12:30:49']),
    )
    @ddt.unpack
    def test_get_grade_display_stage(self, expected_result, grade_display_dates):
        stages = []
        for display_datetime in grade_display_dates:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.open_date = parse_datetime(display_datetime)
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages
            self.assertEqual(
                getattr(self.block.get_grade_display_stage(), "open_date", None), parse_datetime(expected_result)
            )
class TestEventsAndCompletionGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    STANDARD_DATA = (
        (1, [], 100),
        (2, [1, 2], 10),
        (3, [1, 2, 3], 92)
    )

    def setUp(self):
        self.project_api_mock = mock.Mock()
        self.project_api_mock.get_workgroup_by_id = mock.Mock(return_value=_make_workgroup([]))
        self.runtime_mock = mock.create_autospec(spec=Runtime)
        self.runtime_mock.service = mock.Mock(return_value=None)
        self.block = GroupActivityXBlock(
            self.runtime_mock, field_data=DictFieldData({'weight': 100}), scope_ids=mock.Mock()
        )

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.calculate_grade_mock = self.make_patch(self.block, 'calculate_grade')
        self.mark_complete = self.make_patch(self.block, 'mark_complete')

    @ddt.data(
        (1, [], 100),
        (2, [1, 2], 10),
        (3, [1, 2, 3], 92)
    )
    @ddt.unpack
    def test_marks_complete_for_workgroup(self, group_id, workgroup_users, grade):
        self.calculate_grade_mock.return_value = grade
        self.project_api_mock.get_workgroup_by_id.return_value = _make_workgroup(workgroup_users)

        self.block.calculate_and_send_grade(group_id)

        mark_complete_calls = self.block.mark_complete.call_args_list
        self.assertEqual(mark_complete_calls, [mock.call(user_id) for user_id in workgroup_users])

    @ddt.data(
        (1, 100, 'course1', 'content1', 100),
        (2, 10, 'course2', 'content2', 50),
        (3, 92, 'course3', 'contrent3', 150)
    )
    @ddt.unpack
    def test_sets_group_grade(self, group_id, grade, course_id, content_id, weight):
        self.calculate_grade_mock.return_value = grade
        self.block.weight = weight

        with mock.patch.object(GroupActivityXBlock, 'course_id', mock.PropertyMock(return_value=course_id)), \
                mock.patch.object(GroupActivityXBlock, 'content_id', mock.PropertyMock(return_value=content_id)):

            self.block.calculate_and_send_grade(group_id)

            self.project_api_mock.set_group_grade.assert_called_once_with(
                group_id, course_id, content_id, grade, weight
            )

    @ddt.data(1, 2, 3)
    def test_sends_notifications_message(self, group_id):
        self.calculate_grade_mock.return_value = 100
        # self.runtime_mock.service.return_value = None
        stage_mock = mock.create_autospec(BaseGroupActivityStage)
        stage_mock.open_date = datetime.now().replace(tzinfo=pytz.UTC)  # pylint: disable=maybe-no-member

        with mock.patch.object(GroupActivityXBlock, 'get_grade_display_stage', return_value=stage_mock):
            with mock.patch.object(stage_mock, 'fire_grades_posted_notification') as grades_posted_mock:
                self.block.calculate_and_send_grade(group_id)
                self.runtime_mock.service.assert_called_with(self.block, 'notifications')
                grades_posted_mock.assert_not_called()

                notifications_service_mock = mock.Mock()
                self.runtime_mock.service.return_value = notifications_service_mock

                self.block.calculate_and_send_grade(group_id)

                self.runtime_mock.service.assert_called_with(self.block, 'notifications')
                grades_posted_mock.assert_called_once_with(
                    group_id, notifications_service_mock
                )
class TestCalculateGradeGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    def setUp(self):
        self.project_api_mock = mock.PropertyMock()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.grade_questions_mock = mock.PropertyMock()
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=mock.Mock())

        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))
        self.grade_questions_mock = self.make_patch(GroupActivityXBlock, 'grade_questions', mock.PropertyMock())
        self.real_user_id_mock = self.make_patch(self.block, 'real_user_id')
        self.real_user_id_mock.side_effect = lambda u_id: u_id

    @ddt.data(
        (1, ["q1"], [1], [], None),
        (2, ["q1", "q2"], [1], [], None),
        (3, ["q1"], [1, 2], [], None),
        (4, ["q1"], [1], [(1, "q1", 100)], 100),
        (5, ["q1", "q2"], [1], [(1, "q1", 20), (1, "q2", 30)], 25),
        (6, ["q1", "q2"], [1], [(1, "q1", 1), (1, "q2", 2)], round((1.0+2.0)/2.0)),  # rounding
        (7, ["q1", "q2"], [1, 2], [(1, "q1", 1), (1, "q2", 2)], None),
        (8, ["q1", "q2"], [1, 2], [(1, "q1", 1), (1, "q2", 2), (2, "q2", 0)], None),
        (9, ["q1", "q2"], [1, 2], [(1, "q1", 10), (1, "q2", 20), (2, "q1", 30), (2, "q2", 60)], 30.0),
    )
    @ddt.unpack
    def test_calculate_grade(self, group_id, question_ids, reviewer_ids, reviews, expected_grade):
        self.project_api_mock.get_workgroup_reviewers = mock.Mock(
            return_value=[{"id": rew_id} for rew_id in reviewer_ids]
        )
        self.project_api_mock.get_workgroup_review_items_for_group = mock.Mock(return_value=_make_reviews(reviews))

        self.grade_questions_mock.return_value = [_make_question(question_id) for question_id in question_ids]

        grade = self.block.calculate_grade(group_id)
        self.assertEqual(grade, expected_grade)
        self.project_api_mock.get_workgroup_reviewers.assert_called_once_with(group_id, self.block.content_id)
        self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with(
            group_id, self.block.content_id
        )

    # pylint: disable=too-many-arguments
    @ddt.data(
        (0, ["q1"], [], [], [], None),  # no reviews at all
        (1, ["q1"], [], [], [(10, "q1", 15)], 15),  # one admin review
        (1, ["q1"], [], [], [(10, "q1", 15), (20, "q1", 75)], 45),  # two admin reviews for same question - mean of two
        (2, ["q1", "q2"], [], [], [(10, "q1", 15)], None),  # incomplete admin review is ignored
        (2, ["q1", "q2"], [], [], [(10, "q1", 15), (10, "q2", 25)], 20),  # two questions
        (2, ["q1", "q2"], [], [], [(10, "q1", 15), (10, "q2", 25), (20, "q2", 100)], 20),  # incomplete second review
        (2, ["q1"], [1], [(1, "q1", 100)], [(10, "q1", 15)], 100),  # user reviews takes precedence
        (2, ["q1"], [1, 2], [(1, "q1", 100)], [(10, "q1", 20)], 60),  # if a user failed to review admin grade used
        (2, ["q1"], [1, 2, 3], [(1, "q1", 100)], [(10, "q1", 25)], 50),  # if a user failed to review admin grade used
    )
    @ddt.unpack
    def test_calculate_grade_with_admins(
        self, group_id, question_ids, reviewer_ids, reviews, admin_reviews, expected_grade
    ):
        self.project_api_mock.get_workgroup_reviewers = mock.Mock(
            return_value=[{"id": rew_id} for rew_id in reviewer_ids]
        )
        self.project_api_mock.get_workgroup_review_items_for_group = mock.Mock(
            return_value=_make_reviews(reviews)+_make_reviews(admin_reviews)
        )

        self.grade_questions_mock.return_value = [_make_question(question_id) for question_id in question_ids]
        self.real_user_id_mock.side_effect = lambda u_id: u_id

        grade = self.block.calculate_grade(group_id)
        self.assertEqual(grade, expected_grade)
        self.project_api_mock.get_workgroup_reviewers.assert_called_once_with(group_id, self.block.content_id)
        self.project_api_mock.get_workgroup_review_items_for_group.assert_called_once_with(
            group_id, self.block.content_id
        )
class TestGetDashboardURL(TestWithPatchesMixin, TestCase):
    def setUp(self):
        super(TestGetDashboardURL, self).setUp()
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.runtime_mock.course_id = 'course_id'
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.user_pref_mock = self.make_patch(
            GroupActivityXBlock, 'user_preferences', mock.PropertyMock(return_value={})
        )
        self.project_mock = mock.Mock()
        self.project_mock.scope_ids.usage_id = 'project1'
        self.make_patch(GroupActivityXBlock, 'project', self.project_mock)

        self.settings_service_mock = mock.Mock()
        self.runtime_mock.service = mock.Mock(return_value=self.settings_service_mock)

    @staticmethod
    def _get_dashboard_url(template, program_id=None, course_id=None, project_id=None, activity_id=None):
        return template.format(
            program_id=program_id, course_id=course_id,  project_id=project_id, activity_id=activity_id
        )

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_service(self, block_id):
        self.runtime_mock.service.return_value = None
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.runtime_mock.service.assert_called_once_with(self.block, 'settings')

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_settings(self, block_id):
        self.settings_service_mock.get_settings_bucket = mock.Mock(return_value=None)
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)

    @ddt.data('activity_1', 'activity_2', 'activity_92', 'qweasdzxc')
    def test_dashboard_details_url_no_settings_key(self, block_id):
        self.settings_service_mock.get_settings_bucket = mock.Mock(return_value={})
        self.scope_ids_mock.usage_id = block_id
        expected_url = self._get_dashboard_url(
            GroupActivityXBlock.DEFAULT_DASHBOARD_DETAILS_URL_TPL,
            activity_id=block_id
        )

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)

    @ddt.data(
        ('qwe', 'na', 'na', 'na', 'na', 'qwe'),
        ('zxc?activity_id={activity_id}', 'activity_2', 'na', 'na', 'na', 'zxc?activity_id=activity_2'),
        ('?activate_block_id={activity_id}', 'activity_92', 'na', 'na', 'na', '?activate_block_id=activity_92'),
        ('/part1/part2/{activity_id}', 'qweasdzxc', 'na', 'na', 'na', '/part1/part2/qweasdzxc'),
        ('/{program_id}/part2/{activity_id}', 'act_1', 'prog_1', 'na', 'na', '/prog_1/part2/act_1'),
        ('/{program_id}/{course_id}/{activity_id}', 'act_2', 'prog_2', 'c_2', 'na', '/prog_2/c_2/act_2'),
        (
                '/{program_id}/{course_id}/{project_id}?act={activity_id}',
                'act_3', 'prog_3', 'c_3', 'proj_3', '/prog_3/c_3/proj_3?act=act_3'
        ),
    )
    @ddt.unpack
    def test_dashboard_details_url_setting_present(  # pylint:disable=too-many-arguments
            self, setting_value, block_id, preferences_program, course_id, project_id, expected_url
    ):
        self.settings_service_mock.get_settings_bucket = mock.Mock(
            return_value={GroupActivityXBlock.DASHBOARD_DETAILS_URL_KEY: setting_value}
        )
        self.scope_ids_mock.usage_id = block_id
        self.project_mock.scope_ids.usage_id = project_id
        self.runtime_mock.course_id = course_id
        self.user_pref_mock.return_value = {self.block.DASHBOARD_PROGRAM_ID_KEY: preferences_program}

        url = self.block.dashboard_details_url()

        self.assertEqual(url, expected_url)
        self.settings_service_mock.get_settings_bucket.assert_called_once_with(self.block)
class TestGroupActivityXBlock(TestWithPatchesMixin, TestCase):
    def setUp(self):
        super(TestGroupActivityXBlock, self).setUp()
        self.project_api_mock = mock.Mock(spec=TypedProjectAPI)
        self.runtime_mock = mock.Mock(spec=Runtime)
        self.scope_ids_mock = mock.Mock(spec=ScopeIds)
        self.block = GroupActivityXBlock(self.runtime_mock, field_data=DictFieldData({}), scope_ids=self.scope_ids_mock)
        self.make_patch(GroupActivityXBlock, 'project_api', mock.PropertyMock(return_value=self.project_api_mock))

        self.group_project_mock = mock.Mock(spec=GroupProjectXBlock)
        self.group_project_mock.content_id = 'test_project_content_id'
        self.make_patch(GroupActivityXBlock, 'project', mock.PropertyMock(return_value=self.group_project_mock))

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_questions_property(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block.__class__, 'stages', mock.PropertyMock()) as stages_mock:
            stages_mock.return_value = stages
            self.assertEqual(self.block.questions, expected_result)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_grade_questions_property(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.grade_questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block.__class__, 'stages', mock.PropertyMock()) as stages_mock:
            stages_mock.return_value = stages
            self.assertEqual(self.block.grade_questions, expected_result)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_team_evaluation_questions(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(TeamEvaluationStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages

            result = self.block.team_evaluation_questions
            self.assertEqual(result, expected_result)

            get_stages.assert_called_with(TeamEvaluationStage.CATEGORY)

    @ddt.data(
        ([], []),
        (['q1', 'q2', 'q3'], ['q1', 'q2'], ['q3']),
        (['q8', 'q12', 'q22', 'q54'], ['q8'], ['q12', 'q22'], ['q54'])
    )
    @ddt.unpack
    def test_peer_grade_questions(self, expected_result, *stage_questions):
        stages = []
        for questions in stage_questions:
            stage_mock = mock.create_autospec(PeerReviewStage)
            stage_mock.questions = questions
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages

            result = self.block.peer_review_questions
            self.assertEqual(result, expected_result)

            get_stages.assert_called_with(PeerReviewStage.CATEGORY)

    @ddt.data(
        (None, []),
        (None, [None, None, None]),
        ('2016-02-27 08:00:00', ['2016-02-26 04:30:48', None, '2016-02-27 08:00:00']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', None, '2016-10-29 04:30:48', None]),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-10-29 04:30:48', '2016-02-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-10-29 04:30:48', '2016-02-26 04:30:48', '2016-02-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-02-29 04:30:48', '2016-10-29 04:30:48']),
        ('2016-10-29 04:30:48', ['2016-02-26 04:30:48', '2016-10-29 04:30:48', '2016-10-29 04:30:48']),
        ('2016-11-30 04:30:48', ['2016-11-30 04:30:48', '2016-10-29 04:30:48', '2016-02-26 04:30:48']),
        ('2016-12-29 12:30:49', ['2016-12-29 12:30:47', '2016-12-29 12:30:48', '2016-12-29 12:30:49']),
    )
    @ddt.unpack
    def test_get_grade_display_stage(self, expected_result, grade_display_dates):
        stages = []
        for display_datetime in grade_display_dates:
            stage_mock = mock.create_autospec(BaseGroupActivityStage)
            stage_mock.open_date = parse_datetime(display_datetime)
            stages.append(stage_mock)

        with mock.patch.object(self.block, 'get_children_by_category', mock.Mock()) as get_stages:
            get_stages.return_value = stages
            self.assertEqual(
                getattr(self.block.get_grade_display_stage(), "open_date", None), parse_datetime(expected_result)
            )