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 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)
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()
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()
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'])
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()
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'])
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'])
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'])
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' }] } })
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()
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' }] } })
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'])