Пример #1
0
 def setUp(self):
     super(AnswerCommentAPITests, self).setUp()
     self.data = AnswerCommentsTestData()
     self.course = self.data.get_course()
     self.assignments = self.data.get_assignments()
     self.answers = self.data.get_answers_by_assignment()
     self.assignment = self.assignments[0]
     self.assignment.enable_self_evaluation = True
     db.session.commit()
     self.assignment.calculate_grades()
     self.lti_data = LTITestData()
Пример #2
0
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = AnswerCommentsTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

        self.self_evaluation_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.self_evaluation)
        self.evaluation_comment  = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.evaluation)
        self.public_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.public)
        self.private_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.private)
Пример #3
0
 def setUp(self):
     super(AnswerCommentAPITests, self).setUp()
     self.data = AnswerCommentsTestData()
     self.course = self.data.get_course()
     self.assignments = self.data.get_assignments()
     self.answers = self.data.get_answers_by_assignment()
     self.assignment = self.assignments[0]
     self.assignment.enable_self_evaluation = True
     db.session.commit()
     self.assignment.calculate_grades()
     self.lti_data = LTITestData()
Пример #4
0
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = AnswerCommentsTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

        self.self_evaluation_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.self_evaluation)
        self.evaluation_comment  = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.evaluation)
        self.public_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.public)
        self.private_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.private)
Пример #5
0
class AnswerCommentAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment API """
    resource = AnswerCommentAPI
    api = api

    def setUp(self):
        super(AnswerCommentAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_single_answer_comment(self):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
        draft_comment = self.data.get_answer_comments_by_assignment(self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=draft_comment.uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized user student fetching draft of another student
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(draft_url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid="999", answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

            # test draft
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])

        # test author
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])


    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_edit_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid
        )

        content = {
            'id': comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value
        }
        draft_comment = self.data.get_answer_comments_by_assignment(self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=draft_comment.uuid)
        draft_content = {
            'id': draft_comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value,
            'draft': True
        }

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid="999", answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test unmatched comment ids
            invalid = content.copy()
            invalid['id'] = self.data.get_answer_comments_by_assignment(self.assignment)[1].uuid
            rv = self.client.post(url, data=json.dumps(invalid), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Comment id does not match URL.", rv.json['error'])

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("The comment content is empty!", rv.json['error'])

            # test empty comment_type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])

        # test author
        with self.login(self.data.get_extra_student(0).username):
            content['content'] = 'I am the author'
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
            self.assertFalse(rv.json['draft'])

            # ignored setting draft to True when draft is already False
            content['draft'] = True
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
            self.assertFalse(rv.json['draft'])

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            draft_content['content'] = 'I am the author'
            rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(draft_content['content'], rv.json['content'])
            self.assertTrue(rv.json['draft'])

            # can change draft to False when draft is True
            draft_content['draft'] = False
            rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['draft'])

        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user, answer, comment_type=AnswerCommentType.self_evaluation, draft=True)
        self_evaluation_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)

        with self.login(answer.user.username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
                answer.user, self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade
            content = {
                'id': self_evaluation.uuid,
                'content': 'insightful.',
                'comment_type': AnswerCommentType.self_evaluation.value,
                'draft': True
            }

            rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
            self.assertTrue(rv.json['draft'])

            # grades should not change
            new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade
            self.assertEqual(new_course_grade, course_grade)
            self.assertEqual(new_assignment_grade, assignment_grade)

            # can change draft to False when draft is True
            content['draft'] = False
            rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['draft'])

            # grades should increase
            new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user)
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user)
            self.assertGreater(new_course_grade.grade, course_grade)
            self.assertGreater(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
            )
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
            )
            mocked_update_course_grades_run.reset_mock()


    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_delete_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)

        # test login required
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test invalid comment id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test author
        with self.login(self.data.get_extra_student(1).username):
            comment = self.data.get_answer_comments_by_assignment(self.assignment)[1]
            url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test delete self-evaulation
        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(answer.user, answer, comment_type=AnswerCommentType.self_evaluation)
        self.assignment.calculate_grade(answer.user)
        self.course.calculate_grade(answer.user)

        lti_consumer = self.lti_data.lti_consumer
        (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
            answer.user, self.course, self.assignment)

        with self.login(self.data.get_authorized_instructor().username):
            course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade

            url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(self_evaluation.uuid, rv.json['id'])

            # grades should decrease
            new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user)
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user)
            self.assertLess(new_course_grade.grade, course_grade)
            self.assertLess(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
            )
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
            )
            mocked_update_course_grades_run.reset_mock()
Пример #6
0
class AnswerCommentListAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment list API """
    resource = AnswerCommentListAPI
    api = api

    def setUp(self):
        super(AnswerCommentListAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_all_answer_comments(self):
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.id, assignment_uuid=self.assignment.uuid, answer_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized user
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(
                self.data.get_non_draft_answer_comments_by_assignment(self.assignment)[1].content, rv.json[0]['content'])
            self.assertIn(
                self.data.get_non_draft_answer_comments_by_assignment(self.assignment)[1].user_fullname,
                rv.json[0]['user']['fullname'])

        # test non-owner student of answer access comments
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json))

        # test owner student of answer access comments
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertNotIn('user_fullname', rv.json[0])

    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.self_evaluation
        )
        draft_comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.evaluation,
            draft=True
        )

        base_params = {
            'course_uuid': self.course.uuid,
            'assignment_uuid': self.assignment.uuid,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params, answer_ids=self.answers[self.assignment.id][0].uuid)
            extra_student2_answer_comment_uuid = self.data.get_answer_comments_by_assignment(self.assignment)[1].uuid
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_answer_comment_uuid, rv.json[0]['id'])

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            # self.assertEqual(1, rv.json['total'])
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            ids = [extra_student2_answer_comment_uuid, comment.uuid]
            rv = self.client.get(self.get_url(ids=','.join(str(x) for x in ids), **base_params))
            self.assert200(rv)
            # self.assertEqual(2, rv.json['total'])
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.uuid, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test assignment_id filter
            rv = self.client.get(self.get_url(**base_params) + '?assignment_id=' + self.assignment.uuid)
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.uuid] + [c.uuid for c in self.data.get_non_draft_answer_comments_by_assignment(self.assignment)],
                [c['id'] for c in rv.json])

            rv = self.client.get(self.get_url(**base_params) + '?assignment_id=' + self.assignments[1].uuid)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [c.uuid for c in self.data.get_non_draft_answer_comments_by_assignment(self.assignments[1])],
                [c['id'] for c in rv.json])

            # test user_ids filter
            user_ids = ','.join([self.data.get_extra_student(0).uuid])
            rv = self.client.get(
                self.get_url(user_ids=user_ids, **base_params) + '&assignment_id=' + self.assignment.uuid)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.uuid, self.data.answer_comments_by_assignment[self.assignment.id][0].uuid],
                [c['id'] for c in rv.json])

        with self.login(self.data.get_extra_student(1).username):
            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # answer is not from the student but comment is
            answer_ids = [self.answers[self.assignment.id][1].uuid]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(0).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(self.data.get_extra_student(0).uuid, rv.json[0]['user_id'])

        # test drafts
        with self.login(self.data.get_extra_student(0).username):
            params = dict(base_params, user_ids=self.data.get_extra_student(0).uuid)
            rv = self.client.get(self.get_url(draft='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

            rv = self.client.get(self.get_url(draft='false', **params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(draft='true', **params))
            self.assert200(rv)
            self.assertEqual(4, len(rv.json))
            self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_create_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)
        content = {
            'comment_type': AnswerCommentType.private.value,
            'content': 'great answer'
        }

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid assignment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid="999",
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid, answer_uuid="999")
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

            # test empty comment type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

            # test authorized user
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
            self.assertFalse(rv.json['draft'])

            # test authorized user draft
            draft_content = content.copy()
            draft_content['draft'] = True
            rv = self.client.post(url, data=json.dumps(draft_content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
            self.assertTrue(rv.json['draft'])

            # test authorized user draft - empty content
            empty = draft_content.copy()
            empty['content'] = None
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(empty['content'], rv.json['content'])
            self.assertTrue(rv.json['draft'])

        with self.login(self.data.get_authorized_student().username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
                self.data.get_authorized_student(), self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(self.course, self.data.get_authorized_student()).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, self.data.get_authorized_student()).grade

            content = {
                'comment_type': AnswerCommentType.self_evaluation.value,
                'content': 'great answer'
            }

            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)

            # grades should increase
            new_course_grade = CourseGrade.get_user_course_grade(self.course, self.data.get_authorized_student())
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, self.data.get_authorized_student())
            self.assertGreater(new_course_grade.grade, course_grade)
            self.assertGreater(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
            )
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
            )
            mocked_update_assignment_grades_run.reset_mock()
Пример #7
0
    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.self_evaluation)
        draft_comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.evaluation,
            draft=True)

        base_params = {
            'course_uuid': self.course.uuid,
            'assignment_uuid': self.assignment.uuid,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params,
                          answer_ids=self.answers[self.assignment.id][0].uuid)
            extra_student2_answer_comment_uuid = self.data.get_answer_comments_by_assignment(
                self.assignment)[1].uuid
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(
                self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_answer_comment_uuid,
                             rv.json[0]['id'])

            rv = self.client.get(self.get_url(self_evaluation='only',
                                              **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            ids = [extra_student2_answer_comment_uuid, comment.uuid]
            rv = self.client.get(self.get_url(ids=','.join(ids),
                                              **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [
                answer.uuid for answer in self.answers[self.assignment.id]
            ]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(
                self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.uuid, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(self_evaluation='only',
                                              **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            answer_ids = [
                answer.uuid for answer in self.answers[self.assignment.id]
            ]
            params = dict(base_params,
                          answer_ids=','.join(answer_ids),
                          user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test user_ids filter
            user_ids = ','.join([self.data.get_extra_student(0).uuid])
            rv = self.client.get(self.get_url(user_ids=user_ids,
                                              **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, [
                comment.uuid, self.data.answer_comments_by_assignment[
                    self.assignment.id][0].uuid
            ], [c['id'] for c in rv.json])

        student = self.data.get_extra_student(1)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                answer_ids = [
                    answer.uuid for answer in self.answers[self.assignment.id]
                ]
                params = dict(base_params,
                              answer_ids=','.join(answer_ids),
                              user_ids=self.data.get_extra_student(1).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))

                # answer is not from the student but comment is
                answer_ids = [self.answers[self.assignment.id][1].uuid]
                params = dict(base_params,
                              answer_ids=','.join(answer_ids),
                              user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(
                    self.data.get_extra_student(0).uuid, rv.json[0]['user_id'])

        # test drafts
        student = self.data.get_extra_student(0)
        for user_context in [
                self.login(student.username),
                self.impersonate(self.data.get_authorized_instructor(),
                                 student)
        ]:
            with user_context:
                params = dict(base_params,
                              user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(draft='only', **params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

                rv = self.client.get(self.get_url(draft='false', **params))
                self.assert200(rv)
                self.assertEqual(2, len(rv.json))

                rv = self.client.get(self.get_url(draft='true', **params))
                self.assert200(rv)
                self.assertEqual(3, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])
Пример #8
0
class AnswerCommentAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment API """
    resource = AnswerCommentAPI
    api = api

    def setUp(self):
        super(AnswerCommentAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_single_answer_comment(self):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
        draft_comment = self.data.get_answer_comments_by_assignment(self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=draft_comment.uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized user student fetching draft of another student
        student = self.data.get_extra_student(0)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(draft_url)
                self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid="999", answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])
            self.assertIn('fullname', rv.json['user'])

            # test draft
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])
            self.assertIn('fullname', rv.json['user'])

        # test author
        student = self.data.get_extra_student(0)
        for user_context in [self.login(student.username), self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(comment.content, rv.json['content'])
                self.assertNotIn('fullname', rv.json['user'])

        # test draft author
        student = self.data.get_extra_student(1)
        for user_context in [self.login(student.username), self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(draft_url)
                self.assert200(rv)
                self.assertEqual(draft_comment.content, rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])


    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_edit_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid
        )

        content = {
            'id': comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value
        }
        draft_comment = self.data.get_answer_comments_by_assignment(self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=draft_comment.uuid)
        draft_content = {
            'id': draft_comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value,
            'draft': True
        }

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid="999", answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test unmatched comment ids
            invalid = content.copy()
            invalid['id'] = self.data.get_answer_comments_by_assignment(self.assignment)[1].uuid
            rv = self.client.post(url, data=json.dumps(invalid), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual("The feedback's ID does not match the URL, which is required in order to save the feedback.",
                rv.json['message'])

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual("Please provide content in the text editor and try saving again.", rv.json['message'])

            # test empty comment_type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            with mail.record_messages() as outbox:
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

        # test author
        with self.login(self.data.get_extra_student(0).username):

            # test student can not change comment to self-eval / eval
            invalid = content.copy()
            invalid['comment_type'] = AnswerCommentType.self_evaluation.value
            rv = self.client.post(url, data=json.dumps(invalid), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual("Feedback type cannot be changed. Please contact support for assistance.", rv.json['message'])

            invalid = content.copy()
            invalid['comment_type'] = AnswerCommentType.evaluation.value
            rv = self.client.post(url, data=json.dumps(invalid), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual("Feedback type cannot be changed. Please contact support for assistance.", rv.json['message'])

            with mail.record_messages() as outbox:
                content['content'] = 'I am the author'
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

            # ignored setting draft to True when draft is already False
            with mail.record_messages() as outbox:
                content['draft'] = True
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

        # test author with impersonation
        student = self.data.get_extra_student(0)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            content['content'] = 'I am the author'
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

            content['draft'] = True
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            with mail.record_messages() as outbox:
                draft_content['content'] = 'I am the author'
                rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(draft_content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                draft_content['draft'] = False
                rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 1)
                self.assertEqual(outbox[0].subject, "New Answer Feedback in "+self.data.get_course().name)
                self.assertEqual(outbox[0].recipients, [self.answers[self.assignment.id][0].user.email])

        # test draft author with impersonation
        student = self.data.get_extra_student(1)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            draft_content['content'] = 'I am the author'
            rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

            # cant change draft to False
            draft_content['draft'] = False
            rv = self.client.post(draft_url, data=json.dumps(draft_content), content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user, answer, comment_type=AnswerCommentType.self_evaluation, draft=True)
        self_evaluation_url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)

        with self.login(answer.user.username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
                answer.user, self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade
            content = {
                'id': self_evaluation.uuid,
                'content': 'insightful.',
                'comment_type': AnswerCommentType.self_evaluation.value,
                'draft': True
            }

            # test student can not submit self-eval after self-eval grace period
            orig_answer_end = self.assignment.answer_end
            self.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
            self.assignment.self_eval_start = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
            self.assignment.self_eval_end = datetime.datetime.utcnow() - datetime.timedelta(minutes=10)

            db.session.add(self.assignment)
            db.session.commit()

            rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)
            self.assertEqual("Self-Evaluation Not Saved", rv.json['title'])
            self.assertEqual("Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.",
                rv.json['message'])

            self.assignment.answer_end = orig_answer_end
            self.assignment.self_eval_start = None
            self.assignment.self_eval_end = None


            with mail.record_messages() as outbox:
                self.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
                rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

                # grades should not change
                new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade
                self.assertEqual(new_course_grade, course_grade)
                self.assertEqual(new_assignment_grade, assignment_grade)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                content['draft'] = False
                rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

                # grades should increase
                new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user)
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user)
                self.assertGreater(new_course_grade.grade, course_grade)
                self.assertGreater(new_assignment_grade.grade, assignment_grade)

                mocked_update_assignment_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
                )
                mocked_update_assignment_grades_run.reset_mock()

                mocked_update_course_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
                )
                mocked_update_course_grades_run.reset_mock()

        # test self-evaluation with impersonation
        answers = self.answers[self.assignment.id]
        for answer in [a for a in answers if a.user.system_role == SystemRole.student]:
            self_evaluation = self.data.create_answer_comment(
                answer.user, answer, comment_type=AnswerCommentType.self_evaluation, draft=True)
            self_evaluation_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)

            with self.impersonate(self.data.get_authorized_instructor(), answer.user):
                content = {
                    'id': self_evaluation.uuid,
                    'content': 'insightful.',
                    'comment_type': AnswerCommentType.self_evaluation.value,
                    'draft': True
                }

                rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])
                # attempt to change draft to False
                content['draft'] = False
                rv = self.client.post(self_evaluation_url, data=json.dumps(content), content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_delete_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)

        # test login required
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test invalid comment id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid="999")
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test author with impersonation
        student = self.data.get_extra_student(1)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        # test author
        with self.login(self.data.get_extra_student(1).username):
            comment = self.data.get_answer_comments_by_assignment(self.assignment)[1]
            url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid, answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test delete self-evaulation with impersonation
        answers = self.answers[self.assignment.id]
        for answer in [a for a in answers if a.user.system_role == SystemRole.student]:
            self_evaluation = self.data.create_answer_comment(
                answer.user, answer, comment_type=AnswerCommentType.self_evaluation, draft=True)

            with self.impersonate(self.data.get_authorized_instructor(), answer.user):
                url = self.get_url(
                    course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                    answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)
                rv = self.client.delete(url)
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])

        # test delete self-evaulation
        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(answer.user, answer, comment_type=AnswerCommentType.self_evaluation)
        self.assignment.calculate_grade(answer.user)
        self.course.calculate_grade(answer.user)

        lti_consumer = self.lti_data.lti_consumer
        (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
            answer.user, self.course, self.assignment)

        with self.login(self.data.get_authorized_instructor().username):
            course_grade = CourseGrade.get_user_course_grade(self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user).grade

            url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
                answer_uuid=answer.uuid, answer_comment_uuid=self_evaluation.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(self_evaluation.uuid, rv.json['id'])

            # grades should decrease
            new_course_grade = CourseGrade.get_user_course_grade(self.course, answer.user)
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, answer.user)
            self.assertLess(new_course_grade.grade, course_grade)
            self.assertLess(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
            )
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
            )
            mocked_update_course_grades_run.reset_mock()
Пример #9
0
class AnswerCommentListAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment list API """
    resource = AnswerCommentListAPI
    api = api

    def setUp(self):
        super(AnswerCommentListAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_all_answer_comments(self):
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.id,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized user
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(
                self.data.get_non_draft_answer_comments_by_assignment(
                    self.assignment)[1].content, rv.json[0]['content'])
            self.assertIn(
                self.data.get_non_draft_answer_comments_by_assignment(
                    self.assignment)[1].user_fullname,
                rv.json[0]['user']['fullname'])

        # test non-owner student of answer access comments
        student = self.data.get_authorized_student()
        for user_context in [
                self.login(student.username),
                self.impersonate(self.data.get_authorized_instructor(),
                                 student)
        ]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(0, len(rv.json))

        # test owner student of answer access comments
        student = self.data.get_extra_student(0)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertNotIn('fullname', rv.json[0]['user'])

    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.self_evaluation)
        draft_comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.evaluation,
            draft=True)

        base_params = {
            'course_uuid': self.course.uuid,
            'assignment_uuid': self.assignment.uuid,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params,
                          answer_ids=self.answers[self.assignment.id][0].uuid)
            extra_student2_answer_comment_uuid = self.data.get_answer_comments_by_assignment(
                self.assignment)[1].uuid
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(
                self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_answer_comment_uuid,
                             rv.json[0]['id'])

            rv = self.client.get(self.get_url(self_evaluation='only',
                                              **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            ids = [extra_student2_answer_comment_uuid, comment.uuid]
            rv = self.client.get(self.get_url(ids=','.join(ids),
                                              **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [
                answer.uuid for answer in self.answers[self.assignment.id]
            ]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(
                self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.uuid, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(self_evaluation='only',
                                              **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            answer_ids = [
                answer.uuid for answer in self.answers[self.assignment.id]
            ]
            params = dict(base_params,
                          answer_ids=','.join(answer_ids),
                          user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test user_ids filter
            user_ids = ','.join([self.data.get_extra_student(0).uuid])
            rv = self.client.get(self.get_url(user_ids=user_ids,
                                              **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, [
                comment.uuid, self.data.answer_comments_by_assignment[
                    self.assignment.id][0].uuid
            ], [c['id'] for c in rv.json])

        student = self.data.get_extra_student(1)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                answer_ids = [
                    answer.uuid for answer in self.answers[self.assignment.id]
                ]
                params = dict(base_params,
                              answer_ids=','.join(answer_ids),
                              user_ids=self.data.get_extra_student(1).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))

                # answer is not from the student but comment is
                answer_ids = [self.answers[self.assignment.id][1].uuid]
                params = dict(base_params,
                              answer_ids=','.join(answer_ids),
                              user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(
                    self.data.get_extra_student(0).uuid, rv.json[0]['user_id'])

        # test drafts
        student = self.data.get_extra_student(0)
        for user_context in [
                self.login(student.username),
                self.impersonate(self.data.get_authorized_instructor(),
                                 student)
        ]:
            with user_context:
                params = dict(base_params,
                              user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(draft='only', **params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

                rv = self.client.get(self.get_url(draft='false', **params))
                self.assert200(rv)
                self.assertEqual(2, len(rv.json))

                rv = self.client.get(self.get_url(draft='true', **params))
                self.assert200(rv)
                self.assertEqual(3, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_create_answer_comment(self, mocked_update_assignment_grades_run,
                                   mocked_update_course_grades_run):
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)
        content = {
            'comment_type': AnswerCommentType.private.value,
            'content': 'great answer'
        }

        # test login required
        rv = self.client.post(url,
                              data=json.dumps(content),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999",
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid assignment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid="999",
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.uuid,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999")
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)

            # test empty comment type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)

            # test authorized user
            with mail.record_messages() as outbox:
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 1)
                self.assertEqual(
                    outbox[0].subject,
                    "New Answer Feedback in " + self.data.get_course().name)
                self.assertEqual(
                    outbox[0].recipients,
                    [self.answers[self.assignment.id][0].user.email])

            # test authorized user draft
            with mail.record_messages() as outbox:
                draft_content = content.copy()
                draft_content['draft'] = True
                rv = self.client.post(url,
                                      data=json.dumps(draft_content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

            # test authorized user draft - empty content
            with mail.record_messages() as outbox:
                empty = draft_content.copy()
                empty['content'] = None
                rv = self.client.post(url,
                                      data=json.dumps(empty),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(empty['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

        with self.login('root'):
            with mail.record_messages() as outbox:
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(len(outbox), 1)
                self.assertIn('fullname', rv.json['user'])

        with self.login(self.data.get_authorized_student().username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2
             ) = self.lti_data.setup_student_user_resource_links(
                 self.data.get_authorized_student(), self.course,
                 self.assignment)

            course_grade = CourseGrade.get_user_course_grade(
                self.course, self.data.get_authorized_student()).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, self.data.get_authorized_student()).grade

            content = {
                'comment_type': AnswerCommentType.self_evaluation.value,
                'content': 'great answer'
            }

            # test student can not submit self-eval after self-eval grace period
            orig_answer_end = self.assignment.answer_end
            self.assignment.answer_end = datetime.datetime.utcnow(
            ) - datetime.timedelta(hours=12)
            self.assignment.self_eval_start = datetime.datetime.utcnow(
            ) - datetime.timedelta(hours=1)
            self.assignment.self_eval_end = datetime.datetime.utcnow(
            ) - datetime.timedelta(minutes=10)

            db.session.add(self.assignment)
            db.session.commit()

            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertEqual("Self-Evaluation Not Saved", rv.json['title'])
            self.assertEqual(
                "Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.",
                rv.json['message'])

            self.assignment.answer_end = orig_answer_end
            self.assignment.self_eval_start = None
            self.assignment.self_eval_end = None

            with mail.record_messages() as outbox:
                orig_answer_end = self.assignment.answer_end
                self.assignment.answer_end = datetime.datetime.utcnow(
                ) - datetime.timedelta(hours=12)

                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)

                self.assertEqual(len(outbox), 0)
                self.assertNotIn('fullname', rv.json['user'])

                # grades should increase
                new_course_grade = CourseGrade.get_user_course_grade(
                    self.course, self.data.get_authorized_student())
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                    self.assignment, self.data.get_authorized_student())
                self.assertGreater(new_course_grade.grade, course_grade)
                self.assertGreater(new_assignment_grade.grade,
                                   assignment_grade)

                mocked_update_assignment_grades_run.assert_called_once_with(
                    lti_consumer.id, [[
                        lti_user_resource_link2.lis_result_sourcedid,
                        new_assignment_grade.id
                    ]])
                mocked_update_assignment_grades_run.reset_mock()

                mocked_update_course_grades_run.assert_called_once_with(
                    lti_consumer.id, [[
                        lti_user_resource_link1.lis_result_sourcedid,
                        new_course_grade.id
                    ]])
                mocked_update_assignment_grades_run.reset_mock()

                self.assignment.answer_end = orig_answer_end

        # test with impersonation
        student = self.data.get_extra_student(0)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2
             ) = self.lti_data.setup_student_user_resource_links(
                 self.data.get_authorized_student(), self.course,
                 self.assignment)

            course_grade = CourseGrade.get_user_course_grade(
                self.course, self.data.get_authorized_student()).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, self.data.get_authorized_student()).grade

            content = {
                'comment_type': AnswerCommentType.self_evaluation.value,
                'content': 'great answer'
            }

            with mail.record_messages() as outbox:
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])
Пример #10
0
class AnswerCommentAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment API """
    resource = AnswerCommentAPI
    api = api

    def setUp(self):
        super(AnswerCommentAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_single_answer_comment(self):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)
        draft_comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=draft_comment.uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized user student fetching draft of another student
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(draft_url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999",
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.uuid,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999",
                                       answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

            # test draft
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])

        # test author
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_edit_answer_comment(self, mocked_update_assignment_grades_run,
                                 mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)

        content = {
            'id': comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value
        }
        draft_comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=draft_comment.uuid)
        draft_content = {
            'id': draft_comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value,
            'draft': True
        }

        # test login required
        rv = self.client.post(url,
                              data=json.dumps(content),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999",
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.uuid,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999",
                                       answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test unmatched comment ids
            invalid = content.copy()
            invalid['id'] = self.data.get_answer_comments_by_assignment(
                self.assignment)[1].uuid
            rv = self.client.post(url,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Reply Not Saved", rv.json['title'])
            self.assertEqual(
                "The reply's ID does not match the URL, which is required in order to save the reply.",
                rv.json['message'])

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Reply Not Saved", rv.json['title'])
            self.assertEqual(
                "Please provide content in the text editor to reply and try saving again.",
                rv.json['message'])

            # test empty comment_type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            with mail.record_messages() as outbox:
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])

                self.assertEqual(len(outbox), 0)

        # test author
        with self.login(self.data.get_extra_student(0).username):
            with mail.record_messages() as outbox:
                content['content'] = 'I am the author'
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

            # ignored setting draft to True when draft is already False
            with mail.record_messages() as outbox:
                content['draft'] = True
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            with mail.record_messages() as outbox:
                draft_content['content'] = 'I am the author'
                rv = self.client.post(draft_url,
                                      data=json.dumps(draft_content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(draft_content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                draft_content['draft'] = False
                rv = self.client.post(draft_url,
                                      data=json.dumps(draft_content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])

                self.assertEqual(len(outbox), 1)
                self.assertEqual(
                    outbox[0].subject,
                    "New Answer Feedback in " + self.data.get_course().name)
                self.assertEqual(
                    outbox[0].recipients,
                    [self.answers[self.assignment.id][0].user.email])

        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user,
            answer,
            comment_type=AnswerCommentType.self_evaluation,
            draft=True)
        self_evaluation_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=answer.uuid,
            answer_comment_uuid=self_evaluation.uuid)

        with self.login(answer.user.username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2
             ) = self.lti_data.setup_student_user_resource_links(
                 answer.user, self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user).grade
            content = {
                'id': self_evaluation.uuid,
                'content': 'insightful.',
                'comment_type': AnswerCommentType.self_evaluation.value,
                'draft': True
            }

            with mail.record_messages() as outbox:
                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

                # grades should not change
                new_course_grade = CourseGrade.get_user_course_grade(
                    self.course, answer.user).grade
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                    self.assignment, answer.user).grade
                self.assertEqual(new_course_grade, course_grade)
                self.assertEqual(new_assignment_grade, assignment_grade)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                content['draft'] = False
                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

                # grades should increase
                new_course_grade = CourseGrade.get_user_course_grade(
                    self.course, answer.user)
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                    self.assignment, answer.user)
                self.assertGreater(new_course_grade.grade, course_grade)
                self.assertGreater(new_assignment_grade.grade,
                                   assignment_grade)

                mocked_update_assignment_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link2.lis_result_sourcedid,
                      new_assignment_grade.id)])
                mocked_update_assignment_grades_run.reset_mock()

                mocked_update_course_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link1.lis_result_sourcedid,
                      new_course_grade.id)])
                mocked_update_course_grades_run.reset_mock()

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_delete_answer_comment(self, mocked_update_assignment_grades_run,
                                   mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)

        # test login required
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test invalid comment id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test author
        with self.login(self.data.get_extra_student(1).username):
            comment = self.data.get_answer_comments_by_assignment(
                self.assignment)[1]
            url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test delete self-evaulation
        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user,
            answer,
            comment_type=AnswerCommentType.self_evaluation)
        self.assignment.calculate_grade(answer.user)
        self.course.calculate_grade(answer.user)

        lti_consumer = self.lti_data.lti_consumer
        (lti_user_resource_link1, lti_user_resource_link2
         ) = self.lti_data.setup_student_user_resource_links(
             answer.user, self.course, self.assignment)

        with self.login(self.data.get_authorized_instructor().username):
            course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user).grade

            url = self.get_url(course_uuid=self.course.uuid,
                               assignment_uuid=self.assignment.uuid,
                               answer_uuid=answer.uuid,
                               answer_comment_uuid=self_evaluation.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(self_evaluation.uuid, rv.json['id'])

            # grades should decrease
            new_course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user)
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user)
            self.assertLess(new_course_grade.grade, course_grade)
            self.assertLess(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link2.lis_result_sourcedid,
                  new_assignment_grade.id)])
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id,
                [(lti_user_resource_link1.lis_result_sourcedid,
                  new_course_grade.id)])
            mocked_update_course_grades_run.reset_mock()
class AnswerCommentListAPITests(ACJAPITestCase):
    """ Tests for answer comment list API """
    api = apiA
    resource = AnswerCommentListAPI

    def setUp(self):
        super(AnswerCommentListAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.questions = self.data.get_questions()
        self.answers = self.data.get_answers_by_question()

    def test_get_all_answer_comments(self):
        url = self.get_url(
            course_id=self.course.id, question_id=self.questions[0].id,
            answer_id=self.answers[self.questions[0].id][0].id)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid answer id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id, answer_id=999)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized user
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(
                self.data.get_answer_comments_by_question(self.questions[0])[1].content, rv.json[0]['content'])
            self.assertIn(
                self.data.get_answer_comments_by_question(self.questions[0])[1].user_fullname,
                rv.json[0]['user_fullname'])

        # test non-owner student of answer access comments
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json))

        # test owner student of answer access comments
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertNotIn('user_fullname', rv.json[0])

    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0), self.course, self.answers[self.questions[0].id][0], selfeval=True)

        base_params = {
            'course_id': self.course.id,
            'question_id': self.questions[0].id,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params, answer_ids=self.answers[self.questions[0].id][0].id)
            extra_student2_comment_id = self.data.get_answer_comments_by_question(self.questions[0])[1].id
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(self.get_url(selfeval='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_comment_id, rv.json[0]['id'])

            rv = self.client.get(self.get_url(selfeval='only', **params))
            self.assert200(rv)
            # self.assertEqual(1, rv.json['total'])
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.id, rv.json[0]['id'])

            ids = [extra_student2_comment_id, comment.id]
            rv = self.client.get(self.get_url(ids=','.join(str(x) for x in ids), **base_params))
            self.assert200(rv)
            # self.assertEqual(2, rv.json['total'])
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(selfeval='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.id, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(selfeval='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.id, rv.json[0]['id'])

            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test question_id filter
            rv = self.client.get(self.get_url(**base_params) + '?question_id=' + str(self.questions[0].id))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.id] + [c.id for c in self.data.answer_comments_by_question[self.questions[0].id]],
                [c['id'] for c in rv.json])

            rv = self.client.get(self.get_url(**base_params) + '?question_id=' + str(self.questions[1].id))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [c.id for c in self.data.answer_comments_by_question[self.questions[1].id]],
                [c['id'] for c in rv.json])

            # test user_ids filter
            user_ids = ','.join([str(self.data.get_extra_student(0).id)])
            rv = self.client.get(
                self.get_url(user_ids=user_ids, **base_params) + '&question_id=' + str(self.questions[0].id))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.id, self.data.answer_comments_by_question[self.questions[0].id][0].id],
                [c['id'] for c in rv.json])

        with self.login(self.data.get_extra_student(1).username):
            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # answer is not from the student but comment is
            answer_ids = [str(self.answers[self.questions[0].id][1].id)]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(0).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(self.data.get_extra_student(0).id, rv.json[0]['user_id'])

    def test_create_answer_comment(self):
        url = self.get_url(
            course_id=self.course.id, question_id=self.questions[0].id,
            answer_id=self.answers[self.questions[0].id][0].id)
        content = {'content': 'great answer'}

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_id=999, question_id=self.questions[0].id, answer_id=self.answers[self.questions[0].id][0].id)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid question id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=999, answer_id=self.answers[self.questions[0].id][0].id)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id, answer_id=999)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

            # test authorized user
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])
    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0), self.course, self.answers[self.questions[0].id][0], selfeval=True)

        base_params = {
            'course_id': self.course.id,
            'question_id': self.questions[0].id,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params, answer_ids=self.answers[self.questions[0].id][0].id)
            extra_student2_comment_id = self.data.get_answer_comments_by_question(self.questions[0])[1].id
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(self.get_url(selfeval='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_comment_id, rv.json[0]['id'])

            rv = self.client.get(self.get_url(selfeval='only', **params))
            self.assert200(rv)
            # self.assertEqual(1, rv.json['total'])
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.id, rv.json[0]['id'])

            ids = [extra_student2_comment_id, comment.id]
            rv = self.client.get(self.get_url(ids=','.join(str(x) for x in ids), **base_params))
            self.assert200(rv)
            # self.assertEqual(2, rv.json['total'])
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(selfeval='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.id, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(selfeval='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.id, rv.json[0]['id'])

            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test question_id filter
            rv = self.client.get(self.get_url(**base_params) + '?question_id=' + str(self.questions[0].id))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.id] + [c.id for c in self.data.answer_comments_by_question[self.questions[0].id]],
                [c['id'] for c in rv.json])

            rv = self.client.get(self.get_url(**base_params) + '?question_id=' + str(self.questions[1].id))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [c.id for c in self.data.answer_comments_by_question[self.questions[1].id]],
                [c['id'] for c in rv.json])

            # test user_ids filter
            user_ids = ','.join([str(self.data.get_extra_student(0).id)])
            rv = self.client.get(
                self.get_url(user_ids=user_ids, **base_params) + '&question_id=' + str(self.questions[0].id))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.id, self.data.answer_comments_by_question[self.questions[0].id][0].id],
                [c['id'] for c in rv.json])

        with self.login(self.data.get_extra_student(1).username):
            answer_ids = [str(answer.id) for answer in self.answers[self.questions[0].id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # answer is not from the student but comment is
            answer_ids = [str(self.answers[self.questions[0].id][1].id)]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(0).id)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(self.data.get_extra_student(0).id, rv.json[0]['user_id'])
 def setUp(self):
     super(AnswerCommentAPITests, self).setUp()
     self.data = AnswerCommentsTestData()
     self.course = self.data.get_course()
     self.questions = self.data.get_questions()
     self.answers = self.data.get_answers_by_question()
class AnswerCommentAPITests(ACJAPITestCase):
    """ Tests for answer comment API """
    api = apiA
    resource = AnswerCommentAPI

    def setUp(self):
        super(AnswerCommentAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.questions = self.data.get_questions()
        self.answers = self.data.get_answers_by_question()

    def test_get_single_answer_comment(self):
        comment = self.data.get_answer_comments_by_question(self.questions[0])[0]
        url = self.get_url(
            course_id=self.course.id, question_id=self.questions[0].id,
            answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_id=999, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id, answer_id=999, comment_id=comment.id)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=999)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

        # test author
        with self.login(self.data.get_extra_student(0).username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])

    def test_edit_answer_comment(self):
        comment = self.data.get_answer_comments_by_question(self.questions[0])[0]
        url = self.get_url(
            course_id=self.course.id, question_id=self.questions[0].id,
            answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)
        content = {'id': comment.id, 'content': 'insightful.'}

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_id=999, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id, answer_id=999, comment_id=comment.id)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=999)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test unmatched comment ids
            invalid = content.copy()
            invalid['id'] = self.data.get_answer_comments_by_question(self.questions[0])[1].id
            rv = self.client.post(url, data=json.dumps(invalid), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Comment id does not match URL.", rv.json['error'])

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("The comment content is empty!", rv.json['error'])

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])

        # test author
        with self.login(self.data.get_extra_student(0).username):
            content['content'] = 'I am the author'
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(content['content'], rv.json['content'])

    def test_delete_answer_comment(self):
        comment = self.data.get_answer_comments_by_question(self.questions[0])[0]
        url = self.get_url(
            course_id=self.course.id, question_id=self.questions[0].id,
            answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)

        # test login required
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test invalid comment id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=999)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.id, rv.json['id'])

        # test author
        with self.login(self.data.get_extra_student(1).username):
            comment = self.data.get_answer_comments_by_question(self.questions[0])[1]
            url = self.get_url(
                course_id=self.course.id, question_id=self.questions[0].id,
                answer_id=self.answers[self.questions[0].id][0].id, comment_id=comment.id)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.id, rv.json['id'])
Пример #15
0
    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.self_evaluation
        )
        draft_comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.evaluation,
            draft=True
        )

        base_params = {
            'course_uuid': self.course.uuid,
            'assignment_uuid': self.assignment.uuid,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params, answer_ids=self.answers[self.assignment.id][0].uuid)
            extra_student2_answer_comment_uuid = self.data.get_answer_comments_by_assignment(self.assignment)[1].uuid
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_answer_comment_uuid, rv.json[0]['id'])

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            # self.assertEqual(1, rv.json['total'])
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            ids = [extra_student2_answer_comment_uuid, comment.uuid]
            rv = self.client.get(self.get_url(ids=','.join(str(x) for x in ids), **base_params))
            self.assert200(rv)
            # self.assertEqual(2, rv.json['total'])
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.uuid, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test assignment_id filter
            rv = self.client.get(self.get_url(**base_params) + '?assignment_id=' + self.assignment.uuid)
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.uuid] + [c.uuid for c in self.data.get_non_draft_answer_comments_by_assignment(self.assignment)],
                [c['id'] for c in rv.json])

            rv = self.client.get(self.get_url(**base_params) + '?assignment_id=' + self.assignments[1].uuid)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [c.uuid for c in self.data.get_non_draft_answer_comments_by_assignment(self.assignments[1])],
                [c['id'] for c in rv.json])

            # test user_ids filter
            user_ids = ','.join([self.data.get_extra_student(0).uuid])
            rv = self.client.get(
                self.get_url(user_ids=user_ids, **base_params) + '&assignment_id=' + self.assignment.uuid)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.uuid, self.data.answer_comments_by_assignment[self.assignment.id][0].uuid],
                [c['id'] for c in rv.json])

        with self.login(self.data.get_extra_student(1).username):
            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # answer is not from the student but comment is
            answer_ids = [self.answers[self.assignment.id][1].uuid]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(0).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(self.data.get_extra_student(0).uuid, rv.json[0]['user_id'])

        # test drafts
        with self.login(self.data.get_extra_student(0).username):
            params = dict(base_params, user_ids=self.data.get_extra_student(0).uuid)
            rv = self.client.get(self.get_url(draft='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

            rv = self.client.get(self.get_url(draft='false', **params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(draft='true', **params))
            self.assert200(rv)
            self.assertEqual(4, len(rv.json))
            self.assertEqual(draft_comment.uuid, rv.json[0]['id'])
Пример #16
0
class AnswerCommentXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = AnswerCommentsTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

        self.self_evaluation_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.self_evaluation)
        self.evaluation_comment  = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.evaluation)
        self.public_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.public)
        self.private_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.private)

    def test_on_answer_comment_create(self):
        # test self_evaluation_comment
        on_answer_comment_create.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_create.name,
            user=self.user,
            answer_comment=self.self_evaluation_comment
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test evaluation_comment
        on_answer_comment_create.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_create.name,
            user=self.user,
            answer_comment=self.evaluation_comment
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test public_comment/private_comment
        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_create.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_create.name,
                user=self.user,
                answer_comment=comment
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            self.assertEqual(statements[0]['verb'], {
                'id': 'http://adlnet.gov/expapi/verbs/commented',
                'display': {'en-US': 'commented'}
            })
            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/comment/'+comment.uuid,
                'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}},
                'objectType': 'Activity'
            })
            self.assertEqual(statements[0]['result'], {
                'extensions': {
                    'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content),
                    'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" "))
                },
                'response': comment.content
            })
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })

    def test_on_answer_comment_modified(self):
        for draft in [True, False]:
            self.self_evaluation_comment.draft = draft
            db.session.commit()

            # test self_evaluation_comment without tracking
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=self.self_evaluation_comment
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 2)
            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))

            if draft:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft',
                    'display': {'en-US': 'drafted'}
                })
            else:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://activitystrea.ms/schema/1.0/submit',
                    'display': {'en-US': 'submitted'}
                })

            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/comment/'+self.self_evaluation_comment.uuid,
                'definition': {'type': 'http://activitystrea.ms/schema/1.0/review', 'name': {'en-US': 'Assignment self-evaluation review'}},
                'objectType': 'Activity'
            })
            if draft:
                self.assertEqual(statements[0]['result'], {
                    'extensions': {
                        'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content),
                        'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split(" "))
                    },
                    'response': self.self_evaluation_comment.content
                })
            else:
                self.assertEqual(statements[0]['result'], {
                    'extensions': {
                        'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content),
                        'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split(" "))
                    },
                    'response': self.self_evaluation_comment.content,
                    'success': True
                })

            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation',
                        'objectType': 'Activity'
                    }]
                }
            })

            self.assertEqual(statements[1]['actor'], self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(statements[1]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/suspended',
                    'display': {'en-US': 'suspended'}
                })
            else:
                self.assertEqual(statements[1]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/completed',
                    'display': {'en-US': 'completed'}
                })

            self.assertEqual(statements[1]['object'], {
                'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation',
                'definition': {'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': 'Assignment self-evaluation'}},
                'objectType': 'Activity'
            })
            self.assertEqual(statements[1]['result'], {
                'completion': not draft,
                'success': True
            })
            self.assertEqual(statements[1]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })

            # test self_evaluation_comment with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({ 'tracking':  tracking })
            with self.app.test_request_context(content_type='application/json', method='POST',
                    content_length=len(tracking_json), data=tracking_json):
                on_answer_comment_modified.send(
                    current_app._get_current_object(),
                    event_name=on_answer_comment_modified.name,
                    user=self.user,
                    answer_comment=self.self_evaluation_comment
                )

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 2)
                self.assertEqual(statements[0]['actor'], tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'], tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'], tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'], tracking_statements[0]['result'])
                self.assertEqual(tracking_statements[0]['context'], {
                    'registration': tracking.get('registration'),
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                            'objectType': 'Activity'
                        }],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation',
                            'objectType': 'Activity'
                        }]
                    }
                })
                self.assertEqual(statements[1]['actor'], tracking_statements[1]['actor'])
                self.assertEqual(statements[1]['verb'], tracking_statements[1]['verb'])
                self.assertEqual(statements[1]['object'], tracking_statements[1]['object'])
                self.assertEqual(tracking_statements[1]['context'], {
                    'registration': tracking.get('registration'),
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        }],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                            'objectType': 'Activity'
                        }]
                    }
                })
                self.assertEqual(tracking_statements[1]['result'], {
                    'completion': not draft,
                    'duration': tracking.get('duration'),
                    'success': True
                })

        for draft in [True, False]:
            self.evaluation_comment.draft = draft
            db.session.commit()

            # test evaluation_comment
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=self.evaluation_comment
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)

            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft',
                    'display': {'en-US': 'drafted'}
                })
            else:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/commented',
                    'display': {'en-US': 'commented'}
                })

            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/comment/'+self.evaluation_comment.uuid,
                'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer evaluation comment'}},
                'objectType': 'Activity'
            })
            self.assertEqual(statements[0]['result'], {
                'extensions': {
                    'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.evaluation_comment.content),
                    'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.evaluation_comment.content.split(" "))
                },
                'response': self.evaluation_comment.content
            })
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })

            # test evaluation_comment with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({ 'tracking':  tracking })
            with self.app.test_request_context(content_type='application/json', method='POST',
                    content_length=len(tracking_json), data=tracking_json):
                on_answer_comment_modified.send(
                    current_app._get_current_object(),
                    event_name=on_answer_comment_modified.name,
                    user=self.user,
                    answer_comment=self.evaluation_comment
                )

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 1)
                self.assertEqual(statements[0]['actor'], tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'], tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'], tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'], tracking_statements[0]['result'])
                self.assertEqual(tracking_statements[0]['context'], {
                    'registration': tracking.get('registration'),
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                            'objectType': 'Activity'
                        }],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                            'objectType': 'Activity'
                        }]
                    }
                })

        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=comment
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            self.assertEqual(statements[0]['verb'], {
                'id': 'http://activitystrea.ms/schema/1.0/update',
                'display': {'en-US': 'updated'}
            })
            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/comment/'+comment.uuid,
                'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}},
                'objectType': 'Activity'
            })
            self.assertEqual(statements[0]['result'], {
                'extensions': {
                    'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content),
                    'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" "))
                },
                'response': comment.content
            })
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })

    def test_on_answer_comment_delete(self):
        # test self_evaluation_comment
        on_answer_comment_delete.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_delete.name,
            user=self.user,
            answer_comment=self.self_evaluation_comment
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/delete',
            'display': {'en-US': 'deleted'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/xapi/answer/comment/'+self.self_evaluation_comment.uuid,
            'definition': {'type': 'http://activitystrea.ms/schema/1.0/review', 'name': {'en-US': 'Assignment self-evaluation review'}},
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertEqual(statements[0]['context'], {
            'contextActivities': {
                'grouping': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                    'objectType': 'Activity'
                }],
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation',
                    'objectType': 'Activity'
                }]
            }
        })

        # test evaluation_comment
        on_answer_comment_delete.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_delete.name,
            user=self.user,
            answer_comment=self.evaluation_comment
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/delete',
            'display': {'en-US': 'deleted'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/xapi/answer/comment/'+self.evaluation_comment.uuid,
            'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer evaluation comment'}},
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertEqual(statements[0]['context'], {
            'contextActivities': {
                'grouping': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                    'objectType': 'Activity'
                }],
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                    'objectType': 'Activity'
                }]
            }
        })


        # test public_comment/private_comment
        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_delete.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_delete.name,
                user=self.user,
                answer_comment=comment
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            self.assertEqual(statements[0]['verb'], {
                'id': 'http://activitystrea.ms/schema/1.0/delete',
                'display': {'en-US': 'deleted'}
            })
            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/comment/'+comment.uuid,
                'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}},
                'objectType': 'Activity'
            })
            self.assertNotIn('result', statements[0])
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })
Пример #17
0
class AnswerCommentAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment API """
    resource = AnswerCommentAPI
    api = api

    def setUp(self):
        super(AnswerCommentAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_single_answer_comment(self):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)
        draft_comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=draft_comment.uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized user student fetching draft of another student
        student = self.data.get_extra_student(0)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(draft_url)
                self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999",
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.uuid,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999",
                                       answer_comment_uuid=comment.uuid)
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(comment.content, rv.json['content'])
            self.assertIn('fullname', rv.json['user'])

            # test draft
            rv = self.client.get(draft_url)
            self.assert200(rv)
            self.assertEqual(draft_comment.content, rv.json['content'])
            self.assertTrue(rv.json['draft'])
            self.assertIn('fullname', rv.json['user'])

        # test author
        student = self.data.get_extra_student(0)
        for user_context in [
                self.login(student.username),
                self.impersonate(self.data.get_authorized_instructor(),
                                 student)
        ]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(comment.content, rv.json['content'])
                self.assertNotIn('fullname', rv.json['user'])

        # test draft author
        student = self.data.get_extra_student(1)
        for user_context in [
                self.login(student.username),
                self.impersonate(self.data.get_authorized_instructor(),
                                 student)
        ]:
            with user_context:
                rv = self.client.get(draft_url)
                self.assert200(rv)
                self.assertEqual(draft_comment.content, rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_edit_answer_comment(self, mocked_update_assignment_grades_run,
                                 mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)

        content = {
            'id': comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value
        }
        draft_comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[2]
        draft_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=draft_comment.uuid)
        draft_content = {
            'id': draft_comment.uuid,
            'content': 'insightful.',
            'comment_type': AnswerCommentType.private.value,
            'draft': True
        }

        # test login required
        rv = self.client.post(url,
                              data=json.dumps(content),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999",
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(course_uuid=self.course.uuid,
                                       assignment_uuid=self.assignment.uuid,
                                       answer_uuid="999",
                                       answer_comment_uuid=comment.uuid)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid comment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.post(invalid_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert404(rv)

            # test unmatched comment ids
            invalid = content.copy()
            invalid['id'] = self.data.get_answer_comments_by_assignment(
                self.assignment)[1].uuid
            rv = self.client.post(url,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual(
                "The feedback's ID does not match the URL, which is required in order to save the feedback.",
                rv.json['message'])

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual(
                "Please provide content in the text editor and try saving again.",
                rv.json['message'])

            # test empty comment_type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url,
                                  data=json.dumps(empty),
                                  content_type='application/json')
            self.assert400(rv)

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            with mail.record_messages() as outbox:
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

        # test author
        with self.login(self.data.get_extra_student(0).username):

            # test student can not change comment to self-eval / eval
            invalid = content.copy()
            invalid['comment_type'] = AnswerCommentType.self_evaluation.value
            rv = self.client.post(url,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual(
                "Feedback type cannot be changed. Please contact support for assistance.",
                rv.json['message'])

            invalid = content.copy()
            invalid['comment_type'] = AnswerCommentType.evaluation.value
            rv = self.client.post(url,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Feedback Not Saved", rv.json['title'])
            self.assertEqual(
                "Feedback type cannot be changed. Please contact support for assistance.",
                rv.json['message'])

            with mail.record_messages() as outbox:
                content['content'] = 'I am the author'
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

            # ignored setting draft to True when draft is already False
            with mail.record_messages() as outbox:
                content['draft'] = True
                rv = self.client.post(url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

        # test author with impersonation
        student = self.data.get_extra_student(0)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            content['content'] = 'I am the author'
            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

            content['draft'] = True
            rv = self.client.post(url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        # test draft author
        with self.login(self.data.get_extra_student(1).username):
            with mail.record_messages() as outbox:
                draft_content['content'] = 'I am the author'
                rv = self.client.post(draft_url,
                                      data=json.dumps(draft_content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(draft_content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                draft_content['draft'] = False
                rv = self.client.post(draft_url,
                                      data=json.dumps(draft_content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 1)
                self.assertEqual(
                    outbox[0].subject,
                    "New Answer Feedback in " + self.data.get_course().name)
                self.assertEqual(
                    outbox[0].recipients,
                    [self.answers[self.assignment.id][0].user.email])

        # test draft author with impersonation
        student = self.data.get_extra_student(1)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            draft_content['content'] = 'I am the author'
            rv = self.client.post(draft_url,
                                  data=json.dumps(draft_content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

            # cant change draft to False
            draft_content['draft'] = False
            rv = self.client.post(draft_url,
                                  data=json.dumps(draft_content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user,
            answer,
            comment_type=AnswerCommentType.self_evaluation,
            draft=True)
        self_evaluation_url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=answer.uuid,
            answer_comment_uuid=self_evaluation.uuid)

        with self.login(answer.user.username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2
             ) = self.lti_data.setup_student_user_resource_links(
                 answer.user, self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user).grade
            content = {
                'id': self_evaluation.uuid,
                'content': 'insightful.',
                'comment_type': AnswerCommentType.self_evaluation.value,
                'draft': True
            }

            # test student can not submit self-eval after self-eval grace period
            orig_answer_end = self.assignment.answer_end
            self.assignment.answer_end = datetime.datetime.utcnow(
            ) - datetime.timedelta(hours=12)
            self.assignment.self_eval_start = datetime.datetime.utcnow(
            ) - datetime.timedelta(hours=1)
            self.assignment.self_eval_end = datetime.datetime.utcnow(
            ) - datetime.timedelta(minutes=10)

            db.session.add(self.assignment)
            db.session.commit()

            rv = self.client.post(self_evaluation_url,
                                  data=json.dumps(content),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertEqual("Self-Evaluation Not Saved", rv.json['title'])
            self.assertEqual(
                "Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.",
                rv.json['message'])

            self.assignment.answer_end = orig_answer_end
            self.assignment.self_eval_start = None
            self.assignment.self_eval_end = None

            with mail.record_messages() as outbox:
                self.assignment.answer_end = datetime.datetime.utcnow(
                ) - datetime.timedelta(hours=12)
                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

                # grades should not change
                new_course_grade = CourseGrade.get_user_course_grade(
                    self.course, answer.user).grade
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                    self.assignment, answer.user).grade
                self.assertEqual(new_course_grade, course_grade)
                self.assertEqual(new_assignment_grade, assignment_grade)

            # can change draft to False when draft is True
            with mail.record_messages() as outbox:
                content['draft'] = False
                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert200(rv)
                self.assertFalse(rv.json['draft'])
                self.assertNotIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 0)

                # grades should increase
                new_course_grade = CourseGrade.get_user_course_grade(
                    self.course, answer.user)
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                    self.assignment, answer.user)
                self.assertGreater(new_course_grade.grade, course_grade)
                self.assertGreater(new_assignment_grade.grade,
                                   assignment_grade)

                mocked_update_assignment_grades_run.assert_called_once_with(
                    lti_consumer.id, [[
                        lti_user_resource_link2.lis_result_sourcedid,
                        new_assignment_grade.id
                    ]])
                mocked_update_assignment_grades_run.reset_mock()

                mocked_update_course_grades_run.assert_called_once_with(
                    lti_consumer.id, [[
                        lti_user_resource_link1.lis_result_sourcedid,
                        new_course_grade.id
                    ]])
                mocked_update_course_grades_run.reset_mock()

        # test self-evaluation with impersonation
        answers = self.answers[self.assignment.id]
        for answer in [
                a for a in answers if a.user.system_role == SystemRole.student
        ]:
            self_evaluation = self.data.create_answer_comment(
                answer.user,
                answer,
                comment_type=AnswerCommentType.self_evaluation,
                draft=True)
            self_evaluation_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=answer.uuid,
                answer_comment_uuid=self_evaluation.uuid)

            with self.impersonate(self.data.get_authorized_instructor(),
                                  answer.user):
                content = {
                    'id': self_evaluation.uuid,
                    'content': 'insightful.',
                    'comment_type': AnswerCommentType.self_evaluation.value,
                    'draft': True
                }

                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])
                # attempt to change draft to False
                content['draft'] = False
                rv = self.client.post(self_evaluation_url,
                                      data=json.dumps(content),
                                      content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_delete_answer_comment(self, mocked_update_assignment_grades_run,
                                   mocked_update_course_grades_run):
        comment = self.data.get_answer_comments_by_assignment(
            self.assignment)[0]
        url = self.get_url(
            course_uuid=self.course.uuid,
            assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid,
            answer_comment_uuid=comment.uuid)

        # test login required
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test invalid comment id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid="999")
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test author with impersonation
        student = self.data.get_extra_student(1)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert403(rv)
            self.assertTrue(rv.json['disabled_by_impersonation'])

        # test author
        with self.login(self.data.get_extra_student(1).username):
            comment = self.data.get_answer_comments_by_assignment(
                self.assignment)[1]
            url = self.get_url(
                course_uuid=self.course.uuid,
                assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid,
                answer_comment_uuid=comment.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(comment.uuid, rv.json['id'])

        # test delete self-evaulation with impersonation
        answers = self.answers[self.assignment.id]
        for answer in [
                a for a in answers if a.user.system_role == SystemRole.student
        ]:
            self_evaluation = self.data.create_answer_comment(
                answer.user,
                answer,
                comment_type=AnswerCommentType.self_evaluation,
                draft=True)

            with self.impersonate(self.data.get_authorized_instructor(),
                                  answer.user):
                url = self.get_url(course_uuid=self.course.uuid,
                                   assignment_uuid=self.assignment.uuid,
                                   answer_uuid=answer.uuid,
                                   answer_comment_uuid=self_evaluation.uuid)
                rv = self.client.delete(url)
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])

        # test delete self-evaulation
        answer = self.answers[self.assignment.id][0]
        self_evaluation = self.data.create_answer_comment(
            answer.user,
            answer,
            comment_type=AnswerCommentType.self_evaluation)
        self.assignment.calculate_grade(answer.user)
        self.course.calculate_grade(answer.user)

        lti_consumer = self.lti_data.lti_consumer
        (lti_user_resource_link1, lti_user_resource_link2
         ) = self.lti_data.setup_student_user_resource_links(
             answer.user, self.course, self.assignment)

        with self.login(self.data.get_authorized_instructor().username):
            course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user).grade

            url = self.get_url(course_uuid=self.course.uuid,
                               assignment_uuid=self.assignment.uuid,
                               answer_uuid=answer.uuid,
                               answer_comment_uuid=self_evaluation.uuid)
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(self_evaluation.uuid, rv.json['id'])

            # grades should decrease
            new_course_grade = CourseGrade.get_user_course_grade(
                self.course, answer.user)
            new_assignment_grade = AssignmentGrade.get_user_assignment_grade(
                self.assignment, answer.user)
            self.assertLess(new_course_grade.grade, course_grade)
            self.assertLess(new_assignment_grade.grade, assignment_grade)

            mocked_update_assignment_grades_run.assert_called_once_with(
                lti_consumer.id, [[
                    lti_user_resource_link2.lis_result_sourcedid,
                    new_assignment_grade.id
                ]])
            mocked_update_assignment_grades_run.reset_mock()

            mocked_update_course_grades_run.assert_called_once_with(
                lti_consumer.id, [[
                    lti_user_resource_link1.lis_result_sourcedid,
                    new_course_grade.id
                ]])
            mocked_update_course_grades_run.reset_mock()
Пример #18
0
class AnswerCommentXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = AnswerCommentsTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

        self.self_evaluation_comment = self.data.create_answer_comment(
            self.user,
            self.answer,
            comment_type=AnswerCommentType.self_evaluation)
        self.evaluation_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.evaluation)
        self.public_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.public)
        self.private_comment = self.data.create_answer_comment(
            self.user, self.answer, comment_type=AnswerCommentType.private)

    def test_on_answer_comment_create(self):
        # test self_evaluation_comment
        on_answer_comment_create.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_create.name,
            user=self.user,
            answer_comment=self.self_evaluation_comment)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test evaluation_comment
        on_answer_comment_create.send(current_app._get_current_object(),
                                      event_name=on_answer_comment_create.name,
                                      user=self.user,
                                      answer_comment=self.evaluation_comment)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 0)

        # test public_comment/private_comment
        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_create.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_create.name,
                user=self.user,
                answer_comment=comment)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            self.assertEqual(
                statements[0]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/commented',
                    'display': {
                        'en-US': 'commented'
                    }
                })
            self.assertEqual(
                statements[0]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/answer/comment/' +
                    comment.uuid,
                    'definition': {
                        'type': 'http://activitystrea.ms/schema/1.0/comment',
                        'name': {
                            'en-US': 'Assignment answer comment'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            self.assertEqual(
                statements[0]['result'], {
                    'extensions': {
                        'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                        len(comment.content),
                        'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                        len(comment.content.split(" "))
                    },
                    'response': comment.content
                })
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })

    def test_on_answer_comment_modified(self):
        for draft in [True, False]:
            self.self_evaluation_comment.draft = draft
            db.session.commit()

            # test self_evaluation_comment without tracking
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=self.self_evaluation_comment)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 2)
            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))

            if draft:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id':
                        'http://xapi.learninganalytics.ubc.ca/verb/draft',
                        'display': {
                            'en-US': 'drafted'
                        }
                    })
            else:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id': 'http://activitystrea.ms/schema/1.0/submit',
                        'display': {
                            'en-US': 'submitted'
                        }
                    })

            self.assertEqual(
                statements[0]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/answer/comment/' +
                    self.self_evaluation_comment.uuid,
                    'definition': {
                        'type': 'http://activitystrea.ms/schema/1.0/review',
                        'name': {
                            'en-US': 'Assignment self-evaluation review'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            if draft:
                self.assertEqual(
                    statements[0]['result'], {
                        'extensions': {
                            'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                            len(self.self_evaluation_comment.content),
                            'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                            len(self.self_evaluation_comment.content.split(
                                " "))
                        },
                        'response': self.self_evaluation_comment.content
                    })
            else:
                self.assertEqual(
                    statements[0]['result'], {
                        'extensions': {
                            'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                            len(self.self_evaluation_comment.content),
                            'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                            len(self.self_evaluation_comment.content.split(
                                " "))
                        },
                        'response': self.self_evaluation_comment.content,
                        'success': True
                    })

            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/self-evaluation',
                            'objectType':
                            'Activity'
                        }]
                    }
                })

            self.assertEqual(statements[1]['actor'],
                             self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(
                    statements[1]['verb'], {
                        'id': 'http://adlnet.gov/expapi/verbs/suspended',
                        'display': {
                            'en-US': 'suspended'
                        }
                    })
            else:
                self.assertEqual(
                    statements[1]['verb'], {
                        'id': 'http://adlnet.gov/expapi/verbs/completed',
                        'display': {
                            'en-US': 'completed'
                        }
                    })

            self.assertEqual(
                statements[1]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/assignment/' +
                    self.assignment.uuid + '/self-evaluation',
                    'definition': {
                        'type': 'http://adlnet.gov/expapi/activities/question',
                        'name': {
                            'en-US': 'Assignment self-evaluation'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            self.assertEqual(statements[1]['result'], {
                'completion': not draft,
                'success': True
            })
            self.assertEqual(
                statements[1]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })

            # test self_evaluation_comment with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({'tracking': tracking})
            with self.app.test_request_context(
                    content_type='application/json',
                    method='POST',
                    content_length=len(tracking_json),
                    data=tracking_json):
                on_answer_comment_modified.send(
                    current_app._get_current_object(),
                    event_name=on_answer_comment_modified.name,
                    user=self.user,
                    answer_comment=self.self_evaluation_comment)

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 2)
                self.assertEqual(statements[0]['actor'],
                                 tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'],
                                 tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'],
                                 tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'],
                                 tracking_statements[0]['result'])
                self.assertEqual(
                    tracking_statements[0]['context'], {
                        'registration': tracking.get('registration'),
                        'contextActivities': {
                            'grouping': [{
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            }, {
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            }, {
                                'id':
                                'https://localhost:8888/app/xapi/answer/' +
                                self.answer.uuid,
                                'objectType':
                                'Activity'
                            }],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid + '/self-evaluation',
                                'objectType':
                                'Activity'
                            }]
                        }
                    })
                self.assertEqual(statements[1]['actor'],
                                 tracking_statements[1]['actor'])
                self.assertEqual(statements[1]['verb'],
                                 tracking_statements[1]['verb'])
                self.assertEqual(statements[1]['object'],
                                 tracking_statements[1]['object'])
                self.assertEqual(
                    tracking_statements[1]['context'], {
                        'registration': tracking.get('registration'),
                        'contextActivities': {
                            'grouping': [{
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            }],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            }, {
                                'id':
                                'https://localhost:8888/app/xapi/answer/' +
                                self.answer.uuid,
                                'objectType':
                                'Activity'
                            }]
                        }
                    })
                self.assertEqual(
                    tracking_statements[1]['result'], {
                        'completion': not draft,
                        'duration': tracking.get('duration'),
                        'success': True
                    })

        for draft in [True, False]:
            self.evaluation_comment.draft = draft
            db.session.commit()

            # test evaluation_comment
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=self.evaluation_comment)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)

            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id':
                        'http://xapi.learninganalytics.ubc.ca/verb/draft',
                        'display': {
                            'en-US': 'drafted'
                        }
                    })
            else:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id': 'http://adlnet.gov/expapi/verbs/commented',
                        'display': {
                            'en-US': 'commented'
                        }
                    })

            self.assertEqual(
                statements[0]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/answer/comment/' +
                    self.evaluation_comment.uuid,
                    'definition': {
                        'type': 'http://activitystrea.ms/schema/1.0/comment',
                        'name': {
                            'en-US': 'Assignment answer evaluation comment'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            self.assertEqual(
                statements[0]['result'], {
                    'extensions': {
                        'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                        len(self.evaluation_comment.content),
                        'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                        len(self.evaluation_comment.content.split(" "))
                    },
                    'response': self.evaluation_comment.content
                })
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })

            # test evaluation_comment with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({'tracking': tracking})
            with self.app.test_request_context(
                    content_type='application/json',
                    method='POST',
                    content_length=len(tracking_json),
                    data=tracking_json):
                on_answer_comment_modified.send(
                    current_app._get_current_object(),
                    event_name=on_answer_comment_modified.name,
                    user=self.user,
                    answer_comment=self.evaluation_comment)

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 1)
                self.assertEqual(statements[0]['actor'],
                                 tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'],
                                 tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'],
                                 tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'],
                                 tracking_statements[0]['result'])
                self.assertEqual(
                    tracking_statements[0]['context'], {
                        'registration': tracking.get('registration'),
                        'contextActivities': {
                            'grouping': [{
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            }, {
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            }, {
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid + '/question',
                                'objectType':
                                'Activity'
                            }],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/answer/' +
                                self.answer.uuid,
                                'objectType':
                                'Activity'
                            }]
                        }
                    })

        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_modified.name,
                user=self.user,
                answer_comment=comment)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            self.assertEqual(
                statements[0]['verb'], {
                    'id': 'http://activitystrea.ms/schema/1.0/update',
                    'display': {
                        'en-US': 'updated'
                    }
                })
            self.assertEqual(
                statements[0]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/answer/comment/' +
                    comment.uuid,
                    'definition': {
                        'type': 'http://activitystrea.ms/schema/1.0/comment',
                        'name': {
                            'en-US': 'Assignment answer comment'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            self.assertEqual(
                statements[0]['result'], {
                    'extensions': {
                        'http://xapi.learninganalytics.ubc.ca/extension/character-count':
                        len(comment.content),
                        'http://xapi.learninganalytics.ubc.ca/extension/word-count':
                        len(comment.content.split(" "))
                    },
                    'response': comment.content
                })
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })

    def test_on_answer_comment_delete(self):
        # test self_evaluation_comment
        on_answer_comment_delete.send(
            current_app._get_current_object(),
            event_name=on_answer_comment_delete.name,
            user=self.user,
            answer_comment=self.self_evaluation_comment)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'],
                         self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]['verb'], {
                'id': 'http://activitystrea.ms/schema/1.0/delete',
                'display': {
                    'en-US': 'deleted'
                }
            })
        self.assertEqual(
            statements[0]['object'], {
                'id':
                'https://localhost:8888/app/xapi/answer/comment/' +
                self.self_evaluation_comment.uuid,
                'definition': {
                    'type': 'http://activitystrea.ms/schema/1.0/review',
                    'name': {
                        'en-US': 'Assignment self-evaluation review'
                    }
                },
                'objectType':
                'Activity'
            })
        self.assertNotIn('result', statements[0])
        self.assertEqual(
            statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id':
                        'https://localhost:8888/app/xapi/course/' +
                        self.course.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/answer/' +
                        self.answer.uuid,
                        'objectType':
                        'Activity'
                    }],
                    'parent': [{
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid + '/self-evaluation',
                        'objectType':
                        'Activity'
                    }]
                }
            })

        # test evaluation_comment
        on_answer_comment_delete.send(current_app._get_current_object(),
                                      event_name=on_answer_comment_delete.name,
                                      user=self.user,
                                      answer_comment=self.evaluation_comment)

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'],
                         self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]['verb'], {
                'id': 'http://activitystrea.ms/schema/1.0/delete',
                'display': {
                    'en-US': 'deleted'
                }
            })
        self.assertEqual(
            statements[0]['object'], {
                'id':
                'https://localhost:8888/app/xapi/answer/comment/' +
                self.evaluation_comment.uuid,
                'definition': {
                    'type': 'http://activitystrea.ms/schema/1.0/comment',
                    'name': {
                        'en-US': 'Assignment answer evaluation comment'
                    }
                },
                'objectType':
                'Activity'
            })
        self.assertNotIn('result', statements[0])
        self.assertEqual(
            statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id':
                        'https://localhost:8888/app/xapi/course/' +
                        self.course.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid,
                        'objectType':
                        'Activity'
                    }, {
                        'id':
                        'https://localhost:8888/app/xapi/assignment/' +
                        self.assignment.uuid + '/question',
                        'objectType':
                        'Activity'
                    }],
                    'parent': [{
                        'id':
                        'https://localhost:8888/app/xapi/answer/' +
                        self.answer.uuid,
                        'objectType':
                        'Activity'
                    }]
                }
            })

        # test public_comment/private_comment
        for comment in [self.public_comment, self.private_comment]:
            on_answer_comment_delete.send(
                current_app._get_current_object(),
                event_name=on_answer_comment_delete.name,
                user=self.user,
                answer_comment=comment)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            self.assertEqual(
                statements[0]['verb'], {
                    'id': 'http://activitystrea.ms/schema/1.0/delete',
                    'display': {
                        'en-US': 'deleted'
                    }
                })
            self.assertEqual(
                statements[0]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/answer/comment/' +
                    comment.uuid,
                    'definition': {
                        'type': 'http://activitystrea.ms/schema/1.0/comment',
                        'name': {
                            'en-US': 'Assignment answer comment'
                        }
                    },
                    'objectType':
                    'Activity'
                })
            self.assertNotIn('result', statements[0])
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id':
                            'https://localhost:8888/app/xapi/course/' +
                            self.course.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }, {
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/answer/' +
                            self.answer.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })
Пример #19
0
class AnswerCommentListAPITests(ComPAIRAPITestCase):
    """ Tests for answer comment list API """
    resource = AnswerCommentListAPI
    api = api

    def setUp(self):
        super(AnswerCommentListAPITests, self).setUp()
        self.data = AnswerCommentsTestData()
        self.course = self.data.get_course()
        self.assignments = self.data.get_assignments()
        self.answers = self.data.get_answers_by_assignment()
        self.assignment = self.assignments[0]
        self.assignment.enable_self_evaluation = True
        db.session.commit()
        self.assignment.calculate_grades()
        self.lti_data = LTITestData()

    def test_get_all_answer_comments(self):
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)

        # test login required
        rv = self.client.get(url)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.id, assignment_uuid=self.assignment.uuid, answer_uuid="999")
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test authorized user
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(
                self.data.get_non_draft_answer_comments_by_assignment(self.assignment)[1].content, rv.json[0]['content'])
            self.assertIn(
                self.data.get_non_draft_answer_comments_by_assignment(self.assignment)[1].user_fullname,
                rv.json[0]['user']['fullname'])

        # test non-owner student of answer access comments
        student = self.data.get_authorized_student()
        for user_context in [self.login(student.username), self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(0, len(rv.json))

        # test owner student of answer access comments
        student = self.data.get_extra_student(0)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertNotIn('fullname', rv.json[0]['user'])

    def test_get_list_query_params(self):
        comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.self_evaluation
        )
        draft_comment = AnswerCommentsTestData.create_answer_comment(
            self.data.get_extra_student(0),
            self.answers[self.assignment.id][0],
            comment_type=AnswerCommentType.evaluation,
            draft=True
        )

        base_params = {
            'course_uuid': self.course.uuid,
            'assignment_uuid': self.assignment.uuid,
        }

        with self.login(self.data.get_authorized_instructor().username):
            # no answer ids
            rv = self.client.get(self.get_url(**base_params))
            self.assert404(rv)

            params = dict(base_params, answer_ids=self.answers[self.assignment.id][0].uuid)
            extra_student2_answer_comment_uuid = self.data.get_answer_comments_by_assignment(self.assignment)[1].uuid
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(extra_student2_answer_comment_uuid, rv.json[0]['id'])

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            ids = [extra_student2_answer_comment_uuid, comment.uuid]
            rv = self.client.get(self.get_url(ids=','.join(ids), **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(self, ids, [c['id'] for c in rv.json])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids))
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(3, len(rv.json))

            rv = self.client.get(self.get_url(self_evaluation='false', **params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            self.assertNotIn(comment.uuid, (c['id'] for c in rv.json))

            rv = self.client.get(self.get_url(self_evaluation='only', **params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))
            self.assertEqual(comment.uuid, rv.json[0]['id'])

            answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
            params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
            rv = self.client.get(self.get_url(**params))
            self.assert200(rv)
            self.assertEqual(1, len(rv.json))

            # test user_ids filter
            user_ids = ','.join([self.data.get_extra_student(0).uuid])
            rv = self.client.get(self.get_url(user_ids=user_ids, **base_params))
            self.assert200(rv)
            self.assertEqual(2, len(rv.json))
            six.assertCountEqual(
                self,
                [comment.uuid, self.data.answer_comments_by_assignment[self.assignment.id][0].uuid],
                [c['id'] for c in rv.json])

        student = self.data.get_extra_student(1)
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                answer_ids = [answer.uuid for answer in self.answers[self.assignment.id]]
                params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(1).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))

                # answer is not from the student but comment is
                answer_ids = [self.answers[self.assignment.id][1].uuid]
                params = dict(base_params, answer_ids=','.join(answer_ids), user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(**params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(self.data.get_extra_student(0).uuid, rv.json[0]['user_id'])

        # test drafts
        student = self.data.get_extra_student(0)
        for user_context in [self.login(student.username), self.impersonate(self.data.get_authorized_instructor(), student)]:
            with user_context:
                params = dict(base_params, user_ids=self.data.get_extra_student(0).uuid)
                rv = self.client.get(self.get_url(draft='only', **params))
                self.assert200(rv)
                self.assertEqual(1, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

                rv = self.client.get(self.get_url(draft='false', **params))
                self.assert200(rv)
                self.assertEqual(2, len(rv.json))

                rv = self.client.get(self.get_url(draft='true', **params))
                self.assert200(rv)
                self.assertEqual(3, len(rv.json))
                self.assertEqual(draft_comment.uuid, rv.json[0]['id'])

    @mock.patch('compair.tasks.lti_outcomes.update_lti_course_grades.run')
    @mock.patch('compair.tasks.lti_outcomes.update_lti_assignment_grades.run')
    def test_create_answer_comment(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run):
        url = self.get_url(
            course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid,
            answer_uuid=self.answers[self.assignment.id][0].uuid)
        content = {
            'comment_type': AnswerCommentType.private.value,
            'content': 'great answer'
        }

        # test login required
        rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = self.get_url(
                course_uuid="999", assignment_uuid=self.assignment.uuid,
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid assignment id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid="999",
                answer_uuid=self.answers[self.assignment.id][0].uuid)
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            invalid_url = self.get_url(
                course_uuid=self.course.uuid, assignment_uuid=self.assignment.uuid, answer_uuid="999")
            rv = self.client.post(invalid_url, data=json.dumps(content), content_type='application/json')
            self.assert404(rv)

            # test empty content
            empty = content.copy()
            empty['content'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

            # test empty comment type
            empty = content.copy()
            empty['comment_type'] = ''
            rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
            self.assert400(rv)

            # test authorized user
            with mail.record_messages() as outbox:
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertFalse(rv.json['draft'])
                self.assertIn('fullname', rv.json['user'])

                self.assertEqual(len(outbox), 1)
                self.assertEqual(outbox[0].subject, "New Answer Feedback in "+self.data.get_course().name)
                self.assertEqual(outbox[0].recipients, [self.answers[self.assignment.id][0].user.email])

            # test authorized user draft
            with mail.record_messages() as outbox:
                draft_content = content.copy()
                draft_content['draft'] = True
                rv = self.client.post(url, data=json.dumps(draft_content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(content['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

            # test authorized user draft - empty content
            with mail.record_messages() as outbox:
                empty = draft_content.copy()
                empty['content'] = None
                rv = self.client.post(url, data=json.dumps(empty), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(empty['content'], rv.json['content'])
                self.assertTrue(rv.json['draft'])

                self.assertEqual(len(outbox), 0)

        with self.login('root'):
            with mail.record_messages() as outbox:
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)
                self.assertEqual(len(outbox), 1)
                self.assertIn('fullname', rv.json['user'])

        with self.login(self.data.get_authorized_student().username):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
                self.data.get_authorized_student(), self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(self.course, self.data.get_authorized_student()).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, self.data.get_authorized_student()).grade

            content = {
                'comment_type': AnswerCommentType.self_evaluation.value,
                'content': 'great answer'
            }

            # test student can not submit self-eval after self-eval grace period
            orig_answer_end = self.assignment.answer_end
            self.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
            self.assignment.self_eval_start = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
            self.assignment.self_eval_end = datetime.datetime.utcnow() - datetime.timedelta(minutes=10)

            db.session.add(self.assignment)
            db.session.commit()

            rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
            self.assert403(rv)
            self.assertEqual("Self-Evaluation Not Saved", rv.json['title'])
            self.assertEqual("Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.",
                rv.json['message'])

            self.assignment.answer_end = orig_answer_end
            self.assignment.self_eval_start = None
            self.assignment.self_eval_end = None


            with mail.record_messages() as outbox:
                orig_answer_end = self.assignment.answer_end
                self.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(hours=12)

                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert200(rv)

                self.assertEqual(len(outbox), 0)
                self.assertNotIn('fullname', rv.json['user'])

                # grades should increase
                new_course_grade = CourseGrade.get_user_course_grade(self.course, self.data.get_authorized_student())
                new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, self.data.get_authorized_student())
                self.assertGreater(new_course_grade.grade, course_grade)
                self.assertGreater(new_assignment_grade.grade, assignment_grade)

                mocked_update_assignment_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link2.lis_result_sourcedid, new_assignment_grade.id)]
                )
                mocked_update_assignment_grades_run.reset_mock()

                mocked_update_course_grades_run.assert_called_once_with(
                    lti_consumer.id,
                    [(lti_user_resource_link1.lis_result_sourcedid, new_course_grade.id)]
                )
                mocked_update_assignment_grades_run.reset_mock()

                self.assignment.answer_end = orig_answer_end

        # test with impersonation
        student = self.data.get_extra_student(0)
        with self.impersonate(self.data.get_authorized_instructor(), student):
            lti_consumer = self.lti_data.lti_consumer
            (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links(
                self.data.get_authorized_student(), self.course, self.assignment)

            course_grade = CourseGrade.get_user_course_grade(self.course, self.data.get_authorized_student()).grade
            assignment_grade = AssignmentGrade.get_user_assignment_grade(self.assignment, self.data.get_authorized_student()).grade

            content = {
                'comment_type': AnswerCommentType.self_evaluation.value,
                'content': 'great answer'
            }

            with mail.record_messages() as outbox:
                rv = self.client.post(url, data=json.dumps(content), content_type='application/json')
                self.assert403(rv)
                self.assertTrue(rv.json['disabled_by_impersonation'])