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