Exemple #1
0
class ClassListAPITest(ComPAIRAPITestCase):
    def setUp(self):
        super(ClassListAPITest, self).setUp()
        self.data = BasicTestData()
        self.url = "/api/courses/" + self.data.get_course().uuid + "/users"

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

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

        expected = [
            (self.data.get_authorized_instructor(), '', ''),
            (self.data.get_authorized_ta(), '', ''),
            (self.data.get_authorized_student(), '', '')]
        expected.sort(key=lambda x: x[0].firstname)

        with self.login(self.data.get_authorized_instructor().username):
            # test authorized user
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, (user, cas_username, group_name) in enumerate(expected):
                self.assertEqual(user.uuid, rv.json['objects'][key]['id'])

            # test export csv
            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with group names
            for user_course in self.data.main_course.user_courses:
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    user_course.group_name = "instructor_group"
                elif user_course.user_id == self.data.get_authorized_ta().id:
                    user_course.group_name = "ta_group"
                elif user_course.user_id == self.data.get_authorized_student().id:
                    user_course.group_name = "student_group"
            db.session.commit()

            expected = [
                (self.data.get_authorized_instructor(), '', "instructor_group"),
                (self.data.get_authorized_ta(), '', "ta_group"),
                (self.data.get_authorized_student(), '', "student_group")]
            expected.sort(key=lambda x: x[0].firstname)

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with cas usernames
            third_party_student = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_instructor = ThirdPartyUserFactory(user=self.data.get_authorized_instructor())
            third_party_ta = ThirdPartyUserFactory(user=self.data.get_authorized_ta())
            db.session.commit()

            expected = [
                (self.data.get_authorized_instructor(), third_party_instructor.unique_identifier, "instructor_group"),
                (self.data.get_authorized_ta(), third_party_ta.unique_identifier, "ta_group"),
                (self.data.get_authorized_student(), third_party_student.unique_identifier, "student_group")]
            expected.sort(key=lambda x: x[0].firstname)

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with cas usernames (student has multiple cas_usernames). should use first username
            third_party_student2 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_student3 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_student4 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            db.session.commit()

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )


        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, (user, cas_username, group_name) in enumerate(expected):
                self.assertEqual(user.uuid, rv.json['objects'][key]['id'])

    def test_get_instructor_labels(self):
        url = self.url + "/instructors/labels"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/instructors/labels')
            self.assert404(rv)

            # test success
            rv = self.client.get(url)
            self.assert200(rv)
            labels = rv.json['instructors']
            expected = {
                self.data.get_authorized_ta().uuid: 'Teaching Assistant',
                self.data.get_authorized_instructor().uuid: 'Instructor'
            }
            self.assertEqual(labels, expected)

    def test_get_students_course(self):
        url = self.url + "/students"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/students')
            self.assert404(rv)

            # test success - instructor
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'])

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'])

        # test success - student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().displayname
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'] + ' (You)')

    def test_enrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_dropped_instructor().uuid)
        role = {'course_role': 'Instructor'}  # defaults to Instructor

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

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

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = '/api/courses/999/users/' + self.data.get_dropped_instructor().uuid
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test enrolling dropped instructor
            expected = {
                'user_id': self.data.get_dropped_instructor().uuid,
                'fullname': self.data.get_dropped_instructor().fullname,
                'course_role': CourseRole.instructor.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling new instructor
            url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid)
            expected = {
                'user_id': self.data.get_unauthorized_instructor().uuid,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': CourseRole.instructor.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling a different role - eg. Student
            role = {'course_role': CourseRole.teaching_assistant.value }
            expected = {
                'user_id': self.data.get_unauthorized_instructor().uuid,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': CourseRole.teaching_assistant.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_unenrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_authorized_instructor().uuid)

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

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

        # test invalid course id
        invalid_url = '/api/courses/999/users/' + self.data.get_authorized_instructor().uuid
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test existing user not in existing course
            invalid_url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test success
            expected = {
                'user_id': self.data.get_authorized_instructor().uuid,
                'fullname': self.data.get_authorized_instructor().fullname,
                'course_role': CourseRole.dropped.value
            }
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_import_compair_classlist(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/users'
        student = self.data.get_authorized_student()
        instructor = self.data.get_authorized_instructor()
        ta = self.data.get_authorized_ta()

        filename = "classlist.csv"

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

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename)))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype)))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join([",\n", student.username, ",password,", student.student_number])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test no password provided
            content = "".join(["nopasswordusername"])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("nopasswordusername", result['invalids'][0]['user']['username'])
            self.assertEqual('The password is required.', result['invalids'][0]['message'])
            uploaded_file.close()

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

            # test duplicate student number in system
            content = "".join(['username1,password,', student.student_number, "\n", student.username, ',password'])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                student.username, ",password,", student.student_number, "\n",
                "username1,password,", student.student_number])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,password,,,," + student.displayname
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2,password')
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user (with password)
            current_password = student.password
            new_password = '******'

            uploaded_file = io.BytesIO((student.username+','+new_password).encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()
            self.assertEqual(student.password, current_password)
            self.assertNotEqual(student.password, new_password)

            student.last_online = None
            db.session.commit()

            uploaded_file = io.BytesIO((student.username+','+new_password).encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()
            self.assertNotEqual(student.password, current_password)
            self.assertEqual(student.password, new_password)

            new_password = '******'
            for set_password in [new_password, '*', '']:
                uploaded_file = io.BytesIO((student.username+','+set_password).encode())
                rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
                self.assert200(rv)
                result = rv.json
                self.assertEqual(1, result['success'])
                self.assertEqual(0, len(result['invalids']))
                uploaded_file.close()
                self.assertEqual(student.password, new_password)

            # test invalid import type app login disabled
            self.app.config['APP_LOGIN_ENABLED'] = False
            uploaded_file = io.BytesIO(student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['APP_LOGIN_ENABLED'] = True

            # test authorized instructor - existing instructor
            uploaded_file = io.BytesIO(instructor.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            instructor_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=instructor.id,
                    course_role=CourseRole.instructor
                ) \
                .one_or_none()
            self.assertIsNotNone(instructor_enrollment)

            # test authorized instructor - existing teaching assistant
            uploaded_file = io.BytesIO(ta.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            ta_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=ta.id,
                    course_role=CourseRole.teaching_assistant
                ) \
                .one_or_none()
            self.assertIsNotNone(ta_enrollment)

            # test authorized instructor - group enrollment
            content = "".join([
                student.username, ",*,,,,,,group_student\n",
                instructor.username, ",*,,,,,,group_instructor\n",
                ta.username, ",*,,,,,,group_ta\n",
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                if user_course.user_id == student.id:
                    self.assertEqual(user_course.group_name, 'group_student')
                elif user_course.user_id == instructor.id:
                    self.assertEqual(user_course.group_name, 'group_instructor')
                elif user_course.user_id == ta.id:
                    self.assertEqual(user_course.group_name, 'group_ta')

            # test authorized instructor - group unenrollment
            content = "".join([
                student.username, ",*,,,,,,\n",
                instructor.username, ",*,,,,,,\n",
                ta.username, ",*,,,,,,\n",
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                self.assertEqual(user_course.group_name, None)

    def test_import_cas_classlist(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/users'
        student = self.data.get_authorized_student()
        third_party_student = ThirdPartyUserFactory(user=student)
        instructor = self.data.get_authorized_instructor()
        third_party_instructor = ThirdPartyUserFactory(user=instructor)
        ta = self.data.get_authorized_ta()
        third_party_ta = ThirdPartyUserFactory(user=ta)
        db.session.commit()

        filename = "classlist.csv"

        # test login required
        uploaded_file = io.BytesIO((third_party_student.unique_identifier).encode())
        rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
        self.assert401(rv)
        uploaded_file.close()

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype), import_type=ThirdPartyType.cas.value))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join([",\n", third_party_student.unique_identifier, ",", student.student_number])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate usernames in file
            content = "".join([third_party_student.unique_identifier, "\n", third_party_student.unique_identifier])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(third_party_student.unique_identifier, result['invalids'][0]['user']['username'])
            self.assertEqual('This username already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in system
            content = "".join(['username1,', student.student_number, "\n", third_party_student.unique_identifier])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                third_party_student.unique_identifier, ",", student.student_number, "\n",
                "username1,", student.student_number])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,,,," + student.displayname
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2')
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test invalid import type cas login disabled
            self.app.config['CAS_LOGIN_ENABLED'] = False
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['CAS_LOGIN_ENABLED'] = True

            # test authorized instructor - existing instructor
            uploaded_file = io.BytesIO(third_party_instructor.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            instructor_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=instructor.id,
                    course_role=CourseRole.instructor
                ) \
                .one_or_none()
            self.assertIsNotNone(instructor_enrollment)

            # test authorized instructor - existing teaching assistant
            uploaded_file = io.BytesIO(third_party_ta.unique_identifier.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            ta_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=ta.id,
                    course_role=CourseRole.teaching_assistant
                ) \
                .one_or_none()
            self.assertIsNotNone(ta_enrollment)

            # test authorized instructor - group enrollment
            content = "".join([
                third_party_student.unique_identifier, ",,,,,,group_student\n",
                third_party_instructor.unique_identifier, ",,,,,,group_instructor\n",
                third_party_ta.unique_identifier, ",,,,,,group_ta\n",
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                if user_course.user_id == student.id:
                    self.assertEqual(user_course.group_name, 'group_student')
                elif user_course.user_id == instructor.id:
                    self.assertEqual(user_course.group_name, 'group_instructor')
                elif user_course.user_id == ta.id:
                    self.assertEqual(user_course.group_name, 'group_ta')

            # test authorized instructor - group unenrollment
            content = "".join([
                third_party_student.unique_identifier, ",,,,,,\n",
                third_party_instructor.unique_identifier, ",,,,,,\n",
                third_party_ta.unique_identifier, ",,,,,,\n",
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                self.assertEqual(user_course.group_name, None)


    def test_update_course_role_miltiple(self):
        url = self.url + '/roles'

        user_ids = [self.data.authorized_instructor.uuid, self.data.authorized_student.uuid, self.data.authorized_ta.uuid]
        params = {
            'ids': user_ids,
            'course_role': CourseRole.instructor.value
        }

        # 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.data.get_unauthorized_instructor().username):
            rv = self.client.post(
                url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert403(rv)

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

            # test missing user ids
            missing_ids = params.copy()
            missing_ids['ids'] = []
            rv = self.client.post(
                url,
                data=json.dumps(missing_ids),
                content_type='application/json')
            self.assert400(rv)

            # test invalid user ids
            invalid_ids = params.copy()
            invalid_ids['ids'] = [self.data.unauthorized_student.uuid]
            rv = self.client.post(
                url,
                data=json.dumps(invalid_ids),
                content_type='application/json')
            self.assert400(rv)

            # cannot change current_user's course role
            params_self = {
                'ids': [self.data.get_authorized_instructor().uuid],
                'course_role': CourseRole.teaching_assistant.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(params_self),
                content_type='application/json')
            self.assert400(rv)

            # test changing role instructor
            rv = self.client.post(
                url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.instructor.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)

            # test changing teaching assistant
            params_ta = params.copy()
            params_ta['course_role'] = CourseRole.teaching_assistant.value
            rv = self.client.post(
                url,
                data=json.dumps(params_ta),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.teaching_assistant.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.teaching_assistant)

            # test changing role student
            params_student = params.copy()
            params_student['course_role'] = CourseRole.student.value
            rv = self.client.post(
                url,
                data=json.dumps(params_student),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.student.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.student)

            # test changing dropped
            params_dropped = { 'ids': user_ids }
            rv = self.client.post(
                url,
                data=json.dumps(params_dropped),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.dropped.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.dropped)


    def _create_enrol_url(self, url, user_id):
        return url + '/' + str(user_id)
class UsersAPITests(ACJAPITestCase):
    def setUp(self):
        super(UsersAPITests, self).setUp()
        self.data = BasicTestData()

    def test_unauthorized(self):
        rv = self.client.get('/api/users')
        self.assert401(rv)

    def test_login(self):
        with self.login('root', 'password') as rv:
            userid = rv.json['userid']
            self.assertEqual(userid, 1, "Logged in user's id does not match!")
            self._verify_permissions(userid, rv.json['permissions'])

    def test_users_root(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/' +
                                 str(DefaultFixture.ROOT_USER.id))
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['username'], 'root')
            self.assertEqual(root['displayname'], 'root')
            self.assertNotIn('_password', root)

    def test_users_invalid_id(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/99999')
            self.assert404(rv)

    def test_users_info_unrestricted(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/' +
                                 str(DefaultFixture.ROOT_USER.id))
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['displayname'], 'root')
            # personal information should be transmitted
            self.assertIn('firstname', root)
            self.assertIn('lastname', root)
            self.assertIn('fullname', root)
            self.assertIn('email', root)

    def test_users_info_restricted(self):
        user = UsersFactory(usertypeforsystem=DefaultFixture.SYS_ROLE_NORMAL)

        with self.login(user.username, user.password):
            rv = self.client.get('/api/users/' +
                                 str(DefaultFixture.ROOT_USER.id))
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['displayname'], 'root')
            # personal information shouldn't be transmitted
            self.assertNotIn('firstname', root)
            self.assertNotIn('lastname', root)
            self.assertNotIn('fullname', root)
            self.assertNotIn('email', root)

    def test_users_list(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users')
            self.assert200(rv)
            users = rv.json
            self.assertEqual(users['total'], 7)
            self.assertEqual(users['objects'][0]['username'], 'root')

            rv = self.client.get('/api/users?search={}'.format(
                self.data.get_unauthorized_instructor().firstname))
            self.assert200(rv)
            users = rv.json
            self.assertEqual(users['total'], 1)
            self.assertEqual(users['objects'][0]['username'],
                             self.data.get_unauthorized_instructor().username)

    def test_usertypes(self):
        # test login required
        rv = self.client.get('/api/usertypes')
        self.assert401(rv)

        # test results
        with self.login('root'):
            rv = self.client.get('/api/usertypes')
            self.assert200(rv)
            types = rv.json
            self.assertEqual(len(types), 3)
            self.assertEqual(types[0]['name'], UserTypesForSystem.TYPE_NORMAL)
            self.assertEqual(types[1]['name'],
                             UserTypesForSystem.TYPE_INSTRUCTOR)
            self.assertEqual(types[2]['name'],
                             UserTypesForSystem.TYPE_SYSADMIN)

        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/usertypes')
            self.assert200(rv)
            types = rv.json
            self.assertEqual(len(types), 2)
            self.assertEqual(types[0]['name'], UserTypesForSystem.TYPE_NORMAL)
            self.assertEqual(types[1]['name'],
                             UserTypesForSystem.TYPE_INSTRUCTOR)

    def test_create_user(self):
        url = '/api/users'
        self.client.file_name = 'user_create.json'

        # test login required
        expected = UsersFactory.stub(
            usertypesforsystem_id=DefaultFixture.SYS_ROLE_NORMAL.id)
        rv = self.client.post(url,
                              data=json.dumps(expected.__dict__),
                              content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_NORMAL.id)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type='application/json',
                                  record='unauthorized')
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            # test duplicate username
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_NORMAL.id,
                username=self.data.get_authorized_student().username)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type='application/json',
                                  record='duplicate_username')
            self.assertStatus(rv, 409)
            self.assertEqual(
                "This username already exists. Please pick another.",
                rv.json['error'])

            # test duplicate student number
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_NORMAL.id,
                student_no=self.data.get_authorized_student().student_no)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual(
                "This student number already exists. Please pick another.",
                rv.json['error'])

            # test creating student
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_NORMAL.id)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type="application/json",
                                  record='create_student')
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            # test creating instructor
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_INSTRUCTOR.id)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            # test creating admin
            expected = UsersFactory.stub(
                usertypesforsystem_id=DefaultFixture.SYS_ROLE_ADMIN.id)
            rv = self.client.post(url,
                                  data=json.dumps(expected.__dict__),
                                  content_type="application/json")
            self.assert403(rv)

    def test_edit_user(self):
        user = self.data.get_authorized_instructor()
        url = 'api/users/' + str(user.id)
        expected = {
            'id': user.id,
            'username': user.username,
            'student_no': user.student_no,
            'usertypesforsystem_id': user.usertypesforsystem_id,
            'firstname': user.firstname,
            'lastname': user.lastname,
            'displayname': user.displayname,
            'email': user.email
        }

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

        # test unauthorized user
        # currently, instructors cannot edit users - except their own profile
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert403(rv)

        # test invalid user id
        with self.login('root'):
            rv = self.client.post('/api/users/999',
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test unmatched user's id
            invalid_url = '/api/users/' + str(
                self.data.get_unauthorized_instructor().id)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert400(rv)

            # test duplicate username
            duplicate = expected.copy()
            duplicate['username'] = self.data.get_unauthorized_student(
            ).username
            rv = self.client.post(url,
                                  data=json.dumps(duplicate),
                                  content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual(
                "This username already exists. Please pick another.",
                rv.json['error'])

            # test duplicate student number
            duplicate = expected.copy()
            duplicate['student_no'] = self.data.get_unauthorized_student(
            ).student_no
            rv = self.client.post(url,
                                  data=json.dumps(duplicate),
                                  content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual(
                "This student number already exists. Please pick another.",
                rv.json['error'])

            # test successful update by admin
            valid = expected.copy()
            valid['displayname'] = "displayzzz"
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual("displayzzz", rv.json['displayname'])

        # test successful update by user
        with self.login(self.data.get_authorized_instructor().username):
            valid = expected.copy()
            valid['displayname'] = "thebest"
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual("thebest", rv.json['displayname'])

        # test unable to update username, student_no, usertypesforsystem_id - instructor
        student = UserTypesForSystem.query.filter_by(
            name=UserTypesForSystem.TYPE_NORMAL).first()
        with self.login(user.username):
            valid = expected.copy()
            valid['username'] = "******"
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.username, rv.json['username'])

            valid = expected.copy()
            valid['student_no'] = "999999999999"
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.student_no, rv.json['student_no'])

            valid = expected.copy()
            valid['usertypesforsystem_id'] = student.id
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.usertypesforsystem_id,
                             rv.json['usertypesforsystem_id'])

        # test updating username, student number, usertype for system - admin
        with self.login('root'):
            valid = expected.copy()
            valid['username'] = '******'
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual('newUsername', rv.json['username'])

            valid = expected.copy()
            valid['student_no'] = '99999999'
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual('99999999', rv.json['student_no'])

            valid = expected.copy()
            valid['usertypesforsystem_id'] = student.id
            rv = self.client.post(url,
                                  data=json.dumps(valid),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.usertypesforsystem_id,
                             rv.json['usertypesforsystem_id'])

    def test_get_course_list(self):
        # test login required
        url = '/api/users/' + str(
            self.data.get_authorized_instructor().id) + '/courses'
        rv = self.client.get(url)
        self.assert401(rv)

        # test invalid user id
        with self.login('root'):
            url = '/api/users/999/courses'
            rv = self.client.get(url)
            self.assert404(rv)

            # test admin
            admin_id = Users.query.filter_by(username='******').first().id
            url = '/api/users/' + str(admin_id) + '/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json['objects']))

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            url = '/api/users/' + str(
                self.data.get_authorized_instructor().id) + '/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name,
                             rv.json['objects'][0]['name'])

        # test authorized student
        with self.login(self.data.get_authorized_student().username):
            url = '/api/users/' + str(
                self.data.get_authorized_student().id) + '/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name,
                             rv.json['objects'][0]['name'])

        # test authorized teaching assistant
        with self.login(self.data.get_authorized_ta().username):
            url = '/api/users/' + str(
                self.data.get_authorized_ta().id) + '/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name,
                             rv.json['objects'][0]['name'])

        # test dropped instructor
        with self.login(self.data.get_dropped_instructor().username):
            url = '/api/users/' + str(
                self.data.get_dropped_instructor().id) + '/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['objects']))

    def test_get_teaching_course(self):
        url = '/api/users/courses/teaching'

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

        # test student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['courses']))

        # test TA
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['courses']))

        # test instructor
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['courses']))

        # test admin
        with self.login('root'):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json['courses']))

    def test_update_password(self):
        url = '/api/users/{}/password'
        data = {'oldpassword': '******', 'newpassword': '******'}

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

        # test student update password
        with self.login(self.data.authorized_student.username):
            # test without old password
            rv = self.client.post(url.format(
                str(self.data.authorized_student.id)),
                                  data=json.dumps({'newpassword': '******'}),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                'The old password is incorrect or you do not have permission to change password.',
                rv.json['error'])

            # test incorrect old password
            invalid_input = data.copy()
            invalid_input['oldpassword'] = '******'
            rv = self.client.post(url.format(
                str(self.data.authorized_student.id)),
                                  data=json.dumps(invalid_input),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                'The old password is incorrect or you do not have permission to change password.',
                rv.json['error'])

            # test with old password
            rv = self.client.post(url.format(
                str(self.data.authorized_student.id)),
                                  data=json.dumps(data),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().id,
                             rv.json['id'])

        # test instructor update password
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url.format(str(999)),
                                  data=json.dumps(data),
                                  content_type='application/json')
            self.assert404(rv)

            # test instructor changes the password of a student in the course
            rv = self.client.post(url.format(
                str(self.data.get_authorized_student().id)),
                                  data=json.dumps(data),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().id,
                             rv.json['id'])

            # test changing own password
            rv = self.client.post(url.format(
                str(self.data.get_authorized_instructor().id)),
                                  data=json.dumps(data),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_instructor().id,
                             rv.json['id'])

        # test instructor changes the password of a student not in the course
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(url.format(
                str(self.data.get_authorized_student().id)),
                                  data=json.dumps(data),
                                  content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                '<p>{} does not have edit access to {}</p>'.format(
                    self.data.get_unauthorized_instructor().username,
                    self.data.get_authorized_student().username),
                rv.json['message'])

        # test admin update password
        with self.login('root'):
            # test admin changes student password without old password
            rv = self.client.post(url.format(
                str(self.data.get_authorized_student().id)),
                                  data=json.dumps({'newpassword': '******'}),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().id,
                             rv.json['id'])

    def test_get_course_roles(self):
        url = '/api/courseroles'

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

        # test successful query
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert200(rv)
            types = rv.json
            self.assertEqual(len(types), 3)
            self.assertEqual(types[0]['name'],
                             UserTypesForCourse.TYPE_INSTRUCTOR)
            self.assertEqual(types[1]['name'], UserTypesForCourse.TYPE_TA)
            self.assertEqual(types[2]['name'], UserTypesForCourse.TYPE_STUDENT)

    def test_get_edit_button(self):
        url = '/api/users/' + str(
            self.data.get_authorized_student().id) + '/edit'

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

        # test invalid user id
        with self.login(self.data.get_unauthorized_student().username):
            invalid_url = '/api/users/999/edit'
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test unavailable button
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

        # test available button
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def _verify_permissions(self, userid, permissions):
        user = Users.query.get(userid)
        with self.app.app_context():
            # can't figure out how to get into logged in app context, so just force a login here
            login_user(user, force=True)
            admin = user.usertypeforsystem.name == 'System Administrator'
            for model_name, operations in permissions.items():
                for operation, permission in operations.items():
                    expected = True
                    try:
                        ensure(operation, model_name)
                    except Unauthorized:
                        expected = False
                    expected = expected or admin
                    self.assertEqual(
                        permission['global'], expected,
                        "Expected permission " + operation + " on " +
                        model_name + " to be " + str(expected))
            # undo the forced login earlier
            logout_user()

    def _generate_search_users(self, user):
        return {
            'id':
            user.id,
            'display':
            user.fullname + ' (' + user.displayname + ') - ' +
            user.usertypeforsystem.name,
            'name':
            user.fullname
        }
Exemple #3
0
class UsersAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(UsersAPITests, self).setUp()
        self.data = BasicTestData()

    def test_unauthorized(self):
        rv = self.client.get('/api/users')
        self.assert401(rv)

    def test_login(self):
        with self.login('root', 'password') as rv:
            root = User.query.get(1)
            user_id = rv.json['user_id']
            self.assertEqual(root.uuid, user_id, "Logged in user's id does not match!")
            self._verify_permissions(root.id, rv.json['permissions'])

    def test_users_root(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/' + DefaultFixture.ROOT_USER.uuid)
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['username'], 'root')
            self.assertEqual(root['displayname'], 'root')
            self.assertNotIn('_password', root)

    def test_users_invalid_id(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/99999')
            self.assert404(rv)

    def test_users_info_unrestricted(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users/' + DefaultFixture.ROOT_USER.uuid)
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['displayname'], 'root')
            # personal information should be transmitted
            self.assertIn('firstname', root)
            self.assertIn('lastname', root)
            self.assertIn('fullname', root)
            self.assertIn('email', root)

    def test_users_info_restricted(self):
        user = UserFactory(system_role=SystemRole.student)

        with self.login(user.username, user.password):
            rv = self.client.get('/api/users/' + DefaultFixture.ROOT_USER.uuid)
            self.assert200(rv)
            root = rv.json
            self.assertEqual(root['displayname'], 'root')
            # personal information shouldn't be transmitted
            self.assertNotIn('firstname', root)
            self.assertNotIn('lastname', root)
            self.assertNotIn('fullname', root)
            self.assertNotIn('email', root)

    def test_users_list(self):
        with self.login('root', 'password'):
            rv = self.client.get('/api/users')
            self.assert200(rv)
            users = rv.json
            self.assertEqual(users['total'], 7)
            self.assertEqual(users['objects'][0]['username'], 'root')

            rv = self.client.get('/api/users?search={}'.format(self.data.get_unauthorized_instructor().firstname))
            self.assert200(rv)
            users = rv.json
            self.assertEqual(users['total'], 1)
            self.assertEqual(users['objects'][0]['username'], self.data.get_unauthorized_instructor().username)

    def test_create_user(self):
        url = '/api/users'

        # test login required
        expected = UserFactory.stub(system_role=SystemRole.student.value)
        rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type='application/json')
        self.assert401(rv)

        # test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            expected = UserFactory.stub(system_role=SystemRole.student.value)
            rv = self.client.post(
                url, data=json.dumps(expected.__dict__), content_type='application/json')
            self.assert403(rv)

        with self.login(self.data.get_authorized_instructor().username):
            expected = UserFactory.stub(system_role=SystemRole.student.value)
            rv = self.client.post(
                url, data=json.dumps(expected.__dict__), content_type='application/json')
            self.assert403(rv)

        # only system admins can create users
        with self.login('root'):
            # test duplicate username
            expected = UserFactory.stub(
                system_role=SystemRole.student.value,
                username=self.data.get_authorized_student().username)
            rv = self.client.post(
                url, data=json.dumps(expected.__dict__), content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual("This username already exists. Please pick another.", rv.json['error'])

            # test duplicate student number
            expected = UserFactory.stub(
                system_role=SystemRole.student.value,
                student_number=self.data.get_authorized_student().student_number)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual("This student number already exists. Please pick another.", rv.json['error'])

            # test creating student
            expected = UserFactory.stub(system_role=SystemRole.student.value)
            rv = self.client.post(
                url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            # test creating instructor
            expected = UserFactory.stub(system_role=SystemRole.instructor.value)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            # test creating admin
            expected = UserFactory.stub(system_role=SystemRole.sys_admin.value)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

    def test_create_user_lti(self):
        url = '/api/users'
        lti_data = LTITestData()

        # test login required when LTI and oauth_create_user_link are not present
        expected = UserFactory.stub(system_role=SystemRole.student.value)
        rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type='application/json')
        self.assert401(rv)

        # Instructor - no context
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=None,
                roles="Instructor") as lti_response:
            self.assert200(lti_response)

            # test create instructor via lti session
            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.instructor, user.system_role)
            self.assertIsNotNone(user.password)
            self.assertEqual(expected.username, user.username)

            # verify not enrolled in any course
            self.assertEqual(len(user.user_courses), 0)

        # Instructor - with context not linked
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=lti_data.generate_context_id(),
                roles="Instructor") as lti_response:
            self.assert200(lti_response)

            # test create instructor via lti session
            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.instructor, user.system_role)
            self.assertIsNotNone(user.password)
            self.assertEqual(expected.username, user.username)

            # verify not enrolled in any course
            self.assertEqual(len(user.user_courses), 0)

        # Instructor - with context linked
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=lti_data.generate_context_id(),
                roles="Instructor") as lti_response:
            self.assert200(lti_response)

            lti_context = LTIContext.query.all()[-1]
            course = self.data.create_course()
            lti_context.compair_course_id = course.id
            db.session.commit()

            # test create instructor via lti session
            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.instructor, user.system_role)
            self.assertIsNotNone(user.password)
            self.assertEqual(expected.username, user.username)

            # verify enrolled in course
            self.assertEqual(len(user.user_courses), 1)
            self.assertEqual(user.user_courses[0].course_id, course.id)

        # test create student via lti session
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=lti_data.generate_context_id(),
                roles="Student") as lti_response:
            self.assert200(lti_response)

            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.student, user.system_role)
            self.assertIsNotNone(user.password)
            self.assertEqual(expected.username, user.username)

        # test create teaching assistant (student role) via lti session
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=lti_data.generate_context_id(),
                roles="TeachingAssistant") as lti_response:
            self.assert200(lti_response)

            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.student, user.system_role)
            self.assertIsNotNone(user.password)
            self.assertEqual(expected.username, user.username)


    def test_create_user_lti_and_CAS(self):
        url = '/api/users'
        lti_data = LTITestData()
        auth_data = ThirdPartyAuthTestData()

        with self.client.session_transaction() as sess:
            sess['CAS_CREATE'] = True
            sess['CAS_UNIQUE_IDENTIFIER'] = "some_unique_identifier"
            self.assertIsNone(sess.get('LTI'))

        # test login required when LTI and oauth_create_user_link are not present (even when CAS params are present)
        expected = UserFactory.stub(system_role=SystemRole.student.value)
        rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type='application/json')
        self.assert401(rv)

        # test create student via lti session
        with self.lti_launch(lti_data.get_consumer(), lti_data.generate_resource_link_id(),
                user_id=lti_data.generate_user_id(), context_id=lti_data.generate_context_id(),
                roles="Student") as lti_response:
            self.assert200(lti_response)

            with self.client.session_transaction() as sess:
                sess['CAS_CREATE'] = True
                sess['CAS_UNIQUE_IDENTIFIER'] = "some_unique_identifier"
                self.assertTrue(sess.get('LTI'))

            expected = UserFactory.stub(system_role=None)
            rv = self.client.post(url, data=json.dumps(expected.__dict__), content_type="application/json")
            self.assert200(rv)
            self.assertEqual(expected.displayname, rv.json['displayname'])

            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(SystemRole.student, user.system_role)
            self.assertIsNone(user.password)
            self.assertIsNone(user.username)

            third_party_user = ThirdPartyUser.query \
                .filter_by(
                    third_party_type=ThirdPartyType.cas,
                    unique_identifier="some_unique_identifier",
                    user_id=user.id
                ) \
                .one_or_none()

            self.assertIsNotNone(third_party_user)

            with self.client.session_transaction() as sess:
                self.assertTrue(sess.get('CAS_LOGIN'))
                self.assertIsNone(sess.get('CAS_CREATE'))
                self.assertIsNone(sess.get('CAS_UNIQUE_IDENTIFIER'))
                self.assertIsNone(sess.get('oauth_create_user_link'))


    def test_edit_user(self):
        user = self.data.get_authorized_student()
        url = 'api/users/' + user.uuid
        expected = {
            'id': user.uuid,
            'username': user.username,
            'student_number': user.student_number,
            'system_role': user.system_role.value,
            'firstname': user.firstname,
            'lastname': user.lastname,
            'displayname': user.displayname,
            'email': user.email
        }
        instructor = self.data.get_authorized_instructor()
        instructor_url = 'api/users/' + instructor.uuid
        expected_instructor = {
            'id': instructor.uuid,
            'username': instructor.username,
            'student_number': instructor.student_number,
            'system_role': instructor.system_role.value,
            'firstname': instructor.firstname,
            'lastname': instructor.lastname,
            'displayname': instructor.displayname,
            'email': instructor.email
        }

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

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

        # test invalid user id
        with self.login('root'):
            rv = self.client.post('/api/users/999', data=json.dumps(expected), content_type='application/json')
            self.assert404(rv)

            # test unmatched user's id
            invalid_url = '/api/users/' + self.data.get_unauthorized_instructor().uuid
            rv = self.client.post(invalid_url, data=json.dumps(expected), content_type='application/json')
            self.assert400(rv)

            # test duplicate username
            duplicate = expected.copy()
            duplicate['username'] = self.data.get_unauthorized_student().username
            rv = self.client.post(url, data=json.dumps(duplicate), content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual("This username already exists. Please pick another.", rv.json['error'])

            # test duplicate student number
            duplicate = expected.copy()
            duplicate['student_number'] = self.data.get_unauthorized_student().student_number
            rv = self.client.post(url, data=json.dumps(duplicate), content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual("This student number already exists. Please pick another.", rv.json['error'])

            # test successful update by admin
            valid = expected.copy()
            valid['displayname'] = "displayzzz"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual("displayzzz", rv.json['displayname'])

        # test successful update by user (as instructor)
        with self.login(self.data.get_authorized_instructor().username):
            valid = expected_instructor.copy()
            valid['displayname'] = "thebest"
            rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual("thebest", rv.json['displayname'])

        # test successful update by user (as student)
        with self.login(self.data.get_authorized_student().username):
            valid = expected.copy()
            valid['displayname'] = "thebest"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual("thebest", rv.json['displayname'])

        # test updating username, student number, usertype for system - instructor
        with self.login(instructor.username):
            # for student
            valid = expected.copy()
            valid['username'] = "******"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.username, rv.json['username'])

            valid = expected.copy()
            valid['student_number'] = "999999999999"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.student_number, rv.json['student_number'])

            valid = expected.copy()
            valid['system_role'] = SystemRole.student.value
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.system_role.value, rv.json['system_role'])

            # for instructor
            valid = expected_instructor.copy()
            valid['username'] = "******"
            rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(instructor.username, rv.json['username'])

            ignored = expected_instructor.copy()
            ignored['student_number'] = "999999999999"
            rv = self.client.post(instructor_url, data=json.dumps(ignored), content_type='application/json')
            self.assert200(rv)
            self.assertIsNone(rv.json['student_number'])
            self.assertEqual(instructor.student_number, rv.json['student_number'])

            valid = expected_instructor.copy()
            valid['system_role'] = SystemRole.student.value
            rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(instructor.system_role.value, rv.json['system_role'])

        # test updating username, student number, usertype for system - admin
        with self.login('root'):
            # for student
            valid = expected.copy()
            valid['username'] = '******'
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual('newUsername', rv.json['username'])

            valid = expected.copy()
            valid['student_number'] = '99999999'
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual('99999999', rv.json['student_number'])

            valid = expected.copy()
            valid['system_role'] = SystemRole.student.value
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(user.system_role.value, rv.json['system_role'])

            # for instructor
            valid = expected_instructor.copy()
            valid['username'] = "******"
            rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(instructor.username, rv.json['username'])

            ignored = expected_instructor.copy()
            ignored['student_number'] = "999999999999"
            rv = self.client.post(instructor_url, data=json.dumps(ignored), content_type='application/json')
            self.assert200(rv)
            self.assertIsNone(rv.json['student_number'])
            self.assertEqual(instructor.student_number, rv.json['student_number'])

            valid = expected_instructor.copy()
            valid['system_role'] = SystemRole.student.value
            rv = self.client.post(instructor_url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(instructor.system_role.value, rv.json['system_role'])

        # test edit user with no compair login
        auth_data = ThirdPartyAuthTestData()
        cas_user_auth = auth_data.create_cas_user_auth(SystemRole.student)
        user = cas_user_auth.user
        self.data.enrol_user(user, self.data.get_course(), CourseRole.student)

        url = 'api/users/' + user.uuid
        expected = {
            'id': user.uuid,
            'username': user.username,
            'student_number': user.student_number,
            'system_role': user.system_role.value,
            'firstname': user.firstname,
            'lastname': user.lastname,
            'displayname': user.displayname,
            'email': user.email
        }

        # edit own profile as cas user
        with self.cas_login(cas_user_auth.unique_identifier):
            # cannot change username (must be None)
            valid = expected.copy()
            valid['username'] = "******"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertIsNone(user.username)

        # test updating username as instructor
        with self.login(instructor.username):
            # cannot change username (must be None)
            valid = expected.copy()
            valid['username'] = "******"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertIsNone(user.username)

        # test updating username as system admin
        with self.login('root'):
            # admin can optionally change username
            valid = expected.copy()
            valid['username'] = ''
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertIsNone(user.username)

            valid = expected.copy()
            valid['username'] = "******"
            rv = self.client.post(url, data=json.dumps(valid), content_type='application/json')
            self.assert200(rv)
            user = User.query.filter_by(uuid=rv.json['id']).one()
            self.assertEqual(user.username, "valid_username")


    def test_get_course_list(self):
        # test login required
        url = '/api/users/courses'
        rv = self.client.get(url)
        self.assert401(rv)

        with self.login('root'):
            # test admin
            url = '/api/users/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json['objects']))

        # test authorized instructor
        with self.login(self.data.get_authorized_instructor().username):
            url = '/api/users/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name, rv.json['objects'][0]['name'])

            # test search filter
            url = '/api/users/courses?search='+self.data.get_course().name
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name, rv.json['objects'][0]['name'])

            # test search filter
            url = '/api/users/courses?search=notfounds'+self.data.get_course().name
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['objects']))

            # test sort order (when some courses have start_dates and other do not)
            url = '/api/users/courses'

            self.data.get_course().start_date = None

            course_2 = self.data.create_course()
            course_2.start_date = datetime.datetime.now()
            self.data.enrol_instructor(self.data.get_authorized_instructor(), course_2)

            course_3 = self.data.create_course()
            course_3.start_date = datetime.datetime.now() + datetime.timedelta(days=10)
            self.data.enrol_instructor(self.data.get_authorized_instructor(), course_3)

            courses = [course_3, course_2, self.data.get_course()]

            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(3, len(rv.json['objects']))
            for index, result in enumerate(rv.json['objects']):
                self.assertEqual(courses[index].uuid, result['id'])

            # test sort order (when course with no start date has assignment)
            assignment = AssignmentFactory(
                user=self.data.get_authorized_instructor(),
                course=self.data.get_course(),
                answer_start=(datetime.datetime.now() + datetime.timedelta(days=5))
            )
            db.session.commit()

            courses = [course_3, self.data.get_course(), course_2]

            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(3, len(rv.json['objects']))
            for index, result in enumerate(rv.json['objects']):
                self.assertEqual(courses[index].uuid, result['id'])

        # test authorized student
        with self.login(self.data.get_authorized_student().username):
            url = '/api/users/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name, rv.json['objects'][0]['name'])

        # test authorized teaching assistant
        with self.login(self.data.get_authorized_ta().username):
            url = '/api/users/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['objects']))
            self.assertEqual(self.data.get_course().name, rv.json['objects'][0]['name'])

        # test dropped instructor
        with self.login(self.data.get_dropped_instructor().username):
            url = '/api/users/courses'
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['objects']))

    def test_get_teaching_course(self):
        url = '/api/users/courses/teaching'

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

        # test student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(0, len(rv.json['courses']))

        # test TA
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['courses']))

        # test instructor
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(1, len(rv.json['courses']))

        # test admin
        with self.login('root'):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(2, len(rv.json['courses']))

    def test_update_password(self):
        url = '/api/users/{}/password'
        data = {
            'oldpassword': '******',
            'newpassword': '******'
        }

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

        # test student update password
        with self.login(self.data.authorized_student.username):
            # test without old password
            rv = self.client.post(
                url.format(self.data.authorized_student.uuid),
                data=json.dumps({'newpassword': '******'}),
                content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                'The old password is incorrect or you do not have permission to change password.',
                rv.json['error'])

            # test incorrect old password
            invalid_input = data.copy()
            invalid_input['oldpassword'] = '******'
            rv = self.client.post(
                url.format(self.data.authorized_student.uuid),
                data=json.dumps(invalid_input), content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                'The old password is incorrect or you do not have permission to change password.',
                rv.json['error'])

            # test with old password
            rv = self.client.post(
                url.format(self.data.authorized_student.uuid),
                data=json.dumps(data),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id'])

        # test instructor update password
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url.format(str(999)), data=json.dumps(data), content_type='application/json')
            self.assert404(rv)

            # test instructor changes the password of a student in the course
            rv = self.client.post(
                url.format(self.data.get_authorized_student().uuid), data=json.dumps(data),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id'])

            # test changing own password
            rv = self.client.post(
                url.format(self.data.get_authorized_instructor().uuid),
                data=json.dumps(data), content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_instructor().uuid, rv.json['id'])

        # test instructor changes the password of a student not in the course
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.post(
                url.format(self.data.get_authorized_student().uuid), data=json.dumps(data),
                content_type='application/json')
            self.assert403(rv)
            self.assertEqual(
                '<p>{} does not have edit access to {}</p>'.format(self.data.get_unauthorized_instructor().username,
                                                                   self.data.get_authorized_student().username),
                rv.json['message'])

        # test admin update password
        with self.login('root'):
            # test admin changes student password without old password
            rv = self.client.post(
                url.format(self.data.get_authorized_student().uuid), data=json.dumps({'newpassword': '******'}),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(self.data.get_authorized_student().uuid, rv.json['id'])


        # test update password of user with no compair login
        auth_data = ThirdPartyAuthTestData()
        cas_user_auth = auth_data.create_cas_user_auth(SystemRole.student)
        user = cas_user_auth.user
        self.data.enrol_user(user, self.data.get_course(), CourseRole.student)
        url = 'api/users/' + user.uuid + '/password'

        # update own password as cas user
        with self.cas_login(cas_user_auth.unique_identifier):
            # cannot change password
            rv = self.client.post(url, data=json.dumps(data), content_type='application/json')
            self.assert400(rv)
            self.assertEqual("Cannot update password. User does not use ComPAIR account login authentication method.", rv.json['error'])

    def test_get_edit_button(self):
        url = '/api/users/' + self.data.get_authorized_student().uuid + '/edit'

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

        # test invalid user id
        with self.login(self.data.get_unauthorized_student().username):
            invalid_url = '/api/users/999/edit'
            rv = self.client.get(invalid_url)
            self.assert404(rv)

            # test unavailable button
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

        # test available button
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def _verify_permissions(self, user_id, permissions):
        user = User.query.get(user_id)
        with self.app.app_context():
            # can't figure out how to get into logged in app context, so just force a login here
            login_user(user, force=True)
            admin = user.system_role == SystemRole.sys_admin
            for model_name, operations in permissions.items():
                for operation, permission in operations.items():
                    expected = True
                    try:
                        ensure(operation, model_name)
                    except Unauthorized:
                        expected = False
                    expected = expected or admin
                    self.assertEqual(
                        permission['global'], expected,
                        "Expected permission " + operation + " on " + model_name + " to be " + str(expected))
            # undo the forced login earlier
            logout_user()

    def _generate_search_users(self, user):
        return {
            'id': user.uuid,
            'display': user.fullname + ' (' + user.displayname + ') - ' + user.system_role,
            'name': user.fullname}
Exemple #4
0
class ClassListAPITest(ComPAIRAPITestCase):
    def setUp(self):
        super(ClassListAPITest, self).setUp()
        self.data = BasicTestData()
        self.url = "/api/courses/" + self.data.get_course().uuid + "/users"

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

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

        expected = [
            (self.data.get_authorized_instructor(), '', ''),
            (self.data.get_authorized_ta(), '', ''),
            (self.data.get_authorized_student(), '', '')]
        expected.sort(key=lambda x: x[0].lastname)

        with self.login(self.data.get_authorized_instructor().username):
            # test authorized user
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, (user, cas_username, group_name) in enumerate(expected):
                self.assertEqual(user.uuid, rv.json['objects'][key]['id'])

            # test export csv
            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with group names
            for user_course in self.data.main_course.user_courses:
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    user_course.group_name = "instructor_group"
                elif user_course.user_id == self.data.get_authorized_ta().id:
                    user_course.group_name = "ta_group"
                elif user_course.user_id == self.data.get_authorized_student().id:
                    user_course.group_name = "student_group"
            db.session.commit()

            expected = [
                (self.data.get_authorized_instructor(), '', "instructor_group"),
                (self.data.get_authorized_ta(), '', "ta_group"),
                (self.data.get_authorized_student(), '', "student_group")]
            expected.sort(key=lambda x: x[0].lastname)

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with cas usernames
            third_party_student = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_instructor = ThirdPartyUserFactory(user=self.data.get_authorized_instructor())
            third_party_ta = ThirdPartyUserFactory(user=self.data.get_authorized_ta())
            db.session.commit()

            expected = [
                (self.data.get_authorized_instructor(), third_party_instructor.unique_identifier, "instructor_group"),
                (self.data.get_authorized_ta(), third_party_ta.unique_identifier, "ta_group"),
                (self.data.get_authorized_student(), third_party_student.unique_identifier, "student_group")]
            expected.sort(key=lambda x: x[0].lastname)

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )

            # test export csv with cas usernames (student has multiple cas_usernames). should use first username
            third_party_student2 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_student3 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            third_party_student4 = ThirdPartyUserFactory(user=self.data.get_authorized_student())
            db.session.commit()

            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.splitlines(), delimiter=',')

            self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader))
            for user, cas_username, group_name in expected:
                self.assertEqual(
                    [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name],
                    next(reader)
                )


        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, (user, cas_username, group_name) in enumerate(expected):
                self.assertEqual(user.uuid, rv.json['objects'][key]['id'])

    def test_get_instructor_labels(self):
        url = self.url + "/instructors/labels"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/instructors/labels')
            self.assert404(rv)

            # test success
            rv = self.client.get(url)
            self.assert200(rv)
            labels = rv.json['instructors']
            expected = {
                self.data.get_authorized_ta().uuid: 'Teaching Assistant',
                self.data.get_authorized_instructor().uuid: 'Instructor'
            }
            self.assertEqual(labels, expected)

    def test_get_students_course(self):
        url = self.url + "/students"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/students')
            self.assert404(rv)

            # test success - instructor
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().fullname_sortable
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'])

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().fullname_sortable
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'])

        # test success - student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['objects']
            expected = {
                'id': self.data.get_authorized_student().uuid,
                'name': self.data.get_authorized_student().displayname
            }
            self.assertEqual(students[0]['id'], expected['id'])
            self.assertEqual(students[0]['name'], expected['name'] + ' (You)')

    def test_enrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_dropped_instructor().uuid)
        role = {'course_role': 'Instructor'}  # defaults to Instructor

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

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

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = '/api/courses/999/users/' + self.data.get_dropped_instructor().uuid
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, "999")
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test enrolling dropped instructor
            expected = {
                'user_id': self.data.get_dropped_instructor().uuid,
                'fullname': self.data.get_dropped_instructor().fullname,
                'fullname_sortable': self.data.get_dropped_instructor().fullname_sortable,
                'course_role': CourseRole.instructor.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling new instructor
            url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid)
            expected = {
                'user_id': self.data.get_unauthorized_instructor().uuid,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'fullname_sortable': self.data.get_unauthorized_instructor().fullname_sortable,
                'course_role': CourseRole.instructor.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling a different role - eg. Student
            role = {'course_role': CourseRole.teaching_assistant.value }
            expected = {
                'user_id': self.data.get_unauthorized_instructor().uuid,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'fullname_sortable': self.data.get_unauthorized_instructor().fullname_sortable,
                'course_role': CourseRole.teaching_assistant.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_unenrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_authorized_instructor().uuid)

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

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

        # test invalid course id
        invalid_url = '/api/courses/999/users/' + self.data.get_authorized_instructor().uuid
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, "999")
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test existing user not in existing course
            invalid_url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test success
            expected = {
                'user_id': self.data.get_authorized_instructor().uuid,
                'fullname': self.data.get_authorized_instructor().fullname,
                'fullname_sortable': self.data.get_authorized_instructor().fullname_sortable,
                'course_role': CourseRole.dropped.value
            }
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_import_compair_classlist(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/users'
        student = self.data.get_authorized_student()
        instructor = self.data.get_authorized_instructor()
        ta = self.data.get_authorized_ta()

        filename = "classlist.csv"

        # test login required
        uploaded_file = io.BytesIO((student.username+",password").encode('utf-8'))
        rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
        self.assert401(rv)
        uploaded_file.close()

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename)))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype)))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join([",\n", student.username, ",password,", student.student_number])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test no password provided
            content = "".join(["nopasswordusername"])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("nopasswordusername", result['invalids'][0]['user']['username'])
            self.assertEqual('The password is required.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate usernames in file
            content = "".join([student.username, ",password\n", student.username, ",password"])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(student.username, result['invalids'][0]['user']['username'])
            self.assertEqual('This username already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in system
            content = "".join(['username1,password,', student.student_number, "\n", student.username, ',password'])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                student.username, ",password,", student.student_number, "\n",
                "username1,password,", student.student_number])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,password,,,," + student.displayname
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2,password')
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user (with password)
            current_password = student.password
            new_password = '******'

            uploaded_file = io.BytesIO((student.username+','+new_password).encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()
            self.assertEqual(student.password, current_password)
            self.assertNotEqual(student.password, new_password)

            student.last_online = None
            db.session.commit()

            uploaded_file = io.BytesIO((student.username+','+new_password).encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()
            self.assertNotEqual(student.password, current_password)
            self.assertEqual(student.password, new_password)

            new_password = '******'
            for set_password in [new_password, '*', '']:
                uploaded_file = io.BytesIO((student.username+','+set_password).encode('utf-8'))
                rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
                self.assert200(rv)
                result = rv.json
                self.assertEqual(1, result['success'])
                self.assertEqual(0, len(result['invalids']))
                uploaded_file.close()
                self.assertEqual(student.password, new_password)

            # test invalid import type app login disabled
            self.app.config['APP_LOGIN_ENABLED'] = False
            uploaded_file = io.BytesIO(student.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['APP_LOGIN_ENABLED'] = True

            # test authorized instructor - existing instructor
            uploaded_file = io.BytesIO(instructor.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            instructor_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=instructor.id,
                    course_role=CourseRole.instructor
                ) \
                .one_or_none()
            self.assertIsNotNone(instructor_enrollment)

            # test authorized instructor - existing teaching assistant
            uploaded_file = io.BytesIO(ta.username.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            ta_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=ta.id,
                    course_role=CourseRole.teaching_assistant
                ) \
                .one_or_none()
            self.assertIsNotNone(ta_enrollment)

            # test authorized instructor - group enrollment
            content = "".join([
                student.username, ",*,,,,,,group_student\n",
                instructor.username, ",*,,,,,,group_instructor\n",
                ta.username, ",*,,,,,,group_ta\n",
            ])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                if user_course.user_id == student.id:
                    self.assertEqual(user_course.group_name, 'group_student')
                elif user_course.user_id == instructor.id:
                    self.assertEqual(user_course.group_name, 'group_instructor')
                elif user_course.user_id == ta.id:
                    self.assertEqual(user_course.group_name, 'group_ta')

            # test authorized instructor - group unenrollment
            content = "".join([
                student.username, ",*,,,,,,\n",
                instructor.username, ",*,,,,,,\n",
                ta.username, ",*,,,,,,\n",
            ])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                self.assertEqual(user_course.group_name, None)

    def test_import_cas_classlist(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/users'
        student = self.data.get_authorized_student()
        third_party_student = ThirdPartyUserFactory(user=student)
        instructor = self.data.get_authorized_instructor()
        third_party_instructor = ThirdPartyUserFactory(user=instructor)
        ta = self.data.get_authorized_ta()
        third_party_ta = ThirdPartyUserFactory(user=ta)
        db.session.commit()

        filename = "classlist.csv"

        # test login required
        uploaded_file = io.BytesIO((third_party_student.unique_identifier).encode('utf-8'))
        rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
        self.assert401(rv)
        uploaded_file.close()

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype), import_type=ThirdPartyType.cas.value))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join([",\n", third_party_student.unique_identifier, ",", student.student_number])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate usernames in file
            content = "".join([third_party_student.unique_identifier, "\n", third_party_student.unique_identifier])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(third_party_student.unique_identifier, result['invalids'][0]['user']['username'])
            self.assertEqual('This username already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in system
            content = "".join(['username1,', student.student_number, "\n", third_party_student.unique_identifier])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                third_party_student.unique_identifier, ",", student.student_number, "\n",
                "username1,", student.student_number])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,,,," + student.displayname
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2')
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test invalid import type cas login disabled
            self.app.config['CAS_LOGIN_ENABLED'] = False
            uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert400(rv)
            uploaded_file.close()
            self.app.config['CAS_LOGIN_ENABLED'] = True

            # test authorized instructor - existing instructor
            uploaded_file = io.BytesIO(third_party_instructor.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            instructor_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=instructor.id,
                    course_role=CourseRole.instructor
                ) \
                .one_or_none()
            self.assertIsNotNone(instructor_enrollment)

            # test authorized instructor - existing teaching assistant
            uploaded_file = io.BytesIO(third_party_ta.unique_identifier.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(0, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            ta_enrollment = UserCourse.query \
                .filter_by(
                    course_id=self.data.get_course().id,
                    user_id=ta.id,
                    course_role=CourseRole.teaching_assistant
                ) \
                .one_or_none()
            self.assertIsNotNone(ta_enrollment)

            # test authorized instructor - group enrollment
            content = "".join([
                third_party_student.unique_identifier, ",,,,,,group_student\n",
                third_party_instructor.unique_identifier, ",,,,,,group_instructor\n",
                third_party_ta.unique_identifier, ",,,,,,group_ta\n",
            ])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                if user_course.user_id == student.id:
                    self.assertEqual(user_course.group_name, 'group_student')
                elif user_course.user_id == instructor.id:
                    self.assertEqual(user_course.group_name, 'group_instructor')
                elif user_course.user_id == ta.id:
                    self.assertEqual(user_course.group_name, 'group_ta')

            # test authorized instructor - group unenrollment
            content = "".join([
                third_party_student.unique_identifier, ",,,,,,\n",
                third_party_instructor.unique_identifier, ",,,,,,\n",
                third_party_ta.unique_identifier, ",,,,,,\n",
            ])
            uploaded_file = io.BytesIO(content.encode('utf-8'))
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            user_courses = UserCourse.query \
                .filter(
                    UserCourse.course_id == self.data.get_course().id,
                    UserCourse.course_role != CourseRole.dropped
                ) \
                .all()

            self.assertEqual(len(user_courses), 3)
            for user_course in user_courses:
                self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id])
                self.assertEqual(user_course.group_name, None)


    def test_update_course_role_miltiple(self):
        url = self.url + '/roles'

        user_ids = [self.data.authorized_instructor.uuid, self.data.authorized_student.uuid, self.data.authorized_ta.uuid]
        params = {
            'ids': user_ids,
            'course_role': CourseRole.instructor.value
        }

        # 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.data.get_unauthorized_instructor().username):
            rv = self.client.post(
                url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert403(rv)

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

            # test missing user ids
            missing_ids = params.copy()
            missing_ids['ids'] = []
            rv = self.client.post(
                url,
                data=json.dumps(missing_ids),
                content_type='application/json')
            self.assert400(rv)

            # test invalid user ids
            invalid_ids = params.copy()
            invalid_ids['ids'] = [self.data.unauthorized_student.uuid]
            rv = self.client.post(
                url,
                data=json.dumps(invalid_ids),
                content_type='application/json')
            self.assert400(rv)

            # cannot change current_user's course role
            params_self = {
                'ids': [self.data.get_authorized_instructor().uuid],
                'course_role': CourseRole.teaching_assistant.value
            }
            rv = self.client.post(
                url,
                data=json.dumps(params_self),
                content_type='application/json')
            self.assert400(rv)

            # test changing role instructor
            rv = self.client.post(
                url,
                data=json.dumps(params),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.instructor.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)

            # test changing teaching assistant
            params_ta = params.copy()
            params_ta['course_role'] = CourseRole.teaching_assistant.value
            rv = self.client.post(
                url,
                data=json.dumps(params_ta),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.teaching_assistant.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.teaching_assistant)

            # test changing role student
            params_student = params.copy()
            params_student['course_role'] = CourseRole.student.value
            rv = self.client.post(
                url,
                data=json.dumps(params_student),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.student.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.student)

            # test changing dropped
            params_dropped = { 'ids': user_ids }
            rv = self.client.post(
                url,
                data=json.dumps(params_dropped),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(rv.json['course_role'], CourseRole.dropped.value)

            for user_course in self.data.get_course().user_courses:
                # ingore changes for current_user
                if user_course.user_id == self.data.get_authorized_instructor().id:
                    self.assertEqual(user_course.course_role, CourseRole.instructor)
                # other users should have course role updated
                elif user_course.user_id in user_ids:
                    self.assertEqual(user_course.course_role, CourseRole.dropped)


    def _create_enrol_url(self, url, user_id):
        return url + '/' + user_id
class ClassListAPITest(ACJAPITestCase):
    def setUp(self):
        super(ClassListAPITest, self).setUp()
        self.data = BasicTestData()
        self.url = "/api/courses/" + str(self.data.get_course().id) + "/users"

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

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

        expected = [
            self.data.get_authorized_instructor(),
            self.data.get_authorized_ta(),
            self.data.get_authorized_student()]
        expected.sort(key=lambda x: x.firstname)

        with self.login(self.data.get_authorized_instructor().username):
            # test authorized user
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, user in enumerate(expected):
                self.assertEqual(user.id, rv.json['objects'][key]['id'])

            # test export csv
            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',')
            self.assertEqual(['username', 'student_no', 'firstname', 'lastname', 'email', 'displayname'], next(reader))

            for key, user in enumerate(expected):
                self.assertEqual(
                    [user.username, user.student_no or '', user.firstname, user.lastname, user.email, user.displayname],
                    next(reader)
                )

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, user in enumerate(expected):
                self.assertEqual(user.id, rv.json['objects'][key]['id'])

    def test_get_instructor_labels(self):
        url = self.url + "/instructors/labels"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/instructors/labels')
            self.assert404(rv)

            # test success
            rv = self.client.get(url)
            self.assert200(rv)
            labels = rv.json['instructors']
            expected = {
                str(self.data.get_authorized_ta().id): 'Teaching Assistant',
                str(self.data.get_authorized_instructor().id): 'Instructor'
            }
            self.assertEqual(labels, expected)

    def test_get_students_course(self):
        url = self.url + "/students"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/students')
            self.assert404(rv)

            # test success - instructor
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'], expected['name'])

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'], expected['name'])

        # test success - student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().displayname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'], expected['name'] + ' (You)')

    def test_enrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_dropped_instructor().id)
        role = {'course_role': 'Instructor'}  # defaults to Instructor

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

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

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = '/api/courses/999/users/instructors/' + str(self.data.get_dropped_instructor().id) + '/enrol'
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.post(
                invalid_url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert404(rv)

            # test enrolling dropped instructor
            expected = {
                'user_id': self.data.get_dropped_instructor().id,
                'fullname': self.data.get_dropped_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_INSTRUCTOR
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling new instructor
            url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().id)
            expected = {
                'user_id': self.data.get_unauthorized_instructor().id,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_INSTRUCTOR
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling a different role - eg. Student
            ta_role_id = UserTypesForCourse.query.filter_by(name=UserTypesForCourse.TYPE_TA).first().id
            role = {'course_role_id': str(ta_role_id)}
            expected = {
                'user_id': self.data.get_unauthorized_instructor().id,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_TA
            }
            rv = self.client.post(
                url,
                data=json.dumps(role),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_unenrol_instructor(self):
        url = self._create_enrol_url(self.url, self.data.get_authorized_instructor().id)
        dropped_role_id = UserTypesForCourse.query.filter_by(name=UserTypesForCourse.TYPE_DROPPED).first().id

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

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

        # test invalid course id
        invalid_url = '/api/courses/999/users/instructors/' + str(self.data.get_authorized_instructor().id) + '/enrol'
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test existing user not in existing course
            invalid_url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().id)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test success
            expected = {
                'user': {
                    'id': self.data.get_authorized_instructor().id,
                    'fullname': self.data.get_authorized_instructor().fullname
                },
                'usertypesforcourse': {
                    'id': dropped_role_id,
                    'name': UserTypesForCourse.TYPE_DROPPED
                }
            }
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_import_classlist(self):
        url = '/api/courses/' + str(self.data.get_course().id) + '/users'
        auth_student = self.data.get_authorized_student()
        filename = "classlist.csv"

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

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename)))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype)))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join([",\n", auth_student.username, ",", auth_student.student_no])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.', result['invalids'][0]['message'])
            uploaded_file.close()

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

            # test duplicate student number in system
            content = "".join(['username1,', auth_student.student_no, "\n", auth_student.username])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                auth_student.username, ",", auth_student.student_no, "\n",
                "username1,", auth_student.student_no])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1", result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,,,,," + auth_student.displayname
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2')
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url, data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

    def _create_enrol_url(self, url, user_id):
        return url + '/' + str(user_id)
Exemple #6
0
class ClassListAPITest(ACJAPITestCase):
    def setUp(self):
        super(ClassListAPITest, self).setUp()
        self.data = BasicTestData()
        self.url = "/api/courses/" + str(self.data.get_course().id) + "/users"

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

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

        expected = [
            self.data.get_authorized_instructor(),
            self.data.get_authorized_ta(),
            self.data.get_authorized_student()
        ]
        expected.sort(key=lambda x: x.firstname)

        with self.login(self.data.get_authorized_instructor().username):
            # test authorized user
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, user in enumerate(expected):
                self.assertEqual(user.id, rv.json['objects'][key]['id'])

            # test export csv
            rv = self.client.get(self.url, headers={'Accept': 'text/csv'})
            self.assert200(rv)
            self.assertEqual('text/csv', rv.content_type)
            reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(),
                                delimiter=',')
            self.assertEqual([
                'username', 'student_no', 'firstname', 'lastname', 'email',
                'displayname'
            ], next(reader))

            for key, user in enumerate(expected):
                self.assertEqual([
                    user.username, user.student_no or '', user.firstname,
                    user.lastname, user.email, user.displayname
                ], next(reader))

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(self.url)
            self.assert200(rv)
            self.assertEqual(len(expected), len(rv.json['objects']))
            for key, user in enumerate(expected):
                self.assertEqual(user.id, rv.json['objects'][key]['id'])

    def test_get_instructor_labels(self):
        url = self.url + "/instructors/labels"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/instructors/labels')
            self.assert404(rv)

            # test success
            rv = self.client.get(url)
            self.assert200(rv)
            labels = rv.json['instructors']
            expected = {
                str(self.data.get_authorized_ta().id): 'Teaching Assistant',
                str(self.data.get_authorized_instructor().id): 'Instructor'
            }
            self.assertEqual(labels, expected)

    def test_get_students_course(self):
        url = self.url + "/students"

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

        # test dropped instructor - unauthorized
        with self.login(self.data.get_dropped_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test unauthorized instructor
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get('/api/courses/999/users/students')
            self.assert404(rv)

            # test success - instructor
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'], expected['name'])

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().fullname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'], expected['name'])

        # test success - student
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert200(rv)
            students = rv.json['students']
            expected = {
                'id': self.data.get_authorized_student().id,
                'name': self.data.get_authorized_student().displayname
            }
            self.assertEqual(students[0]['user']['id'], expected['id'])
            self.assertEqual(students[0]['user']['name'],
                             expected['name'] + ' (You)')

    def test_enrol_instructor(self):
        url = self._create_enrol_url(self.url,
                                     self.data.get_dropped_instructor().id)
        role = {'course_role': 'Instructor'}  # defaults to Instructor

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

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

        # test invalid course id
        with self.login(self.data.get_authorized_instructor().username):
            invalid_url = '/api/courses/999/users/instructors/' + str(
                self.data.get_dropped_instructor().id) + '/enrol'
            rv = self.client.post(invalid_url,
                                  data=json.dumps(role),
                                  content_type='application/json')
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.post(invalid_url,
                                  data=json.dumps(role),
                                  content_type='application/json')
            self.assert404(rv)

            # test enrolling dropped instructor
            expected = {
                'user_id': self.data.get_dropped_instructor().id,
                'fullname': self.data.get_dropped_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_INSTRUCTOR
            }
            rv = self.client.post(url,
                                  data=json.dumps(role),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling new instructor
            url = self._create_enrol_url(
                self.url,
                self.data.get_unauthorized_instructor().id)
            expected = {
                'user_id': self.data.get_unauthorized_instructor().id,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_INSTRUCTOR
            }
            rv = self.client.post(url,
                                  data=json.dumps(role),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

            # test enrolling a different role - eg. Student
            ta_role_id = UserTypesForCourse.query.filter_by(
                name=UserTypesForCourse.TYPE_TA).first().id
            role = {'course_role_id': str(ta_role_id)}
            expected = {
                'user_id': self.data.get_unauthorized_instructor().id,
                'fullname': self.data.get_unauthorized_instructor().fullname,
                'course_role': UserTypesForCourse.TYPE_TA
            }
            rv = self.client.post(url,
                                  data=json.dumps(role),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_unenrol_instructor(self):
        url = self._create_enrol_url(self.url,
                                     self.data.get_authorized_instructor().id)
        dropped_role_id = UserTypesForCourse.query.filter_by(
            name=UserTypesForCourse.TYPE_DROPPED).first().id

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

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

        # test invalid course id
        invalid_url = '/api/courses/999/users/instructors/' + str(
            self.data.get_authorized_instructor().id) + '/enrol'
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test invalid user id
            invalid_url = self._create_enrol_url(self.url, 999)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test existing user not in existing course
            invalid_url = self._create_enrol_url(
                self.url,
                self.data.get_unauthorized_instructor().id)
            rv = self.client.delete(invalid_url)
            self.assert404(rv)

            # test success
            expected = {
                'user': {
                    'id': self.data.get_authorized_instructor().id,
                    'fullname': self.data.get_authorized_instructor().fullname
                },
                'usertypesforcourse': {
                    'id': dropped_role_id,
                    'name': UserTypesForCourse.TYPE_DROPPED
                }
            }
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(expected, rv.json)

    def test_import_classlist(self):
        url = '/api/courses/' + str(self.data.get_course().id) + '/users'
        auth_student = self.data.get_authorized_student()
        filename = "classlist.csv"

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

        # test unauthorized user
        with self.login(self.data.get_unauthorized_instructor().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_student().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_ta().username):
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert403(rv)
            uploaded_file.close()

        with self.login(self.data.get_authorized_instructor().username):
            # test invalid course id
            invalid_url = '/api/courses/999/users'
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(invalid_url,
                                  data=dict(file=(uploaded_file, filename)))
            uploaded_file.close()
            self.assert404(rv)

            # test invalid file type
            invalid_filetype = "classlist.png"
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file,
                                                  invalid_filetype)))
            uploaded_file.close()
            self.assert400(rv)

            # test no username provided
            content = "".join(
                [",\n", auth_student.username, ",", auth_student.student_no])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual(None, result['invalids'][0]['user']['username'])
            self.assertEqual('The username is required.',
                             result['invalids'][0]['message'])
            uploaded_file.close()

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

            # test duplicate student number in system
            content = "".join([
                'username1,', auth_student.student_no, "\n",
                auth_student.username
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1",
                             result['invalids'][0]['user']['username'])
            self.assertEqual(
                'This student number already exists in the system.',
                result['invalids'][0]['message'])
            uploaded_file.close()

            # test duplicate student number in file
            content = "".join([
                auth_student.username, ",", auth_student.student_no, "\n",
                "username1,", auth_student.student_no
            ])
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(1, len(result['invalids']))
            self.assertEqual("username1",
                             result['invalids'][0]['user']['username'])
            self.assertEqual('This student number already exists in the file.',
                             result['invalids'][0]['message'])
            uploaded_file.close()

            # test existing display
            content = "username1,,,,," + auth_student.displayname
            uploaded_file = io.BytesIO(content.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - new user
            uploaded_file = io.BytesIO(b'username2')
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

            # test authorized instructor - existing user
            uploaded_file = io.BytesIO(auth_student.username.encode())
            rv = self.client.post(url,
                                  data=dict(file=(uploaded_file, filename)))
            self.assert200(rv)
            result = rv.json
            self.assertEqual(1, result['success'])
            self.assertEqual(0, len(result['invalids']))
            uploaded_file.close()

    def _create_enrol_url(self, url, user_id):
        return url + '/' + str(user_id)