Exemple #1
0
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)
Exemple #3
0
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'])
Exemple #4
0
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)