def setUp(self): super(AssignmentAttachmentTests, self).setUp() self.fixtures = TestFixture().add_course(num_students=25, num_assignments=1, num_groups=0, num_answers=20, with_draft_student=True) self.filePathsToCleanup = []
def setUp(self): super(FileRetrieveTests, self).setUp() self.fixtures = TestFixture().add_course(num_students=5, num_assignments=1, num_groups=0, num_answers=1, with_draft_student=True) self.files_to_cleanup = []
def setUp(self): super(ComparionExampleAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.instructor) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.ta) self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid)
def setUp(self): super(GroupUsersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3, num_group_assignments=1) self.assignment = self.fixtures.assignment self.group_assignment = self.fixtures.assignments[1] now = datetime.datetime.now() self.assignment_dates = [ # before answer period (no comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), None, None), # during answer period (no comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), None, None), # during compare period (no comparison dates) (True, now - timedelta(days=2), now - timedelta(days=1), None, None), # before answer period (with comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # during answer period (with comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # after answer period (with comparison dates) (False, now - timedelta(days=2), now - timedelta(days=1), now + timedelta(days=12), now + timedelta(days=13)), # during compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=2), now + timedelta(days=3)), # after compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=5), now - timedelta(days=3)) ]
def setUp(self): super(TestLTIOutcome, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.lti_data = LTITestData() self.lti_consumer = self.lti_data.lti_consumer self.lis_outcome_service_url = "TestUrl.com" self.lis_result_sourcedid = "SomeUniqueSourcedId" self.grade = 0.8
def setUp(self): super(GradebookAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=10, num_groups=5, num_group_assignments=1, with_comparisons=True) self.assignment = self.fixtures.assignment self.group_assignment = self.fixtures.assignments[1] self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid)
def setUp(self): super(ReportAPITest, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_assignments=2, num_additional_criteria=1, num_groups=2, num_answers=25, with_draft_student=True, with_comments=True, with_comparisons=True) self.url = "/api/courses/" + self.fixtures.course.uuid + "/report" self.files_to_cleanup = []
def setUp(self): super(GroupUsersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3, num_group_assignments=1) self.assignment = self.fixtures.assignment self.group_assignment = self.fixtures.assignments[1] now = datetime.datetime.now() self.assignment_dates = [ # before answer period (no comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), None, None), # during answer period (no comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), None, None), # during compare period (no comparison dates) (True, now - timedelta(days=2), now - timedelta(days=1), None, None ), # before answer period (with comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # during answer period (with comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # after answer period (with comparison dates) (False, now - timedelta(days=2), now - timedelta(days=1), now + timedelta(days=12), now + timedelta(days=13)), # during compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=2), now + timedelta(days=3)), # after compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=5), now - timedelta(days=3)) ]
class AssignmentAttachmentTests(ComPAIRAPITestCase): base_url = '/app' fixtures = None def setUp(self): super(AssignmentAttachmentTests, self).setUp() self.fixtures = TestFixture().add_course(num_students=25, num_assignments=1, num_groups=0, num_answers=20, with_draft_student=True) self.filePathsToCleanup = [] def tearDown(self): for filePath in self.filePathsToCleanup: try: if os.path.isfile(filePath): os.remove(filePath) except Exception as e: print(e) def _getUrl(self, courseUuid=None, assignmentUuid=None): if courseUuid is None: courseUuid = self.fixtures.course.uuid if assignmentUuid is None: assignmentUuid = self.fixtures.assignment.uuid return "/api/courses/" + courseUuid + "/assignments/" + assignmentUuid \ + "/attachments/download" # we need to create actual files def _createAttachmentsInAssignment(self, assignment): uploadDir = current_app.config['ATTACHMENT_UPLOAD_FOLDER'] for answer in assignment.answers: attachFile = self.fixtures.add_file(answer.user) attachFilename = attachFile.uuid + '.png' attachFile.name = attachFilename attachFile.uuid = attachFile.uuid attachFilePath = uploadDir + '/' + attachFilename img = Image.new('RGB', (60, 30), color='red') img.save(attachFilePath) self.filePathsToCleanup.append(attachFilePath) answer.file_id = attachFile.id db.session.add(assignment) db.session.add(attachFile) db.session.add(answer) db.session.commit() def test_download_all_attachments_block_unauthorized_users(self): # test login required rv = self.client.get(self._getUrl()) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self._getUrl()) self.assert403(rv) def test_download_all_attachments_require_valid_course_and_assignment( self): with self.login(self.fixtures.instructor.username): # invalid course url = self._getUrl("invalidUuid") rv = self.client.get(url) self.assert404(rv) # invalid assignment url = self._getUrl(None, "invalidUuid") rv = self.client.get(url) self.assert404(rv) def test_download_all_attachments_return_msg_if_no_attachments(self): with self.login(self.fixtures.instructor.username): rv = self.client.get(self._getUrl()) self.assert200(rv) self.assertEqual('Assignment has no attachments', rv.json['msg']) def test_download_all_attachments(self): self._createAttachmentsInAssignment(self.fixtures.assignment) with self.login(self.fixtures.instructor.username): rv = self.client.get(self._getUrl()) self.assert200(rv) # make sure that we got download path and filename in return expectedFilename = '{} [attachments-{}].zip'.format( self.fixtures.assignment.name, self.fixtures.assignment.uuid) self.assertEqual(expectedFilename, rv.json['filename']) self.assertEqual('report/' + expectedFilename, rv.json['file']) # check that the actual zip file created exists zipFilePath = '{}/{}'.format(current_app.config['REPORT_FOLDER'], expectedFilename) self.assertTrue(os.path.isfile(zipFilePath)) self.filePathsToCleanup.append(zipFilePath) # check that the contents of the zip file are as expected archive = zipfile.ZipFile(zipFilePath) self.assertEqual(None, archive.testzip()) actualAttachments = archive.namelist() for answer in self.fixtures.assignment.answers: if not answer.file_id: continue # we don't include inactive, draft, or practice answers if not answer.active or answer.draft or answer.practice: continue # we don't include dropped students if answer.user in self.fixtures.dropped_students: continue expectedAttachment = '{} - {} - {}'.format( answer.user.fullname, answer.user.student_number, answer.file.name) self.assertTrue(expectedAttachment in actualAttachments)
class AnswersAPITests(ComPAIRAPITestCase): def setUp(self): super(AnswersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid) self.lti_data = LTITestData() def _build_url(self, course_uuid, assignment_uuid, tail=""): url = '/api/courses/' + course_uuid + '/assignments/' + assignment_uuid + '/answers' + tail return url def test_get_all_answers(self): # add some answers to top answers top_answers = self.fixtures.answers[:5] for answer in top_answers: answer.top_answer = True db.session.commit() # Test login required rv = self.client.get(self.base_url) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.students[0].username): # test non-existent entry rv = self.client.get(self._build_url(self.fixtures.course.uuid, "4903409")) self.assert404(rv) # test data retrieve is correct self.fixtures.assignment.answer_end = datetime.datetime.now() - datetime.timedelta(days=1) db.session.add(self.fixtures.assignment) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = Answer.query \ .filter_by(active=True, draft=False, assignment_id=self.fixtures.assignment.id) \ .filter(~Answer.id.in_([a.id for a in self.fixtures.dropped_answers])) \ .order_by(Answer.created.desc()) \ .paginate(1, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) if expected.score: self.assertEqual(expected.score.rank, actual['score']['rank']) self.assertFalse('normalized_score' in actual['score']) else: self.assertIsNone(actual['score']) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test the second page rv = self.client.get(self.base_url + '?page=2') self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = Answer.query \ .filter_by(active=True, draft=False, assignment_id=self.fixtures.assignment.id) \ .filter(~Answer.id.in_([a.id for a in self.fixtures.dropped_answers])) \ .order_by(Answer.created.desc()) \ .paginate(2, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) if expected.score: self.assertEqual(expected.score.rank, actual['score']['rank']) self.assertFalse('normalized_score' in actual['score']) else: self.assertIsNone(actual['score']) self.assertEqual(2, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test sorting by rank (display_rank_limit 10) self.fixtures.assignment.rank_display_limit = 10 db.session.commit() rv = self.client.get(self.base_url + '?orderBy=score') self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted expected = sorted( [answer for answer in self.fixtures.answers if answer.score], key=lambda ans: (ans.score.score, ans.created), reverse=True)[:10] self.assertEqual([a.uuid for a in expected], [a['id'] for a in result]) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(expected), rv.json['total']) # test sorting by rank (display_rank_limit 20) self.fixtures.assignment.rank_display_limit = 20 db.session.commit() rv = self.client.get(self.base_url + '?orderBy=score') self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted expected = sorted( [answer for answer in self.fixtures.answers if answer.score], key=lambda ans: (ans.score.score, ans.created), reverse=True)[:20] self.assertEqual([a.uuid for a in expected], [a['id'] for a in result]) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(expected), rv.json['total']) # test sorting by rank (display_rank_limit None) self.fixtures.assignment.rank_display_limit = None db.session.commit() rv = self.client.get(self.base_url + '?orderBy=score') self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted expected = sorted( [answer for answer in self.fixtures.answers if answer.score], key=lambda ans: (ans.score.score, ans.created), reverse=True)[:20] self.assertEqual([a.uuid for a in expected], [a['id'] for a in result]) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(expected), rv.json['total']) # test author filter rv = self.client.get(self.base_url + '?author={}'.format(self.fixtures.students[0].uuid)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].uuid) # test group filter rv = self.client.get(self.base_url + '?group={}'.format(self.fixtures.groups[0])) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), len(self.fixtures.answers) / len(self.fixtures.groups)) # test ids filter ids = {a.uuid for a in self.fixtures.answers[:3]} rv = self.client.get(self.base_url + '?ids={}'.format(','.join(ids))) self.assert200(rv) result = rv.json['objects'] self.assertEqual(ids, {str(a['id']) for a in result}) # test top_answer filter top_answer_ids = {a.uuid for a in top_answers} rv = self.client.get(self.base_url + '?top=true') self.assert200(rv) result = rv.json['objects'] self.assertEqual(top_answer_ids, {a['id'] for a in result}) # test combined filter rv = self.client.get( self.base_url + '?orderBy=score&group={}'.format( self.fixtures.groups[0] ) ) self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted answers_per_group = int(len(self.fixtures.answers) / len(self.fixtures.groups)) if len( self.fixtures.groups) else 0 answers = self.fixtures.answers[:answers_per_group] expected = sorted(answers, key=lambda ans: ans.score.score, reverse=True) self.assertEqual([a.uuid for a in expected], [a['id'] for a in result]) # all filters rv = self.client.get( self.base_url + '?orderBy=score&group={}&author={}&top=true&page=1&perPage=20'.format( self.fixtures.groups[0], self.fixtures.students[0].uuid ) ) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].uuid) # add instructor answer answer = AnswerFactory( assignment=self.fixtures.assignment, user=self.fixtures.instructor ) self.fixtures.answers.append(answer) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) result = rv.json['objects'] user_uuids = [a['user_id'] for a in result] self.assertEqual(len(self.fixtures.answers), rv.json['total']) # first answer should be instructor answer self.assertEqual(self.fixtures.instructor.uuid, result[0]['user_id']) # no dropped student answers should be included for dropped_student in self.fixtures.dropped_students: self.assertNotIn(dropped_student.uuid, user_uuids) # test data retrieve before answer period ended with non-privileged user self.fixtures.assignment.answer_end = datetime.datetime.now() + datetime.timedelta(days=2) db.session.add(self.fixtures.assignment) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(1, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(1, rv.json['total']) # test data retrieve before answer period ended with privileged user with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(20, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(self.fixtures.answers), rv.json['total']) @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(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run): # test login required expected_answer = {'content': 'this is some answer content'} rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): rv = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(rv) # test invalid format with self.login(self.fixtures.students[0].username): invalid_answer = {'post': {'blah': 'blah'}} rv = self.client.post( self.base_url, data=json.dumps(invalid_answer), content_type='application/json') self.assert400(rv) # test invalid assignment rv = self.client.post( self._build_url(self.fixtures.course.uuid, "9392402"), data=json.dumps(expected_answer), content_type='application/json') self.assert404(rv) # test invalid course rv = self.client.post( self._build_url("9392402", self.fixtures.assignment.uuid), data=json.dumps(expected_answer), content_type='application/json') self.assert404(rv) # test create successful with self.login(self.fixtures.instructor.username): rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) # retrieve again and verify actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # user should not have grades new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.instructor) new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.fixtures.assignment, self.fixtures.instructor) self.assertIsNone(new_course_grade) self.assertIsNone(new_assignment_grade) # test instructor could submit multiple answers for his/her own rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # user should not have grades new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.instructor) new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.fixtures.assignment, self.fixtures.instructor) self.assertIsNone(new_course_grade) self.assertIsNone(new_assignment_grade) # test instructor could submit multiple answers for his/her own expected_answer.update({'user_id': self.fixtures.instructor.uuid}) rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # user should not have grades new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.instructor) new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.fixtures.assignment, self.fixtures.instructor) self.assertIsNone(new_course_grade) self.assertIsNone(new_assignment_grade) # test instructor could submit on behave of a student self.fixtures.add_students(1) expected_answer.update({'user_id': self.fixtures.students[-1].uuid}) rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # user should have grades new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[-1]) new_assignment_grade = AssignmentGrade.get_user_assignment_grade(self.fixtures.assignment, self.fixtures.students[-1]) self.assertIsNotNone(new_course_grade) self.assertIsNotNone(new_assignment_grade) # test instructor can not submit additional answers for a student expected_answer.update({'user_id': self.fixtures.students[0].uuid}) rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Answer Not Submitted") self.assertEqual(rv.json['message'], "An answer has already been submitted for this assignment by you or on your behalf.") self.fixtures.add_students(1) self.fixtures.course.calculate_grade(self.fixtures.students[-1]) self.fixtures.assignment.calculate_grade(self.fixtures.students[-1]) expected_answer = {'content': 'this is some answer content', 'draft': True} with self.login(self.fixtures.students[-1].username): course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[-1]).grade assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.students[-1]).grade # test create draft successful rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) self.assertEqual(expected_answer['draft'], actual_answer.draft) # grades should not change new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.draft_student).grade new_assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.draft_student).grade self.assertEqual(new_course_grade, course_grade) self.assertEqual(new_assignment_grade, assignment_grade) with self.login(self.fixtures.instructor.username): # test instructor can submit outside of grace period self.fixtures.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(minutes=2) db.session.add(self.fixtures.assignment) db.session.commit() self.fixtures.add_students(1) expected_answer.update({'user_id': self.fixtures.students[-1].uuid}) rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # test create successful self.fixtures.add_students(1) self.fixtures.course.calculate_grade(self.fixtures.students[-1]) self.fixtures.assignment.calculate_grade(self.fixtures.students[-1]) expected_answer = {'content': 'this is some answer content'} with self.login(self.fixtures.students[-1].username): # test student can not submit answers after answer grace period self.fixtures.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(minutes=2) db.session.add(self.fixtures.assignment) db.session.commit() rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(rv) self.assertEqual("Answer Not Submitted", rv.json['title']) self.assertEqual("Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.", rv.json['message']) # test student can submit answers within answer grace period self.fixtures.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(seconds=15) db.session.add(self.fixtures.assignment) db.session.commit() course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[-1]).grade assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignment, self.fixtures.students[-1]).grade lti_consumer = self.lti_data.lti_consumer student = self.fixtures.students[-1] (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links( student, self.fixtures.course, self.fixtures.assignment) rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # grades should increase new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[-1]) new_assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignment, self.fixtures.students[-1]) 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 create successful for system admin with self.login('root'): rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) # retrieve again and verify actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) # test system admin could submit multiple answers for his/her own rv = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(rv) actual_answer = Answer.query.filter_by(uuid=rv.json['id']).one() self.assertEqual(expected_answer['content'], actual_answer.content) def test_get_answer(self): assignment_uuid = self.fixtures.assignments[0].uuid answer = self.fixtures.answers[0] draft_answer = self.fixtures.draft_answers[0] # test login required rv = self.client.get(self.base_url + '/' + answer.uuid) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url + '/' + answer.uuid) self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.get(self._build_url("999", assignment_uuid, '/' + answer.uuid)) self.assert404(rv) # test invalid answer id rv = self.client.get(self._build_url(self.fixtures.course.uuid, assignment_uuid, '/' + "999")) self.assert404(rv) # test invalid get another user's draft answer rv = self.client.get(self.base_url + '/' + draft_answer.uuid) self.assert403(rv) # test authorized student rv = self.client.get(self.base_url + '/' + answer.uuid) self.assert200(rv) self.assertEqual(assignment_uuid, rv.json['assignment_id']) self.assertEqual(answer.user_uuid, rv.json['user_id']) self.assertEqual(answer.content, rv.json['content']) self.assertFalse(rv.json['draft']) self.assertEqual(answer.score.rank, rv.json['score']['rank']) self.assertFalse('normalized_score' in rv.json['score']) # test authorized student draft answer with self.login(self.fixtures.draft_student.username): rv = self.client.get(self.base_url + '/' + draft_answer.uuid) self.assert200(rv) self.assertEqual(assignment_uuid, rv.json['assignment_id']) self.assertEqual(draft_answer.user_uuid, rv.json['user_id']) self.assertEqual(draft_answer.content, rv.json['content']) self.assertTrue(rv.json['draft']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(self.base_url + '/' + answer.uuid) self.assert200(rv) self.assertEqual(assignment_uuid, rv.json['assignment_id']) self.assertEqual(answer.user_uuid, rv.json['user_id']) self.assertEqual(answer.content, rv.json['content']) self.assertEqual(answer.score.rank, rv.json['score']['rank']) self.assertEqual(int(answer.score.normalized_score), rv.json['score']['normalized_score']) # test authorized instructor with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url + '/' + answer.uuid) self.assert200(rv) self.assertEqual(assignment_uuid, rv.json['assignment_id']) self.assertEqual(answer.user_uuid, rv.json['user_id']) self.assertEqual(answer.content, rv.json['content']) self.assertEqual(answer.score.rank, rv.json['score']['rank']) self.assertEqual(int(answer.score.normalized_score), rv.json['score']['normalized_score']) @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(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run): assignment_uuid = self.fixtures.assignments[0].uuid answer = self.fixtures.answers[0] expected = {'id': answer.uuid, 'content': 'This is an edit'} draft_answer = self.fixtures.draft_answers[0] draft_expected = {'id': draft_answer.uuid, 'content': 'This is an edit', 'draft': True} # test login required rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.students[1].username): rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.post( self._build_url("999", assignment_uuid, '/' + answer.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid assignment id rv = self.client.post( self._build_url(self.fixtures.course.uuid, "999", '/' + answer.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid answer id rv = self.client.post( self.base_url + '/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test unmatched answer id with self.login(self.fixtures.students[1].username): rv = self.client.post( self.base_url + '/' + self.fixtures.answers[1].uuid, data=json.dumps(expected), content_type='application/json') self.assert400(rv) with self.login(self.fixtures.draft_student.username): course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.draft_student).grade assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.draft_student).grade lti_consumer = self.lti_data.lti_consumer student = self.fixtures.draft_student (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links( student, self.fixtures.course, self.fixtures.assignment) # test edit draft by author rv = self.client.post( self.base_url + '/' + draft_answer.uuid, data=json.dumps(draft_expected), content_type='application/json') self.assert200(rv) self.assertEqual(draft_answer.uuid, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) self.assertEqual(draft_answer.draft, rv.json['draft']) self.assertTrue(rv.json['draft']) # grades should not change new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.draft_student).grade new_assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.draft_student).grade self.assertEqual(new_course_grade, course_grade) self.assertEqual(new_assignment_grade, assignment_grade) mocked_update_assignment_grades_run.assert_not_called() mocked_update_course_grades_run.assert_not_called() # set draft to false draft_expected_copy = draft_expected.copy() draft_expected_copy['draft'] = False rv = self.client.post( self.base_url + '/' + draft_answer.uuid, data=json.dumps(draft_expected_copy), content_type='application/json') self.assert200(rv) self.assertEqual(draft_answer.uuid, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) self.assertEqual(draft_answer.draft, rv.json['draft']) self.assertFalse(rv.json['draft']) # grades should increase new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.draft_student) new_assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.draft_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_course_grades_run.reset_mock() # setting draft to true when false should not work rv = self.client.post( self.base_url + '/' + draft_answer.uuid, data=json.dumps(draft_expected), content_type='application/json') self.assert200(rv) self.assertEqual(draft_answer.uuid, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) self.assertEqual(draft_answer.draft, rv.json['draft']) self.assertFalse(rv.json['draft']) # test edit by author with self.login(self.fixtures.students[0].username): rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.uuid, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) # test edit by user that can manage posts manage_expected = { 'id': answer.uuid, 'content': 'This is another edit' } with self.login(self.fixtures.instructor.username): rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(manage_expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.uuid, rv.json['id']) self.assertEqual('This is another edit', rv.json['content']) # test edit by author with self.login(self.fixtures.students[0].username): # test student can not submit answers after answer grace period self.fixtures.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(minutes=10) db.session.add(self.fixtures.assignment) db.session.commit() rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) self.assertEqual("Answer Not Submitted", rv.json['title']) self.assertEqual("Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.", rv.json['message']) # test student can submit answers within answer grace period self.fixtures.assignment.answer_end = datetime.datetime.utcnow() - datetime.timedelta(seconds=15) db.session.add(self.fixtures.assignment) db.session.commit() rv = self.client.post( self.base_url + '/' + answer.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.uuid, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) @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(self, mocked_update_assignment_grades_run, mocked_update_course_grades_run): answer_uuid = self.fixtures.answers[0].uuid # test login required rv = self.client.delete(self.base_url + '/' + answer_uuid) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.students[1].username): rv = self.client.delete(self.base_url + '/' + answer_uuid) self.assert403(rv) # test invalid answer id with self.login(self.fixtures.students[0].username): rv = self.client.delete(self.base_url + '/999') self.assert404(rv) lti_consumer = self.lti_data.lti_consumer student = self.fixtures.students[0] (lti_user_resource_link1, lti_user_resource_link2) = self.lti_data.setup_student_user_resource_links( student, self.fixtures.course, self.fixtures.assignment) course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[0]).grade assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.students[0]).grade # test deletion by author rv = self.client.delete(self.base_url + '/' + answer_uuid) self.assert200(rv) self.assertEqual(answer_uuid, rv.json['id']) # grades should decrease new_course_grade = CourseGrade.get_user_course_grade( self.fixtures.course, self.fixtures.students[0]) new_assignment_grade = AssignmentGrade.get_user_assignment_grade( self.fixtures.assignments[0], self.fixtures.students[0]) 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() # test deletion by user that can manage posts with self.login(self.fixtures.instructor.username): answer_uuid2 = self.fixtures.answers[1].uuid rv = self.client.delete(self.base_url + '/' + answer_uuid2) self.assert200(rv) self.assertEqual(answer_uuid2, rv.json['id']) def test_get_user_answers(self): assignment = self.fixtures.assignments[0] answer = self.fixtures.answers[0] draft_answer = self.fixtures.draft_answers[0] url = self._build_url(self.fixtures.course.uuid, assignment.uuid, '/user') # test login required rv = self.client.get(url) self.assert401(rv) with self.login(self.fixtures.students[0].username): # test invalid course rv = self.client.get(self._build_url("999", assignment.uuid, '/user')) self.assert404(rv) # test invalid assignment rv = self.client.get(self._build_url(self.fixtures.course.uuid, "999", '/user')) self.assert404(rv) # test successful query rv = self.client.get(url) self.assert200(rv) self.assertEqual(1, len(rv.json['objects'])) self.assertEqual(answer.uuid, rv.json['objects'][0]['id']) self.assertEqual(answer.content, rv.json['objects'][0]['content']) self.assertEqual(answer.draft, rv.json['objects'][0]['draft']) # test draft query rv = self.client.get(url, query_string={'draft': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) # test unsaved query rv = self.client.get(url, query_string={'unsaved': True}) self.assert200(rv) self.assertEqual(1, len(rv.json['objects'])) self.assertEqual(answer.uuid, rv.json['objects'][0]['id']) self.assertEqual(answer.content, rv.json['objects'][0]['content']) self.assertEqual(answer.draft, rv.json['objects'][0]['draft']) answer.content = answer.content+"123" db.session.commit() rv = self.client.get(url, query_string={'unsaved': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) with self.login(self.fixtures.draft_student.username): # test successful query rv = self.client.get(url) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) # test draft query rv = self.client.get(url, query_string={'draft': True}) self.assert200(rv) self.assertEqual(1, len(rv.json['objects'])) self.assertEqual(draft_answer.uuid, rv.json['objects'][0]['id']) self.assertEqual(draft_answer.content, rv.json['objects'][0]['content']) self.assertEqual(draft_answer.draft, rv.json['objects'][0]['draft']) # test unsaved query rv = self.client.get(url, query_string={'unsaved': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) # test draft + unsaved query rv = self.client.get(url, query_string={'draft': True, 'unsaved': True}) self.assert200(rv) self.assertEqual(1, len(rv.json['objects'])) self.assertEqual(draft_answer.uuid, rv.json['objects'][0]['id']) self.assertEqual(draft_answer.content, rv.json['objects'][0]['content']) self.assertEqual(draft_answer.draft, rv.json['objects'][0]['draft']) draft_answer.content = draft_answer.content+"123" db.session.commit() rv = self.client.get(url, query_string={'draft': True, 'unsaved': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) with self.login(self.fixtures.instructor.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) # test draft query rv = self.client.get(url, query_string={'draft': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) # test unsaved query rv = self.client.get(url, query_string={'unsaved': True}) self.assert200(rv) self.assertEqual(0, len(rv.json['objects'])) def test_flag_answer(self): answer = self.fixtures.answers[0] flag_url = self.base_url + "/" + answer.uuid + "/flagged" # test login required expected_flag_on = {'flagged': True} expected_flag_off = {'flagged': False} rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert403(rv) # test flagging with self.login(self.fixtures.students[0].username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_on['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test unflagging rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test prevent unflagging by other students with self.login(self.fixtures.students[0].username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) # create another student self.fixtures.add_students(1) other_student = self.fixtures.students[-1] # try to unflag answer as other student, should fail with self.login(other_student.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert400(rv) # test allow unflagging by instructor with self.login(self.fixtures.instructor.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") def test_top_answer(self): answer = self.fixtures.answers[0] top_answer_url = self.base_url + "/" + answer.uuid + "/top" expected_top_on = {'top_answer': True} expected_top_off = {'top_answer': False} # test login required rv = self.client.post( top_answer_url, data=json.dumps(expected_top_on), content_type='application/json') self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): rv = self.client.post( top_answer_url, data=json.dumps(expected_top_on), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.students[0].username): rv = self.client.post( top_answer_url, data=json.dumps(expected_top_on), content_type='application/json') self.assert403(rv) # test allow setting top_answer by instructor with self.login(self.fixtures.instructor.username): rv = self.client.post( top_answer_url, data=json.dumps(expected_top_on), content_type='application/json') self.assert200(rv) self.assertTrue(rv.json['top_answer']) rv = self.client.post( top_answer_url, data=json.dumps(expected_top_off), content_type='application/json') self.assert200(rv) self.assertFalse(rv.json['top_answer']) # test allow setting top_answer by teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.post( top_answer_url, data=json.dumps(expected_top_on), content_type='application/json') self.assert200(rv) self.assertTrue(rv.json['top_answer']) rv = self.client.post( top_answer_url, data=json.dumps(expected_top_off), content_type='application/json') self.assert200(rv) self.assertFalse(rv.json['top_answer'])
def setUp(self): super(CourseGroupsAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)
class CourseGroupsAPITests(ComPAIRAPITestCase): def setUp(self): super(CourseGroupsAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3) def test_get_active_groups(self): url = '/api/courses/'+self.fixtures.course.uuid+'/groups' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): invalid_url = '/api/courses/999/groups' rv = self.client.get(invalid_url) self.assert404(rv) # test successful query rv = self.client.get(url) self.assert200(rv) actual = rv.json['objects'] self.assertEqual(len(actual), 3) self.assertEqual(actual[0], self.fixtures.groups[0]) # test TA with self.login(self.fixtures.ta.username): self.assert200(rv) actual = rv.json['objects'] self.assertEqual(len(actual), 3) self.assertEqual(actual[0], self.fixtures.groups[0]) def test_get_group_members(self): course = self.fixtures.course group_name = self.fixtures.groups[0] url = '/api/courses/'+course.uuid+'/groups/'+group_name # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): rv = self.client.get('/api/courses/999/groups/'+group_name) self.assert404(rv) # test invalid group id rv = self.client.get('/api/courses/'+course.uuid+'/groups/asdasdasdasd') self.assert404(rv) # test authorized instructor rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['students'])) self.assertEqual(self.fixtures.students[0].uuid, rv.json['students'][0]['id']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['students'])) self.assertEqual(self.fixtures.students[0].uuid, rv.json['students'][0]['id']) def test_group_enrolment(self): # frequently used objects course = self.fixtures.course group_name = self.fixtures.groups[0] # test login required url = self._create_group_user_url(course, self.fixtures.students[0], group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): url = self._create_group_user_url(course, self.fixtures.students[0], group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test user that is already in group url = self._create_group_user_url(course, self.fixtures.students[0], group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['group_name'], group_name) # test user that has never been in the group url = self._create_group_user_url(course, self.fixtures.instructor, group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['group_name'], group_name) # test user that has left the group url = self._create_group_user_url(course, self.fixtures.ta, group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['group_name'], group_name) # test user that is not enroled in the course anymore - eg. DROPPED url = self._create_group_user_url(course, self.fixtures.dropped_instructor, group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test user that has never been in the course url = self._create_group_user_url(course, self.fixtures.unauthorized_student, group_name) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid course id url = '/api/courses/999/users/'+self.fixtures.students[0].uuid+'/groups/'+group_name rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid user id url = '/api/courses/'+course.uuid+'/users/999/groups/'+group_name rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) def test_group_unenrolment(self): course = self.fixtures.course # test login required url = self._create_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): url = self._create_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test user in course url = self._create_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert200(rv) self.assertEqual(rv.json['user_id'], self.fixtures.students[0].uuid) self.assertEqual(rv.json['course_id'], course.uuid) # test user not in course url = self._create_group_user_url(course, self.fixtures.unauthorized_student) rv = self.client.delete(url) self.assert404(rv) # test invalid course id url = '/api/courses/999/users/'+self.fixtures.students[0].uuid+'/groups' rv = self.client.delete(url) self.assert404(rv) # test invalid user id url = '/api/courses/'+course.uuid+'/users/999/groups' rv = self.client.delete(url) self.assert404(rv) def test_import_groups(self): auth_data = ThirdPartyAuthTestData() url = '/api/courses/' + self.fixtures.course.uuid + '/groups' content = self.fixtures.students[0].username + "," + self.fixtures.groups[0] encoded_content = content.encode() filename = "groups.csv" # test login required uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert401(rv) uploaded_file.close() # test unauthorized user uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.students[0].username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() uploaded_file = io.BytesIO(encoded_content) with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups' uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(invalid_url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert404(rv) uploaded_file.close() # test invalid file type invalid_file = "groups.png" uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, invalid_file))) self.assert400(rv) uploaded_file.close() # test invalid user identifier uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(userIdentifier="lastname", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(0, rv.json['success']) self.assertEqual({}, rv.json['invalids'][0]['member']) self.assertEqual("A valid user identifier is not given.", rv.json['invalids'][0]['message']) uploaded_file.close() # test missing user identifier uploaded_file = io.BytesIO(encoded_content) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() # test duplicate users in file duplicate = "".join([content, "\n", content]) uploaded_file = io.BytesIO(duplicate.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = [ '["', self.fixtures.students[0].username, '", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("This user already exists in the file.", invalid['message']) uploaded_file.close() # test missing username missing_username = "******" + self.fixtures.groups[0] uploaded_file = io.BytesIO(missing_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("No user with this ComPAIR username exists.", invalid['message']) uploaded_file.close() # test missing group name missing_group = self.fixtures.students[0].username + "," uploaded_file = io.BytesIO(missing_group.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(0, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["', self.fixtures.students[0].username, '", ""]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("The group name is invalid.", invalid['message']) uploaded_file.close() # test invalid user invalid_user = "******" + self.fixtures.groups[0] uploaded_file = io.BytesIO(invalid_user.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = ['["username9999", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("No user with this ComPAIR username exists.", invalid['message']) uploaded_file.close() # test successful import with username with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test invalid import with username self.app.config['APP_LOGIN_ENABLED'] = False with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_username.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() self.app.config['APP_LOGIN_ENABLED'] = True # test successful import with student number with_studentno = self.fixtures.students[0].student_number + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_studentno.encode()) rv = self.client.post(url, data=dict(userIdentifier="student_number", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test successful import with cas username cas_auth = auth_data.create_cas_user_auth(CourseRole.student) cas_user = cas_auth.user self.fixtures.enrol_user(cas_user, self.fixtures.course, CourseRole.student) with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_cas_username.encode()) rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test invalid import with cas username self.app.config['CAS_LOGIN_ENABLED'] = False with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(with_cas_username.encode()) rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() self.app.config['CAS_LOGIN_ENABLED'] = True # test import user not in course unauthorized_student = self.fixtures.unauthorized_student.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(unauthorized_student.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(1, len(rv.json['invalids'])) invalid = rv.json['invalids'][0] member = [ '["', self.fixtures.unauthorized_student.username, '", "', self.fixtures.groups[0], '"]'] self.assertEqual("".join(member), invalid['member']) self.assertEqual("The user is not enroled in the course", invalid['message']) uploaded_file.close() # test placing instructor in group add_instructor = self.fixtures.instructor.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(add_instructor.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() # test placing TA in group add_ta = self.fixtures.ta.username + "," + self.fixtures.groups[0] uploaded_file = io.BytesIO(add_ta.encode()) rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename))) self.assert200(rv) self.assertEqual(1, rv.json['success']) self.assertEqual(0, len(rv.json['invalids'])) uploaded_file.close() def test_group_multiple_enrolment(self): # frequently used objects course = self.fixtures.course group_name = self.fixtures.groups[0] group_name_2 = self.fixtures.groups[1] student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid] url = self._create_group_users_url(course, group_name) params = { 'ids': student_ids } # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/users/groups/'+group_name, data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({'ids': []}), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post(url, data=json.dumps({'ids': [self.fixtures.unauthorized_student.uuid]}), content_type='application/json') self.assert400(rv) # test enrol users into group rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['group_name'], group_name) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_name, group_name) # test enrol users into another group url = self._create_group_users_url(course, group_name_2) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['group_name'], group_name_2) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_name, group_name_2) def test_group_multiple_unenrolment(self): course = self.fixtures.course url = self._create_group_users_url(course) student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid] params = { 'ids': student_ids } # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/users/groups', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({ 'ids': [] }), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post(url, data=json.dumps({ 'ids': [self.fixtures.unauthorized_student.uuid] }), content_type='application/json') self.assert400(rv) # test users in course rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['course_id'], course.uuid) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_name, None) def _create_group_user_url(self, course, user, group_name=None): url = '/api/courses/'+course.uuid+'/users/'+user.uuid+'/groups' if group_name: url = url+'/'+group_name return url def _create_group_users_url(self, course, group_name=None): url = '/api/courses/'+course.uuid+'/users/groups' if group_name: url = url+'/'+group_name return url
def setUp(self): super(AnswersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2) self.base_url = self._build_url(self.fixtures.course.id, self.fixtures.question.id)
def setUp(self): super(AnswersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid) self.lti_data = LTITestData()
class FileRetrieveTests(ComPAIRAPITestCase): base_url = '/app' fixtures = None def setUp(self): super(FileRetrieveTests, self).setUp() self.fixtures = TestFixture().add_course( num_students=5, num_assignments=1, num_groups=0, num_answers=1, with_draft_student=True) self.files_to_cleanup = [] def tearDown(self): folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER'] for file_name in self.files_to_cleanup: file_path = os.path.join(folder, file_name) try: if os.path.isfile(file_path): os.remove(file_path) except Exception as e: print(e) def test_view_file(self): db_file = self.fixtures.add_file(self.fixtures.instructor) filename = db_file.name url = self.base_url + '/attachment/' + filename # test login required rv = self.client.get(url) self.assert401(rv) # TODO: no authorization control right now and needs to be added in the future # test unauthorized user # with self.login(self.fixtures.unauthorized_instructor.username): # rv = self.client.get(url) # self.assert403(rv) # valid instructor with self.login(self.fixtures.instructor.username): # invalid file name (db is not actually touched) rv = self.client.get(self.base_url + '/attachment/'+filename) self.assert404(rv) self.assertEqual('invalid file name', str(rv.get_data(as_text=True))) with mock.patch('compair.api.os.path.exists', return_value=True): with mock.patch('compair.api.send_file', return_value=make_response("OK")) as mock_send_file: # test all attachment types for extension in ['pdf','mp3','mp4','jpg','jpeg','png']: db_file = self.fixtures.add_file(self.fixtures.instructor, name="file_name."+extension) filename = db_file.name url = self.base_url + '/attachment/' + filename self.client.get(url) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), mimetype=db_file.mimetype, attachment_filename=None, as_attachment=False ) else: mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), mimetype=db_file.mimetype, attachment_filename=None, as_attachment=True ) mock_send_file.reset_mock() # test overriding attachment filename override_name = "override."+db_file.extension self.client.get(url+"?name="+override_name) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), mimetype=db_file.mimetype, attachment_filename=None, as_attachment=False ) else: mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), mimetype=db_file.mimetype, attachment_filename=override_name, as_attachment=True ) mock_send_file.reset_mock() def test_create_attachment(self): url = '/api/attachment' test_formats = [ ('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'), ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg') ] # test login required uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, 'alias.pdf'))) self.assert401(rv) uploaded_file.close() with self.login(self.fixtures.instructor.username): for extension, mimetype in test_formats: filename = 'alias.'+extension uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) uploaded_file.close() actual_file = rv.json['file'] self.files_to_cleanup.append(actual_file['name']) self.assertEqual(actual_file['id']+"."+extension, actual_file['name']) self.assertEqual(filename, actual_file['alias']) self.assertEqual(extension, actual_file['extension']) self.assertEqual(mimetype, actual_file['mimetype']) def test_delete_attachment(self): # test file not attached to answer or assignment db_file = self.fixtures.add_file(self.fixtures.instructor) url = '/api/attachment/' + db_file.uuid # test login required rv = self.client.delete(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(url) self.assert403(rv) # valid instructor with self.login(self.fixtures.instructor.username): # invalid file id rv = self.client.delete('/api/attachment/9999') self.assert404(rv) rv = self.client.delete(url) self.assert200(rv) actual_file = rv.json self.assertEqual(db_file.uuid, actual_file['id']) self.assertFalse(db_file.active) rv = self.client.delete(url) self.assert404(rv) # test file attached to assignment db_file = self.fixtures.add_file(self.fixtures.ta) self.fixtures.assignment.file_id = db_file.id db.session.commit() url = '/api/attachment/' + db_file.uuid # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(url) self.assert403(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.delete(url) self.assert403(rv) # test unauthorized user with self.login(self.fixtures.students[0].username): rv = self.client.delete(url) self.assert403(rv) # valid instructor with self.login(self.fixtures.instructor.username): rv = self.client.delete(url) self.assert200(rv) actual_file = rv.json self.assertEqual(db_file.uuid, actual_file['id']) self.assertFalse(db_file.active) # test file attached to answer student = self.fixtures.answers[0].user db_file = self.fixtures.add_file(student) self.fixtures.answers[0].file_id = db_file.id db.session.commit() url = '/api/attachment/' + db_file.uuid # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(url) self.assert403(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.delete(url) self.assert403(rv) # author with self.login(student.username): rv = self.client.delete(url) self.assert200(rv) actual_file = rv.json self.assertEqual(db_file.uuid, actual_file['id']) self.assertFalse(db_file.active) # test file attached to answer student = self.fixtures.answers[0].user db_file = self.fixtures.add_file(student) self.fixtures.answers[0].file_id = db_file.id db.session.commit() url = '/api/attachment/' + db_file.uuid # valid instructor with self.login(self.fixtures.instructor.username): rv = self.client.delete(url) self.assert200(rv) actual_file = rv.json self.assertEqual(db_file.uuid, actual_file['id']) self.assertFalse(db_file.active)
class GroupsAPITests(ComPAIRAPITestCase): def setUp(self): super(GroupsAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3) def test_get_groups(self): url = '/api/courses/'+self.fixtures.course.uuid+'/groups' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): invalid_url = '/api/courses/999/groups' rv = self.client.get(invalid_url) self.assert404(rv) # test successful query rv = self.client.get(url) self.assert200(rv) actual = rv.json['objects'] expected = sorted(self.fixtures.groups, key=lambda group: group.name) self.assertEqual(len(actual), 4) for index, group in enumerate(expected): self.assertEqual(actual[index]['id'], group.uuid) # test TA with self.login(self.fixtures.ta.username): rv = self.client.get(url) self.assert200(rv) actual = rv.json['objects'] expected = sorted(self.fixtures.groups, key=lambda group: group.name) self.assertEqual(len(actual), 4) for index, group in enumerate(expected): self.assertEqual(actual[index]['id'], group.uuid) def test_create_group(self): url = '/api/courses/'+self.fixtures.course.uuid+'/groups' group_expected = { 'name': 'Group 101', } invalid_expected = { 'name': self.fixtures.groups[1].name, } # test login required rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups' rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test non-unique name rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Added") self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.") # test successful query rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert200(rv) group_actual = rv.json self.assertEqual(group_expected['name'], group_actual['name']) def test_edit_group(self): group = self.fixtures.groups[0] url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid group_expected = { 'id': group.uuid, 'name': 'New Group Name', } invalid_expected = { 'id': group.uuid, 'name': self.fixtures.groups[1].name, } # test login required rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups/'+group.uuid rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test invalid group id invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999' rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test non-unique name rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.") # test successful query rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert200(rv) group_actual = rv.json self.assertEqual(group_expected['id'], group_actual['id']) self.assertEqual(group_expected['name'], group_actual['name']) def test_delete_group(self): group = self.fixtures.groups[0] self.fixtures.add_assignments(num_assignments=1, with_group_answers=True) url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid # test login required rv = self.client.delete(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(url) self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.delete(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.delete(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups/'+group.uuid rv = self.client.delete(invalid_url) self.assert404(rv) # test invalid group id invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999' rv = self.client.delete(invalid_url) self.assert404(rv) # test successful query rv = self.client.delete(url) self.assert200(rv) # test cannot delete again rv = self.client.delete(url) self.assert404(rv) # the group answer flags should be false rv = self.client.get('/api/courses/'+self.fixtures.course.uuid+'/groups') self.assert200(rv) for the_group in [g for g in rv.json['objects']]: self.assertFalse(the_group['group_answer_exists']) # test cannot delete group if it has an answer self.fixtures.add_answers() group = self.fixtures.groups[1] url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid rv = self.client.delete(url) self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Deleted") self.assertEqual(rv.json['message'], "Sorry, you cannot remove groups that have submitted answers.") # verify the group answer exists flag rv = self.client.get('/api/courses/'+self.fixtures.course.uuid+'/groups') self.assert200(rv) the_group = [g for g in rv.json['objects'] if g['id'] == group.uuid] self.assertEqual(len(the_group), 1) self.assertTrue(the_group[0]['group_answer_exists']) # verfiy that after group is deleted, can re-create it with same name deleted_group = self.fixtures.groups[0] new_group = { 'name': deleted_group.name, } rv = self.client.post('/api/courses/'+self.fixtures.course.uuid+'/groups', data=json.dumps(new_group), content_type='application/json') self.assert200(rv)
class FileRetrieveTests(ComPAIRAPITestCase): base_url = '/app' fixtures = None def setUp(self): super(FileRetrieveTests, self).setUp() self.fixtures = TestFixture().add_course(num_students=5, num_assignments=1, num_groups=0, num_answers=1, with_draft_student=True) self.files_to_cleanup = [] def tearDown(self): folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER'] for file_name in self.files_to_cleanup: file_path = os.path.join(folder, file_name) try: if os.path.isfile(file_path): os.remove(file_path) except Exception as e: print(e) def test_view_file(self): db_file = self.fixtures.add_file(self.fixtures.instructor) filename = db_file.name url = self.base_url + '/attachment/' + filename # test login required rv = self.client.get(url) self.assert401(rv) # TODO: no authorization control right now and needs to be added in the future # test unauthorized user # with self.login(self.fixtures.unauthorized_instructor.username): # rv = self.client.get(url) # self.assert403(rv) # valid instructor with self.login(self.fixtures.instructor.username): # invalid file name (db is not actually touched) rv = self.client.get(self.base_url + '/attachment/' + filename) self.assert404(rv) self.assertEqual('invalid file name', text_type(rv.get_data(as_text=True))) with mock.patch('compair.api.os.path.exists', return_value=True): with mock.patch( 'compair.api.send_file', return_value=make_response("OK")) as mock_send_file: # test all attachment types extensions = [('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'), ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg'), ('png', 'image/png')] for (extension, mimetype) in extensions: db_file = self.fixtures.add_file( self.fixtures.instructor, name="file_name." + extension) filename = db_file.name url = self.base_url + '/attachment/' + filename self.client.get(url) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format( current_app. config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=False, mimetype=mimetype) else: mock_send_file.assert_called_once_with( '{}/{}'.format( current_app. config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=True, mimetype=mimetype) mock_send_file.reset_mock() # test overriding attachment filename override_name = "override." + db_file.extension self.client.get(url + "?name=" + override_name) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format( current_app. config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=False, mimetype=mimetype) else: mock_send_file.assert_called_once_with( '{}/{}'.format( current_app. config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=override_name, as_attachment=True, mimetype=mimetype) mock_send_file.reset_mock() def test_create_attachment(self): url = '/api/attachment' test_formats = [('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'), ('webm', 'video/webm'), ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg')] # test login required uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, 'alias.pdf'))) self.assert401(rv) uploaded_file.close() with self.login(self.fixtures.instructor.username): # test no file uploaded filename = 'alias.pdf' rv = self.client.post(url, data=dict()) self.assert400(rv) self.assertEqual("File Not Uploaded", rv.json['title']) self.assertEqual( "Sorry, no file was found to upload. Please try uploading again.", rv.json['message']) # test no file uploaded filename = 'alias.xyz' uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert400(rv) self.assertEqual("File Not Uploaded", rv.json['title']) self.assertEqual( "Please try again with an approved file type, which includes: JPEG, JPG, MP3, MP4, PDF, PNG, WEBM.", rv.json['message']) for extension, mimetype in test_formats: filename = 'alias.' + extension uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) uploaded_file.close() actual_file = rv.json['file'] self.files_to_cleanup.append(actual_file['name']) self.assertEqual(actual_file['id'] + "." + extension.lower(), actual_file['name']) self.assertEqual(filename, actual_file['alias']) self.assertEqual(extension.lower(), actual_file['extension']) self.assertEqual(mimetype, actual_file['mimetype']) # test with uppercase extension filename = 'alias.' + extension.upper() uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) uploaded_file.close() actual_file = rv.json['file'] self.files_to_cleanup.append(actual_file['name']) self.assertEqual(actual_file['id'] + "." + extension.lower(), actual_file['name']) self.assertEqual(filename, actual_file['alias']) self.assertEqual(extension.lower(), actual_file['extension']) self.assertEqual(mimetype, actual_file['mimetype']) @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start') @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end') @mock.patch('compair.kaltura.upload_token.UploadToken._api_add') def test_get_kaltura(self, mocked_upload_token_add, mocked_kaltura_session_end, mocked_kaltura_session_start): url = '/api/attachment/kaltura' current_app.config['KALTURA_ENABLED'] = True current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com" current_app.config['KALTURA_PARTNER_ID'] = 123 current_app.config['KALTURA_USER_ID'] = "*****@*****.**" current_app.config['KALTURA_SECRET'] = "abc" current_app.config['KALTURA_PLAYER_ID'] = 456 mocked_kaltura_session_start.return_value = "ks_mock" mocked_kaltura_session_end.return_value = {} mocked_upload_token_add.return_value = {"id": "mocked_upload_token_id"} expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id&ks=ks_mock" # test login required rv = self.client.get(url) self.assert401(rv) with self.login(self.fixtures.instructor.username): # test kaltura disabled current_app.config['KALTURA_ENABLED'] = False rv = self.client.get(url) self.assert400(rv) # test kaltura enabled current_app.config['KALTURA_ENABLED'] = True rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call( "*****@*****.**", privileges= "edit:mocked_upload_token_id,urirestrict:/api_v3/service/uploadtoken/action/upload*" ) mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 1) self.assertEqual(kaltura_media_items[0].user_id, self.fixtures.instructor.id) self.assertEqual(kaltura_media_items[0].partner_id, 123) self.assertEqual(kaltura_media_items[0].player_id, 456) self.assertEqual(kaltura_media_items[0].upload_ks, "ks_mock") self.assertEqual(kaltura_media_items[0].upload_token_id, "mocked_upload_token_id") self.assertIsNone(kaltura_media_items[0].file_name) self.assertIsNone(kaltura_media_items[0].entry_id) self.assertIsNone(kaltura_media_items[0].download_url) # use global unique identifer (user has no global unique identifer) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True mocked_upload_token_add.return_value = { "id": "mocked_upload_token_id2" } expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id2&ks=ks_mock" rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call( "*****@*****.**", privileges= "edit:mocked_upload_token_id2,urirestrict:/api_v3/service/uploadtoken/action/upload*" ) mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 2) self.assertEqual(kaltura_media_items[1].user_id, self.fixtures.instructor.id) # use global unique identifer (user has global unique identifer) self.fixtures.instructor.global_unique_identifier = "*****@*****.**" mocked_upload_token_add.return_value = { "id": "mocked_upload_token_id3" } expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id3&ks=ks_mock" rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call( "*****@*****.**", privileges= "edit:mocked_upload_token_id3,urirestrict:/api_v3/service/uploadtoken/action/upload*" ) mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 3) self.assertEqual(kaltura_media_items[2].user_id, self.fixtures.instructor.id) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start') @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end') @mock.patch('compair.kaltura.upload_token.UploadToken._api_get') @mock.patch('compair.kaltura.media.Media._api_add') @mock.patch('compair.kaltura.media.Media._api_add_content') def test_post_kaltura(self, mocked_kaltura_media_add_content, mocked_kaltura_media_add, mocked_upload_token_get, mocked_kaltura_session_end, mocked_kaltura_session_start): url = '/api/attachment/kaltura/mocked_upload_token_id' invalid_url = '/api/attachment/kaltura/mocked_upload_token_id_invalid' current_app.config['KALTURA_ENABLED'] = True current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com" current_app.config['KALTURA_PARTNER_ID'] = 123 current_app.config['KALTURA_USER_ID'] = "*****@*****.**" current_app.config['KALTURA_SECRET'] = "abc" current_app.config['KALTURA_PLAYER_ID'] = 456 mocked_kaltura_session_start.return_value = "ks_mock" mocked_kaltura_session_end.return_value = {} mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id", "fileName": "uploaded_audio_file.mp3" } mocked_kaltura_media_add.return_value = {"id": "mock_entry_id"} mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id", "downloadUrl": "http://www.download/url.com" } kaltura_media = KalturaMedia(user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock", upload_token_id="mocked_upload_token_id") db.session.add(kaltura_media) kaltura_media2 = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock2", upload_token_id="mocked_upload_token_id2") db.session.add(kaltura_media) kaltura_media3 = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock3", upload_token_id="mocked_upload_token_id3") db.session.add(kaltura_media) invalid_kaltura_media = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock", upload_token_id="mocked_upload_token_id_invalid", entry_id="def") db.session.add(invalid_kaltura_media) db.session.commit() # test login required rv = self.client.post(url) self.assert401(rv) with self.login(self.fixtures.instructor.username): # test kaltura disabled current_app.config['KALTURA_ENABLED'] = False rv = self.client.post(url) self.assert400(rv) # test invalid upload_token_id current_app.config['KALTURA_ENABLED'] = True rv = self.client.post(invalid_url) self.assert400(rv) # test valid rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media.files.all()[0].uuid) self.assertEqual(kaltura_media.file_name, "uploaded_audio_file.mp3") self.assertEqual(kaltura_media.entry_id, "mock_entry_id") self.assertEqual(kaltura_media.download_url, "http://www.download/url.com") self.assertEqual(kaltura_media.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with( "*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with( "ks_mock", "mocked_upload_token_id") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with( "ks_mock", "mock_entry_id", "mocked_upload_token_id") mocked_kaltura_media_add_content.reset_mock() # test direct download from kaltura via /attachment kaltura_attachment_url = self.base_url + '/attachment/' + rv.json[ 'file']['name'] + '?name=uploaded_audio_file.mp3' rv = self.client.get(kaltura_attachment_url) self.assertTrue(rv.location.startswith( kaltura_media.download_url)) # redirecting to Kaltura mocked_kaltura_session_start.assert_called_once_with("*****@*****.**", \ expiry=60, \ privileges='sview:'+kaltura_media.entry_id+',urirestrict:/url.com/*' ) mocked_kaltura_session_start.reset_mock() # use global unique identifer (user has no global unique identifer) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True url = '/api/attachment/kaltura/mocked_upload_token_id2' mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id2", "fileName": "uploaded_audio_file2.mp3" } mocked_kaltura_media_add.return_value = {"id": "mock_entry_id2"} mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id2", "downloadUrl": "www.download/url2.com" } rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media2.files.all()[0].uuid) self.assertEqual(kaltura_media2.file_name, "uploaded_audio_file2.mp3") self.assertEqual(kaltura_media2.entry_id, "mock_entry_id2") self.assertEqual(kaltura_media2.download_url, "www.download/url2.com") self.assertEqual(kaltura_media2.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with( "*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock2") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with( "ks_mock", "mocked_upload_token_id2") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with( "ks_mock", "mock_entry_id2", "mocked_upload_token_id2") mocked_kaltura_media_add_content.reset_mock() # use global unique identifer (user has global unique identifer) self.fixtures.instructor.global_unique_identifier = "*****@*****.**" url = '/api/attachment/kaltura/mocked_upload_token_id3' mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id3", "fileName": "uploaded_audio_file3.mp3" } mocked_kaltura_media_add.return_value = {"id": "mock_entry_id3"} mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id3", "downloadUrl": "www.download/url3.com" } rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media3.files.all()[0].uuid) self.assertEqual(kaltura_media3.file_name, "uploaded_audio_file3.mp3") self.assertEqual(kaltura_media3.entry_id, "mock_entry_id3") self.assertEqual(kaltura_media3.download_url, "www.download/url3.com") self.assertEqual(kaltura_media3.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with( "*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock3") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with( "ks_mock", "mocked_upload_token_id3") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with( "ks_mock", "mock_entry_id3", "mocked_upload_token_id3") mocked_kaltura_media_add_content.reset_mock() current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
class GroupUsersAPITests(ComPAIRAPITestCase): def setUp(self): super(GroupUsersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3, num_group_assignments=1) self.assignment = self.fixtures.assignment self.group_assignment = self.fixtures.assignments[1] now = datetime.datetime.now() self.assignment_dates = [ # before answer period (no comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), None, None), # during answer period (no comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), None, None), # during compare period (no comparison dates) (True, now - timedelta(days=2), now - timedelta(days=1), None, None ), # before answer period (with comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # during answer period (with comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # after answer period (with comparison dates) (False, now - timedelta(days=2), now - timedelta(days=1), now + timedelta(days=12), now + timedelta(days=13)), # during compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=2), now + timedelta(days=3)), # after compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=5), now - timedelta(days=3)) ] def test_get_group_members(self): course = self.fixtures.course group = self.fixtures.groups[0] url = '/api/courses/' + course.uuid + '/groups/' + group.uuid + '/users' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): rv = self.client.get('/api/courses/999/groups/' + group.uuid + '/users') self.assert404(rv) # test invalid group id rv = self.client.get('/api/courses/' + course.uuid + '/groups/999/users') self.assert404(rv) # test authorized instructor rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['objects'])) members = sorted([uc.user for uc in group.user_courses.all()], key=lambda user: (user.lastname, user.firstname)) for index, user in enumerate(members): self.assertEqual(user.uuid, rv.json['objects'][index]['id']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['objects'])) members = sorted([uc.user for uc in group.user_courses.all()], key=lambda user: (user.lastname, user.firstname)) for index, user in enumerate(members): self.assertEqual(user.uuid, rv.json['objects'][index]['id']) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: rv = self.client.get(url) self.assert403(rv) def test_get_current_user_group(self): course = self.fixtures.course url = '/api/courses/' + course.uuid + '/groups/user' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.get('/api/courses/999/groups/user') self.assert404(rv) # test authorized instructor for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.instructor, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test authorized teaching assistant with self.login(self.fixtures.ta.username): for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.ta, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test root admin (not enrolled in course) with self.login(self.fixtures.root_user.username): rv = self.client.get(url) self.assert200(rv) self.assertIsNone(rv.json) # test root admin (enrolled in course) self.fixtures.enrol_user(self.fixtures.root_user, course, CourseRole.student, None) for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.root_user, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, student, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) def test_add_group_member(self): # frequently used objects course = self.fixtures.course group = self.fixtures.groups[0] # test login required url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: url = self._add_group_user_url(course, group, self.fixtures.instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id url = '/api/courses/999/groups/' + group.uuid + '/users/' + self.fixtures.students[ 0].uuid rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid user id url = '/api/courses/' + course.uuid + '/groups/999/users/' + self.fixtures.students[ 0].uuid rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid user id url = '/api/courses/' + course.uuid + '/groups/' + group.uuid + '/users/999' rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test user that is already in group url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that has never been in the group url = self._add_group_user_url(course, group, self.fixtures.instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that has left the group url = self._add_group_user_url(course, group, self.fixtures.ta) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that is not enrolled in the course anymore - eg. DROPPED url = self._add_group_user_url(course, group, self.fixtures.dropped_instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test user that has never been in the course url = self._add_group_user_url(course, group, self.fixtures.unauthorized_student) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(2) grouped_student = self.fixtures.students[-2] self.fixtures.change_user_group(self.fixtures.course, grouped_student, self.fixtures.groups[0]) ungrouped_student = self.fixtures.students[-1] group = self.fixtures.groups[1] url = self._add_group_user_url(course, group, grouped_student) if groups_locked: # grouped_student should not be able to change groups rv = self.client.post(url, data={}, content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual( rv.json['message'], "The course groups are locked. This user is already assigned to a different group." ) else: # grouped_student should be able to change groups rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) # regardless ungrouped_student should be able to change groups url = self._add_group_user_url(course, group, ungrouped_student) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) def test_remove_group_member(self): course = self.fixtures.course # test login required url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id url = '/api/courses/999/groups/users/' + self.fixtures.students[ 0].uuid rv = self.client.delete(url) self.assert404(rv) # test invalid user id url = '/api/courses/' + course.uuid + '/groups/users/999' rv = self.client.delete(url) self.assert404(rv) # test user in course url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert200(rv) self.assertTrue(rv.json['success']) # test user not in course url = self._remove_group_user_url( course, self.fixtures.unauthorized_student) rv = self.client.delete(url) self.assert404(rv) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(2) grouped_student = self.fixtures.students[-2] self.fixtures.change_user_group(self.fixtures.course, grouped_student, self.fixtures.groups[0]) ungrouped_student = self.fixtures.students[-1] url = self._remove_group_user_url(course, grouped_student) if groups_locked: # grouped_student should not be able to be removed from groups rv = self.client.delete(url) self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual( rv.json['message'], "The course groups are locked. You may not remove users from the group they are already assigned to." ) else: # grouped_student should be able to be removed from groups rv = self.client.delete(url) self.assert200(rv) # regardless ungrouped_student should be able to be removed from groups url = self._remove_group_user_url(course, ungrouped_student) rv = self.client.delete(url) self.assert200(rv) def test_add_multiple_group_member(self): # frequently used objects course = self.fixtures.course group = self.fixtures.groups[0] group2 = self.fixtures.groups[1] student_ids = [ self.fixtures.students[0].uuid, self.fixtures.students[1].uuid ] url = self._add_group_user_url(course, group) params = {'ids': student_ids} # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/groups/' + group.uuid + '/users', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test invalid group id rv = self.client.post('/api/courses/' + course.uuid + '/groups/999/users', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({'ids': []}), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post( url, data=json.dumps( {'ids': [self.fixtures.unauthorized_student.uuid]}), content_type='application/json') self.assert400(rv) # test add users into group rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, group.id) # test add users into another group url = self._add_group_user_url(course, group2) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group2.uuid) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, group2.id) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(4) grouped_students = [ self.fixtures.students[-4], self.fixtures.students[-3] ] self.fixtures.change_user_group(self.fixtures.course, grouped_students[0], self.fixtures.groups[0]) self.fixtures.change_user_group(self.fixtures.course, grouped_students[1], self.fixtures.groups[0]) grouped_student_uuids = [ student.uuid for student in grouped_students ] ungrouped_student_uuids = [ self.fixtures.students[-2].uuid, self.fixtures.students[-1].uuid ] group = self.fixtures.groups[1] url = self._add_group_user_url(course, group) params = { 'ids': grouped_student_uuids + ungrouped_student_uuids } if groups_locked: # grouped_students should not be able to change groups (groups should not change) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual( rv.json['message'], "The course groups are locked. One or more users are already assigned to a different group." ) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids: self.assertEqual(user_course.group_id, self.fixtures.groups[0].id) if user_course.user_uuid in ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) else: # grouped_students should be able to change groups rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids + ungrouped_student_uuids: self.assertEqual(user_course.group_id, group.id) # regardless ungrouped_student should be able to change groups url = self._add_group_user_url(course, group) params = {'ids': ungrouped_student_uuids} rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) def test_remove_multiple_group_member(self): course = self.fixtures.course url = self._remove_group_user_url(course) student_ids = [ self.fixtures.students[0].uuid, self.fixtures.students[1].uuid ] params = {'ids': student_ids} # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student) ]: with user_context: rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/users/groups', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({'ids': []}), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post( url, data=json.dumps( {'ids': [self.fixtures.unauthorized_student.uuid]}), content_type='application/json') self.assert400(rv) # test users in course rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertTrue(rv.json['success']) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, None) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(4) grouped_students = [ self.fixtures.students[-4], self.fixtures.students[-3] ] self.fixtures.change_user_group(self.fixtures.course, grouped_students[0], self.fixtures.groups[0]) self.fixtures.change_user_group(self.fixtures.course, grouped_students[1], self.fixtures.groups[0]) grouped_student_uuids = [ student.uuid for student in grouped_students ] ungrouped_student_uuids = [ self.fixtures.students[-2].uuid, self.fixtures.students[-1].uuid ] group = self.fixtures.groups[1] params = { 'ids': grouped_student_uuids + ungrouped_student_uuids } if groups_locked: # grouped_students should not be able to be removed from groups (groups should not change) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual( rv.json['message'], "The course groups are locked. You may not remove users from the group they are already assigned to." ) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids: self.assertEqual(user_course.group_id, self.fixtures.groups[0].id) if user_course.user_uuid in ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) else: # grouped_students should be able to be removed from groups rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids + ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) # regardless ungrouped_student should be able to be removed from groups params = {'ids': ungrouped_student_uuids} rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) def _add_group_user_url(self, course, group, user=None): url = '/api/courses/' + course.uuid + '/groups/' + group.uuid + '/users' url += '/' + user.uuid if user else '' return url def _remove_group_user_url(self, course, user=None): url = '/api/courses/' + course.uuid + '/groups/users' url += '/' + user.uuid if user else '' return url
class GroupUsersAPITests(ComPAIRAPITestCase): def setUp(self): super(GroupUsersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3, num_group_assignments=1) self.assignment = self.fixtures.assignment self.group_assignment = self.fixtures.assignments[1] now = datetime.datetime.now() self.assignment_dates = [ # before answer period (no comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), None, None), # during answer period (no comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), None, None), # during compare period (no comparison dates) (True, now - timedelta(days=2), now - timedelta(days=1), None, None), # before answer period (with comparison dates) (False, now + timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # during answer period (with comparison dates) (False, now - timedelta(days=2), now + timedelta(days=3), now + timedelta(days=12), now + timedelta(days=13)), # after answer period (with comparison dates) (False, now - timedelta(days=2), now - timedelta(days=1), now + timedelta(days=12), now + timedelta(days=13)), # during compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=2), now + timedelta(days=3)), # after compare period (with comparison dates) (True, now - timedelta(days=12), now - timedelta(days=11), now - timedelta(days=5), now - timedelta(days=3)) ] def test_get_group_members(self): course = self.fixtures.course group = self.fixtures.groups[0] url = '/api/courses/'+course.uuid+'/groups/'+group.uuid+'/users' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): rv = self.client.get('/api/courses/999/groups/'+group.uuid+'/users') self.assert404(rv) # test invalid group id rv = self.client.get('/api/courses/'+course.uuid+'/groups/999/users') self.assert404(rv) # test authorized instructor rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['objects'])) members = sorted( [uc.user for uc in group.user_courses.all()], key=lambda user: (user.lastname, user.firstname) ) for index, user in enumerate(members): self.assertEqual(user.uuid, rv.json['objects'][index]['id']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(10, len(rv.json['objects'])) members = sorted( [uc.user for uc in group.user_courses.all()], key=lambda user: (user.lastname, user.firstname) ) for index, user in enumerate(members): self.assertEqual(user.uuid, rv.json['objects'][index]['id']) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.get(url) self.assert403(rv) def test_get_current_user_group(self): course = self.fixtures.course url = '/api/courses/'+course.uuid+'/groups/user' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.get('/api/courses/999/groups/user') self.assert404(rv) # test authorized instructor for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.instructor, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test authorized teaching assistant with self.login(self.fixtures.ta.username): for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.ta, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test root admin (not enrolled in course) with self.login(self.fixtures.root_user.username): rv = self.client.get(url) self.assert200(rv) self.assertIsNone(rv.json) # test root admin (enrolled in course) self.fixtures.enrol_user(self.fixtures.root_user, course, CourseRole.student, None) for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, self.fixtures.root_user, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: for group in [None, self.fixtures.groups[0]]: self.fixtures.change_user_group(course, student, group) rv = self.client.get(url) self.assert200(rv) if group == None: self.assertIsNone(rv.json) else: self.assertEquals(rv.json['id'], group.uuid) self.assertEquals(rv.json['name'], group.name) def test_add_group_member(self): # frequently used objects course = self.fixtures.course group = self.fixtures.groups[0] # test login required url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: url = self._add_group_user_url(course, group, self.fixtures.instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id url = '/api/courses/999/groups/'+group.uuid+'/users/'+self.fixtures.students[0].uuid rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid user id url = '/api/courses/'+course.uuid+'/groups/999/users/'+self.fixtures.students[0].uuid rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test invalid user id url = '/api/courses/'+course.uuid+'/groups/'+group.uuid+'/users/999' rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test user that is already in group url = self._add_group_user_url(course, group, self.fixtures.students[0]) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that has never been in the group url = self._add_group_user_url(course, group, self.fixtures.instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that has left the group url = self._add_group_user_url(course, group, self.fixtures.ta) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) # test user that is not enrolled in the course anymore - eg. DROPPED url = self._add_group_user_url(course, group, self.fixtures.dropped_instructor) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) # test user that has never been in the course url = self._add_group_user_url(course, group, self.fixtures.unauthorized_student) rv = self.client.post(url, data={}, content_type='application/json') self.assert404(rv) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(2) grouped_student = self.fixtures.students[-2] self.fixtures.change_user_group(self.fixtures.course, grouped_student, self.fixtures.groups[0]) ungrouped_student = self.fixtures.students[-1] group = self.fixtures.groups[1] url = self._add_group_user_url(course, group, grouped_student) if groups_locked: # grouped_student should not be able to change groups rv = self.client.post(url, data={}, content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "The course groups are locked. This user is already assigned to a different group.") else: # grouped_student should be able to change groups rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) # regardless ungrouped_student should be able to change groups url = self._add_group_user_url(course, group, ungrouped_student) rv = self.client.post(url, data={}, content_type='application/json') self.assert200(rv) def test_remove_group_member(self): course = self.fixtures.course # test login required url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id url = '/api/courses/999/groups/users/'+self.fixtures.students[0].uuid rv = self.client.delete(url) self.assert404(rv) # test invalid user id url = '/api/courses/'+course.uuid+'/groups/users/999' rv = self.client.delete(url) self.assert404(rv) # test user in course url = self._remove_group_user_url(course, self.fixtures.students[0]) rv = self.client.delete(url) self.assert200(rv) self.assertTrue(rv.json['success']) # test user not in course url = self._remove_group_user_url(course, self.fixtures.unauthorized_student) rv = self.client.delete(url) self.assert404(rv) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(2) grouped_student = self.fixtures.students[-2] self.fixtures.change_user_group(self.fixtures.course, grouped_student, self.fixtures.groups[0]) ungrouped_student = self.fixtures.students[-1] url = self._remove_group_user_url(course, grouped_student) if groups_locked: # grouped_student should not be able to be removed from groups rv = self.client.delete(url) self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "The course groups are locked. You may not remove users from the group they are already assigned to.") else: # grouped_student should be able to be removed from groups rv = self.client.delete(url) self.assert200(rv) # regardless ungrouped_student should be able to be removed from groups url = self._remove_group_user_url(course, ungrouped_student) rv = self.client.delete(url) self.assert200(rv) def test_add_multiple_group_member(self): # frequently used objects course = self.fixtures.course group = self.fixtures.groups[0] group2 = self.fixtures.groups[1] student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid] url = self._add_group_user_url(course, group) params = { 'ids': student_ids } # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/groups/'+group.uuid+'/users', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test invalid group id rv = self.client.post('/api/courses/'+course.uuid+'/groups/999/users', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({'ids': []}), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post(url, data=json.dumps({'ids': [self.fixtures.unauthorized_student.uuid]}), content_type='application/json') self.assert400(rv) # test add users into group rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group.uuid) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, group.id) # test add users into another group url = self._add_group_user_url(course, group2) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['id'], group2.uuid) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, group2.id) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(4) grouped_students = [self.fixtures.students[-4], self.fixtures.students[-3]] self.fixtures.change_user_group(self.fixtures.course, grouped_students[0], self.fixtures.groups[0]) self.fixtures.change_user_group(self.fixtures.course, grouped_students[1], self.fixtures.groups[0]) grouped_student_uuids = [student.uuid for student in grouped_students] ungrouped_student_uuids = [self.fixtures.students[-2].uuid, self.fixtures.students[-1].uuid] group = self.fixtures.groups[1] url = self._add_group_user_url(course, group) params = { 'ids': grouped_student_uuids + ungrouped_student_uuids } if groups_locked: # grouped_students should not be able to change groups (groups should not change) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "The course groups are locked. One or more users are already assigned to a different group.") for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids: self.assertEqual(user_course.group_id, self.fixtures.groups[0].id) if user_course.user_uuid in ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) else: # grouped_students should be able to change groups rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids + ungrouped_student_uuids: self.assertEqual(user_course.group_id, group.id) # regardless ungrouped_student should be able to change groups url = self._add_group_user_url(course, group) params = { 'ids': ungrouped_student_uuids } rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) def test_remove_multiple_group_member(self): course = self.fixtures.course url = self._remove_group_user_url(course) student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid] params = { 'ids': student_ids } # test login required rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorzied user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post('/api/courses/999/users/groups', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing ids rv = self.client.post(url, data=json.dumps({ 'ids': [] }), content_type='application/json') self.assert400(rv) # test invalid ids rv = self.client.post(url, data=json.dumps({ 'ids': [self.fixtures.unauthorized_student.uuid] }), content_type='application/json') self.assert400(rv) # test users in course rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertTrue(rv.json['success']) for user_course in course.user_courses: if user_course.user_id in student_ids: self.assertEqual(user_course.group_id, None) for (groups_locked, answer_start, answer_end, compare_start, compare_end) in self.assignment_dates: self.group_assignment.answer_end = answer_end self.group_assignment.compare_start = compare_start self.group_assignment.compare_end = compare_end self.fixtures.add_students(4) grouped_students = [self.fixtures.students[-4], self.fixtures.students[-3]] self.fixtures.change_user_group(self.fixtures.course, grouped_students[0], self.fixtures.groups[0]) self.fixtures.change_user_group(self.fixtures.course, grouped_students[1], self.fixtures.groups[0]) grouped_student_uuids = [student.uuid for student in grouped_students] ungrouped_student_uuids = [self.fixtures.students[-2].uuid, self.fixtures.students[-1].uuid] group = self.fixtures.groups[1] params = { 'ids': grouped_student_uuids + ungrouped_student_uuids } if groups_locked: # grouped_students should not be able to be removed from groups (groups should not change) rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "The course groups are locked. You may not remove users from the group they are already assigned to.") for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids: self.assertEqual(user_course.group_id, self.fixtures.groups[0].id) if user_course.user_uuid in ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) else: # grouped_students should be able to be removed from groups rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) for user_course in course.user_courses: if user_course.user_uuid in grouped_student_uuids + ungrouped_student_uuids: self.assertEqual(user_course.group_id, None) # regardless ungrouped_student should be able to be removed from groups params = { 'ids': ungrouped_student_uuids } rv = self.client.post(url, data=json.dumps(params), content_type='application/json') self.assert200(rv) def _add_group_user_url(self, course, group, user=None): url = '/api/courses/'+course.uuid+'/groups/'+group.uuid+'/users' url += '/'+user.uuid if user else '' return url def _remove_group_user_url(self, course, user=None): url = '/api/courses/'+course.uuid+'/groups/users' url += '/'+user.uuid if user else '' return url
class FileRetrieveTests(ComPAIRAPITestCase): base_url = '/app' fixtures = None def setUp(self): super(FileRetrieveTests, self).setUp() self.fixtures = TestFixture().add_course( num_students=5, num_assignments=1, num_groups=0, num_answers=1, with_draft_student=True) self.files_to_cleanup = [] def tearDown(self): folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER'] for file_name in self.files_to_cleanup: file_path = os.path.join(folder, file_name) try: if os.path.isfile(file_path): os.remove(file_path) except Exception as e: print(e) def test_view_file(self): db_file = self.fixtures.add_file(self.fixtures.instructor) filename = db_file.name url = self.base_url + '/attachment/' + filename # test login required rv = self.client.get(url) self.assert401(rv) # TODO: no authorization control right now and needs to be added in the future # test unauthorized user # with self.login(self.fixtures.unauthorized_instructor.username): # rv = self.client.get(url) # self.assert403(rv) # valid instructor with self.login(self.fixtures.instructor.username): # invalid file name (db is not actually touched) rv = self.client.get(self.base_url + '/attachment/'+filename) self.assert404(rv) self.assertEqual('invalid file name', text_type(rv.get_data(as_text=True))) with mock.patch('compair.api.os.path.exists', return_value=True): with mock.patch('compair.api.send_file', return_value=make_response("OK")) as mock_send_file: # test all attachment types extensions = [ ('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'), ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg'), ('png', 'image/png') ] for (extension, mimetype) in extensions: db_file = self.fixtures.add_file(self.fixtures.instructor, name="file_name."+extension) filename = db_file.name url = self.base_url + '/attachment/' + filename self.client.get(url) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=False, mimetype=mimetype ) else: mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=True, mimetype=mimetype ) mock_send_file.reset_mock() # test overriding attachment filename override_name = "override."+db_file.extension self.client.get(url+"?name="+override_name) if extension == 'pdf': mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=None, as_attachment=False, mimetype=mimetype ) else: mock_send_file.assert_called_once_with( '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename), attachment_filename=override_name, as_attachment=True, mimetype=mimetype ) mock_send_file.reset_mock() def test_create_attachment(self): url = '/api/attachment' test_formats = [ ('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'), ('webm', 'video/webm'), ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg') ] # test login required uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, 'alias.pdf'))) self.assert401(rv) uploaded_file.close() with self.login(self.fixtures.instructor.username): # test no file uploaded filename = 'alias.pdf' rv = self.client.post(url, data=dict()) self.assert400(rv) self.assertEqual("File Not Uploaded", rv.json['title']) self.assertEqual("Sorry, no file was found to upload. Please try uploading again.", rv.json['message']) # test no file uploaded filename = 'alias.xyz' uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert400(rv) self.assertEqual("File Not Uploaded", rv.json['title']) self.assertEqual("Please try again with an approved file type, which includes: JPEG, JPG, MP3, MP4, PDF, PNG, WEBM.", rv.json['message']) for extension, mimetype in test_formats: filename = 'alias.'+extension uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) uploaded_file.close() actual_file = rv.json['file'] self.files_to_cleanup.append(actual_file['name']) self.assertEqual(actual_file['id']+"."+extension.lower(), actual_file['name']) self.assertEqual(filename, actual_file['alias']) self.assertEqual(extension.lower(), actual_file['extension']) self.assertEqual(mimetype, actual_file['mimetype']) # test with uppercase extension filename = 'alias.'+extension.upper() uploaded_file = io.BytesIO(b"this is a test") rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) uploaded_file.close() actual_file = rv.json['file'] self.files_to_cleanup.append(actual_file['name']) self.assertEqual(actual_file['id']+"."+extension.lower(), actual_file['name']) self.assertEqual(filename, actual_file['alias']) self.assertEqual(extension.lower(), actual_file['extension']) self.assertEqual(mimetype, actual_file['mimetype']) @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start') @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end') @mock.patch('compair.kaltura.upload_token.UploadToken._api_add') def test_get_kaltura(self, mocked_upload_token_add, mocked_kaltura_session_end, mocked_kaltura_session_start): url = '/api/attachment/kaltura' current_app.config['KALTURA_ENABLED'] = True current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com" current_app.config['KALTURA_PARTNER_ID'] = 123 current_app.config['KALTURA_USER_ID'] = "*****@*****.**" current_app.config['KALTURA_SECRET'] = "abc" current_app.config['KALTURA_PLAYER_ID'] = 456 mocked_kaltura_session_start.return_value = "ks_mock" mocked_kaltura_session_end.return_value = {} mocked_upload_token_add.return_value = { "id": "mocked_upload_token_id" } expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id&ks=ks_mock" # test login required rv = self.client.get(url) self.assert401(rv) with self.login(self.fixtures.instructor.username): # test kaltura disabled current_app.config['KALTURA_ENABLED'] = False rv = self.client.get(url) self.assert400(rv) # test kaltura enabled current_app.config['KALTURA_ENABLED'] = True rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call("*****@*****.**", privileges="edit:mocked_upload_token_id,urirestrict:/api_v3/service/uploadtoken/action/upload*") mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 1) self.assertEqual(kaltura_media_items[0].user_id, self.fixtures.instructor.id) self.assertEqual(kaltura_media_items[0].partner_id, 123) self.assertEqual(kaltura_media_items[0].player_id, 456) self.assertEqual(kaltura_media_items[0].upload_ks, "ks_mock") self.assertEqual(kaltura_media_items[0].upload_token_id, "mocked_upload_token_id") self.assertIsNone(kaltura_media_items[0].file_name) self.assertIsNone(kaltura_media_items[0].entry_id) self.assertIsNone(kaltura_media_items[0].download_url) # use global unique identifer (user has no global unique identifer) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True mocked_upload_token_add.return_value = { "id": "mocked_upload_token_id2" } expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id2&ks=ks_mock" rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call("*****@*****.**", privileges="edit:mocked_upload_token_id2,urirestrict:/api_v3/service/uploadtoken/action/upload*") mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 2) self.assertEqual(kaltura_media_items[1].user_id, self.fixtures.instructor.id) # use global unique identifer (user has global unique identifer) self.fixtures.instructor.global_unique_identifier = "*****@*****.**" mocked_upload_token_add.return_value = { "id": "mocked_upload_token_id3" } expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id3&ks=ks_mock" rv = self.client.get(url) self.assert200(rv) self.assertEqual(rv.json['upload_url'], expected_upload_url) self.assertEqual(mocked_kaltura_session_start.call_count, 2) mocked_kaltura_session_start.assert_any_call("*****@*****.**") mocked_kaltura_session_start.assert_any_call("*****@*****.**", privileges="edit:mocked_upload_token_id3,urirestrict:/api_v3/service/uploadtoken/action/upload*") mocked_kaltura_session_start.reset_mock() mocked_kaltura_session_end.assert_called_once_with("ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_add.assert_called_once_with("ks_mock") mocked_upload_token_add.reset_mock() kaltura_media_items = KalturaMedia.query.all() self.assertEqual(len(kaltura_media_items), 3) self.assertEqual(kaltura_media_items[2].user_id, self.fixtures.instructor.id) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start') @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end') @mock.patch('compair.kaltura.upload_token.UploadToken._api_get') @mock.patch('compair.kaltura.media.Media._api_add') @mock.patch('compair.kaltura.media.Media._api_add_content') def test_post_kaltura(self, mocked_kaltura_media_add_content, mocked_kaltura_media_add, mocked_upload_token_get, mocked_kaltura_session_end, mocked_kaltura_session_start): url = '/api/attachment/kaltura/mocked_upload_token_id' invalid_url = '/api/attachment/kaltura/mocked_upload_token_id_invalid' current_app.config['KALTURA_ENABLED'] = True current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com" current_app.config['KALTURA_PARTNER_ID'] = 123 current_app.config['KALTURA_USER_ID'] = "*****@*****.**" current_app.config['KALTURA_SECRET'] = "abc" current_app.config['KALTURA_PLAYER_ID'] = 456 mocked_kaltura_session_start.return_value = "ks_mock" mocked_kaltura_session_end.return_value = {} mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id", "fileName": "uploaded_audio_file.mp3" } mocked_kaltura_media_add.return_value = { "id": "mock_entry_id" } mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id", "downloadUrl": "http://www.download/url.com" } kaltura_media = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock", upload_token_id="mocked_upload_token_id" ) db.session.add(kaltura_media) kaltura_media2 = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock2", upload_token_id="mocked_upload_token_id2" ) db.session.add(kaltura_media) kaltura_media3 = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock3", upload_token_id="mocked_upload_token_id3" ) db.session.add(kaltura_media) invalid_kaltura_media = KalturaMedia( user=self.fixtures.instructor, service_url="https://www.kaltura.com", partner_id=123, player_id=456, upload_ks="upload_ks_mock", upload_token_id="mocked_upload_token_id_invalid", entry_id="def" ) db.session.add(invalid_kaltura_media) db.session.commit() # test login required rv = self.client.post(url) self.assert401(rv) with self.login(self.fixtures.instructor.username): # test kaltura disabled current_app.config['KALTURA_ENABLED'] = False rv = self.client.post(url) self.assert400(rv) # test invalid upload_token_id current_app.config['KALTURA_ENABLED'] = True rv = self.client.post(invalid_url) self.assert400(rv) # test valid rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media.files.all()[0].uuid) self.assertEqual(kaltura_media.file_name, "uploaded_audio_file.mp3") self.assertEqual(kaltura_media.entry_id, "mock_entry_id") self.assertEqual(kaltura_media.download_url, "http://www.download/url.com") self.assertEqual(kaltura_media.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with("*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id", "mocked_upload_token_id") mocked_kaltura_media_add_content.reset_mock() # test direct download from kaltura via /attachment kaltura_attachment_url = self.base_url + '/attachment/' + rv.json['file']['name'] + '?name=uploaded_audio_file.mp3' rv = self.client.get(kaltura_attachment_url) self.assertTrue(rv.location.startswith(kaltura_media.download_url)) # redirecting to Kaltura mocked_kaltura_session_start.assert_called_once_with("*****@*****.**", \ expiry=60, \ privileges='sview:'+kaltura_media.entry_id+',urirestrict:/url.com/*' ) mocked_kaltura_session_start.reset_mock() # use global unique identifer (user has no global unique identifer) current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True url = '/api/attachment/kaltura/mocked_upload_token_id2' mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id2", "fileName": "uploaded_audio_file2.mp3" } mocked_kaltura_media_add.return_value = { "id": "mock_entry_id2" } mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id2", "downloadUrl": "www.download/url2.com" } rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media2.files.all()[0].uuid) self.assertEqual(kaltura_media2.file_name, "uploaded_audio_file2.mp3") self.assertEqual(kaltura_media2.entry_id, "mock_entry_id2") self.assertEqual(kaltura_media2.download_url, "www.download/url2.com") self.assertEqual(kaltura_media2.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with("*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock2") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id2") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id2", "mocked_upload_token_id2") mocked_kaltura_media_add_content.reset_mock() # use global unique identifer (user has global unique identifer) self.fixtures.instructor.global_unique_identifier = "*****@*****.**" url = '/api/attachment/kaltura/mocked_upload_token_id3' mocked_upload_token_get.return_value = { "id": "mocked_upload_token_id3", "fileName": "uploaded_audio_file3.mp3" } mocked_kaltura_media_add.return_value = { "id": "mock_entry_id3" } mocked_kaltura_media_add_content.return_value = { "id": "mock_entry_id3", "downloadUrl": "www.download/url3.com" } rv = self.client.post(url) self.assert200(rv) self.assertEqual(rv.json['file']['id'], kaltura_media3.files.all()[0].uuid) self.assertEqual(kaltura_media3.file_name, "uploaded_audio_file3.mp3") self.assertEqual(kaltura_media3.entry_id, "mock_entry_id3") self.assertEqual(kaltura_media3.download_url, "www.download/url3.com") self.assertEqual(kaltura_media3.service_url, "https://www.kaltura.com") mocked_kaltura_session_start.assert_called_once_with("*****@*****.**") mocked_kaltura_session_start.reset_mock() self.assertEqual(mocked_kaltura_session_end.call_count, 2) mocked_kaltura_session_end.assert_any_call("ks_mock") mocked_kaltura_session_end.assert_any_call("upload_ks_mock3") mocked_kaltura_session_end.reset_mock() mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id3") mocked_upload_token_get.reset_mock() mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5) mocked_kaltura_media_add.reset_mock() mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id3", "mocked_upload_token_id3") mocked_kaltura_media_add_content.reset_mock() current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
class ComparionExampleAPITests(ComPAIRAPITestCase): def setUp(self): super(ComparionExampleAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.instructor) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.ta) self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid) def _build_url(self, course_uuid, assignment_uuid, tail=""): url = '/api/courses/' + course_uuid + '/assignments/' + assignment_uuid + '/comparisons/examples' + tail return url def test_get_all_comparison_examples(self): # Test login required rv = self.client.get(self.base_url) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url) self.assert403(rv) for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.get(self.base_url) self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test non-existent entry rv = self.client.get(self._build_url(self.fixtures.course.uuid, "4903409")) self.assert404(rv) rv = self.client.get(self._build_url("4903409", self.fixtures.assignment.uuid)) self.assert404(rv) rv = self.client.get(self.base_url) self.assert200(rv) results = rv.json['objects'] self.assertEqual(len(results), 2) comparison_examples = self.fixtures.assignment.comparison_examples for index, comparison_example in enumerate(comparison_examples): self.assertEqual(results[index]['id'], comparison_example.uuid) self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid) self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid) # ta with self.login(self.fixtures.ta.username): rv = self.client.get(self.base_url) self.assert200(rv) results = rv.json['objects'] self.assertEqual(len(results), 2) comparison_examples = self.fixtures.assignment.comparison_examples for index, comparison_example in enumerate(comparison_examples): self.assertEqual(results[index]['id'], comparison_example.uuid) self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid) self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid) def test_create_comparison_example(self): self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.instructor) self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta) expected_comparison_example = { 'answer1_id': self.fixtures.answers[-2].uuid, 'answer2_id': self.fixtures.answers[-1].uuid } # test login required rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert401(rv) # test unauthorized users student = self.fixtures.unauthorized_student for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) student = self.fixtures.students[0] for user_context in [ \ self.login(student.username), \ self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test non-existent entry rv = self.client.post( self._build_url(self.fixtures.course.uuid, "4903409"), data=json.dumps(expected_comparison_example), content_type='application/json') self.assert404(rv) rv = self.client.post( self._build_url("4903409", self.fixtures.assignment.uuid), data=json.dumps(expected_comparison_example), content_type='application/json') self.assert404(rv) # test invalid format - answer1_id invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer1_id'] = None rv = self.client.post( self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert400(rv) # test invalid format - answer1_id not exists invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer1_id'] = "999" rv = self.client.post( self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert404(rv) # test invalid format - answer2_id invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer2_id'] = None rv = self.client.post( self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert400(rv) # test invalid format - answer2_id not exists invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer2_id'] = "999" rv = self.client.post( self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert404(rv) # test create successful rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert200(rv) self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id']) # ta with self.login(self.fixtures.ta.username): # test create successful rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert200(rv) self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id']) def test_edit_comparison_example(self): comparison_example = self.fixtures.comparison_examples[0] self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta) new_answer = self.fixtures.answers[-1] expected = { 'id': comparison_example.uuid, 'answer1_id': new_answer.uuid, 'answer2_id': comparison_example.answer2_uuid, } # test login required rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post( self._build_url("999", self.fixtures.assignment.uuid, '/' + comparison_example.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid assignment id rv = self.client.post( self._build_url(self.fixtures.course.uuid, "999", '/' + comparison_example.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid comparison_example id rv = self.client.post( self.base_url + '/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid format - answer1_id invalid = expected.copy() invalid['answer1_id'] = None rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert400(rv) # test invalid format - answer1_id not exists invalid = expected.copy() invalid['answer1_id'] = "999" rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert404(rv) # test invalid format - answer2_id invalid = expected.copy() invalid['answer2_id'] = None rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert400(rv) # test invalid format - answer2_id not exists invalid = expected.copy() invalid['answer2_id'] = "999" rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert404(rv) # test edit successful rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(expected['id'], rv.json['id']) self.assertEqual(expected['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected['answer2_id'], rv.json['answer2_id']) # ta with self.login(self.fixtures.ta.username): # test edit successful rv = self.client.post( self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(expected['id'], rv.json['id']) self.assertEqual(expected['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected['answer2_id'], rv.json['answer2_id']) def test_delete_comparison_example(self): comparison_example = self.fixtures.comparison_examples[0] comparison_example2 = self.fixtures.comparison_examples[1] # test login required rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert403(rv) for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid assignment id rv = self.client.delete(self._build_url(self.fixtures.course.uuid, "4903409") + '/' + comparison_example.uuid) self.assert404(rv) # test invalid course id rv = self.client.delete(self._build_url("4903409", self.fixtures.assignment.uuid) + '/' + comparison_example.uuid) self.assert404(rv) # test invalid comparison example id rv = self.client.delete(self.base_url + '/999') self.assert404(rv) # test deletion by instructor rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert200(rv) self.assertEqual(comparison_example.uuid, rv.json['id']) # test deletion by ta with self.login(self.fixtures.instructor.username): rv = self.client.delete(self.base_url + '/' + comparison_example2.uuid) self.assert200(rv) self.assertEqual(comparison_example2.uuid, rv.json['id'])
def setUp(self): super(FileRetrieveTests, self).setUp() self.fixtures = TestFixture().add_course( num_students=5, num_assignments=1, num_groups=0, num_answers=1, with_draft_student=True) self.files_to_cleanup = []
class ComparionExampleAPITests(ComPAIRAPITestCase): def setUp(self): super(ComparionExampleAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.instructor) self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.ta) self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid) def _build_url(self, course_uuid, assignment_uuid, tail=""): url = '/api/courses/' + course_uuid + '/assignments/' + assignment_uuid + '/comparisons/examples' + tail return url def test_get_all_comparison_examples(self): # Test login required rv = self.client.get(self.base_url) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url) self.assert403(rv) for student in [ self.fixtures.unauthorized_student, self.fixtures.students[0] ]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.get(self.base_url) self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test non-existent entry rv = self.client.get( self._build_url(self.fixtures.course.uuid, "4903409")) self.assert404(rv) rv = self.client.get( self._build_url("4903409", self.fixtures.assignment.uuid)) self.assert404(rv) rv = self.client.get(self.base_url) self.assert200(rv) results = rv.json['objects'] self.assertEqual(len(results), 2) comparison_examples = self.fixtures.assignment.comparison_examples for index, comparison_example in enumerate(comparison_examples): self.assertEqual(results[index]['id'], comparison_example.uuid) self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid) self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid) # ta with self.login(self.fixtures.ta.username): rv = self.client.get(self.base_url) self.assert200(rv) results = rv.json['objects'] self.assertEqual(len(results), 2) comparison_examples = self.fixtures.assignment.comparison_examples for index, comparison_example in enumerate(comparison_examples): self.assertEqual(results[index]['id'], comparison_example.uuid) self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid) self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid) def test_create_comparison_example(self): self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.instructor) self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta) expected_comparison_example = { 'answer1_id': self.fixtures.answers[-2].uuid, 'answer2_id': self.fixtures.answers[-1].uuid } # test login required rv = self.client.post(self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert401(rv) # test unauthorized users student = self.fixtures.unauthorized_student for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) student = self.fixtures.students[0] for user_context in [ \ self.login(student.username), \ self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post( self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test non-existent entry rv = self.client.post(self._build_url(self.fixtures.course.uuid, "4903409"), data=json.dumps(expected_comparison_example), content_type='application/json') self.assert404(rv) rv = self.client.post(self._build_url( "4903409", self.fixtures.assignment.uuid), data=json.dumps(expected_comparison_example), content_type='application/json') self.assert404(rv) # test invalid format - answer1_id invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer1_id'] = None rv = self.client.post(self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert400(rv) # test invalid format - answer1_id not exists invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer1_id'] = "999" rv = self.client.post(self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert404(rv) # test invalid format - answer2_id invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer2_id'] = None rv = self.client.post(self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert400(rv) # test invalid format - answer2_id not exists invalid_comparison_example = expected_comparison_example.copy() invalid_comparison_example['answer2_id'] = "999" rv = self.client.post(self.base_url, data=json.dumps(invalid_comparison_example), content_type='application/json') self.assert404(rv) # test create successful rv = self.client.post(self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert200(rv) self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id']) # ta with self.login(self.fixtures.ta.username): # test create successful rv = self.client.post(self.base_url, data=json.dumps(expected_comparison_example), content_type='application/json') self.assert200(rv) self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id']) def test_edit_comparison_example(self): comparison_example = self.fixtures.comparison_examples[0] self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta) new_answer = self.fixtures.answers[-1] expected = { 'id': comparison_example.uuid, 'answer1_id': new_answer.uuid, 'answer2_id': comparison_example.answer2_uuid, } # test login required rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) for student in [ self.fixtures.unauthorized_student, self.fixtures.students[0] ]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert403(rv) # instructor with self.login(self.fixtures.instructor.username): # test invalid course id rv = self.client.post(self._build_url( "999", self.fixtures.assignment.uuid, '/' + comparison_example.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid assignment id rv = self.client.post(self._build_url( self.fixtures.course.uuid, "999", '/' + comparison_example.uuid), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid comparison_example id rv = self.client.post(self.base_url + '/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid format - answer1_id invalid = expected.copy() invalid['answer1_id'] = None rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert400(rv) # test invalid format - answer1_id not exists invalid = expected.copy() invalid['answer1_id'] = "999" rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert404(rv) # test invalid format - answer2_id invalid = expected.copy() invalid['answer2_id'] = None rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert400(rv) # test invalid format - answer2_id not exists invalid = expected.copy() invalid['answer2_id'] = "999" rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(invalid), content_type='application/json') self.assert404(rv) # test edit successful rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(expected['id'], rv.json['id']) self.assertEqual(expected['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected['answer2_id'], rv.json['answer2_id']) # ta with self.login(self.fixtures.ta.username): # test edit successful rv = self.client.post(self.base_url + '/' + comparison_example.uuid, data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(expected['id'], rv.json['id']) self.assertEqual(expected['answer1_id'], rv.json['answer1_id']) self.assertEqual(expected['answer2_id'], rv.json['answer2_id']) def test_delete_comparison_example(self): comparison_example = self.fixtures.comparison_examples[0] comparison_example2 = self.fixtures.comparison_examples[1] # test login required rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert403(rv) for student in [ self.fixtures.unauthorized_student, self.fixtures.students[0] ]: for user_context in [ \ self.login(student.username), \ self.impersonate(DefaultFixture.ROOT_USER, student)]: with user_context: rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid assignment id rv = self.client.delete( self._build_url(self.fixtures.course.uuid, "4903409") + '/' + comparison_example.uuid) self.assert404(rv) # test invalid course id rv = self.client.delete( self._build_url("4903409", self.fixtures.assignment.uuid) + '/' + comparison_example.uuid) self.assert404(rv) # test invalid comparison example id rv = self.client.delete(self.base_url + '/999') self.assert404(rv) # test deletion by instructor rv = self.client.delete(self.base_url + '/' + comparison_example.uuid) self.assert200(rv) self.assertEqual(comparison_example.uuid, rv.json['id']) # test deletion by ta with self.login(self.fixtures.instructor.username): rv = self.client.delete(self.base_url + '/' + comparison_example2.uuid) self.assert200(rv) self.assertEqual(comparison_example2.uuid, rv.json['id'])
def setUp(self): super(GroupsAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)
class AnswersAPITests(ACJAPITestCase): def setUp(self): super(AnswersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2) self.base_url = self._build_url(self.fixtures.course.id, self.fixtures.question.id) def _build_url(self, course_id, question_id, tail=""): url = '/api/courses/' + str(course_id) + '/questions/' + str( question_id) + '/answers' + tail return url def test_get_all_answers(self): # Test login required rv = self.client.get(self.base_url) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.students[0].username): # test non-existent entry rv = self.client.get( self._build_url(self.fixtures.course.id, 4903409)) self.assert404(rv) # test data retrieve is correct self.fixtures.question.answer_end = datetime.datetime.now( ) - datetime.timedelta(days=1) db.session.add(self.fixtures.question) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = PostsForAnswers.query.filter_by( questions_id=self.fixtures.question.id).paginate(1, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test the second page rv = self.client.get(self.base_url + '?page=2') self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = PostsForAnswers.query.filter_by( questions_id=self.fixtures.question.id).paginate(2, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) self.assertEqual(2, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test sorting rv = self.client.get( self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id)) self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted expected = sorted(self.fixtures.answers, key=lambda ans: ans.scores[0].score if len(ans.scores) else 0, reverse=True)[:20] self.assertEqual([a.id for a in expected], [a['id'] for a in result]) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test author filter rv = self.client.get( self.base_url + '?author={}'.format(self.fixtures.students[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id) # test group filter rv = self.client.get( self.base_url + '?group={}'.format(self.fixtures.groups[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual( len(result), len(self.fixtures.answers) / len(self.fixtures.groups)) # test ids filter ids = {str(a.id) for a in self.fixtures.answers[:3]} rv = self.client.get(self.base_url + '?ids={}'.format(','.join(ids))) self.assert200(rv) result = rv.json['objects'] self.assertEqual(ids, {str(a['id']) for a in result}) # test combined filter rv = self.client.get(self.base_url + '?orderBy={}&group={}'.format( self.fixtures.question.criteria[0].id, self.fixtures.groups[0].id)) self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted answers_per_group = int( len(self.fixtures.answers) / len(self.fixtures.groups)) if len(self.fixtures.groups) else 0 answers = self.fixtures.answers[:answers_per_group] expected = sorted(answers, key=lambda ans: ans.scores[0].score, reverse=True) self.assertEqual([a.id for a in expected], [a['id'] for a in result]) # all filters rv = self.client.get( self.base_url + '?orderBy={}&group={}&author={}&page=1&perPage=20'.format( self.fixtures.question.criteria[0].id, self.fixtures.groups[0].id, self.fixtures.students[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id) # add instructor answer post = PostsFactory(course=self.fixtures.course, user=self.fixtures.instructor) answer = PostsForAnswersFactory(question=self.fixtures.question, post=post) self.fixtures.answers.append(answer) db.session.commit() rv = self.client.get( self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(self.fixtures.answers), rv.json['total']) # first answer should be instructor answer self.assertEqual(self.fixtures.instructor.id, result[0]['user_id']) # test data retrieve before answer period ended with non-privileged user self.fixtures.question.answer_end = datetime.datetime.now( ) + datetime.timedelta(days=2) db.session.add(self.fixtures.question) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(1, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(1, rv.json['total']) # test data retrieve before answer period ended with privileged user with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(20, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(self.fixtures.answers), rv.json['total']) def test_create_answer(self): # test login required expected_answer = {'content': 'this is some answer content'} response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert401(response) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(response) with self.login(self.fixtures.unauthorized_instructor.username): response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(response) # test invalid format with self.login(self.fixtures.students[0].username): invalid_answer = {'post': {'blah': 'blah'}} response = self.client.post(self.base_url, data=json.dumps(invalid_answer), content_type='application/json') self.assert400(response) # test invalid question response = self.client.post(self._build_url( self.fixtures.course.id, 9392402), data=json.dumps(expected_answer), content_type='application/json') self.assert404(response) # test invalid course response = self.client.post(self._build_url( 9392402, self.fixtures.question.id), data=json.dumps(expected_answer), content_type='application/json') self.assert404(response) # test create successful with self.login(self.fixtures.instructor.username): response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) # retrieve again and verify rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor could submit multiple answers for his/her own response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor could submit on behave of a student self.fixtures.add_students(1) expected_answer.update({'user': self.fixtures.students[-1].id}) response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor can not submit additional answers for a student expected_answer.update({'user': self.fixtures.students[0].id}) response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert400(response) rv = json.loads(response.data.decode('utf-8')) self.assertEqual( {"error": "An answer has already been submitted."}, rv) def test_get_answer(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] # test login required rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.get( self._build_url(999, question_id, '/' + str(answer.id))) self.assert404(rv) # test invalid answer id rv = self.client.get( self._build_url(self.fixtures.course.id, question_id, '/' + str(999))) self.assert404(rv) # test authorized student rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) # test authorized instructor with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) def test_edit_answer(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] expected = {'id': str(answer.id), 'content': 'This is an edit'} # test login required rv = self.client.post(self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.students[1].username): rv = self.client.post(self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.post(self._build_url(999, question_id, '/' + str(answer.id)), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid question id rv = self.client.post(self._build_url(self.fixtures.course.id, 999, '/' + str(answer.id)), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid answer id rv = self.client.post(self.base_url + '/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test unmatched answer id with self.login(self.fixtures.students[1].username): rv = self.client.post(self.base_url + '/' + str(self.fixtures.answers[1].id), data=json.dumps(expected), content_type='application/json') self.assert400(rv) # test edit by author with self.login(self.fixtures.students[0].username): rv = self.client.post(self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.id, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) # test edit by user that can manage posts expected['content'] = 'This is another edit' with self.login(self.fixtures.instructor.username): rv = self.client.post(self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.id, rv.json['id']) self.assertEqual('This is another edit', rv.json['content']) def test_delete_answer(self): answer_id = self.fixtures.answers[0].id # test login required rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.students[1].username): rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert403(rv) # test invalid answer id with self.login(self.fixtures.students[0].username): rv = self.client.delete(self.base_url + '/999') self.assert404(rv) # test deletion by author rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert200(rv) self.assertEqual(answer_id, rv.json['id']) # test deletion by user that can manage posts with self.login(self.fixtures.instructor.username): answer_id2 = self.fixtures.answers[1].id rv = self.client.delete(self.base_url + '/' + str(answer_id2)) self.assert200(rv) self.assertEqual(answer_id2, rv.json['id']) def test_get_user_answers(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] url = self._build_url(self.fixtures.course.id, question_id, '/user') # test login required rv = self.client.get(url) self.assert401(rv) # test invalid course with self.login(self.fixtures.students[0].username): rv = self.client.get(self._build_url(999, question_id, '/user')) self.assert404(rv) # test invalid question rv = self.client.get( self._build_url(self.fixtures.course.id, 999, '/user')) self.assert404(rv) # test successful queries rv = self.client.get(url) self.assert200(rv) self.assertEqual(1, len(rv.json['answer'])) self.assertEqual(answer.id, rv.json['answer'][0]['id']) self.assertEqual(answer.post.content, rv.json['answer'][0]['content']) with self.login(self.fixtures.instructor.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(0, len(rv.json['answer'])) def test_flag_answer(self): answer = self.fixtures.question.answers[0] flag_url = self.base_url + "/" + str(answer.id) + "/flagged" # test login required expected_flag_on = {'flagged': True} expected_flag_off = {'flagged': False} rv = self.client.post(flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): rv = self.client.post(flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert403(rv) # test flagging with self.login(self.fixtures.students[0].username): rv = self.client.post(flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) self.assertEqual(expected_flag_on['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test unflagging rv = self.client.post(flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual(expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test prevent unflagging by other students with self.login(self.fixtures.students[0].username): rv = self.client.post(flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) # create another student self.fixtures.add_students(1) other_student = self.fixtures.students[-1] # try to unflag answer as other student, should fail with self.login(other_student.username): rv = self.client.post(flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert400(rv) # test allow unflagging by instructor with self.login(self.fixtures.instructor.username): rv = self.client.post(flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual(expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") def test_get_question_answered(self): count_url = self.base_url + '/count' # test login required rv = self.client.get(count_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(count_url) self.assert403(rv) # test invalid course id self.fixtures.add_students(1) with self.login(self.fixtures.students[-1].username): rv = self.client.get('/api/courses/999/questions/1/answers/count') self.assert404(rv) # test invalid question id rv = self.client.get('/api/courses/1/questions/999/answers/count') self.assert404(rv) # test successful query - no answers rv = self.client.get(count_url) self.assert200(rv) self.assertEqual(0, rv.json['answered']) # test successful query - answered with self.login(self.fixtures.students[0].username): rv = self.client.get(count_url) self.assert200(rv) self.assertEqual(1, rv.json['answered']) def test_get_answered_count(self): answered_url = '/api/courses/' + str( self.fixtures.course.id) + '/answers/answered' # test login required rv = self.client.get(answered_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(answered_url) self.assert403(rv) # test invalid course id self.fixtures.add_students(1) with self.login(self.fixtures.students[-1].username): rv = self.client.get('/api/courses/999/answered') self.assert404(rv) # test successful query - have not answered any questions in the course rv = self.client.get(answered_url) self.assert200(rv) self.assertEqual(0, len(rv.json['answered'])) # test successful query - have submitted one answer per question with self.login(self.fixtures.students[0].username): rv = self.client.get(answered_url) self.assert200(rv) expected = { str(question.id): 1 for question in self.fixtures.questions } self.assertEqual(expected, rv.json['answered']) def test_get_answers_view(self): view_url = \ '/api/courses/' + str(self.fixtures.course.id) + '/questions/' + \ str(self.fixtures.questions[0].id) + '/answers/view' # test login required rv = self.client.get(view_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(view_url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): rv = self.client.get('/api/courses/999/questions/' + str(self.fixtures.questions[0].id) + '/answers/view') self.assert404(rv) # test invalid question id rv = self.client.get('/api/courses/' + str(self.fixtures.course.id) + '/questions/999/answers/view') self.assert404(rv) # test successful query rv = self.client.get(view_url) self.assert200(rv) expected = self.fixtures.answers self.assertEqual(30, len(rv.json['answers'])) for i, exp in enumerate(expected): actual = rv.json['answers'][str(exp.id)] self.assertEqual(exp.id, actual['id']) self.assertEqual(exp.post.content, actual['content']) self.assertFalse(actual['file']) # test successful query - student with self.login(self.fixtures.students[0].username): rv = self.client.get(view_url) self.assert200(rv) actual = rv.json['answers'] expected = self.fixtures.answers[0] self.assertEqual(1, len(actual)) self.assertTrue(str(expected.id) in actual) answer = actual[str(expected.id)] self.assertEqual(expected.id, answer['id']) self.assertEqual(expected.post.content, answer['content']) self.assertFalse(answer['file']) self.assertFalse('scores' in answer)
class GroupsAPITests(ComPAIRAPITestCase): def setUp(self): super(GroupsAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=3) def test_get_groups(self): url = '/api/courses/'+self.fixtures.course.uuid+'/groups' # test login required rv = self.client.get(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): invalid_url = '/api/courses/999/groups' rv = self.client.get(invalid_url) self.assert404(rv) # test successful query rv = self.client.get(url) self.assert200(rv) actual = rv.json['objects'] expected = sorted(self.fixtures.groups, key=lambda group: group.name) self.assertEqual(len(actual), 4) for index, group in enumerate(expected): self.assertEqual(actual[index]['id'], group.uuid) # test TA with self.login(self.fixtures.ta.username): rv = self.client.get(url) self.assert200(rv) actual = rv.json['objects'] expected = sorted(self.fixtures.groups, key=lambda group: group.name) self.assertEqual(len(actual), 4) for index, group in enumerate(expected): self.assertEqual(actual[index]['id'], group.uuid) def test_create_group(self): url = '/api/courses/'+self.fixtures.course.uuid+'/groups' group_expected = { 'name': 'Group 101', } invalid_expected = { 'name': self.fixtures.groups[1].name, } # test login required rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups' rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test non-unique name rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Added") self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.") # test successful query rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert200(rv) group_actual = rv.json self.assertEqual(group_expected['name'], group_actual['name']) def test_edit_group(self): group = self.fixtures.groups[0] url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid group_expected = { 'id': group.uuid, 'name': 'New Group Name', } invalid_expected = { 'id': group.uuid, 'name': self.fixtures.groups[1].name, } # test login required rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups/'+group.uuid rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test invalid group id invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999' rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json') self.assert404(rv) # test non-unique name rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json') self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Saved") self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.") # test successful query rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json') self.assert200(rv) group_actual = rv.json self.assertEqual(group_expected['id'], group_actual['id']) self.assertEqual(group_expected['name'], group_actual['name']) def test_delete_group(self): group = self.fixtures.groups[0] self.fixtures.add_assignments(num_assignments=1, with_group_answers=True) url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid # test login required rv = self.client.delete(url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.delete(url) self.assert403(rv) # test ta with self.login(self.fixtures.ta.username): rv = self.client.delete(url) self.assert403(rv) # test student student = self.fixtures.students[0] for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]: with user_context: rv = self.client.delete(url) self.assert403(rv) with self.login(self.fixtures.instructor.username): # test invalid course id invalid_url = '/api/courses/999/groups/'+group.uuid rv = self.client.delete(invalid_url) self.assert404(rv) # test invalid group id invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999' rv = self.client.delete(invalid_url) self.assert404(rv) # test successful query rv = self.client.delete(url) self.assert200(rv) # test cannot delete again rv = self.client.delete(url) self.assert404(rv) # test cannot delete group if it has an answer self.fixtures.add_answers() group = self.fixtures.groups[1] url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid rv = self.client.delete(url) self.assert400(rv) self.assertEqual(rv.json['title'], "Group Not Deleted") self.assertEqual(rv.json['message'], "Sorry, you cannot remove groups that have submitted answers.")
class AnswersAPITests(ACJAPITestCase): def setUp(self): super(AnswersAPITests, self).setUp() self.fixtures = TestFixture().add_course(num_students=30, num_groups=2) self.base_url = self._build_url(self.fixtures.course.id, self.fixtures.question.id) def _build_url(self, course_id, question_id, tail=""): url = '/api/courses/' + str(course_id) + '/questions/' + str(question_id) + '/answers' + tail return url def test_get_all_answers(self): # Test login required rv = self.client.get(self.base_url) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(self.base_url) self.assert403(rv) with self.login(self.fixtures.students[0].username): # test non-existent entry rv = self.client.get(self._build_url(self.fixtures.course.id, 4903409)) self.assert404(rv) # test data retrieve is correct self.fixtures.question.answer_end = datetime.datetime.now() - datetime.timedelta(days=1) db.session.add(self.fixtures.question) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = PostsForAnswers.query.filter_by(questions_id=self.fixtures.question.id).paginate(1, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test the second page rv = self.client.get(self.base_url + '?page=2') self.assert200(rv) actual_answers = rv.json['objects'] expected_answers = PostsForAnswers.query.filter_by(questions_id=self.fixtures.question.id).paginate(2, 20) for i, expected in enumerate(expected_answers.items): actual = actual_answers[i] self.assertEqual(expected.content, actual['content']) self.assertEqual(2, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test sorting rv = self.client.get( self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id) ) self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted expected = sorted( self.fixtures.answers, key=lambda ans: ans.scores[0].score if len(ans.scores) else 0, reverse=True)[:20] self.assertEqual([a.id for a in expected], [a['id'] for a in result]) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(expected_answers.total, rv.json['total']) # test author filter rv = self.client.get(self.base_url + '?author={}'.format(self.fixtures.students[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id) # test group filter rv = self.client.get(self.base_url + '?group={}'.format(self.fixtures.groups[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), len(self.fixtures.answers) / len(self.fixtures.groups)) # test ids filter ids = {str(a.id) for a in self.fixtures.answers[:3]} rv = self.client.get(self.base_url + '?ids={}'.format(','.join(ids))) self.assert200(rv) result = rv.json['objects'] self.assertEqual(ids, {str(a['id']) for a in result}) # test combined filter rv = self.client.get( self.base_url + '?orderBy={}&group={}'.format( self.fixtures.question.criteria[0].id, self.fixtures.groups[0].id ) ) self.assert200(rv) result = rv.json['objects'] # test the result is paged and sorted answers_per_group = int(len(self.fixtures.answers) / len(self.fixtures.groups)) if len( self.fixtures.groups) else 0 answers = self.fixtures.answers[:answers_per_group] expected = sorted(answers, key=lambda ans: ans.scores[0].score, reverse=True) self.assertEqual([a.id for a in expected], [a['id'] for a in result]) # all filters rv = self.client.get( self.base_url + '?orderBy={}&group={}&author={}&page=1&perPage=20'.format( self.fixtures.question.criteria[0].id, self.fixtures.groups[0].id, self.fixtures.students[0].id ) ) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id) # add instructor answer post = PostsFactory(course=self.fixtures.course, user=self.fixtures.instructor) answer = PostsForAnswersFactory(question=self.fixtures.question, post=post) self.fixtures.answers.append(answer) db.session.commit() rv = self.client.get(self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id)) self.assert200(rv) result = rv.json['objects'] self.assertEqual(len(self.fixtures.answers), rv.json['total']) # first answer should be instructor answer self.assertEqual(self.fixtures.instructor.id, result[0]['user_id']) # test data retrieve before answer period ended with non-privileged user self.fixtures.question.answer_end = datetime.datetime.now() + datetime.timedelta(days=2) db.session.add(self.fixtures.question) db.session.commit() rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(1, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(1, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(1, rv.json['total']) # test data retrieve before answer period ended with privileged user with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url) self.assert200(rv) actual_answers = rv.json['objects'] self.assertEqual(20, len(actual_answers)) self.assertEqual(1, rv.json['page']) self.assertEqual(2, rv.json['pages']) self.assertEqual(20, rv.json['per_page']) self.assertEqual(len(self.fixtures.answers), rv.json['total']) def test_create_answer(self): # test login required expected_answer = {'content': 'this is some answer content'} response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert401(response) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): response = self.client.post(self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(response) with self.login(self.fixtures.unauthorized_instructor.username): response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert403(response) # test invalid format with self.login(self.fixtures.students[0].username): invalid_answer = {'post': {'blah': 'blah'}} response = self.client.post( self.base_url, data=json.dumps(invalid_answer), content_type='application/json') self.assert400(response) # test invalid question response = self.client.post( self._build_url(self.fixtures.course.id, 9392402), data=json.dumps(expected_answer), content_type='application/json') self.assert404(response) # test invalid course response = self.client.post( self._build_url(9392402, self.fixtures.question.id), data=json.dumps(expected_answer), content_type='application/json') self.assert404(response) # test create successful with self.login(self.fixtures.instructor.username): response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) # retrieve again and verify rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor could submit multiple answers for his/her own response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor could submit on behave of a student self.fixtures.add_students(1) expected_answer.update({'user': self.fixtures.students[-1].id}) response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert200(response) rv = json.loads(response.data.decode('utf-8')) actual_answer = PostsForAnswers.query.get(rv['id']) self.assertEqual(expected_answer['content'], actual_answer.post.content) # test instructor can not submit additional answers for a student expected_answer.update({'user': self.fixtures.students[0].id}) response = self.client.post( self.base_url, data=json.dumps(expected_answer), content_type='application/json') self.assert400(response) rv = json.loads(response.data.decode('utf-8')) self.assertEqual({"error": "An answer has already been submitted."}, rv) def test_get_answer(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] # test login required rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.get(self._build_url(999, question_id, '/' + str(answer.id))) self.assert404(rv) # test invalid answer id rv = self.client.get(self._build_url(self.fixtures.course.id, question_id, '/' + str(999))) self.assert404(rv) # test authorized student rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) # test authorized teaching assistant with self.login(self.fixtures.ta.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) # test authorized instructor with self.login(self.fixtures.instructor.username): rv = self.client.get(self.base_url + '/' + str(answer.id)) self.assert200(rv) self.assertEqual(question_id, rv.json['questions_id']) self.assertEqual(answer.post.users_id, rv.json['user_id']) self.assertEqual(answer.post.content, rv.json['content']) def test_edit_answer(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] expected = {'id': str(answer.id), 'content': 'This is an edit'} # test login required rv = self.client.post( self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.fixtures.students[1].username): rv = self.client.post( self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert403(rv) # test invalid course id with self.login(self.fixtures.students[0].username): rv = self.client.post( self._build_url(999, question_id, '/' + str(answer.id)), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid question id rv = self.client.post( self._build_url(self.fixtures.course.id, 999, '/' + str(answer.id)), data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test invalid answer id rv = self.client.post( self.base_url + '/999', data=json.dumps(expected), content_type='application/json') self.assert404(rv) # test unmatched answer id with self.login(self.fixtures.students[1].username): rv = self.client.post( self.base_url + '/' + str(self.fixtures.answers[1].id), data=json.dumps(expected), content_type='application/json') self.assert400(rv) # test edit by author with self.login(self.fixtures.students[0].username): rv = self.client.post( self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.id, rv.json['id']) self.assertEqual('This is an edit', rv.json['content']) # test edit by user that can manage posts expected['content'] = 'This is another edit' with self.login(self.fixtures.instructor.username): rv = self.client.post( self.base_url + '/' + str(answer.id), data=json.dumps(expected), content_type='application/json') self.assert200(rv) self.assertEqual(answer.id, rv.json['id']) self.assertEqual('This is another edit', rv.json['content']) def test_delete_answer(self): answer_id = self.fixtures.answers[0].id # test login required rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert401(rv) # test unauthorized users with self.login(self.fixtures.students[1].username): rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert403(rv) # test invalid answer id with self.login(self.fixtures.students[0].username): rv = self.client.delete(self.base_url + '/999') self.assert404(rv) # test deletion by author rv = self.client.delete(self.base_url + '/' + str(answer_id)) self.assert200(rv) self.assertEqual(answer_id, rv.json['id']) # test deletion by user that can manage posts with self.login(self.fixtures.instructor.username): answer_id2 = self.fixtures.answers[1].id rv = self.client.delete(self.base_url + '/' + str(answer_id2)) self.assert200(rv) self.assertEqual(answer_id2, rv.json['id']) def test_get_user_answers(self): question_id = self.fixtures.questions[0].id answer = self.fixtures.answers[0] url = self._build_url(self.fixtures.course.id, question_id, '/user') # test login required rv = self.client.get(url) self.assert401(rv) # test invalid course with self.login(self.fixtures.students[0].username): rv = self.client.get(self._build_url(999, question_id, '/user')) self.assert404(rv) # test invalid question rv = self.client.get(self._build_url(self.fixtures.course.id, 999, '/user')) self.assert404(rv) # test successful queries rv = self.client.get(url) self.assert200(rv) self.assertEqual(1, len(rv.json['answer'])) self.assertEqual(answer.id, rv.json['answer'][0]['id']) self.assertEqual(answer.post.content, rv.json['answer'][0]['content']) with self.login(self.fixtures.instructor.username): rv = self.client.get(url) self.assert200(rv) self.assertEqual(0, len(rv.json['answer'])) def test_flag_answer(self): answer = self.fixtures.question.answers[0] flag_url = self.base_url + "/" + str(answer.id) + "/flagged" # test login required expected_flag_on = {'flagged': True} expected_flag_off = {'flagged': False} rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert401(rv) # test unauthorized users with self.login(self.fixtures.unauthorized_student.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert403(rv) # test flagging with self.login(self.fixtures.students[0].username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_on['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test unflagging rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") # test prevent unflagging by other students with self.login(self.fixtures.students[0].username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_on), content_type='application/json') self.assert200(rv) # create another student self.fixtures.add_students(1) other_student = self.fixtures.students[-1] # try to unflag answer as other student, should fail with self.login(other_student.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert400(rv) # test allow unflagging by instructor with self.login(self.fixtures.instructor.username): rv = self.client.post( flag_url, data=json.dumps(expected_flag_off), content_type='application/json') self.assert200(rv) self.assertEqual( expected_flag_off['flagged'], rv.json['flagged'], "Expected answer to be flagged.") def test_get_question_answered(self): count_url = self.base_url + '/count' # test login required rv = self.client.get(count_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(count_url) self.assert403(rv) # test invalid course id self.fixtures.add_students(1) with self.login(self.fixtures.students[-1].username): rv = self.client.get('/api/courses/999/questions/1/answers/count') self.assert404(rv) # test invalid question id rv = self.client.get('/api/courses/1/questions/999/answers/count') self.assert404(rv) # test successful query - no answers rv = self.client.get(count_url) self.assert200(rv) self.assertEqual(0, rv.json['answered']) # test successful query - answered with self.login(self.fixtures.students[0].username): rv = self.client.get(count_url) self.assert200(rv) self.assertEqual(1, rv.json['answered']) def test_get_answered_count(self): answered_url = '/api/courses/' + str(self.fixtures.course.id) + '/answers/answered' # test login required rv = self.client.get(answered_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_student.username): rv = self.client.get(answered_url) self.assert403(rv) # test invalid course id self.fixtures.add_students(1) with self.login(self.fixtures.students[-1].username): rv = self.client.get('/api/courses/999/answered') self.assert404(rv) # test successful query - have not answered any questions in the course rv = self.client.get(answered_url) self.assert200(rv) self.assertEqual(0, len(rv.json['answered'])) # test successful query - have submitted one answer per question with self.login(self.fixtures.students[0].username): rv = self.client.get(answered_url) self.assert200(rv) expected = {str(question.id): 1 for question in self.fixtures.questions} self.assertEqual(expected, rv.json['answered']) def test_get_answers_view(self): view_url = \ '/api/courses/' + str(self.fixtures.course.id) + '/questions/' + \ str(self.fixtures.questions[0].id) + '/answers/view' # test login required rv = self.client.get(view_url) self.assert401(rv) # test unauthorized user with self.login(self.fixtures.unauthorized_instructor.username): rv = self.client.get(view_url) self.assert403(rv) # test invalid course id with self.login(self.fixtures.instructor.username): rv = self.client.get('/api/courses/999/questions/' + str(self.fixtures.questions[0].id) + '/answers/view') self.assert404(rv) # test invalid question id rv = self.client.get('/api/courses/' + str(self.fixtures.course.id) + '/questions/999/answers/view') self.assert404(rv) # test successful query rv = self.client.get(view_url) self.assert200(rv) expected = self.fixtures.answers self.assertEqual(30, len(rv.json['answers'])) for i, exp in enumerate(expected): actual = rv.json['answers'][str(exp.id)] self.assertEqual(exp.id, actual['id']) self.assertEqual(exp.post.content, actual['content']) self.assertFalse(actual['file']) # test successful query - student with self.login(self.fixtures.students[0].username): rv = self.client.get(view_url) self.assert200(rv) actual = rv.json['answers'] expected = self.fixtures.answers[0] self.assertEqual(1, len(actual)) self.assertTrue(str(expected.id) in actual) answer = actual[str(expected.id)] self.assertEqual(expected.id, answer['id']) self.assertEqual(expected.post.content, answer['content']) self.assertFalse(answer['file']) self.assertFalse('scores' in answer)