Exemple #1
0
 def setUp(self):
     super(AssignmentAttachmentTests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=25,
                                              num_assignments=1,
                                              num_groups=0,
                                              num_answers=20,
                                              with_draft_student=True)
     self.filePathsToCleanup = []
Exemple #2
0
 def setUp(self):
     super(FileRetrieveTests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=5,
                                              num_assignments=1,
                                              num_groups=0,
                                              num_answers=1,
                                              with_draft_student=True)
     self.files_to_cleanup = []
Exemple #3
0
 def setUp(self):
     super(ComparionExampleAPITests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30,
                                              num_groups=2,
                                              with_draft_student=True)
     self.fixtures.add_comparison_example(self.fixtures.assignment,
                                          self.fixtures.instructor)
     self.fixtures.add_comparison_example(self.fixtures.assignment,
                                          self.fixtures.ta)
     self.base_url = self._build_url(self.fixtures.course.uuid,
                                     self.fixtures.assignment.uuid)
Exemple #4
0
    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))
        ]
Exemple #5
0
    def setUp(self):
        super(TestLTIOutcome, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30,
                                                 num_groups=2,
                                                 with_draft_student=True)
        self.lti_data = LTITestData()
        self.lti_consumer = self.lti_data.lti_consumer

        self.lis_outcome_service_url = "TestUrl.com"
        self.lis_result_sourcedid = "SomeUniqueSourcedId"
        self.grade = 0.8
Exemple #6
0
    def setUp(self):
        super(GradebookAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=10,
                                                 num_groups=5,
                                                 num_group_assignments=1,
                                                 with_comparisons=True)

        self.assignment = self.fixtures.assignment
        self.group_assignment = self.fixtures.assignments[1]

        self.base_url = self._build_url(self.fixtures.course.uuid,
                                        self.fixtures.assignment.uuid)
Exemple #7
0
 def setUp(self):
     super(ReportAPITest, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30,
                                              num_assignments=2,
                                              num_additional_criteria=1,
                                              num_groups=2,
                                              num_answers=25,
                                              with_draft_student=True,
                                              with_comments=True,
                                              with_comparisons=True)
     self.url = "/api/courses/" + self.fixtures.course.uuid + "/report"
     self.files_to_cleanup = []
Exemple #8
0
    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))
        ]
Exemple #9
0
class AssignmentAttachmentTests(ComPAIRAPITestCase):
    base_url = '/app'
    fixtures = None

    def setUp(self):
        super(AssignmentAttachmentTests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=25,
                                                 num_assignments=1,
                                                 num_groups=0,
                                                 num_answers=20,
                                                 with_draft_student=True)
        self.filePathsToCleanup = []

    def tearDown(self):
        for filePath in self.filePathsToCleanup:
            try:
                if os.path.isfile(filePath):
                    os.remove(filePath)
            except Exception as e:
                print(e)

    def _getUrl(self, courseUuid=None, assignmentUuid=None):
        if courseUuid is None:
            courseUuid = self.fixtures.course.uuid
        if assignmentUuid is None:
            assignmentUuid = self.fixtures.assignment.uuid
        return "/api/courses/" + courseUuid + "/assignments/" + assignmentUuid \
            + "/attachments/download"

    # we need to create actual files
    def _createAttachmentsInAssignment(self, assignment):
        uploadDir = current_app.config['ATTACHMENT_UPLOAD_FOLDER']

        for answer in assignment.answers:
            attachFile = self.fixtures.add_file(answer.user)
            attachFilename = attachFile.uuid + '.png'
            attachFile.name = attachFilename
            attachFile.uuid = attachFile.uuid
            attachFilePath = uploadDir + '/' + attachFilename
            img = Image.new('RGB', (60, 30), color='red')
            img.save(attachFilePath)
            self.filePathsToCleanup.append(attachFilePath)
            answer.file_id = attachFile.id
            db.session.add(assignment)
            db.session.add(attachFile)
            db.session.add(answer)
            db.session.commit()

    def test_download_all_attachments_block_unauthorized_users(self):
        # test login required
        rv = self.client.get(self._getUrl())
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self._getUrl())
            self.assert403(rv)

    def test_download_all_attachments_require_valid_course_and_assignment(
            self):
        with self.login(self.fixtures.instructor.username):
            # invalid course
            url = self._getUrl("invalidUuid")
            rv = self.client.get(url)
            self.assert404(rv)
            # invalid assignment
            url = self._getUrl(None, "invalidUuid")
            rv = self.client.get(url)
            self.assert404(rv)

    def test_download_all_attachments_return_msg_if_no_attachments(self):
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self._getUrl())
            self.assert200(rv)
            self.assertEqual('Assignment has no attachments', rv.json['msg'])

    def test_download_all_attachments(self):
        self._createAttachmentsInAssignment(self.fixtures.assignment)
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self._getUrl())
            self.assert200(rv)
            # make sure that we got download path and filename in return
            expectedFilename = '{} [attachments-{}].zip'.format(
                self.fixtures.assignment.name, self.fixtures.assignment.uuid)
            self.assertEqual(expectedFilename, rv.json['filename'])
            self.assertEqual('report/' + expectedFilename, rv.json['file'])
            # check that the actual zip file created exists
            zipFilePath = '{}/{}'.format(current_app.config['REPORT_FOLDER'],
                                         expectedFilename)
            self.assertTrue(os.path.isfile(zipFilePath))
            self.filePathsToCleanup.append(zipFilePath)
            # check that the contents of the zip file are as expected
            archive = zipfile.ZipFile(zipFilePath)
            self.assertEqual(None, archive.testzip())
            actualAttachments = archive.namelist()
            for answer in self.fixtures.assignment.answers:
                if not answer.file_id:
                    continue
                # we don't include inactive, draft, or practice answers
                if not answer.active or answer.draft or answer.practice:
                    continue
                # we don't include dropped students
                if answer.user in self.fixtures.dropped_students:
                    continue
                expectedAttachment = '{} - {} - {}'.format(
                    answer.user.fullname, answer.user.student_number,
                    answer.file.name)
                self.assertTrue(expectedAttachment in actualAttachments)
Exemple #10
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 #11
0
 def setUp(self):
     super(CourseGroupsAPITests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)
Exemple #12
0
class CourseGroupsAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(CourseGroupsAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)

    def test_get_active_groups(self):
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups'

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

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

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            invalid_url = '/api/courses/999/groups'
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test successful query
            rv = self.client.get(url)
            self.assert200(rv)
            actual = rv.json['objects']
            self.assertEqual(len(actual), 3)
            self.assertEqual(actual[0], self.fixtures.groups[0])

        # test TA
        with self.login(self.fixtures.ta.username):
            self.assert200(rv)
            actual = rv.json['objects']
            self.assertEqual(len(actual), 3)
            self.assertEqual(actual[0], self.fixtures.groups[0])

    def test_get_group_members(self):
        course = self.fixtures.course
        group_name = self.fixtures.groups[0]
        url = '/api/courses/'+course.uuid+'/groups/'+group_name

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

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

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get('/api/courses/999/groups/'+group_name)
            self.assert404(rv)

            # test invalid group id
            rv = self.client.get('/api/courses/'+course.uuid+'/groups/asdasdasdasd')
            self.assert404(rv)

            # test authorized instructor
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(10, len(rv.json['students']))
            self.assertEqual(self.fixtures.students[0].uuid, rv.json['students'][0]['id'])

        # test authorized teaching assistant
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(10, len(rv.json['students']))
            self.assertEqual(self.fixtures.students[0].uuid, rv.json['students'][0]['id'])

    def test_group_enrolment(self):
        # frequently used objects
        course = self.fixtures.course
        group_name = self.fixtures.groups[0]

        # test login required
        url = self._create_group_user_url(course, self.fixtures.students[0], group_name)
        rv = self.client.post(url, data={}, content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            url = self._create_group_user_url(course, self.fixtures.students[0], group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test user that is already in group
            url = self._create_group_user_url(course, self.fixtures.students[0], group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['group_name'], group_name)

            # test user that has never been in the group
            url = self._create_group_user_url(course, self.fixtures.instructor, group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['group_name'], group_name)

            # test user that has left the group
            url = self._create_group_user_url(course, self.fixtures.ta, group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['group_name'], group_name)

            # test user that is not enroled in the course anymore - eg. DROPPED
            url = self._create_group_user_url(course, self.fixtures.dropped_instructor, group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert404(rv)

            # test user that has never been in the course
            url = self._create_group_user_url(course, self.fixtures.unauthorized_student, group_name)
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert404(rv)

            # test invalid course id
            url = '/api/courses/999/users/'+self.fixtures.students[0].uuid+'/groups/'+group_name
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert404(rv)

            # test invalid user id
            url = '/api/courses/'+course.uuid+'/users/999/groups/'+group_name
            rv = self.client.post(url, data={}, content_type='application/json')
            self.assert404(rv)

    def test_group_unenrolment(self):
        course = self.fixtures.course

        # test login required
        url = self._create_group_user_url(course, self.fixtures.students[0])
        rv = self.client.delete(url)
        self.assert401(rv)

        # test unauthorzied user
        with self.login(self.fixtures.unauthorized_instructor.username):
            url = self._create_group_user_url(course, self.fixtures.students[0])
            rv = self.client.delete(url)
            self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test user in course
            url = self._create_group_user_url(course, self.fixtures.students[0])
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(rv.json['user_id'], self.fixtures.students[0].uuid)
            self.assertEqual(rv.json['course_id'], course.uuid)

            # test user not in course
            url = self._create_group_user_url(course, self.fixtures.unauthorized_student)
            rv = self.client.delete(url)
            self.assert404(rv)

            # test invalid course id
            url = '/api/courses/999/users/'+self.fixtures.students[0].uuid+'/groups'
            rv = self.client.delete(url)
            self.assert404(rv)

            # test invalid user id
            url = '/api/courses/'+course.uuid+'/users/999/groups'
            rv = self.client.delete(url)
            self.assert404(rv)

    def test_import_groups(self):
        auth_data = ThirdPartyAuthTestData()
        url = '/api/courses/' + self.fixtures.course.uuid + '/groups'

        content = self.fixtures.students[0].username + "," + self.fixtures.groups[0]
        encoded_content = content.encode()
        filename = "groups.csv"

        # test login required
        uploaded_file = io.BytesIO(encoded_content)
        rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
        self.assert401(rv)
        uploaded_file.close()

        # test unauthorized user
        uploaded_file = io.BytesIO(encoded_content)
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        uploaded_file = io.BytesIO(encoded_content)
        with self.login(self.fixtures.ta.username):
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        uploaded_file = io.BytesIO(encoded_content)
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups'
            uploaded_file = io.BytesIO(encoded_content)
            rv = self.client.post(invalid_url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert404(rv)
            uploaded_file.close()

            # test invalid file type
            invalid_file = "groups.png"
            uploaded_file = io.BytesIO(encoded_content)
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, invalid_file)))
            self.assert400(rv)
            uploaded_file.close()

            # test invalid user identifier
            uploaded_file = io.BytesIO(encoded_content)
            rv = self.client.post(url, data=dict(userIdentifier="lastname", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(0, rv.json['success'])
            self.assertEqual({}, rv.json['invalids'][0]['member'])
            self.assertEqual("A valid user identifier is not given.", rv.json['invalids'][0]['message'])
            uploaded_file.close()

            # test missing user identifier
            uploaded_file = io.BytesIO(encoded_content)
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert400(rv)
            uploaded_file.close()

            # test duplicate users in file
            duplicate = "".join([content, "\n", content])
            uploaded_file = io.BytesIO(duplicate.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(1, len(rv.json['invalids']))
            invalid = rv.json['invalids'][0]
            member = [
                '["', self.fixtures.students[0].username, '", "',
                self.fixtures.groups[0], '"]']
            self.assertEqual("".join(member), invalid['member'])
            self.assertEqual("This user already exists in the file.", invalid['message'])
            uploaded_file.close()

            # test missing username
            missing_username = "******" + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(missing_username.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(1, len(rv.json['invalids']))
            invalid = rv.json['invalids'][0]
            member = ['["", "', self.fixtures.groups[0], '"]']
            self.assertEqual("".join(member), invalid['member'])
            self.assertEqual("No user with this ComPAIR username exists.", invalid['message'])
            uploaded_file.close()

            # test missing group name
            missing_group = self.fixtures.students[0].username + ","
            uploaded_file = io.BytesIO(missing_group.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(0, rv.json['success'])
            self.assertEqual(1, len(rv.json['invalids']))
            invalid = rv.json['invalids'][0]
            member = ['["', self.fixtures.students[0].username, '", ""]']
            self.assertEqual("".join(member), invalid['member'])
            self.assertEqual("The group name is invalid.", invalid['message'])
            uploaded_file.close()

            # test invalid user
            invalid_user = "******" + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(invalid_user.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(1, len(rv.json['invalids']))
            invalid = rv.json['invalids'][0]
            member = ['["username9999", "', self.fixtures.groups[0], '"]']
            self.assertEqual("".join(member), invalid['member'])
            self.assertEqual("No user with this ComPAIR username exists.", invalid['message'])
            uploaded_file.close()

            # test successful import with username
            with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(with_username.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(0, len(rv.json['invalids']))
            uploaded_file.close()

            # test invalid import with username
            self.app.config['APP_LOGIN_ENABLED'] = False
            with_username = self.fixtures.students[0].username + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(with_username.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['APP_LOGIN_ENABLED'] = True

            # test successful import with student number
            with_studentno = self.fixtures.students[0].student_number + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(with_studentno.encode())
            rv = self.client.post(url, data=dict(userIdentifier="student_number", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(0, len(rv.json['invalids']))
            uploaded_file.close()

            # test successful import with cas username
            cas_auth = auth_data.create_cas_user_auth(CourseRole.student)
            cas_user = cas_auth.user
            self.fixtures.enrol_user(cas_user, self.fixtures.course, CourseRole.student)

            with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(with_cas_username.encode())
            rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(0, len(rv.json['invalids']))
            uploaded_file.close()

            # test invalid import with cas username
            self.app.config['CAS_LOGIN_ENABLED'] = False
            with_cas_username = cas_auth.unique_identifier + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(with_cas_username.encode())
            rv = self.client.post(url, data=dict(userIdentifier=ThirdPartyType.cas.value, file=(uploaded_file, filename)))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['CAS_LOGIN_ENABLED'] = True

            # test import user not in course
            unauthorized_student = self.fixtures.unauthorized_student.username + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(unauthorized_student.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(1, len(rv.json['invalids']))
            invalid = rv.json['invalids'][0]
            member = [
                '["', self.fixtures.unauthorized_student.username, '", "',
                self.fixtures.groups[0], '"]']
            self.assertEqual("".join(member), invalid['member'])
            self.assertEqual("The user is not enroled in the course", invalid['message'])
            uploaded_file.close()

            # test placing instructor in group
            add_instructor = self.fixtures.instructor.username + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(add_instructor.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(0, len(rv.json['invalids']))
            uploaded_file.close()

            # test placing TA in group
            add_ta = self.fixtures.ta.username + "," + self.fixtures.groups[0]
            uploaded_file = io.BytesIO(add_ta.encode())
            rv = self.client.post(url, data=dict(userIdentifier="username", file=(uploaded_file, filename)))
            self.assert200(rv)
            self.assertEqual(1, rv.json['success'])
            self.assertEqual(0, len(rv.json['invalids']))
            uploaded_file.close()

    def test_group_multiple_enrolment(self):
        # frequently used objects
        course = self.fixtures.course
        group_name = self.fixtures.groups[0]
        group_name_2 = self.fixtures.groups[1]
        student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid]
        url = self._create_group_users_url(course, group_name)

        params = { 'ids': student_ids }

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

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

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            rv = self.client.post('/api/courses/999/users/groups/'+group_name,
                data=json.dumps(params),
                content_type='application/json')
            self.assert404(rv)

            # test missing ids
            rv = self.client.post(url,
                data=json.dumps({'ids': []}),
                content_type='application/json')
            self.assert400(rv)

            # test invalid ids
            rv = self.client.post(url,
                data=json.dumps({'ids': [self.fixtures.unauthorized_student.uuid]}),
                content_type='application/json')
            self.assert400(rv)

            # test enrol users into group
            rv = self.client.post(url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['group_name'], group_name)

            for user_course in course.user_courses:
                if user_course.user_id in student_ids:
                    self.assertEqual(user_course.group_name, group_name)

            # test enrol users into another group
            url = self._create_group_users_url(course, group_name_2)
            rv = self.client.post(url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['group_name'], group_name_2)

            for user_course in course.user_courses:
                if user_course.user_id in student_ids:
                    self.assertEqual(user_course.group_name, group_name_2)

    def test_group_multiple_unenrolment(self):
        course = self.fixtures.course
        url = self._create_group_users_url(course)

        student_ids = [self.fixtures.students[0].uuid, self.fixtures.students[1].uuid]
        params = { 'ids': student_ids }

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

        # test unauthorzied user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            rv = self.client.post('/api/courses/999/users/groups',
                data=json.dumps(params),
                content_type='application/json')
            self.assert404(rv)

            # test missing ids
            rv = self.client.post(url,
                data=json.dumps({ 'ids': [] }),
                content_type='application/json')
            self.assert400(rv)

            # test invalid ids
            rv = self.client.post(url,
                data=json.dumps({ 'ids': [self.fixtures.unauthorized_student.uuid] }),
                content_type='application/json')
            self.assert400(rv)

            # test users in course
            rv = self.client.post(url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_id'], course.uuid)

            for user_course in course.user_courses:
                if user_course.user_id in student_ids:
                    self.assertEqual(user_course.group_name, None)

    def _create_group_user_url(self, course, user, group_name=None):
        url = '/api/courses/'+course.uuid+'/users/'+user.uuid+'/groups'
        if group_name:
            url = url+'/'+group_name
        return url

    def _create_group_users_url(self, course, group_name=None):
        url = '/api/courses/'+course.uuid+'/users/groups'
        if group_name:
            url = url+'/'+group_name
        return url
 def setUp(self):
     super(AnswersAPITests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30, num_groups=2)
     self.base_url = self._build_url(self.fixtures.course.id,
                                     self.fixtures.question.id)
Exemple #14
0
 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()
Exemple #15
0
class FileRetrieveTests(ComPAIRAPITestCase):
    base_url = '/app'
    fixtures = None

    def setUp(self):
        super(FileRetrieveTests, self).setUp()
        self.fixtures = TestFixture().add_course(
            num_students=5, num_assignments=1, num_groups=0,
            num_answers=1, with_draft_student=True)
        self.files_to_cleanup = []

    def tearDown(self):
        folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER']

        for file_name in self.files_to_cleanup:
            file_path = os.path.join(folder, file_name)
            try:
                if os.path.isfile(file_path):
                    os.remove(file_path)
            except Exception as e:
                print(e)

    def test_view_file(self):
        db_file = self.fixtures.add_file(self.fixtures.instructor)
        filename = db_file.name
        url = self.base_url + '/attachment/' + filename

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

        # TODO: no authorization control right now and needs to be added in the future
        # test unauthorized user
        # with self.login(self.fixtures.unauthorized_instructor.username):
        #     rv = self.client.get(url)
        #     self.assert403(rv)

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            # invalid file name (db is not actually touched)
            rv = self.client.get(self.base_url + '/attachment/'+filename)
            self.assert404(rv)
            self.assertEqual('invalid file name', str(rv.get_data(as_text=True)))

            with mock.patch('compair.api.os.path.exists', return_value=True):
                with mock.patch('compair.api.send_file', return_value=make_response("OK")) as mock_send_file:
                    # test all attachment types
                    for extension in ['pdf','mp3','mp4','jpg','jpeg','png']:
                        db_file = self.fixtures.add_file(self.fixtures.instructor, name="file_name."+extension)
                        filename = db_file.name
                        url = self.base_url + '/attachment/' + filename

                        self.client.get(url)
                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                mimetype=db_file.mimetype,
                                attachment_filename=None,
                                as_attachment=False
                            )
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                mimetype=db_file.mimetype,
                                attachment_filename=None,
                                as_attachment=True
                            )
                        mock_send_file.reset_mock()

                        # test overriding attachment filename
                        override_name = "override."+db_file.extension
                        self.client.get(url+"?name="+override_name)

                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                mimetype=db_file.mimetype,
                                attachment_filename=None,
                                as_attachment=False
                            )
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                mimetype=db_file.mimetype,
                                attachment_filename=override_name,
                                as_attachment=True
                            )
                        mock_send_file.reset_mock()


    def test_create_attachment(self):
        url = '/api/attachment'
        test_formats = [
            ('pdf', 'application/pdf'),
            ('mp3', 'audio/mpeg'),
            ('mp4', 'video/mp4'),
            ('jpg', 'image/jpeg'),
            ('jpeg', 'image/jpeg')
        ]

        # test login required
        uploaded_file = io.BytesIO(b"this is a test")
        rv = self.client.post(url, data=dict(file=(uploaded_file, 'alias.pdf')))
        self.assert401(rv)
        uploaded_file.close()

        with self.login(self.fixtures.instructor.username):
            for extension, mimetype in test_formats:
                filename = 'alias.'+extension

                uploaded_file = io.BytesIO(b"this is a test")
                rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
                self.assert200(rv)
                uploaded_file.close()

                actual_file = rv.json['file']
                self.files_to_cleanup.append(actual_file['name'])
                self.assertEqual(actual_file['id']+"."+extension, actual_file['name'])
                self.assertEqual(filename, actual_file['alias'])
                self.assertEqual(extension, actual_file['extension'])
                self.assertEqual(mimetype, actual_file['mimetype'])

    def test_delete_attachment(self):
        # test file not attached to answer or assignment
        db_file = self.fixtures.add_file(self.fixtures.instructor)
        url = '/api/attachment/' + db_file.uuid

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

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

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            # invalid file id
            rv = self.client.delete('/api/attachment/9999')
            self.assert404(rv)

            rv = self.client.delete(url)
            self.assert200(rv)
            actual_file = rv.json
            self.assertEqual(db_file.uuid, actual_file['id'])
            self.assertFalse(db_file.active)

            rv = self.client.delete(url)
            self.assert404(rv)

        # test file attached to assignment
        db_file = self.fixtures.add_file(self.fixtures.ta)
        self.fixtures.assignment.file_id = db_file.id
        db.session.commit()
        url = '/api/attachment/' + db_file.uuid

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

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

        # test unauthorized user
        with self.login(self.fixtures.students[0].username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.delete(url)
            self.assert200(rv)
            actual_file = rv.json
            self.assertEqual(db_file.uuid, actual_file['id'])
            self.assertFalse(db_file.active)

        # test file attached to answer
        student = self.fixtures.answers[0].user
        db_file = self.fixtures.add_file(student)
        self.fixtures.answers[0].file_id = db_file.id
        db.session.commit()
        url = '/api/attachment/' + db_file.uuid

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

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

        # author
        with self.login(student.username):
            rv = self.client.delete(url)
            self.assert200(rv)
            actual_file = rv.json
            self.assertEqual(db_file.uuid, actual_file['id'])
            self.assertFalse(db_file.active)

        # test file attached to answer
        student = self.fixtures.answers[0].user
        db_file = self.fixtures.add_file(student)
        self.fixtures.answers[0].file_id = db_file.id
        db.session.commit()
        url = '/api/attachment/' + db_file.uuid

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.delete(url)
            self.assert200(rv)
            actual_file = rv.json
            self.assertEqual(db_file.uuid, actual_file['id'])
            self.assertFalse(db_file.active)
Exemple #16
0
class GroupsAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(GroupsAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)

    def test_get_groups(self):
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups'

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

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

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            invalid_url = '/api/courses/999/groups'
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test successful query
            rv = self.client.get(url)
            self.assert200(rv)
            actual = rv.json['objects']
            expected = sorted(self.fixtures.groups, key=lambda group: group.name)
            self.assertEqual(len(actual), 4)
            for index, group in enumerate(expected):
                self.assertEqual(actual[index]['id'], group.uuid)

        # test TA
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(url)
            self.assert200(rv)
            actual = rv.json['objects']
            expected = sorted(self.fixtures.groups, key=lambda group: group.name)
            self.assertEqual(len(actual), 4)
            for index, group in enumerate(expected):
                self.assertEqual(actual[index]['id'], group.uuid)

    def test_create_group(self):
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups'

        group_expected = {
            'name': 'Group 101',
        }

        invalid_expected = {
            'name': self.fixtures.groups[1].name,
        }

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups'
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test non-unique name
            rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json')
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Added")
            self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.")

            # test successful query
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert200(rv)

            group_actual = rv.json
            self.assertEqual(group_expected['name'], group_actual['name'])

    def test_edit_group(self):
        group = self.fixtures.groups[0]
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid

        group_expected = {
            'id': group.uuid,
            'name': 'New Group Name',
        }

        invalid_expected = {
            'id': group.uuid,
            'name': self.fixtures.groups[1].name,
        }

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups/'+group.uuid
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test invalid group id
            invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999'
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test non-unique name
            rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json')
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Saved")
            self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.")

            # test successful query
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert200(rv)

            group_actual = rv.json
            self.assertEqual(group_expected['id'], group_actual['id'])
            self.assertEqual(group_expected['name'], group_actual['name'])


    def test_delete_group(self):
        group = self.fixtures.groups[0]
        self.fixtures.add_assignments(num_assignments=1, with_group_answers=True)

        url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.delete(url)
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups/'+group.uuid
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid group id
            invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999'
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test successful query
            rv = self.client.delete(url)
            self.assert200(rv)

            # test cannot delete again
            rv = self.client.delete(url)
            self.assert404(rv)

            # the group answer flags should be false
            rv = self.client.get('/api/courses/'+self.fixtures.course.uuid+'/groups')
            self.assert200(rv)
            for the_group in [g for g in rv.json['objects']]:
                self.assertFalse(the_group['group_answer_exists'])

            # test cannot delete group if it has an answer
            self.fixtures.add_answers()
            group = self.fixtures.groups[1]
            url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid
            rv = self.client.delete(url)
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Deleted")
            self.assertEqual(rv.json['message'], "Sorry, you cannot remove groups that have submitted answers.")

            # verify the group answer exists flag
            rv = self.client.get('/api/courses/'+self.fixtures.course.uuid+'/groups')
            self.assert200(rv)
            the_group = [g for g in rv.json['objects'] if g['id'] == group.uuid]
            self.assertEqual(len(the_group), 1)
            self.assertTrue(the_group[0]['group_answer_exists'])

            # verfiy that after group is deleted, can re-create it with same name
            deleted_group = self.fixtures.groups[0]
            new_group = {
                'name': deleted_group.name,
            }
            rv = self.client.post('/api/courses/'+self.fixtures.course.uuid+'/groups', data=json.dumps(new_group), content_type='application/json')
            self.assert200(rv)
Exemple #17
0
class FileRetrieveTests(ComPAIRAPITestCase):
    base_url = '/app'
    fixtures = None

    def setUp(self):
        super(FileRetrieveTests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=5,
                                                 num_assignments=1,
                                                 num_groups=0,
                                                 num_answers=1,
                                                 with_draft_student=True)
        self.files_to_cleanup = []

    def tearDown(self):
        folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER']

        for file_name in self.files_to_cleanup:
            file_path = os.path.join(folder, file_name)
            try:
                if os.path.isfile(file_path):
                    os.remove(file_path)
            except Exception as e:
                print(e)

    def test_view_file(self):
        db_file = self.fixtures.add_file(self.fixtures.instructor)
        filename = db_file.name
        url = self.base_url + '/attachment/' + filename

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

        # TODO: no authorization control right now and needs to be added in the future
        # test unauthorized user
        # with self.login(self.fixtures.unauthorized_instructor.username):
        #     rv = self.client.get(url)
        #     self.assert403(rv)

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            # invalid file name (db is not actually touched)
            rv = self.client.get(self.base_url + '/attachment/' + filename)
            self.assert404(rv)
            self.assertEqual('invalid file name',
                             text_type(rv.get_data(as_text=True)))

            with mock.patch('compair.api.os.path.exists', return_value=True):
                with mock.patch(
                        'compair.api.send_file',
                        return_value=make_response("OK")) as mock_send_file:
                    # test all attachment types
                    extensions = [('pdf', 'application/pdf'),
                                  ('mp3', 'audio/mpeg'), ('mp4', 'video/mp4'),
                                  ('jpg', 'image/jpeg'),
                                  ('jpeg', 'image/jpeg'), ('png', 'image/png')]
                    for (extension, mimetype) in extensions:
                        db_file = self.fixtures.add_file(
                            self.fixtures.instructor,
                            name="file_name." + extension)
                        filename = db_file.name
                        url = self.base_url + '/attachment/' + filename

                        self.client.get(url)
                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(
                                    current_app.
                                    config['ATTACHMENT_UPLOAD_FOLDER'],
                                    filename),
                                attachment_filename=None,
                                as_attachment=False,
                                mimetype=mimetype)
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(
                                    current_app.
                                    config['ATTACHMENT_UPLOAD_FOLDER'],
                                    filename),
                                attachment_filename=None,
                                as_attachment=True,
                                mimetype=mimetype)
                        mock_send_file.reset_mock()

                        # test overriding attachment filename
                        override_name = "override." + db_file.extension
                        self.client.get(url + "?name=" + override_name)

                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(
                                    current_app.
                                    config['ATTACHMENT_UPLOAD_FOLDER'],
                                    filename),
                                attachment_filename=None,
                                as_attachment=False,
                                mimetype=mimetype)
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(
                                    current_app.
                                    config['ATTACHMENT_UPLOAD_FOLDER'],
                                    filename),
                                attachment_filename=override_name,
                                as_attachment=True,
                                mimetype=mimetype)
                        mock_send_file.reset_mock()

    def test_create_attachment(self):
        url = '/api/attachment'
        test_formats = [('pdf', 'application/pdf'), ('mp3', 'audio/mpeg'),
                        ('mp4', 'video/mp4'), ('webm', 'video/webm'),
                        ('jpg', 'image/jpeg'), ('jpeg', 'image/jpeg')]

        # test login required
        uploaded_file = io.BytesIO(b"this is a test")
        rv = self.client.post(url,
                              data=dict(file=(uploaded_file, 'alias.pdf')))
        self.assert401(rv)
        uploaded_file.close()

        with self.login(self.fixtures.instructor.username):
            # test no file uploaded
            filename = 'alias.pdf'
            rv = self.client.post(url, data=dict())
            self.assert400(rv)
            self.assertEqual("File Not Uploaded", rv.json['title'])
            self.assertEqual(
                "Sorry, no file was found to upload. Please try uploading again.",
                rv.json['message'])

            # test no file uploaded
            filename = 'alias.xyz'
            uploaded_file = io.BytesIO(b"this is a test")
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert400(rv)
            self.assertEqual("File Not Uploaded", rv.json['title'])
            self.assertEqual(
                "Please try again with an approved file type, which includes: JPEG, JPG, MP3, MP4, PDF, PNG, WEBM.",
                rv.json['message'])

            for extension, mimetype in test_formats:
                filename = 'alias.' + extension

                uploaded_file = io.BytesIO(b"this is a test")
                rv = self.client.post(url,
                                      data=dict(file=(uploaded_file,
                                                      filename)))
                self.assert200(rv)
                uploaded_file.close()

                actual_file = rv.json['file']
                self.files_to_cleanup.append(actual_file['name'])
                self.assertEqual(actual_file['id'] + "." + extension.lower(),
                                 actual_file['name'])
                self.assertEqual(filename, actual_file['alias'])
                self.assertEqual(extension.lower(), actual_file['extension'])
                self.assertEqual(mimetype, actual_file['mimetype'])

                # test with uppercase extension
                filename = 'alias.' + extension.upper()

                uploaded_file = io.BytesIO(b"this is a test")
                rv = self.client.post(url,
                                      data=dict(file=(uploaded_file,
                                                      filename)))
                self.assert200(rv)
                uploaded_file.close()

                actual_file = rv.json['file']
                self.files_to_cleanup.append(actual_file['name'])
                self.assertEqual(actual_file['id'] + "." + extension.lower(),
                                 actual_file['name'])
                self.assertEqual(filename, actual_file['alias'])
                self.assertEqual(extension.lower(), actual_file['extension'])
                self.assertEqual(mimetype, actual_file['mimetype'])

    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start')
    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end')
    @mock.patch('compair.kaltura.upload_token.UploadToken._api_add')
    def test_get_kaltura(self, mocked_upload_token_add,
                         mocked_kaltura_session_end,
                         mocked_kaltura_session_start):
        url = '/api/attachment/kaltura'
        current_app.config['KALTURA_ENABLED'] = True
        current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com"
        current_app.config['KALTURA_PARTNER_ID'] = 123
        current_app.config['KALTURA_USER_ID'] = "*****@*****.**"
        current_app.config['KALTURA_SECRET'] = "abc"
        current_app.config['KALTURA_PLAYER_ID'] = 456

        mocked_kaltura_session_start.return_value = "ks_mock"
        mocked_kaltura_session_end.return_value = {}
        mocked_upload_token_add.return_value = {"id": "mocked_upload_token_id"}
        expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id&ks=ks_mock"

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

        with self.login(self.fixtures.instructor.username):
            # test kaltura disabled
            current_app.config['KALTURA_ENABLED'] = False
            rv = self.client.get(url)
            self.assert400(rv)

            # test kaltura enabled
            current_app.config['KALTURA_ENABLED'] = True
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call(
                "*****@*****.**",
                privileges=
                "edit:mocked_upload_token_id,urirestrict:/api_v3/service/uploadtoken/action/upload*"
            )
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 1)
            self.assertEqual(kaltura_media_items[0].user_id,
                             self.fixtures.instructor.id)
            self.assertEqual(kaltura_media_items[0].partner_id, 123)
            self.assertEqual(kaltura_media_items[0].player_id, 456)
            self.assertEqual(kaltura_media_items[0].upload_ks, "ks_mock")
            self.assertEqual(kaltura_media_items[0].upload_token_id,
                             "mocked_upload_token_id")
            self.assertIsNone(kaltura_media_items[0].file_name)
            self.assertIsNone(kaltura_media_items[0].entry_id)
            self.assertIsNone(kaltura_media_items[0].download_url)

            # use global unique identifer (user has no global unique identifer)
            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            mocked_upload_token_add.return_value = {
                "id": "mocked_upload_token_id2"
            }
            expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id2&ks=ks_mock"
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call(
                "*****@*****.**",
                privileges=
                "edit:mocked_upload_token_id2,urirestrict:/api_v3/service/uploadtoken/action/upload*"
            )
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 2)
            self.assertEqual(kaltura_media_items[1].user_id,
                             self.fixtures.instructor.id)

            # use global unique identifer (user has global unique identifer)
            self.fixtures.instructor.global_unique_identifier = "*****@*****.**"
            mocked_upload_token_add.return_value = {
                "id": "mocked_upload_token_id3"
            }
            expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id3&ks=ks_mock"
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call(
                "*****@*****.**",
                privileges=
                "edit:mocked_upload_token_id3,urirestrict:/api_v3/service/uploadtoken/action/upload*"
            )
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 3)
            self.assertEqual(kaltura_media_items[2].user_id,
                             self.fixtures.instructor.id)

            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False

    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start')
    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end')
    @mock.patch('compair.kaltura.upload_token.UploadToken._api_get')
    @mock.patch('compair.kaltura.media.Media._api_add')
    @mock.patch('compair.kaltura.media.Media._api_add_content')
    def test_post_kaltura(self, mocked_kaltura_media_add_content,
                          mocked_kaltura_media_add, mocked_upload_token_get,
                          mocked_kaltura_session_end,
                          mocked_kaltura_session_start):
        url = '/api/attachment/kaltura/mocked_upload_token_id'
        invalid_url = '/api/attachment/kaltura/mocked_upload_token_id_invalid'
        current_app.config['KALTURA_ENABLED'] = True
        current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com"
        current_app.config['KALTURA_PARTNER_ID'] = 123
        current_app.config['KALTURA_USER_ID'] = "*****@*****.**"
        current_app.config['KALTURA_SECRET'] = "abc"
        current_app.config['KALTURA_PLAYER_ID'] = 456

        mocked_kaltura_session_start.return_value = "ks_mock"
        mocked_kaltura_session_end.return_value = {}
        mocked_upload_token_get.return_value = {
            "id": "mocked_upload_token_id",
            "fileName": "uploaded_audio_file.mp3"
        }
        mocked_kaltura_media_add.return_value = {"id": "mock_entry_id"}
        mocked_kaltura_media_add_content.return_value = {
            "id": "mock_entry_id",
            "downloadUrl": "http://www.download/url.com"
        }

        kaltura_media = KalturaMedia(user=self.fixtures.instructor,
                                     service_url="https://www.kaltura.com",
                                     partner_id=123,
                                     player_id=456,
                                     upload_ks="upload_ks_mock",
                                     upload_token_id="mocked_upload_token_id")
        db.session.add(kaltura_media)

        kaltura_media2 = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock2",
            upload_token_id="mocked_upload_token_id2")
        db.session.add(kaltura_media)

        kaltura_media3 = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock3",
            upload_token_id="mocked_upload_token_id3")
        db.session.add(kaltura_media)

        invalid_kaltura_media = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock",
            upload_token_id="mocked_upload_token_id_invalid",
            entry_id="def")
        db.session.add(invalid_kaltura_media)
        db.session.commit()

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

        with self.login(self.fixtures.instructor.username):
            # test kaltura disabled
            current_app.config['KALTURA_ENABLED'] = False
            rv = self.client.post(url)
            self.assert400(rv)

            # test invalid upload_token_id
            current_app.config['KALTURA_ENABLED'] = True
            rv = self.client.post(invalid_url)
            self.assert400(rv)

            # test valid
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'],
                             kaltura_media.files.all()[0].uuid)
            self.assertEqual(kaltura_media.file_name,
                             "uploaded_audio_file.mp3")
            self.assertEqual(kaltura_media.entry_id, "mock_entry_id")
            self.assertEqual(kaltura_media.download_url,
                             "http://www.download/url.com")
            self.assertEqual(kaltura_media.service_url,
                             "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with(
                "*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with(
                "ks_mock", "mocked_upload_token_id")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with(
                "ks_mock", "mock_entry_id", "mocked_upload_token_id")
            mocked_kaltura_media_add_content.reset_mock()

            # test direct download from kaltura via /attachment
            kaltura_attachment_url = self.base_url + '/attachment/' + rv.json[
                'file']['name'] + '?name=uploaded_audio_file.mp3'
            rv = self.client.get(kaltura_attachment_url)
            self.assertTrue(rv.location.startswith(
                kaltura_media.download_url))  # redirecting to Kaltura
            mocked_kaltura_session_start.assert_called_once_with("*****@*****.**", \
                expiry=60, \
                privileges='sview:'+kaltura_media.entry_id+',urirestrict:/url.com/*'
                )
            mocked_kaltura_session_start.reset_mock()

            # use global unique identifer (user has no global unique identifer)
            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            url = '/api/attachment/kaltura/mocked_upload_token_id2'
            mocked_upload_token_get.return_value = {
                "id": "mocked_upload_token_id2",
                "fileName": "uploaded_audio_file2.mp3"
            }
            mocked_kaltura_media_add.return_value = {"id": "mock_entry_id2"}
            mocked_kaltura_media_add_content.return_value = {
                "id": "mock_entry_id2",
                "downloadUrl": "www.download/url2.com"
            }
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'],
                             kaltura_media2.files.all()[0].uuid)
            self.assertEqual(kaltura_media2.file_name,
                             "uploaded_audio_file2.mp3")
            self.assertEqual(kaltura_media2.entry_id, "mock_entry_id2")
            self.assertEqual(kaltura_media2.download_url,
                             "www.download/url2.com")
            self.assertEqual(kaltura_media2.service_url,
                             "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with(
                "*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock2")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with(
                "ks_mock", "mocked_upload_token_id2")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with(
                "ks_mock", "mock_entry_id2", "mocked_upload_token_id2")
            mocked_kaltura_media_add_content.reset_mock()

            # use global unique identifer (user has global unique identifer)
            self.fixtures.instructor.global_unique_identifier = "*****@*****.**"
            url = '/api/attachment/kaltura/mocked_upload_token_id3'
            mocked_upload_token_get.return_value = {
                "id": "mocked_upload_token_id3",
                "fileName": "uploaded_audio_file3.mp3"
            }
            mocked_kaltura_media_add.return_value = {"id": "mock_entry_id3"}
            mocked_kaltura_media_add_content.return_value = {
                "id": "mock_entry_id3",
                "downloadUrl": "www.download/url3.com"
            }
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'],
                             kaltura_media3.files.all()[0].uuid)
            self.assertEqual(kaltura_media3.file_name,
                             "uploaded_audio_file3.mp3")
            self.assertEqual(kaltura_media3.entry_id, "mock_entry_id3")
            self.assertEqual(kaltura_media3.download_url,
                             "www.download/url3.com")
            self.assertEqual(kaltura_media3.service_url,
                             "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with(
                "*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock3")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with(
                "ks_mock", "mocked_upload_token_id3")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with(
                "ks_mock", "mock_entry_id3", "mocked_upload_token_id3")
            mocked_kaltura_media_add_content.reset_mock()

            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
 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)
Exemple #19
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
Exemple #20
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
Exemple #21
0
class FileRetrieveTests(ComPAIRAPITestCase):
    base_url = '/app'
    fixtures = None

    def setUp(self):
        super(FileRetrieveTests, self).setUp()
        self.fixtures = TestFixture().add_course(
            num_students=5, num_assignments=1, num_groups=0,
            num_answers=1, with_draft_student=True)
        self.files_to_cleanup = []

    def tearDown(self):
        folder = current_app.config['ATTACHMENT_UPLOAD_FOLDER']

        for file_name in self.files_to_cleanup:
            file_path = os.path.join(folder, file_name)
            try:
                if os.path.isfile(file_path):
                    os.remove(file_path)
            except Exception as e:
                print(e)

    def test_view_file(self):
        db_file = self.fixtures.add_file(self.fixtures.instructor)
        filename = db_file.name
        url = self.base_url + '/attachment/' + filename

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

        # TODO: no authorization control right now and needs to be added in the future
        # test unauthorized user
        # with self.login(self.fixtures.unauthorized_instructor.username):
        #     rv = self.client.get(url)
        #     self.assert403(rv)

        # valid instructor
        with self.login(self.fixtures.instructor.username):
            # invalid file name (db is not actually touched)
            rv = self.client.get(self.base_url + '/attachment/'+filename)
            self.assert404(rv)
            self.assertEqual('invalid file name', text_type(rv.get_data(as_text=True)))

            with mock.patch('compair.api.os.path.exists', return_value=True):
                with mock.patch('compair.api.send_file', return_value=make_response("OK")) as mock_send_file:
                    # test all attachment types
                    extensions = [
                        ('pdf', 'application/pdf'),
                        ('mp3', 'audio/mpeg'),
                        ('mp4', 'video/mp4'),
                        ('jpg', 'image/jpeg'),
                        ('jpeg', 'image/jpeg'),
                        ('png', 'image/png')
                    ]
                    for (extension, mimetype) in extensions:
                        db_file = self.fixtures.add_file(self.fixtures.instructor, name="file_name."+extension)
                        filename = db_file.name
                        url = self.base_url + '/attachment/' + filename

                        self.client.get(url)
                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                attachment_filename=None,
                                as_attachment=False,
                                mimetype=mimetype
                            )
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                attachment_filename=None,
                                as_attachment=True,
                                mimetype=mimetype
                            )
                        mock_send_file.reset_mock()

                        # test overriding attachment filename
                        override_name = "override."+db_file.extension
                        self.client.get(url+"?name="+override_name)

                        if extension == 'pdf':
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                attachment_filename=None,
                                as_attachment=False,
                                mimetype=mimetype
                            )
                        else:
                            mock_send_file.assert_called_once_with(
                                '{}/{}'.format(current_app.config['ATTACHMENT_UPLOAD_FOLDER'], filename),
                                attachment_filename=override_name,
                                as_attachment=True,
                                mimetype=mimetype
                            )
                        mock_send_file.reset_mock()


    def test_create_attachment(self):
        url = '/api/attachment'
        test_formats = [
            ('pdf', 'application/pdf'),
            ('mp3', 'audio/mpeg'),
            ('mp4', 'video/mp4'),
            ('webm', 'video/webm'),
            ('jpg', 'image/jpeg'),
            ('jpeg', 'image/jpeg')
        ]

        # test login required
        uploaded_file = io.BytesIO(b"this is a test")
        rv = self.client.post(url, data=dict(file=(uploaded_file, 'alias.pdf')))
        self.assert401(rv)
        uploaded_file.close()

        with self.login(self.fixtures.instructor.username):
            # test no file uploaded
            filename = 'alias.pdf'
            rv = self.client.post(url, data=dict())
            self.assert400(rv)
            self.assertEqual("File Not Uploaded", rv.json['title'])
            self.assertEqual("Sorry, no file was found to upload. Please try uploading again.", rv.json['message'])

            # test no file uploaded
            filename = 'alias.xyz'
            uploaded_file = io.BytesIO(b"this is a test")
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert400(rv)
            self.assertEqual("File Not Uploaded", rv.json['title'])
            self.assertEqual("Please try again with an approved file type, which includes: JPEG, JPG, MP3, MP4, PDF, PNG, WEBM.",
                rv.json['message'])

            for extension, mimetype in test_formats:
                filename = 'alias.'+extension

                uploaded_file = io.BytesIO(b"this is a test")
                rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
                self.assert200(rv)
                uploaded_file.close()

                actual_file = rv.json['file']
                self.files_to_cleanup.append(actual_file['name'])
                self.assertEqual(actual_file['id']+"."+extension.lower(), actual_file['name'])
                self.assertEqual(filename, actual_file['alias'])
                self.assertEqual(extension.lower(), actual_file['extension'])
                self.assertEqual(mimetype, actual_file['mimetype'])

                # test with uppercase extension
                filename = 'alias.'+extension.upper()

                uploaded_file = io.BytesIO(b"this is a test")
                rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
                self.assert200(rv)
                uploaded_file.close()

                actual_file = rv.json['file']
                self.files_to_cleanup.append(actual_file['name'])
                self.assertEqual(actual_file['id']+"."+extension.lower(), actual_file['name'])
                self.assertEqual(filename, actual_file['alias'])
                self.assertEqual(extension.lower(), actual_file['extension'])
                self.assertEqual(mimetype, actual_file['mimetype'])


    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start')
    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end')
    @mock.patch('compair.kaltura.upload_token.UploadToken._api_add')
    def test_get_kaltura(self, mocked_upload_token_add, mocked_kaltura_session_end, mocked_kaltura_session_start):
        url = '/api/attachment/kaltura'
        current_app.config['KALTURA_ENABLED'] = True
        current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com"
        current_app.config['KALTURA_PARTNER_ID'] = 123
        current_app.config['KALTURA_USER_ID'] = "*****@*****.**"
        current_app.config['KALTURA_SECRET'] = "abc"
        current_app.config['KALTURA_PLAYER_ID'] = 456

        mocked_kaltura_session_start.return_value = "ks_mock"
        mocked_kaltura_session_end.return_value = {}
        mocked_upload_token_add.return_value = {
            "id": "mocked_upload_token_id"
        }
        expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id&ks=ks_mock"


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

        with self.login(self.fixtures.instructor.username):
            # test kaltura disabled
            current_app.config['KALTURA_ENABLED'] = False
            rv = self.client.get(url)
            self.assert400(rv)

            # test kaltura enabled
            current_app.config['KALTURA_ENABLED'] = True
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call("*****@*****.**",
                privileges="edit:mocked_upload_token_id,urirestrict:/api_v3/service/uploadtoken/action/upload*")
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 1)
            self.assertEqual(kaltura_media_items[0].user_id, self.fixtures.instructor.id)
            self.assertEqual(kaltura_media_items[0].partner_id, 123)
            self.assertEqual(kaltura_media_items[0].player_id, 456)
            self.assertEqual(kaltura_media_items[0].upload_ks, "ks_mock")
            self.assertEqual(kaltura_media_items[0].upload_token_id, "mocked_upload_token_id")
            self.assertIsNone(kaltura_media_items[0].file_name)
            self.assertIsNone(kaltura_media_items[0].entry_id)
            self.assertIsNone(kaltura_media_items[0].download_url)

            # use global unique identifer (user has no global unique identifer)
            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            mocked_upload_token_add.return_value = {
                "id": "mocked_upload_token_id2"
            }
            expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id2&ks=ks_mock"
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call("*****@*****.**",
                privileges="edit:mocked_upload_token_id2,urirestrict:/api_v3/service/uploadtoken/action/upload*")
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 2)
            self.assertEqual(kaltura_media_items[1].user_id, self.fixtures.instructor.id)


            # use global unique identifer (user has global unique identifer)
            self.fixtures.instructor.global_unique_identifier = "*****@*****.**"
            mocked_upload_token_add.return_value = {
                "id": "mocked_upload_token_id3"
            }
            expected_upload_url = "https://www.kaltura.com/api_v3/service/uploadtoken/action/upload?format=1&uploadTokenId=mocked_upload_token_id3&ks=ks_mock"
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(rv.json['upload_url'], expected_upload_url)

            self.assertEqual(mocked_kaltura_session_start.call_count, 2)
            mocked_kaltura_session_start.assert_any_call("*****@*****.**")
            mocked_kaltura_session_start.assert_any_call("*****@*****.**",
                privileges="edit:mocked_upload_token_id3,urirestrict:/api_v3/service/uploadtoken/action/upload*")
            mocked_kaltura_session_start.reset_mock()

            mocked_kaltura_session_end.assert_called_once_with("ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_add.assert_called_once_with("ks_mock")
            mocked_upload_token_add.reset_mock()

            kaltura_media_items = KalturaMedia.query.all()
            self.assertEqual(len(kaltura_media_items), 3)
            self.assertEqual(kaltura_media_items[2].user_id, self.fixtures.instructor.id)

            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False

    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_start')
    @mock.patch('compair.kaltura.kaltura_session.KalturaSession._api_end')
    @mock.patch('compair.kaltura.upload_token.UploadToken._api_get')
    @mock.patch('compair.kaltura.media.Media._api_add')
    @mock.patch('compair.kaltura.media.Media._api_add_content')
    def test_post_kaltura(self, mocked_kaltura_media_add_content, mocked_kaltura_media_add, mocked_upload_token_get,
            mocked_kaltura_session_end, mocked_kaltura_session_start):
        url = '/api/attachment/kaltura/mocked_upload_token_id'
        invalid_url = '/api/attachment/kaltura/mocked_upload_token_id_invalid'
        current_app.config['KALTURA_ENABLED'] = True
        current_app.config['KALTURA_SERVICE_URL'] = "https://www.kaltura.com"
        current_app.config['KALTURA_PARTNER_ID'] = 123
        current_app.config['KALTURA_USER_ID'] = "*****@*****.**"
        current_app.config['KALTURA_SECRET'] = "abc"
        current_app.config['KALTURA_PLAYER_ID'] = 456

        mocked_kaltura_session_start.return_value = "ks_mock"
        mocked_kaltura_session_end.return_value = {}
        mocked_upload_token_get.return_value = {
            "id": "mocked_upload_token_id",
            "fileName": "uploaded_audio_file.mp3"
        }
        mocked_kaltura_media_add.return_value = {
            "id": "mock_entry_id"
        }
        mocked_kaltura_media_add_content.return_value = {
            "id": "mock_entry_id",
            "downloadUrl": "http://www.download/url.com"
        }

        kaltura_media = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock",
            upload_token_id="mocked_upload_token_id"
        )
        db.session.add(kaltura_media)

        kaltura_media2 = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock2",
            upload_token_id="mocked_upload_token_id2"
        )
        db.session.add(kaltura_media)

        kaltura_media3 = KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock3",
            upload_token_id="mocked_upload_token_id3"
        )
        db.session.add(kaltura_media)

        invalid_kaltura_media =  KalturaMedia(
            user=self.fixtures.instructor,
            service_url="https://www.kaltura.com",
            partner_id=123,
            player_id=456,
            upload_ks="upload_ks_mock",
            upload_token_id="mocked_upload_token_id_invalid",
            entry_id="def"
        )
        db.session.add(invalid_kaltura_media)
        db.session.commit()

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

        with self.login(self.fixtures.instructor.username):
            # test kaltura disabled
            current_app.config['KALTURA_ENABLED'] = False
            rv = self.client.post(url)
            self.assert400(rv)

            # test invalid upload_token_id
            current_app.config['KALTURA_ENABLED'] = True
            rv = self.client.post(invalid_url)
            self.assert400(rv)

            # test valid
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'], kaltura_media.files.all()[0].uuid)
            self.assertEqual(kaltura_media.file_name, "uploaded_audio_file.mp3")
            self.assertEqual(kaltura_media.entry_id, "mock_entry_id")
            self.assertEqual(kaltura_media.download_url, "http://www.download/url.com")
            self.assertEqual(kaltura_media.service_url, "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with("*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id", "mocked_upload_token_id")
            mocked_kaltura_media_add_content.reset_mock()

            # test direct download from kaltura via /attachment
            kaltura_attachment_url = self.base_url + '/attachment/' + rv.json['file']['name'] + '?name=uploaded_audio_file.mp3'
            rv = self.client.get(kaltura_attachment_url)
            self.assertTrue(rv.location.startswith(kaltura_media.download_url))  # redirecting to Kaltura
            mocked_kaltura_session_start.assert_called_once_with("*****@*****.**", \
                expiry=60, \
                privileges='sview:'+kaltura_media.entry_id+',urirestrict:/url.com/*'
                )
            mocked_kaltura_session_start.reset_mock()

            # use global unique identifer (user has no global unique identifer)
            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            url = '/api/attachment/kaltura/mocked_upload_token_id2'
            mocked_upload_token_get.return_value = {
                "id": "mocked_upload_token_id2",
                "fileName": "uploaded_audio_file2.mp3"
            }
            mocked_kaltura_media_add.return_value = {
                "id": "mock_entry_id2"
            }
            mocked_kaltura_media_add_content.return_value = {
                "id": "mock_entry_id2",
                "downloadUrl": "www.download/url2.com"
            }
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'], kaltura_media2.files.all()[0].uuid)
            self.assertEqual(kaltura_media2.file_name, "uploaded_audio_file2.mp3")
            self.assertEqual(kaltura_media2.entry_id, "mock_entry_id2")
            self.assertEqual(kaltura_media2.download_url, "www.download/url2.com")
            self.assertEqual(kaltura_media2.service_url, "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with("*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock2")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id2")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id2", "mocked_upload_token_id2")
            mocked_kaltura_media_add_content.reset_mock()

            # use global unique identifer (user has global unique identifer)
            self.fixtures.instructor.global_unique_identifier = "*****@*****.**"
            url = '/api/attachment/kaltura/mocked_upload_token_id3'
            mocked_upload_token_get.return_value = {
                "id": "mocked_upload_token_id3",
                "fileName": "uploaded_audio_file3.mp3"
            }
            mocked_kaltura_media_add.return_value = {
                "id": "mock_entry_id3"
            }
            mocked_kaltura_media_add_content.return_value = {
                "id": "mock_entry_id3",
                "downloadUrl": "www.download/url3.com"
            }
            rv = self.client.post(url)
            self.assert200(rv)
            self.assertEqual(rv.json['file']['id'], kaltura_media3.files.all()[0].uuid)
            self.assertEqual(kaltura_media3.file_name, "uploaded_audio_file3.mp3")
            self.assertEqual(kaltura_media3.entry_id, "mock_entry_id3")
            self.assertEqual(kaltura_media3.download_url, "www.download/url3.com")
            self.assertEqual(kaltura_media3.service_url, "https://www.kaltura.com")

            mocked_kaltura_session_start.assert_called_once_with("*****@*****.**")
            mocked_kaltura_session_start.reset_mock()

            self.assertEqual(mocked_kaltura_session_end.call_count, 2)
            mocked_kaltura_session_end.assert_any_call("ks_mock")
            mocked_kaltura_session_end.assert_any_call("upload_ks_mock3")
            mocked_kaltura_session_end.reset_mock()

            mocked_upload_token_get.assert_called_once_with("ks_mock", "mocked_upload_token_id3")
            mocked_upload_token_get.reset_mock()

            mocked_kaltura_media_add.assert_called_once_with("ks_mock", 5)
            mocked_kaltura_media_add.reset_mock()

            mocked_kaltura_media_add_content.assert_called_once_with("ks_mock", "mock_entry_id3", "mocked_upload_token_id3")
            mocked_kaltura_media_add_content.reset_mock()

            current_app.config['KALTURA_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
class ComparionExampleAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(ComparionExampleAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True)
        self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.instructor)
        self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.ta)
        self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid)

    def _build_url(self, course_uuid, assignment_uuid, tail=""):
        url = '/api/courses/' + course_uuid + '/assignments/' + assignment_uuid + '/comparisons/examples' + tail
        return url

    def test_get_all_comparison_examples(self):
        # Test login required
        rv = self.client.get(self.base_url)
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.get(self.base_url)
                    self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test non-existent entry
            rv = self.client.get(self._build_url(self.fixtures.course.uuid, "4903409"))
            self.assert404(rv)
            rv = self.client.get(self._build_url("4903409", self.fixtures.assignment.uuid))
            self.assert404(rv)

            rv = self.client.get(self.base_url)
            self.assert200(rv)
            results = rv.json['objects']
            self.assertEqual(len(results), 2)
            comparison_examples = self.fixtures.assignment.comparison_examples
            for index, comparison_example in enumerate(comparison_examples):
                self.assertEqual(results[index]['id'], comparison_example.uuid)
                self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid)
                self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid)

        # ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            results = rv.json['objects']
            self.assertEqual(len(results), 2)
            comparison_examples = self.fixtures.assignment.comparison_examples
            for index, comparison_example in enumerate(comparison_examples):
                self.assertEqual(results[index]['id'], comparison_example.uuid)
                self.assertEqual(results[index]['answer1_id'], comparison_example.answer1_uuid)
                self.assertEqual(results[index]['answer2_id'], comparison_example.answer2_uuid)

    def test_create_comparison_example(self):
        self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.instructor)
        self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta)

        expected_comparison_example = {
            'answer1_id': self.fixtures.answers[-2].uuid,
            'answer2_id': self.fixtures.answers[-1].uuid
        }

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

        # test unauthorized users
        student = self.fixtures.unauthorized_student
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(DefaultFixture.ROOT_USER, student)]:
            with user_context:
                rv = self.client.post(
                    self.base_url,
                    data=json.dumps(expected_comparison_example),
                    content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(
                self.base_url,
                data=json.dumps(expected_comparison_example),
                content_type='application/json')
            self.assert403(rv)

        student = self.fixtures.students[0]
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(
                    self.base_url,
                    data=json.dumps(expected_comparison_example),
                    content_type='application/json')
                self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test non-existent entry
            rv = self.client.post(
                self._build_url(self.fixtures.course.uuid, "4903409"),
                data=json.dumps(expected_comparison_example),
                content_type='application/json')
            self.assert404(rv)

            rv = self.client.post(
                self._build_url("4903409", self.fixtures.assignment.uuid),
                data=json.dumps(expected_comparison_example),
                content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer1_id
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer1_id'] = None
            rv = self.client.post(
                self.base_url,
                data=json.dumps(invalid_comparison_example),
                content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer1_id not exists
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer1_id'] = "999"
            rv = self.client.post(
                self.base_url,
                data=json.dumps(invalid_comparison_example),
                content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer2_id
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer2_id'] = None
            rv = self.client.post(
                self.base_url,
                data=json.dumps(invalid_comparison_example),
                content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer2_id not exists
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer2_id'] = "999"
            rv = self.client.post(
                self.base_url,
                data=json.dumps(invalid_comparison_example),
                content_type='application/json')
            self.assert404(rv)

            # test create successful
            rv = self.client.post(
                self.base_url,
                data=json.dumps(expected_comparison_example),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id'])

        # ta
        with self.login(self.fixtures.ta.username):
            # test create successful
            rv = self.client.post(
                self.base_url,
                data=json.dumps(expected_comparison_example),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_comparison_example['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected_comparison_example['answer2_id'], rv.json['answer2_id'])

    def test_edit_comparison_example(self):
        comparison_example = self.fixtures.comparison_examples[0]
        self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta)
        new_answer = self.fixtures.answers[-1]

        expected = {
            'id': comparison_example.uuid,
            'answer1_id': new_answer.uuid,
            'answer2_id': comparison_example.answer2_uuid,
        }

        # test login required
        rv = self.client.post(
            self.base_url + '/' + comparison_example.uuid,
            data=json.dumps(expected),
            content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(expected),
                content_type='application/json')
            self.assert403(rv)

        for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.post(
                        self.base_url + '/' + comparison_example.uuid,
                        data=json.dumps(expected),
                        content_type='application/json')
                    self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            rv = self.client.post(
                self._build_url("999", self.fixtures.assignment.uuid, '/' + comparison_example.uuid),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

            # test invalid assignment id
            rv = self.client.post(
                self._build_url(self.fixtures.course.uuid, "999", '/' + comparison_example.uuid),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

            # test invalid comparison_example id
            rv = self.client.post(
                self.base_url + '/999',
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer1_id
            invalid = expected.copy()
            invalid['answer1_id'] = None
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(invalid),
                content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer1_id not exists
            invalid = expected.copy()
            invalid['answer1_id'] = "999"
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(invalid),
                content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer2_id
            invalid = expected.copy()
            invalid['answer2_id'] = None
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(invalid),
                content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer2_id not exists
            invalid = expected.copy()
            invalid['answer2_id'] = "999"
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(invalid),
                content_type='application/json')
            self.assert404(rv)

            # test edit successful
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(expected),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected['answer2_id'], rv.json['answer2_id'])

        # ta
        with self.login(self.fixtures.ta.username):
            # test edit successful
            rv = self.client.post(
                self.base_url + '/' + comparison_example.uuid,
                data=json.dumps(expected),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected['answer2_id'], rv.json['answer2_id'])


    def test_delete_comparison_example(self):
        comparison_example = self.fixtures.comparison_examples[0]
        comparison_example2 = self.fixtures.comparison_examples[1]

        # test login required
        rv = self.client.delete(self.base_url + '/' + comparison_example.uuid)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.delete(self.base_url + '/' + comparison_example.uuid)
            self.assert403(rv)

        for student in [self.fixtures.unauthorized_student, self.fixtures.students[0]]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.delete(self.base_url + '/' + comparison_example.uuid)
                    self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
             # test invalid assignment id
            rv = self.client.delete(self._build_url(self.fixtures.course.uuid, "4903409") + '/' + comparison_example.uuid)
            self.assert404(rv)

             # test invalid course id
            rv = self.client.delete(self._build_url("4903409", self.fixtures.assignment.uuid) + '/' + comparison_example.uuid)
            self.assert404(rv)

            # test invalid comparison example id
            rv = self.client.delete(self.base_url + '/999')
            self.assert404(rv)

            # test deletion by instructor
            rv = self.client.delete(self.base_url + '/' + comparison_example.uuid)
            self.assert200(rv)
            self.assertEqual(comparison_example.uuid, rv.json['id'])

        # test deletion by ta
        with self.login(self.fixtures.instructor.username):
            rv = self.client.delete(self.base_url + '/' + comparison_example2.uuid)
            self.assert200(rv)
            self.assertEqual(comparison_example2.uuid, rv.json['id'])
Exemple #23
0
 def setUp(self):
     super(FileRetrieveTests, self).setUp()
     self.fixtures = TestFixture().add_course(
         num_students=5, num_assignments=1, num_groups=0,
         num_answers=1, with_draft_student=True)
     self.files_to_cleanup = []
 def setUp(self):
     super(ComparionExampleAPITests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30, num_groups=2, with_draft_student=True)
     self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.instructor)
     self.fixtures.add_comparison_example(self.fixtures.assignment, self.fixtures.ta)
     self.base_url = self._build_url(self.fixtures.course.uuid, self.fixtures.assignment.uuid)
Exemple #25
0
class ComparionExampleAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(ComparionExampleAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30,
                                                 num_groups=2,
                                                 with_draft_student=True)
        self.fixtures.add_comparison_example(self.fixtures.assignment,
                                             self.fixtures.instructor)
        self.fixtures.add_comparison_example(self.fixtures.assignment,
                                             self.fixtures.ta)
        self.base_url = self._build_url(self.fixtures.course.uuid,
                                        self.fixtures.assignment.uuid)

    def _build_url(self, course_uuid, assignment_uuid, tail=""):
        url = '/api/courses/' + course_uuid + '/assignments/' + assignment_uuid + '/comparisons/examples' + tail
        return url

    def test_get_all_comparison_examples(self):
        # Test login required
        rv = self.client.get(self.base_url)
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        for student in [
                self.fixtures.unauthorized_student, self.fixtures.students[0]
        ]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.get(self.base_url)
                    self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test non-existent entry
            rv = self.client.get(
                self._build_url(self.fixtures.course.uuid, "4903409"))
            self.assert404(rv)
            rv = self.client.get(
                self._build_url("4903409", self.fixtures.assignment.uuid))
            self.assert404(rv)

            rv = self.client.get(self.base_url)
            self.assert200(rv)
            results = rv.json['objects']
            self.assertEqual(len(results), 2)
            comparison_examples = self.fixtures.assignment.comparison_examples
            for index, comparison_example in enumerate(comparison_examples):
                self.assertEqual(results[index]['id'], comparison_example.uuid)
                self.assertEqual(results[index]['answer1_id'],
                                 comparison_example.answer1_uuid)
                self.assertEqual(results[index]['answer2_id'],
                                 comparison_example.answer2_uuid)

        # ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            results = rv.json['objects']
            self.assertEqual(len(results), 2)
            comparison_examples = self.fixtures.assignment.comparison_examples
            for index, comparison_example in enumerate(comparison_examples):
                self.assertEqual(results[index]['id'], comparison_example.uuid)
                self.assertEqual(results[index]['answer1_id'],
                                 comparison_example.answer1_uuid)
                self.assertEqual(results[index]['answer2_id'],
                                 comparison_example.answer2_uuid)

    def test_create_comparison_example(self):
        self.fixtures.add_answer(self.fixtures.assignment,
                                 self.fixtures.instructor)
        self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta)

        expected_comparison_example = {
            'answer1_id': self.fixtures.answers[-2].uuid,
            'answer2_id': self.fixtures.answers[-1].uuid
        }

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

        # test unauthorized users
        student = self.fixtures.unauthorized_student
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(DefaultFixture.ROOT_USER, student)]:
            with user_context:
                rv = self.client.post(
                    self.base_url,
                    data=json.dumps(expected_comparison_example),
                    content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(self.base_url,
                                  data=json.dumps(expected_comparison_example),
                                  content_type='application/json')
            self.assert403(rv)

        student = self.fixtures.students[0]
        for user_context in [ \
                self.login(student.username), \
                self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(
                    self.base_url,
                    data=json.dumps(expected_comparison_example),
                    content_type='application/json')
                self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test non-existent entry
            rv = self.client.post(self._build_url(self.fixtures.course.uuid,
                                                  "4903409"),
                                  data=json.dumps(expected_comparison_example),
                                  content_type='application/json')
            self.assert404(rv)

            rv = self.client.post(self._build_url(
                "4903409", self.fixtures.assignment.uuid),
                                  data=json.dumps(expected_comparison_example),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer1_id
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer1_id'] = None
            rv = self.client.post(self.base_url,
                                  data=json.dumps(invalid_comparison_example),
                                  content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer1_id not exists
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer1_id'] = "999"
            rv = self.client.post(self.base_url,
                                  data=json.dumps(invalid_comparison_example),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer2_id
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer2_id'] = None
            rv = self.client.post(self.base_url,
                                  data=json.dumps(invalid_comparison_example),
                                  content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer2_id not exists
            invalid_comparison_example = expected_comparison_example.copy()
            invalid_comparison_example['answer2_id'] = "999"
            rv = self.client.post(self.base_url,
                                  data=json.dumps(invalid_comparison_example),
                                  content_type='application/json')
            self.assert404(rv)

            # test create successful
            rv = self.client.post(self.base_url,
                                  data=json.dumps(expected_comparison_example),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_comparison_example['answer1_id'],
                             rv.json['answer1_id'])
            self.assertEqual(expected_comparison_example['answer2_id'],
                             rv.json['answer2_id'])

        # ta
        with self.login(self.fixtures.ta.username):
            # test create successful
            rv = self.client.post(self.base_url,
                                  data=json.dumps(expected_comparison_example),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_comparison_example['answer1_id'],
                             rv.json['answer1_id'])
            self.assertEqual(expected_comparison_example['answer2_id'],
                             rv.json['answer2_id'])

    def test_edit_comparison_example(self):
        comparison_example = self.fixtures.comparison_examples[0]
        self.fixtures.add_answer(self.fixtures.assignment, self.fixtures.ta)
        new_answer = self.fixtures.answers[-1]

        expected = {
            'id': comparison_example.uuid,
            'answer1_id': new_answer.uuid,
            'answer2_id': comparison_example.answer2_uuid,
        }

        # test login required
        rv = self.client.post(self.base_url + '/' + comparison_example.uuid,
                              data=json.dumps(expected),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert403(rv)

        for student in [
                self.fixtures.unauthorized_student, self.fixtures.students[0]
        ]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.post(self.base_url + '/' +
                                          comparison_example.uuid,
                                          data=json.dumps(expected),
                                          content_type='application/json')
                    self.assert403(rv)

        # instructor
        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            rv = self.client.post(self._build_url(
                "999", self.fixtures.assignment.uuid,
                '/' + comparison_example.uuid),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid assignment id
            rv = self.client.post(self._build_url(
                self.fixtures.course.uuid, "999",
                '/' + comparison_example.uuid),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid comparison_example id
            rv = self.client.post(self.base_url + '/999',
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer1_id
            invalid = expected.copy()
            invalid['answer1_id'] = None
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer1_id not exists
            invalid = expected.copy()
            invalid['answer1_id'] = "999"
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid format - answer2_id
            invalid = expected.copy()
            invalid['answer2_id'] = None
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert400(rv)

            # test invalid format - answer2_id not exists
            invalid = expected.copy()
            invalid['answer2_id'] = "999"
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(invalid),
                                  content_type='application/json')
            self.assert404(rv)

            # test edit successful
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected['answer2_id'], rv.json['answer2_id'])

        # ta
        with self.login(self.fixtures.ta.username):
            # test edit successful
            rv = self.client.post(self.base_url + '/' +
                                  comparison_example.uuid,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['answer1_id'], rv.json['answer1_id'])
            self.assertEqual(expected['answer2_id'], rv.json['answer2_id'])

    def test_delete_comparison_example(self):
        comparison_example = self.fixtures.comparison_examples[0]
        comparison_example2 = self.fixtures.comparison_examples[1]

        # test login required
        rv = self.client.delete(self.base_url + '/' + comparison_example.uuid)
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.delete(self.base_url + '/' +
                                    comparison_example.uuid)
            self.assert403(rv)

        for student in [
                self.fixtures.unauthorized_student, self.fixtures.students[0]
        ]:
            for user_context in [ \
                    self.login(student.username), \
                    self.impersonate(DefaultFixture.ROOT_USER, student)]:
                with user_context:
                    rv = self.client.delete(self.base_url + '/' +
                                            comparison_example.uuid)
                    self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid assignment id
            rv = self.client.delete(
                self._build_url(self.fixtures.course.uuid, "4903409") + '/' +
                comparison_example.uuid)
            self.assert404(rv)

            # test invalid course id
            rv = self.client.delete(
                self._build_url("4903409", self.fixtures.assignment.uuid) +
                '/' + comparison_example.uuid)
            self.assert404(rv)

            # test invalid comparison example id
            rv = self.client.delete(self.base_url + '/999')
            self.assert404(rv)

            # test deletion by instructor
            rv = self.client.delete(self.base_url + '/' +
                                    comparison_example.uuid)
            self.assert200(rv)
            self.assertEqual(comparison_example.uuid, rv.json['id'])

        # test deletion by ta
        with self.login(self.fixtures.instructor.username):
            rv = self.client.delete(self.base_url + '/' +
                                    comparison_example2.uuid)
            self.assert200(rv)
            self.assertEqual(comparison_example2.uuid, rv.json['id'])
Exemple #26
0
 def setUp(self):
     super(GroupsAPITests, self).setUp()
     self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)
class AnswersAPITests(ACJAPITestCase):
    def setUp(self):
        super(AnswersAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=2)
        self.base_url = self._build_url(self.fixtures.course.id,
                                        self.fixtures.question.id)

    def _build_url(self, course_id, question_id, tail=""):
        url = '/api/courses/' + str(course_id) + '/questions/' + str(
            question_id) + '/answers' + tail
        return url

    def test_get_all_answers(self):
        # Test login required
        rv = self.client.get(self.base_url)
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        with self.login(self.fixtures.students[0].username):
            # test non-existent entry
            rv = self.client.get(
                self._build_url(self.fixtures.course.id, 4903409))
            self.assert404(rv)
            # test data retrieve is correct
            self.fixtures.question.answer_end = datetime.datetime.now(
            ) - datetime.timedelta(days=1)
            db.session.add(self.fixtures.question)
            db.session.commit()
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            expected_answers = PostsForAnswers.query.filter_by(
                questions_id=self.fixtures.question.id).paginate(1, 20)
            for i, expected in enumerate(expected_answers.items):
                actual = actual_answers[i]
                self.assertEqual(expected.content, actual['content'])
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test the second page
            rv = self.client.get(self.base_url + '?page=2')
            self.assert200(rv)
            actual_answers = rv.json['objects']
            expected_answers = PostsForAnswers.query.filter_by(
                questions_id=self.fixtures.question.id).paginate(2, 20)
            for i, expected in enumerate(expected_answers.items):
                actual = actual_answers[i]
                self.assertEqual(expected.content, actual['content'])
            self.assertEqual(2, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test sorting
            rv = self.client.get(
                self.base_url +
                '?orderBy={}'.format(self.fixtures.question.criteria[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            # test the result is paged and sorted
            expected = sorted(self.fixtures.answers,
                              key=lambda ans: ans.scores[0].score
                              if len(ans.scores) else 0,
                              reverse=True)[:20]
            self.assertEqual([a.id for a in expected],
                             [a['id'] for a in result])
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test author filter
            rv = self.client.get(
                self.base_url +
                '?author={}'.format(self.fixtures.students[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(result), 1)
            self.assertEqual(result[0]['user_id'],
                             self.fixtures.students[0].id)

            # test group filter
            rv = self.client.get(
                self.base_url + '?group={}'.format(self.fixtures.groups[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(
                len(result),
                len(self.fixtures.answers) / len(self.fixtures.groups))

            # test ids filter
            ids = {str(a.id) for a in self.fixtures.answers[:3]}
            rv = self.client.get(self.base_url +
                                 '?ids={}'.format(','.join(ids)))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(ids, {str(a['id']) for a in result})

            # test combined filter
            rv = self.client.get(self.base_url + '?orderBy={}&group={}'.format(
                self.fixtures.question.criteria[0].id,
                self.fixtures.groups[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            # test the result is paged and sorted
            answers_per_group = int(
                len(self.fixtures.answers) /
                len(self.fixtures.groups)) if len(self.fixtures.groups) else 0
            answers = self.fixtures.answers[:answers_per_group]
            expected = sorted(answers,
                              key=lambda ans: ans.scores[0].score,
                              reverse=True)
            self.assertEqual([a.id for a in expected],
                             [a['id'] for a in result])

            # all filters
            rv = self.client.get(
                self.base_url +
                '?orderBy={}&group={}&author={}&page=1&perPage=20'.format(
                    self.fixtures.question.criteria[0].id,
                    self.fixtures.groups[0].id, self.fixtures.students[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(result), 1)
            self.assertEqual(result[0]['user_id'],
                             self.fixtures.students[0].id)

            # add instructor answer
            post = PostsFactory(course=self.fixtures.course,
                                user=self.fixtures.instructor)
            answer = PostsForAnswersFactory(question=self.fixtures.question,
                                            post=post)
            self.fixtures.answers.append(answer)
            db.session.commit()
            rv = self.client.get(
                self.base_url +
                '?orderBy={}'.format(self.fixtures.question.criteria[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(self.fixtures.answers), rv.json['total'])
            # first answer should be instructor answer
            self.assertEqual(self.fixtures.instructor.id, result[0]['user_id'])

            # test data retrieve before answer period ended with non-privileged user
            self.fixtures.question.answer_end = datetime.datetime.now(
            ) + datetime.timedelta(days=2)
            db.session.add(self.fixtures.question)
            db.session.commit()
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            self.assertEqual(1, len(actual_answers))
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(1, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(1, rv.json['total'])

        # test data retrieve before answer period ended with privileged user
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            self.assertEqual(20, len(actual_answers))
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(len(self.fixtures.answers), rv.json['total'])

    def test_create_answer(self):
        # test login required
        expected_answer = {'content': 'this is some answer content'}
        response = self.client.post(self.base_url,
                                    data=json.dumps(expected_answer),
                                    content_type='application/json')
        self.assert401(response)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_student.username):
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert403(response)
        with self.login(self.fixtures.unauthorized_instructor.username):
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert403(response)
        # test invalid format
        with self.login(self.fixtures.students[0].username):
            invalid_answer = {'post': {'blah': 'blah'}}
            response = self.client.post(self.base_url,
                                        data=json.dumps(invalid_answer),
                                        content_type='application/json')
            self.assert400(response)
            # test invalid question
            response = self.client.post(self._build_url(
                self.fixtures.course.id, 9392402),
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert404(response)
            # test invalid course
            response = self.client.post(self._build_url(
                9392402, self.fixtures.question.id),
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert404(response)

        # test create successful
        with self.login(self.fixtures.instructor.username):
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert200(response)
            # retrieve again and verify
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'],
                             actual_answer.post.content)

            # test instructor could submit multiple answers for his/her own
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert200(response)
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'],
                             actual_answer.post.content)

            # test instructor could submit on behave of a student
            self.fixtures.add_students(1)
            expected_answer.update({'user': self.fixtures.students[-1].id})
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert200(response)
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'],
                             actual_answer.post.content)

            # test instructor can not submit additional answers for a student
            expected_answer.update({'user': self.fixtures.students[0].id})
            response = self.client.post(self.base_url,
                                        data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert400(response)
            rv = json.loads(response.data.decode('utf-8'))
            self.assertEqual(
                {"error": "An answer has already been submitted."}, rv)

    def test_get_answer(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]

        # test login required
        rv = self.client.get(self.base_url + '/' + str(answer.id))
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(
                self._build_url(999, question_id, '/' + str(answer.id)))
            self.assert404(rv)

            # test invalid answer id
            rv = self.client.get(
                self._build_url(self.fixtures.course.id, question_id,
                                '/' + str(999)))
            self.assert404(rv)

            # test authorized student
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

        # test authorized teaching assistant
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

        # test authorized instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

    def test_edit_answer(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]
        expected = {'id': str(answer.id), 'content': 'This is an edit'}

        # test login required
        rv = self.client.post(self.base_url + '/' + str(answer.id),
                              data=json.dumps(expected),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.students[1].username):
            rv = self.client.post(self.base_url + '/' + str(answer.id),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(self._build_url(999, question_id,
                                                  '/' + str(answer.id)),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.post(self._build_url(self.fixtures.course.id, 999,
                                                  '/' + str(answer.id)),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            rv = self.client.post(self.base_url + '/999',
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

        # test unmatched answer id
        with self.login(self.fixtures.students[1].username):
            rv = self.client.post(self.base_url + '/' +
                                  str(self.fixtures.answers[1].id),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert400(rv)

        # test edit by author
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(self.base_url + '/' + str(answer.id),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(answer.id, rv.json['id'])
            self.assertEqual('This is an edit', rv.json['content'])

        # test edit by user that can manage posts
        expected['content'] = 'This is another edit'
        with self.login(self.fixtures.instructor.username):
            rv = self.client.post(self.base_url + '/' + str(answer.id),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(answer.id, rv.json['id'])
            self.assertEqual('This is another edit', rv.json['content'])

    def test_delete_answer(self):
        answer_id = self.fixtures.answers[0].id

        # test login required
        rv = self.client.delete(self.base_url + '/' + str(answer_id))
        self.assert401(rv)

        # test unauthorized users
        with self.login(self.fixtures.students[1].username):
            rv = self.client.delete(self.base_url + '/' + str(answer_id))
            self.assert403(rv)

        # test invalid answer id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.delete(self.base_url + '/999')
            self.assert404(rv)

            # test deletion by author
            rv = self.client.delete(self.base_url + '/' + str(answer_id))
            self.assert200(rv)
            self.assertEqual(answer_id, rv.json['id'])

        # test deletion by user that can manage posts
        with self.login(self.fixtures.instructor.username):
            answer_id2 = self.fixtures.answers[1].id
            rv = self.client.delete(self.base_url + '/' + str(answer_id2))
            self.assert200(rv)
            self.assertEqual(answer_id2, rv.json['id'])

    def test_get_user_answers(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]
        url = self._build_url(self.fixtures.course.id, question_id, '/user')

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

        # test invalid course
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(self._build_url(999, question_id, '/user'))
            self.assert404(rv)

            # test invalid question
            rv = self.client.get(
                self._build_url(self.fixtures.course.id, 999, '/user'))
            self.assert404(rv)

            # test successful queries
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['answer']))
            self.assertEqual(answer.id, rv.json['answer'][0]['id'])
            self.assertEqual(answer.post.content,
                             rv.json['answer'][0]['content'])

        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['answer']))

    def test_flag_answer(self):
        answer = self.fixtures.question.answers[0]
        flag_url = self.base_url + "/" + str(answer.id) + "/flagged"
        # test login required
        expected_flag_on = {'flagged': True}
        expected_flag_off = {'flagged': False}
        rv = self.client.post(flag_url,
                              data=json.dumps(expected_flag_on),
                              content_type='application/json')
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_on),
                                  content_type='application/json')
            self.assert403(rv)

        # test flagging
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_on),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_flag_on['flagged'], rv.json['flagged'],
                             "Expected answer to be flagged.")
            # test unflagging
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_off),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_flag_off['flagged'], rv.json['flagged'],
                             "Expected answer to be flagged.")

        # test prevent unflagging by other students
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_on),
                                  content_type='application/json')
            self.assert200(rv)

        # create another student
        self.fixtures.add_students(1)
        other_student = self.fixtures.students[-1]
        # try to unflag answer as other student, should fail
        with self.login(other_student.username):
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_off),
                                  content_type='application/json')
            self.assert400(rv)

        # test allow unflagging by instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.post(flag_url,
                                  data=json.dumps(expected_flag_off),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected_flag_off['flagged'], rv.json['flagged'],
                             "Expected answer to be flagged.")

    def test_get_question_answered(self):
        count_url = self.base_url + '/count'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(count_url)
            self.assert403(rv)

        # test invalid course id
        self.fixtures.add_students(1)
        with self.login(self.fixtures.students[-1].username):
            rv = self.client.get('/api/courses/999/questions/1/answers/count')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.get('/api/courses/1/questions/999/answers/count')
            self.assert404(rv)

            # test successful query - no answers
            rv = self.client.get(count_url)
            self.assert200(rv)
            self.assertEqual(0, rv.json['answered'])

        # test successful query - answered
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(count_url)
            self.assert200(rv)
            self.assertEqual(1, rv.json['answered'])

    def test_get_answered_count(self):
        answered_url = '/api/courses/' + str(
            self.fixtures.course.id) + '/answers/answered'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(answered_url)
            self.assert403(rv)

        # test invalid course id
        self.fixtures.add_students(1)
        with self.login(self.fixtures.students[-1].username):
            rv = self.client.get('/api/courses/999/answered')
            self.assert404(rv)

            # test successful query - have not answered any questions in the course
            rv = self.client.get(answered_url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['answered']))

        # test successful query - have submitted one answer per question
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(answered_url)
            self.assert200(rv)
            expected = {
                str(question.id): 1
                for question in self.fixtures.questions
            }
            self.assertEqual(expected, rv.json['answered'])

    def test_get_answers_view(self):
        view_url = \
            '/api/courses/' + str(self.fixtures.course.id) + '/questions/' + \
            str(self.fixtures.questions[0].id) + '/answers/view'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(view_url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get('/api/courses/999/questions/' +
                                 str(self.fixtures.questions[0].id) +
                                 '/answers/view')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.get('/api/courses/' +
                                 str(self.fixtures.course.id) +
                                 '/questions/999/answers/view')
            self.assert404(rv)

            # test successful query
            rv = self.client.get(view_url)
            self.assert200(rv)
            expected = self.fixtures.answers

            self.assertEqual(30, len(rv.json['answers']))
            for i, exp in enumerate(expected):
                actual = rv.json['answers'][str(exp.id)]
                self.assertEqual(exp.id, actual['id'])
                self.assertEqual(exp.post.content, actual['content'])
                self.assertFalse(actual['file'])

        # test successful query - student
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(view_url)
            self.assert200(rv)

            actual = rv.json['answers']
            expected = self.fixtures.answers[0]

            self.assertEqual(1, len(actual))
            self.assertTrue(str(expected.id) in actual)
            answer = actual[str(expected.id)]
            self.assertEqual(expected.id, answer['id'])
            self.assertEqual(expected.post.content, answer['content'])
            self.assertFalse(answer['file'])
            self.assertFalse('scores' in answer)
Exemple #28
0
class GroupsAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(GroupsAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=3)

    def test_get_groups(self):
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups'

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

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

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.get(url)
                self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            invalid_url = '/api/courses/999/groups'
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test successful query
            rv = self.client.get(url)
            self.assert200(rv)
            actual = rv.json['objects']
            expected = sorted(self.fixtures.groups, key=lambda group: group.name)
            self.assertEqual(len(actual), 4)
            for index, group in enumerate(expected):
                self.assertEqual(actual[index]['id'], group.uuid)

        # test TA
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(url)
            self.assert200(rv)
            actual = rv.json['objects']
            expected = sorted(self.fixtures.groups, key=lambda group: group.name)
            self.assertEqual(len(actual), 4)
            for index, group in enumerate(expected):
                self.assertEqual(actual[index]['id'], group.uuid)

    def test_create_group(self):
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups'

        group_expected = {
            'name': 'Group 101',
        }

        invalid_expected = {
            'name': self.fixtures.groups[1].name,
        }

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups'
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test non-unique name
            rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json')
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Added")
            self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.")

            # test successful query
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert200(rv)

            group_actual = rv.json
            self.assertEqual(group_expected['name'], group_actual['name'])

    def test_edit_group(self):
        group = self.fixtures.groups[0]
        url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid

        group_expected = {
            'id': group.uuid,
            'name': 'New Group Name',
        }

        invalid_expected = {
            'id': group.uuid,
            'name': self.fixtures.groups[1].name,
        }

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups/'+group.uuid
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test invalid group id
            invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999'
            rv = self.client.post(invalid_url, data=json.dumps(group_expected), content_type='application/json')
            self.assert404(rv)

            # test non-unique name
            rv = self.client.post(url, data=json.dumps(invalid_expected), content_type='application/json')
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Saved")
            self.assertEqual(rv.json['message'], "Sorry, the group name you have entered already exists. Please choose a different name.")

            # test successful query
            rv = self.client.post(url, data=json.dumps(group_expected), content_type='application/json')
            self.assert200(rv)

            group_actual = rv.json
            self.assertEqual(group_expected['id'], group_actual['id'])
            self.assertEqual(group_expected['name'], group_actual['name'])


    def test_delete_group(self):
        group = self.fixtures.groups[0]
        self.fixtures.add_assignments(num_assignments=1, with_group_answers=True)

        url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid

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

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

        # test ta
        with self.login(self.fixtures.ta.username):
            rv = self.client.delete(url)
            self.assert403(rv)

        # test student
        student = self.fixtures.students[0]
        for user_context in [ self.login(student.username), self.impersonate(self.fixtures.instructor, student)]:
            with user_context:
                rv = self.client.delete(url)
                self.assert403(rv)

        with self.login(self.fixtures.instructor.username):
            # test invalid course id
            invalid_url = '/api/courses/999/groups/'+group.uuid
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid group id
            invalid_url = '/api/courses/'+self.fixtures.course.uuid+'/groups/999'
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test successful query
            rv = self.client.delete(url)
            self.assert200(rv)

            # test cannot delete again
            rv = self.client.delete(url)
            self.assert404(rv)

            # test cannot delete group if it has an answer
            self.fixtures.add_answers()
            group = self.fixtures.groups[1]
            url = '/api/courses/'+self.fixtures.course.uuid+'/groups/'+group.uuid
            rv = self.client.delete(url)
            self.assert400(rv)
            self.assertEqual(rv.json['title'], "Group Not Deleted")
            self.assertEqual(rv.json['message'], "Sorry, you cannot remove groups that have submitted answers.")
class AnswersAPITests(ACJAPITestCase):
    def setUp(self):
        super(AnswersAPITests, self).setUp()
        self.fixtures = TestFixture().add_course(num_students=30, num_groups=2)
        self.base_url = self._build_url(self.fixtures.course.id, self.fixtures.question.id)

    def _build_url(self, course_id, question_id, tail=""):
        url = '/api/courses/' + str(course_id) + '/questions/' + str(question_id) + '/answers' + tail
        return url

    def test_get_all_answers(self):
        # Test login required
        rv = self.client.get(self.base_url)
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(self.base_url)
            self.assert403(rv)

        with self.login(self.fixtures.students[0].username):
            # test non-existent entry
            rv = self.client.get(self._build_url(self.fixtures.course.id, 4903409))
            self.assert404(rv)
            # test data retrieve is correct
            self.fixtures.question.answer_end = datetime.datetime.now() - datetime.timedelta(days=1)
            db.session.add(self.fixtures.question)
            db.session.commit()
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            expected_answers = PostsForAnswers.query.filter_by(questions_id=self.fixtures.question.id).paginate(1, 20)
            for i, expected in enumerate(expected_answers.items):
                actual = actual_answers[i]
                self.assertEqual(expected.content, actual['content'])
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test the second page
            rv = self.client.get(self.base_url + '?page=2')
            self.assert200(rv)
            actual_answers = rv.json['objects']
            expected_answers = PostsForAnswers.query.filter_by(questions_id=self.fixtures.question.id).paginate(2, 20)
            for i, expected in enumerate(expected_answers.items):
                actual = actual_answers[i]
                self.assertEqual(expected.content, actual['content'])
            self.assertEqual(2, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test sorting
            rv = self.client.get(
                self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id)
            )
            self.assert200(rv)
            result = rv.json['objects']
            # test the result is paged and sorted
            expected = sorted(
                self.fixtures.answers,
                key=lambda ans: ans.scores[0].score if len(ans.scores) else 0,
                reverse=True)[:20]
            self.assertEqual([a.id for a in expected], [a['id'] for a in result])
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(expected_answers.total, rv.json['total'])

            # test author filter
            rv = self.client.get(self.base_url + '?author={}'.format(self.fixtures.students[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(result), 1)
            self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id)

            # test group filter
            rv = self.client.get(self.base_url + '?group={}'.format(self.fixtures.groups[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(result), len(self.fixtures.answers) / len(self.fixtures.groups))

            # test ids filter
            ids = {str(a.id) for a in self.fixtures.answers[:3]}
            rv = self.client.get(self.base_url + '?ids={}'.format(','.join(ids)))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(ids, {str(a['id']) for a in result})

            # test combined filter
            rv = self.client.get(
                self.base_url + '?orderBy={}&group={}'.format(
                    self.fixtures.question.criteria[0].id,
                    self.fixtures.groups[0].id
                )
            )
            self.assert200(rv)
            result = rv.json['objects']
            # test the result is paged and sorted
            answers_per_group = int(len(self.fixtures.answers) / len(self.fixtures.groups)) if len(
                self.fixtures.groups) else 0
            answers = self.fixtures.answers[:answers_per_group]
            expected = sorted(answers, key=lambda ans: ans.scores[0].score, reverse=True)
            self.assertEqual([a.id for a in expected], [a['id'] for a in result])

            # all filters
            rv = self.client.get(
                self.base_url + '?orderBy={}&group={}&author={}&page=1&perPage=20'.format(
                    self.fixtures.question.criteria[0].id,
                    self.fixtures.groups[0].id,
                    self.fixtures.students[0].id
                )
            )
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(result), 1)
            self.assertEqual(result[0]['user_id'], self.fixtures.students[0].id)

            # add instructor answer
            post = PostsFactory(course=self.fixtures.course, user=self.fixtures.instructor)
            answer = PostsForAnswersFactory(question=self.fixtures.question, post=post)
            self.fixtures.answers.append(answer)
            db.session.commit()
            rv = self.client.get(self.base_url + '?orderBy={}'.format(self.fixtures.question.criteria[0].id))
            self.assert200(rv)
            result = rv.json['objects']
            self.assertEqual(len(self.fixtures.answers), rv.json['total'])
            # first answer should be instructor answer
            self.assertEqual(self.fixtures.instructor.id, result[0]['user_id'])

            # test data retrieve before answer period ended with non-privileged user
            self.fixtures.question.answer_end = datetime.datetime.now() + datetime.timedelta(days=2)
            db.session.add(self.fixtures.question)
            db.session.commit()
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            self.assertEqual(1, len(actual_answers))
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(1, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(1, rv.json['total'])

        # test data retrieve before answer period ended with privileged user
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self.base_url)
            self.assert200(rv)
            actual_answers = rv.json['objects']
            self.assertEqual(20, len(actual_answers))
            self.assertEqual(1, rv.json['page'])
            self.assertEqual(2, rv.json['pages'])
            self.assertEqual(20, rv.json['per_page'])
            self.assertEqual(len(self.fixtures.answers), rv.json['total'])

    def test_create_answer(self):
        # test login required
        expected_answer = {'content': 'this is some answer content'}
        response = self.client.post(
            self.base_url,
            data=json.dumps(expected_answer),
            content_type='application/json')
        self.assert401(response)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_student.username):
            response = self.client.post(self.base_url, data=json.dumps(expected_answer),
                                        content_type='application/json')
            self.assert403(response)
        with self.login(self.fixtures.unauthorized_instructor.username):
            response = self.client.post(
                self.base_url,
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert403(response)
        # test invalid format
        with self.login(self.fixtures.students[0].username):
            invalid_answer = {'post': {'blah': 'blah'}}
            response = self.client.post(
                self.base_url,
                data=json.dumps(invalid_answer),
                content_type='application/json')
            self.assert400(response)
            # test invalid question
            response = self.client.post(
                self._build_url(self.fixtures.course.id, 9392402),
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert404(response)
            # test invalid course
            response = self.client.post(
                self._build_url(9392402, self.fixtures.question.id),
                data=json.dumps(expected_answer), content_type='application/json')
            self.assert404(response)

        # test create successful
        with self.login(self.fixtures.instructor.username):
            response = self.client.post(
                self.base_url,
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert200(response)
            # retrieve again and verify
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'], actual_answer.post.content)

            # test instructor could submit multiple answers for his/her own
            response = self.client.post(
                self.base_url,
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert200(response)
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'], actual_answer.post.content)

            # test instructor could submit on behave of a student
            self.fixtures.add_students(1)
            expected_answer.update({'user': self.fixtures.students[-1].id})
            response = self.client.post(
                self.base_url,
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert200(response)
            rv = json.loads(response.data.decode('utf-8'))
            actual_answer = PostsForAnswers.query.get(rv['id'])
            self.assertEqual(expected_answer['content'], actual_answer.post.content)

            # test instructor can not submit additional answers for a student
            expected_answer.update({'user': self.fixtures.students[0].id})
            response = self.client.post(
                self.base_url,
                data=json.dumps(expected_answer),
                content_type='application/json')
            self.assert400(response)
            rv = json.loads(response.data.decode('utf-8'))
            self.assertEqual({"error": "An answer has already been submitted."}, rv)

    def test_get_answer(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]

        # test login required
        rv = self.client.get(self.base_url + '/' + str(answer.id))
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(self._build_url(999, question_id, '/' + str(answer.id)))
            self.assert404(rv)

            # test invalid answer id
            rv = self.client.get(self._build_url(self.fixtures.course.id, question_id, '/' + str(999)))
            self.assert404(rv)

            # test authorized student
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

        # test authorized teaching assistant
        with self.login(self.fixtures.ta.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

        # test authorized instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(self.base_url + '/' + str(answer.id))
            self.assert200(rv)
            self.assertEqual(question_id, rv.json['questions_id'])
            self.assertEqual(answer.post.users_id, rv.json['user_id'])
            self.assertEqual(answer.post.content, rv.json['content'])

    def test_edit_answer(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]
        expected = {'id': str(answer.id), 'content': 'This is an edit'}

        # test login required
        rv = self.client.post(
            self.base_url + '/' + str(answer.id),
            data=json.dumps(expected),
            content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.fixtures.students[1].username):
            rv = self.client.post(
                self.base_url + '/' + str(answer.id),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(
                self._build_url(999, question_id, '/' + str(answer.id)),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.post(
                self._build_url(self.fixtures.course.id, 999, '/' + str(answer.id)),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

            # test invalid answer id
            rv = self.client.post(
                self.base_url + '/999',
                data=json.dumps(expected),
                content_type='application/json')
            self.assert404(rv)

        # test unmatched answer id
        with self.login(self.fixtures.students[1].username):
            rv = self.client.post(
                self.base_url + '/' + str(self.fixtures.answers[1].id),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert400(rv)

        # test edit by author
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(
                self.base_url + '/' + str(answer.id),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(answer.id, rv.json['id'])
            self.assertEqual('This is an edit', rv.json['content'])

        # test edit by user that can manage posts
        expected['content'] = 'This is another edit'
        with self.login(self.fixtures.instructor.username):
            rv = self.client.post(
                self.base_url + '/' + str(answer.id),
                data=json.dumps(expected),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(answer.id, rv.json['id'])
            self.assertEqual('This is another edit', rv.json['content'])

    def test_delete_answer(self):
        answer_id = self.fixtures.answers[0].id

        # test login required
        rv = self.client.delete(self.base_url + '/' + str(answer_id))
        self.assert401(rv)

        # test unauthorized users
        with self.login(self.fixtures.students[1].username):
            rv = self.client.delete(self.base_url + '/' + str(answer_id))
            self.assert403(rv)

        # test invalid answer id
        with self.login(self.fixtures.students[0].username):
            rv = self.client.delete(self.base_url + '/999')
            self.assert404(rv)

            # test deletion by author
            rv = self.client.delete(self.base_url + '/' + str(answer_id))
            self.assert200(rv)
            self.assertEqual(answer_id, rv.json['id'])

        # test deletion by user that can manage posts
        with self.login(self.fixtures.instructor.username):
            answer_id2 = self.fixtures.answers[1].id
            rv = self.client.delete(self.base_url + '/' + str(answer_id2))
            self.assert200(rv)
            self.assertEqual(answer_id2, rv.json['id'])

    def test_get_user_answers(self):
        question_id = self.fixtures.questions[0].id
        answer = self.fixtures.answers[0]
        url = self._build_url(self.fixtures.course.id, question_id, '/user')

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

        # test invalid course
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(self._build_url(999, question_id, '/user'))
            self.assert404(rv)

            # test invalid question
            rv = self.client.get(self._build_url(self.fixtures.course.id, 999, '/user'))
            self.assert404(rv)

            # test successful queries
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['answer']))
            self.assertEqual(answer.id, rv.json['answer'][0]['id'])
            self.assertEqual(answer.post.content, rv.json['answer'][0]['content'])

        with self.login(self.fixtures.instructor.username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['answer']))

    def test_flag_answer(self):
        answer = self.fixtures.question.answers[0]
        flag_url = self.base_url + "/" + str(answer.id) + "/flagged"
        # test login required
        expected_flag_on = {'flagged': True}
        expected_flag_off = {'flagged': False}
        rv = self.client.post(
            flag_url,
            data=json.dumps(expected_flag_on),
            content_type='application/json')
        self.assert401(rv)
        # test unauthorized users
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_on),
                content_type='application/json')
            self.assert403(rv)

        # test flagging
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_on),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(
                expected_flag_on['flagged'],
                rv.json['flagged'],
                "Expected answer to be flagged.")
            # test unflagging
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_off),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(
                expected_flag_off['flagged'],
                rv.json['flagged'],
                "Expected answer to be flagged.")

        # test prevent unflagging by other students
        with self.login(self.fixtures.students[0].username):
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_on),
                content_type='application/json')
            self.assert200(rv)

        # create another student
        self.fixtures.add_students(1)
        other_student = self.fixtures.students[-1]
        # try to unflag answer as other student, should fail
        with self.login(other_student.username):
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_off),
                content_type='application/json')
            self.assert400(rv)

        # test allow unflagging by instructor
        with self.login(self.fixtures.instructor.username):
            rv = self.client.post(
                flag_url,
                data=json.dumps(expected_flag_off),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(
                expected_flag_off['flagged'],
                rv.json['flagged'],
                "Expected answer to be flagged.")

    def test_get_question_answered(self):
        count_url = self.base_url + '/count'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(count_url)
            self.assert403(rv)

        # test invalid course id
        self.fixtures.add_students(1)
        with self.login(self.fixtures.students[-1].username):
            rv = self.client.get('/api/courses/999/questions/1/answers/count')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.get('/api/courses/1/questions/999/answers/count')
            self.assert404(rv)

            # test successful query - no answers
            rv = self.client.get(count_url)
            self.assert200(rv)
            self.assertEqual(0, rv.json['answered'])

        # test successful query - answered
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(count_url)
            self.assert200(rv)
            self.assertEqual(1, rv.json['answered'])

    def test_get_answered_count(self):
        answered_url = '/api/courses/' + str(self.fixtures.course.id) + '/answers/answered'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_student.username):
            rv = self.client.get(answered_url)
            self.assert403(rv)

        # test invalid course id
        self.fixtures.add_students(1)
        with self.login(self.fixtures.students[-1].username):
            rv = self.client.get('/api/courses/999/answered')
            self.assert404(rv)

            # test successful query - have not answered any questions in the course
            rv = self.client.get(answered_url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['answered']))

        # test successful query - have submitted one answer per question
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(answered_url)
            self.assert200(rv)
            expected = {str(question.id): 1 for question in self.fixtures.questions}
            self.assertEqual(expected, rv.json['answered'])

    def test_get_answers_view(self):
        view_url = \
            '/api/courses/' + str(self.fixtures.course.id) + '/questions/' + \
            str(self.fixtures.questions[0].id) + '/answers/view'

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

        # test unauthorized user
        with self.login(self.fixtures.unauthorized_instructor.username):
            rv = self.client.get(view_url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.fixtures.instructor.username):
            rv = self.client.get('/api/courses/999/questions/' + str(self.fixtures.questions[0].id) + '/answers/view')
            self.assert404(rv)

            # test invalid question id
            rv = self.client.get('/api/courses/' + str(self.fixtures.course.id) + '/questions/999/answers/view')
            self.assert404(rv)

            # test successful query
            rv = self.client.get(view_url)
            self.assert200(rv)
            expected = self.fixtures.answers

            self.assertEqual(30, len(rv.json['answers']))
            for i, exp in enumerate(expected):
                actual = rv.json['answers'][str(exp.id)]
                self.assertEqual(exp.id, actual['id'])
                self.assertEqual(exp.post.content, actual['content'])
                self.assertFalse(actual['file'])

        # test successful query - student
        with self.login(self.fixtures.students[0].username):
            rv = self.client.get(view_url)
            self.assert200(rv)

            actual = rv.json['answers']
            expected = self.fixtures.answers[0]

            self.assertEqual(1, len(actual))
            self.assertTrue(str(expected.id) in actual)
            answer = actual[str(expected.id)]
            self.assertEqual(expected.id, answer['id'])
            self.assertEqual(expected.post.content, answer['content'])
            self.assertFalse(answer['file'])
            self.assertFalse('scores' in answer)