def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = BasicTestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)


        self.expected_caliper_object = {
            "id": 'https://localhost:8888',
            "type": "SoftwareApplication",
            "name": "ComPAIR",
            "description": "The ComPAIR learning application pairs student answers for deeper learning through comparison of peer work.",
            "version": self.app.config.get("COMPAIR_VERSION")
        }
        self.expected_xapi_object = {
            'id': 'https://localhost:8888',
            'objectType': 'Activity',
            'definition': {
                'type': 'http://activitystrea.ms/schema/1.0/service',
                'name': {'en-US': 'ComPAIR'},
                'extensions': {
                    'http://id.tincanapi.com/extension/version': self.app.config.get("COMPAIR_VERSION")
                }
            }
        }
Example #2
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = BasicTestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.criterion = self.data.create_criterion(self.user)

        self.expected_caliper_criterion = {
            'dateCreated':
            self.criterion.created.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'dateModified':
            self.criterion.modified.replace(
                tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'id':
            "https://localhost:8888/app/criterion/" + self.criterion.uuid,
            'name':
            self.criterion.name,
            'description':
            self.criterion.description,
            'type':
            'DigitalResource'
        }

        self.expected_xapi_criterion = {
            'id':
            "https://localhost:8888/app/criterion/" + self.criterion.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {
                    'en-US': self.criterion.name
                },
                'description': {
                    'en-US': self.criterion.description
                }
            },
            'objectType': 'Activity'
        }
Example #3
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = BasicTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )

        self.expected_caliper_course = {
            'academicSession': self.course.term,
            'dateCreated': self.course.created.replace(tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'dateModified': self.course.modified.replace(tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'name': self.course.name,
            'type': 'CourseOffering',
            'otherIdentifiers': [{
                'identifier': self.lti_context.context_id,
                'identifierType': 'LtiContextId',
                'type': 'SystemIdentifier',
                'extensions': {
                    'lis_course_offering_sourcedid': 'sis_course_id',
                    'lis_course_section_sourcedid': 'sis_section_id',
                    'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key,
                },
            }]
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/"+self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {'en-US': self.course.name}
            },
            'objectType': 'Activity'
        }
Example #4
0
class CoursesAPITests(ACJAPITestCase):
    def setUp(self):
        super(CoursesAPITests, self).setUp()
        self.data = BasicTestData()

    def _verify_course_info(self, course_expected, course_actual):
        self.assertEqual(course_expected.name, course_actual['name'],
                         "Expected course name does not match actual.")
        self.assertEqual(course_expected.id, course_actual['id'],
                         "Expected course id does not match actual.")
        self.assertTrue(course_expected.criteriaandcourses,
                        "Course is missing a criteria")

    def test_get_single_course(self):
        course_api_url = '/api/courses/' + str(self.data.get_course().id)

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test root get course
        with self.login('root'):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test enroled users get course info
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test unenroled user not permitted to get info
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_unauthorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        # Test get invalid course
        with self.login("root"):
            rv = self.client.get('/api/courses/38940450')
            self.assert404(rv)

    def test_get_all_courses(self):
        course_api_url = '/api/courses'

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test only root can get a list of all courses
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login("root"):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(),
                                     rv.json['objects'][0])

    def test_create_course(self):
        course_expected = {
            'name': 'ExpectedCourse1',
            'description': 'Test Course One Description Test'
        }
        # Test login required
        rv = self.client.post('/api/courses',
                              data=json.dumps(course_expected),
                              content_type='application/json')
        self.assert401(rv)
        # Test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert403(rv)

        # Test course creation
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert200(rv)
            # Verify return
            course_actual = rv.json
            self.assertEqual(course_expected['name'], course_actual['name'])
            self.assertEqual(course_expected['description'],
                             course_actual['description'])

            # Verify the course is created in db
            course_in_db = Courses.query.get(course_actual['id'])
            self.assertEqual(course_in_db.name, course_actual['name'])
            self.assertEqual(course_in_db.description,
                             course_actual['description'])

            # create course with criteria
            course = course_expected.copy()
            course['name'] = 'ExpectedCourse2'
            course['criteria'] = [{'id': 1}]
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course),
                                  content_type='application/json')
            self.assert200(rv)
            course_actual = rv.json

            # Verify the course is created in db
            course_in_db = Courses.query.get(course_actual['id'])
            self.assertEqual(len(course_in_db.criteriaandcourses), 1)
            self.assertEqual(course_in_db.criteriaandcourses[0].criteria_id,
                             course['criteria'][0]['id'])

    def test_create_duplicate_course(self):
        with self.login(self.data.get_authorized_instructor().username):
            course_existing = self.data.get_course()
            course_expected = {
                'name': course_existing.name,
                'description': course_existing.description
            }
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert400(rv)

    def test_create_course_with_bad_data_format(self):
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps({'description': 'd'}),
                                  content_type='application/json')
            self.assert400(rv)

    def test_edit_course(self):
        expected = {
            'id': self.data.get_course().id,
            'name': 'ExpectedCourse',
            'description': 'Test Description'
        }
        url = '/api/courses/' + str(self.data.get_course().id)

        # 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 unmatched course id
            rv = self.client.post('/api/courses/' +
                                  str(self.data.get_secondary_course().id),
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert400(rv)

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

            # test authorized user
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            db.session.expire_all()
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['name'], rv.json['name'])
            self.assertEqual(expected['description'], rv.json['description'])

            # test add criteria
            course = expected.copy()
            course['criteria'] = [{'id': 1}]
            rv = self.client.post(url,
                                  data=json.dumps(course),
                                  content_type='application/json')
            self.assert200(rv)

            db.session.expire_all()
            course_in_db = Courses.query.get(course['id'])
            self.assertEqual(len(course_in_db.criteriaandcourses), 1)
            self.assertEqual(course_in_db.criteriaandcourses[0].criteria_id,
                             course['criteria'][0]['id'])

            # test remove criteria
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)

            # expire all instances in session and force session to query from db
            db.session.expire_all()
            course_in_db = Courses.query.get(course['id'])
            self.assertEqual(len(course_in_db.criteriaandcourses), 0)
Example #5
0
 def setUp(self):
     super(CoursesAPITests, self).setUp()
     self.data = BasicTestData()
Example #6
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = BasicTestData()
     self.user = self.data.authorized_student
     self.course = self.data.main_course
Example #7
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = BasicTestData()
     self.user = self.data.authorized_student
     self.criterion = self.data.create_criterion(self.user)
Example #8
0
 def setUp(self):
     super(ClassListAPITest, self).setUp()
     self.data = BasicTestData()
     self.url = "/api/courses/" + self.data.get_course().uuid + "/users"
Example #9
0
 def setUp(self):
     super(LTIConsumersAPITests, self).setUp()
     self.data = BasicTestData()
     self.lti_data = LTITestData()
Example #10
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}
Example #11
0
 def setUp(self):
     super(ClassListAPITest, self).setUp()
     self.data = BasicTestData()
     self.url = "/api/courses/" + self.data.get_course().uuid + "/users"
Example #12
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)
Example #14
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = BasicTestData()
        self.lti_data = LTITestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.course = self.data.main_course
        self.lti_context = self.lti_data.create_context(
            self.lti_data.lti_consumer,
            compair_course_id=self.course.id,
            lis_course_offering_sourcedid="sis_course_id",
            lis_course_section_sourcedid="sis_section_id",
        )

        self.expected_caliper_course = {
            'academicSession':
            self.course.term,
            'dateCreated':
            self.course.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.course.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id':
            "https://localhost:8888/app/course/" + self.course.uuid,
            'name':
            self.course.name,
            'type':
            'CourseOffering',
            'extensions': {
                'ltiContexts': [{
                    'context_id':
                    self.lti_context.context_id,
                    'oauth_consumer_key':
                    self.lti_data.lti_consumer.oauth_consumer_key,
                    'lis_course_offering_sourcedid':
                    "sis_course_id",
                    'lis_course_section_sourcedid':
                    "sis_section_id",
                }]
            }
        }

        self.expected_xapi_course = {
            'id': "https://localhost:8888/app/course/" + self.course.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/course',
                'name': {
                    'en-US': self.course.name
                }
            },
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_course = {
            'id': 'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid,
            'objectType': 'Activity'
        }

        self.expected_xapi_sis_section = {
            'id':
            'https://localhost:8888/course/' +
            self.lti_context.lis_course_offering_sourcedid + '/section/' +
            self.lti_context.lis_course_section_sourcedid,
            'objectType':
            'Activity'
        }
Example #15
0
 def setUp(self):
     super(UsersAPITests, self).setUp()
     self.data = BasicTestData()
Example #16
0
class LTIConsumersAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(LTIConsumersAPITests, self).setUp()
        self.data = BasicTestData()
        self.lti_data = LTITestData()

    def _build_consumer_url(self, consumer_uuid=None):
        return '/api/lti/consumers' + ('/' +
                                       consumer_uuid if consumer_uuid else '')

    def test_create_lti_consumer(self):
        url = self._build_consumer_url()

        consumer_expected = {
            'oauth_consumer_key': 'new_consumer_key',
            'oauth_consumer_secret': 'new_consumer_secret',
            'global_unique_identifier_param':
            'new_global_unique_identifier_param',
            'student_number_param': 'new_student_number_param'
        }

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

        # Test unauthorized access
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)

        # Test authorized access
        with self.login('root'):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(consumer_expected['oauth_consumer_key'],
                             rv.json['oauth_consumer_key'])
            self.assertEqual(consumer_expected['oauth_consumer_secret'],
                             rv.json['oauth_consumer_secret'])
            self.assertEqual(
                consumer_expected['global_unique_identifier_param'],
                rv.json['global_unique_identifier_param'])
            self.assertEqual(consumer_expected['student_number_param'],
                             rv.json['student_number_param'])
            self.assertTrue(rv.json['active'])

            # test unique oauth_consumer_key by submitting again
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual(rv.json['title'], "Consumer Not Saved")
            self.assertEqual(
                rv.json['message'],
                "An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again."
            )

    def test_list_lti_consumers(self):
        url = self._build_consumer_url()

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

        # Test unauthorized access
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert403(rv)
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # Test authorized access
        with self.login('root'):
            lti_consumers = self.lti_data.lti_consumers

            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(len(rv.json['objects']), 1)
            self.assertEqual(rv.json['total'], 1)

            for index, lti_consumer in enumerate(lti_consumers):
                self.assertEqual(
                    lti_consumer.oauth_consumer_key,
                    rv.json['objects'][index]['oauth_consumer_key'])
                self.assertEqual(
                    lti_consumer.global_unique_identifier_param,
                    rv.json['objects'][index]
                    ['global_unique_identifier_param'])
                self.assertEqual(
                    lti_consumer.student_number_param,
                    rv.json['objects'][index]['student_number_param'])
                self.assertEqual(lti_consumer.active,
                                 rv.json['objects'][index]['active'])
                self.assertNotIn('oauth_consumer_secret',
                                 rv.json['objects'][index])

            # test paging
            for i in range(1, 30):  # add 29 more consumers
                if i % 2 == 0:
                    self.lti_data.create_consumer(
                        oauth_consumer_key='lti_consumer_key_' + str(i))
                else:
                    self.lti_data.create_consumer(
                        oauth_consumer_key='lti_consumer_key_' + str(i),
                        global_unique_identifier_param=
                        'global_unique_identifier_param_' + str(i))
            lti_consumers = self.lti_data.lti_consumers

            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(len(rv.json['objects']), 20)
            self.assertEqual(rv.json['total'], 30)

            for index, lti_consumer in enumerate(lti_consumers[:20]):
                self.assertEqual(
                    lti_consumer.oauth_consumer_key,
                    rv.json['objects'][index]['oauth_consumer_key'])
                self.assertEqual(
                    lti_consumer.global_unique_identifier_param,
                    rv.json['objects'][index]
                    ['global_unique_identifier_param'])
                self.assertEqual(
                    lti_consumer.student_number_param,
                    rv.json['objects'][index]['student_number_param'])
                self.assertEqual(lti_consumer.active,
                                 rv.json['objects'][index]['active'])
                self.assertNotIn('oauth_consumer_secret',
                                 rv.json['objects'][index])

            rv = self.client.get(url + "?page=2")
            self.assert200(rv)
            self.assertEqual(len(rv.json['objects']), 10)
            self.assertEqual(rv.json['total'], 30)

            for index, lti_consumer in enumerate(lti_consumers[20:]):
                self.assertEqual(
                    lti_consumer.oauth_consumer_key,
                    rv.json['objects'][index]['oauth_consumer_key'])
                self.assertEqual(
                    lti_consumer.global_unique_identifier_param,
                    rv.json['objects'][index]
                    ['global_unique_identifier_param'])
                self.assertEqual(
                    lti_consumer.student_number_param,
                    rv.json['objects'][index]['student_number_param'])
                self.assertEqual(lti_consumer.active,
                                 rv.json['objects'][index]['active'])
                self.assertNotIn('oauth_consumer_secret',
                                 rv.json['objects'][index])

            # test order by
            rv = self.client.get(url + "?orderBy=oauth_consumer_key")
            self.assert200(rv)
            self.assertEqual(len(rv.json['objects']), 20)
            self.assertEqual(rv.json['total'], 30)

            sorted_lti_consumers = sorted(
                [consumer for consumer in lti_consumers],
                key=lambda consumer: consumer.oauth_consumer_key)[:20]

            for index, lti_consumer in enumerate(sorted_lti_consumers):
                self.assertEqual(
                    lti_consumer.oauth_consumer_key,
                    rv.json['objects'][index]['oauth_consumer_key'])
                self.assertEqual(
                    lti_consumer.global_unique_identifier_param,
                    rv.json['objects'][index]
                    ['global_unique_identifier_param'])
                self.assertEqual(
                    lti_consumer.student_number_param,
                    rv.json['objects'][index]['student_number_param'])
                self.assertEqual(lti_consumer.active,
                                 rv.json['objects'][index]['active'])
                self.assertNotIn('oauth_consumer_secret',
                                 rv.json['objects'][index])

            rv = self.client.get(url +
                                 "?orderBy=oauth_consumer_key&reverse=true")
            self.assert200(rv)
            self.assertEqual(len(rv.json['objects']), 20)
            self.assertEqual(rv.json['total'], 30)

            sorted_lti_consumers = sorted(
                [consumer for consumer in lti_consumers],
                key=lambda consumer: consumer.oauth_consumer_key,
                reverse=True)[:20]

            for index, lti_consumer in enumerate(sorted_lti_consumers):
                self.assertEqual(
                    lti_consumer.oauth_consumer_key,
                    rv.json['objects'][index]['oauth_consumer_key'])
                self.assertEqual(
                    lti_consumer.global_unique_identifier_param,
                    rv.json['objects'][index]
                    ['global_unique_identifier_param'])
                self.assertEqual(
                    lti_consumer.student_number_param,
                    rv.json['objects'][index]['student_number_param'])
                self.assertEqual(lti_consumer.active,
                                 rv.json['objects'][index]['active'])
                self.assertNotIn('oauth_consumer_secret',
                                 rv.json['objects'][index])

    def test_get_lti_consumer(self):
        lti_consumer = self.lti_data.lti_consumer
        url = self._build_consumer_url(lti_consumer.uuid)

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

        # Test unauthorized access
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(url)
            self.assert403(rv)
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.get(url)
            self.assert403(rv)
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(url)
            self.assert403(rv)

        # Test authorized access
        with self.login('root'):
            # invalid id
            rv = self.client.get(self._build_consumer_url("999"))
            self.assert404(rv)

            # valid url
            rv = self.client.get(url)
            self.assert200(rv)
            self.assertEqual(lti_consumer.oauth_consumer_key,
                             rv.json['oauth_consumer_key'])
            self.assertEqual(lti_consumer.oauth_consumer_secret,
                             rv.json['oauth_consumer_secret'])
            self.assertEqual(lti_consumer.global_unique_identifier_param,
                             rv.json['global_unique_identifier_param'])
            self.assertEqual(lti_consumer.student_number_param,
                             rv.json['student_number_param'])
            self.assertTrue(lti_consumer.active, rv.json['active'])

    def test_edit_lti_consumer(self):
        lti_consumer = self.lti_data.lti_consumer
        url = self._build_consumer_url(lti_consumer.uuid)

        consumer_expected = {
            'id': lti_consumer.uuid,
            'oauth_consumer_key': 'edit_consumer_key',
            'oauth_consumer_secret': 'edit_consumer_secret',
            'global_unique_identifier_param':
            'edit_consumer_global_unique_identifier_param',
            'student_number_param': 'edit_student_number_param',
            'active': False
        }

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

        # Test unauthorized access
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)
        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert403(rv)

        # Test authorized access
        with self.login('root'):
            # invalid id
            rv = self.client.post(self._build_consumer_url("999"),
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert404(rv)

            # invalid id
            invalid_expected = consumer_expected.copy()
            invalid_expected['id'] = "999"
            rv = self.client.post(url,
                                  data=json.dumps(invalid_expected),
                                  content_type='application/json')
            self.assert400(rv)

            # valid url
            rv = self.client.post(url,
                                  data=json.dumps(consumer_expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertEqual(consumer_expected['oauth_consumer_key'],
                             rv.json['oauth_consumer_key'])
            self.assertEqual(consumer_expected['oauth_consumer_secret'],
                             rv.json['oauth_consumer_secret'])
            self.assertEqual(
                consumer_expected['global_unique_identifier_param'],
                rv.json['global_unique_identifier_param'])
            self.assertEqual(consumer_expected['student_number_param'],
                             rv.json['student_number_param'])
            self.assertEqual(consumer_expected['active'], rv.json['active'])

            # valid url (empty global_unique_identifier_param)
            consumer_expected_no_override = consumer_expected.copy()
            consumer_expected_no_override[
                'global_unique_identifier_param'] = ""
            rv = self.client.post(
                url,
                data=json.dumps(consumer_expected_no_override),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(consumer_expected['oauth_consumer_key'],
                             rv.json['oauth_consumer_key'])
            self.assertEqual(consumer_expected['oauth_consumer_secret'],
                             rv.json['oauth_consumer_secret'])
            self.assertIsNone(rv.json['global_unique_identifier_param'])
            self.assertEqual(consumer_expected['student_number_param'],
                             rv.json['student_number_param'])
            self.assertEqual(consumer_expected['active'], rv.json['active'])

            # valid url (empty student_number_param)
            consumer_expected_no_override = consumer_expected.copy()
            consumer_expected_no_override['student_number_param'] = ""
            rv = self.client.post(
                url,
                data=json.dumps(consumer_expected_no_override),
                content_type='application/json')
            self.assert200(rv)
            self.assertEqual(consumer_expected['oauth_consumer_key'],
                             rv.json['oauth_consumer_key'])
            self.assertEqual(consumer_expected['oauth_consumer_secret'],
                             rv.json['oauth_consumer_secret'])
            self.assertEqual(
                consumer_expected['global_unique_identifier_param'],
                rv.json['global_unique_identifier_param'])
            self.assertIsNone(rv.json['student_number_param'])
            self.assertEqual(consumer_expected['active'], rv.json['active'])

            # test edit duplicate consumer key
            lti_consumer2 = self.lti_data.create_consumer()
            url = self._build_consumer_url(lti_consumer2.uuid)

            consumer2_expected = consumer_expected.copy()
            consumer2_expected['id'] = lti_consumer2.uuid

            rv = self.client.post(url,
                                  data=json.dumps(consumer2_expected),
                                  content_type='application/json')
            self.assertStatus(rv, 409)
            self.assertEqual(rv.json['title'], "Consumer Not Saved")
            self.assertEqual(
                rv.json['message'],
                "An LTI consumer with the same consumer key already exists. Please double-check the consumer key and try saving again."
            )
Example #17
0
class CriterionLearningRecordTests(ComPAIRLearningRecordTestCase):
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = BasicTestData()
        self.user = self.data.authorized_student
        self.setup_session_data(self.user)
        self.criterion = self.data.create_criterion(self.user)

        self.expected_caliper_criterion = {
            'dateCreated':
            self.criterion.created.replace(tzinfo=pytz.utc).isoformat(),
            'dateModified':
            self.criterion.modified.replace(tzinfo=pytz.utc).isoformat(),
            'id':
            "https://localhost:8888/app/criterion/" + self.criterion.uuid,
            'name':
            self.criterion.name,
            'description':
            self.criterion.description,
            'type':
            'Entity'
        }

        self.expected_xapi_criterion = {
            'id':
            "https://localhost:8888/app/criterion/" + self.criterion.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {
                    'en-US': self.criterion.name
                },
                'description': {
                    'en-US': self.criterion.description
                }
            },
            'objectType': 'Activity'
        }

    def test_on_criterion_create(self):
        on_criterion_create.send(current_app._get_current_object(),
                                 event_name=on_criterion_create.name,
                                 user=self.user,
                                 criterion=self.criterion)

        events = self.get_and_clear_caliper_event_log()
        expected_caliper_event = {
            'action':
            'Created',
            'actor':
            self.get_compair_caliper_actor(self.user),
            'object':
            self.expected_caliper_criterion,
            'session':
            self.get_caliper_session(self.get_compair_caliper_actor(
                self.user)),
            'type':
            'Event'
        }

        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        statements = self.get_and_clear_xapi_statement_log()
        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": {
                'id': 'http://activitystrea.ms/schema/1.0/author',
                'display': {
                    'en-US': 'authored'
                }
            },
            "object": self.expected_xapi_criterion,
            "context": {
                'extensions': {
                    'http://id.tincanapi.com/extension/browser-info': {},
                    'http://id.tincanapi.com/extension/session-info':
                    self.get_xapi_session_info()
                }
            }
        }

        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)

    def test_on_criterion_update(self):
        on_criterion_update.send(current_app._get_current_object(),
                                 event_name=on_criterion_update.name,
                                 user=self.user,
                                 criterion=self.criterion)

        events = self.get_and_clear_caliper_event_log()
        expected_caliper_event = {
            'action':
            'Modified',
            'actor':
            self.get_compair_caliper_actor(self.user),
            'object':
            self.expected_caliper_criterion,
            'session':
            self.get_caliper_session(self.get_compair_caliper_actor(
                self.user)),
            'type':
            'Event'
        }

        self.assertEqual(len(events), 1)
        self.assertEqual(events[0], expected_caliper_event)

        statements = self.get_and_clear_xapi_statement_log()
        expected_xapi_statement = {
            "actor": self.get_compair_xapi_actor(self.user),
            "verb": {
                'id': 'http://activitystrea.ms/schema/1.0/update',
                'display': {
                    'en-US': 'updated'
                }
            },
            "object": self.expected_xapi_criterion,
            "context": {
                'extensions': {
                    'http://id.tincanapi.com/extension/browser-info': {},
                    'http://id.tincanapi.com/extension/session-info':
                    self.get_xapi_session_info()
                }
            }
        }

        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0], expected_xapi_statement)
Example #18
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)
Example #19
0
class CoursesAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(CoursesAPITests, self).setUp()
        self.data = BasicTestData()

    def _verify_course_info(self, course_expected, course_actual):
        self.assertEqual(course_expected.name, course_actual['name'],
                         "Expected course name does not match actual.")
        self.assertEqual(course_expected.uuid, course_actual['id'],
                         "Expected course id does not match actual.")
        self.assertEqual(course_expected.year, course_actual['year'],
                         "Expected course year does not match actual.")
        self.assertEqual(course_expected.term, course_actual['term'],
                         "Expected course term does not match actual.")
        self.assertEqual(
            course_expected.sandbox, course_actual['sandbox'],
            "Expected course sandbox flag does not match actual.")
        self.assertEqual(
            course_expected.available, course_actual['available'],
            "Expected course availability does not match actual.")

    def test_get_single_course(self):
        course_api_url = '/api/courses/' + self.data.get_course().uuid

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test root get course
        with self.login('root'):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test enroled users get course info
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test unenroled user not permitted to get info
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_unauthorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        # Test get invalid course
        with self.login("root"):
            rv = self.client.get('/api/courses/38940450')
            self.assert404(rv)

    def test_create_course(self):
        course_expected = {
            'name': 'ExpectedCourse1',
            'year': 2015,
            'term': 'Winter',
            'sandbox': False,
            'start_date': None,
            'end_date': None
        }
        # Test login required
        rv = self.client.post('/api/courses',
                              data=json.dumps(course_expected),
                              content_type='application/json')
        self.assert401(rv)
        # Test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert403(rv)

        # Test course creation
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert200(rv)
            # Verify return
            course_actual = rv.json
            self.assertEqual(course_expected['name'], course_actual['name'])
            self.assertEqual(course_expected['year'], course_actual['year'])
            self.assertEqual(course_expected['term'], course_actual['term'])
            self.assertEqual(course_expected['sandbox'],
                             course_actual['sandbox'])
            self.assertTrue(course_actual['available'])

            # Verify the course is created in db
            course_in_db = Course.query.filter_by(
                uuid=course_actual['id']).first()
            self.assertEqual(course_in_db.name, course_actual['name'])
            self.assertEqual(course_in_db.year, course_actual['year'])
            self.assertEqual(course_in_db.term, course_actual['term'])
            self.assertEqual(course_in_db.sandbox, course_actual['sandbox'])
            self.assertTrue(course_in_db.available)

            # Verify instructor added to course
            user_course = UserCourse.query \
                .filter_by(
                    user_id=self.data.get_authorized_instructor().id,
                    course_uuid=course_actual['id']
                ) \
                .one_or_none()
            self.assertIsNotNone(user_course)

            # Starts in the future
            now = datetime.datetime.utcnow()
            course_expected['start_date'] = (
                now + datetime.timedelta(days=7)).isoformat() + 'Z',
            course_expected['end_date'] = None
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Ended in the past
            course_expected['start_date'] = None
            course_expected['end_date'] = (
                now - datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Is currently available
            course_expected['start_date'] = (
                now - datetime.timedelta(days=7)).isoformat() + 'Z',
            course_expected['end_date'] = (
                now + datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post('/api/courses',
                                  data=json.dumps(course_expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def test_create_course_with_bad_data_format(self):
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post('/api/courses',
                                  data=json.dumps({'year': 'd'}),
                                  content_type='application/json')
            self.assert400(rv)

    def test_edit_course(self):
        expected = {
            'id': self.data.get_course().uuid,
            'name': 'ExpectedCourse',
            'year': 2015,
            'term': 'Winter',
            'sandbox': False,
            'start_date': None,
            'end_date': None
        }
        url = '/api/courses/' + self.data.get_course().uuid

        # 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 unmatched course id
            rv = self.client.post('/api/courses/' +
                                  self.data.get_secondary_course().uuid,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert400(rv)

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

            # test authorized user
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            db.session.expire_all()
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['name'], rv.json['name'])
            self.assertTrue(rv.json['available'])

            # Starts in the future
            now = datetime.datetime.utcnow()
            expected['start_date'] = (
                now + datetime.timedelta(days=7)).isoformat() + 'Z',
            expected['end_date'] = None
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Ended in the past
            expected['start_date'] = None
            expected['end_date'] = (
                now - datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Is currently available
            expected['start_date'] = (
                now - datetime.timedelta(days=7)).isoformat() + 'Z',
            expected['end_date'] = (
                now + datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def test_delete_course(self):
        course_uuid = self.data.get_course().uuid
        url = '/api/courses/' + course_uuid

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

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

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.delete(url)
            self.assert403(rv)

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

            # test deletion by authorized insturctor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course_uuid, rv.json['id'])

            # test course is deleted
            rv = self.client.delete(url)
            self.assert404(rv)

        course2 = self.data.create_course()
        url = '/api/courses/' + course2.uuid

        with self.login('root'):
            # test deletion by system admin
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course2.uuid, rv.json['id'])

            # test course is deleted
            rv = self.client.delete(url)
            self.assert404(rv)

    def test_duplicate_course_simple(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/duplicate'
        expected = {
            'name': 'duplicate course',
            'year': 2015,
            'term': 'Winter',
            'sandbox': False,
            'start_date': None,
            'end_date': None
        }
        # test login required
        rv = self.client.post(url, 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)

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

            # test year missing
            invalid_expected = {'term': 'Winter'}
            rv = self.client.post('/api/courses/999/duplicate',
                                  data=json.dumps(invalid_expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test term missing
            invalid_expected = {'year': 2015}
            rv = self.client.post('/api/courses/999/duplicate',
                                  data=json.dumps(invalid_expected),
                                  content_type='application/json')
            self.assert404(rv)

            # test authorized user
            original_course = self.data.get_course()
            rv = self.client.post(url,
                                  data=json.dumps(expected),
                                  content_type='application/json')
            self.assert200(rv)

            # verify course duplicated correctly
            self.assertNotEqual(original_course.uuid, rv.json['id'])
            self.assertEqual(expected['name'], rv.json['name'])
            self.assertEqual(expected['year'], rv.json['year'])
            self.assertEqual(expected['term'], rv.json['term'])
            self.assertEqual(expected['sandbox'], rv.json['sandbox'])
            self.assertEqual(expected['start_date'], rv.json['start_date'])
            self.assertEqual(expected['end_date'], rv.json['end_date'])

            # verify instructor added to duplicate course
            user_course = UserCourse.query \
                .filter_by(
                    user_id=self.data.get_authorized_instructor().id,
                    course_uuid=rv.json['id']
                ) \
                .one_or_none()
            self.assertIsNotNone(user_course)
Example #20
0
class CriterionXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = BasicTestData()
        self.user = self.data.authorized_student
        self.criterion = self.data.create_criterion(self.user)

    def test_on_criterion_create(self):
        on_criterion_create.send(
            current_app._get_current_object(),
            event_name=on_criterion_create.name,
            user=self.user,
            criterion=self.criterion
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)

        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/author',
            'display': {'en-US': 'authored'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/xapi/criterion/'+self.criterion.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {'en-US': self.criterion.name },
                'description': {'en-US': self.criterion.description }
            },
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertNotIn('context', statements[0])


    def test_on_criterion_update(self):
        on_criterion_update.send(
            current_app._get_current_object(),
            event_name=on_criterion_update.name,
            user=self.user,
            criterion=self.criterion
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)

        self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
        self.assertEqual(statements[0]['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/update',
            'display': {'en-US': 'updated'}
        })
        self.assertEqual(statements[0]['object'], {
            'id': 'https://localhost:8888/app/xapi/criterion/'+self.criterion.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/question',
                'name': {'en-US': self.criterion.name },
                'description': {'en-US': self.criterion.description }
            },
            'objectType': 'Activity'
        })
        self.assertNotIn('result', statements[0])
        self.assertNotIn('context', statements[0])
Example #21
0
class CriterionXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = BasicTestData()
        self.user = self.data.authorized_student
        self.criterion = self.data.create_criterion(self.user)

    def test_on_criterion_create(self):
        on_criterion_create.send(
            current_app._get_current_object(),
            event_name=on_criterion_create.name,
            user=self.user,
            criterion=self.criterion,
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)

        self.assertEqual(statements[0]["actor"], self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]["verb"], {"id": "http://activitystrea.ms/schema/1.0/author", "display": {"en-US": "authored"}}
        )
        self.assertEqual(
            statements[0]["object"],
            {
                "id": "https://localhost:8888/app/xapi/criterion/" + self.criterion.uuid,
                "definition": {
                    "type": "http://adlnet.gov/expapi/activities/question",
                    "name": {"en-US": self.criterion.name},
                    "description": {"en-US": self.criterion.description},
                },
                "objectType": "Activity",
            },
        )
        self.assertNotIn("result", statements[0])
        self.assertNotIn("context", statements[0])

    def test_on_criterion_update(self):
        on_criterion_update.send(
            current_app._get_current_object(),
            event_name=on_criterion_update.name,
            user=self.user,
            criterion=self.criterion,
        )

        statements = self.get_and_clear_statement_log()
        self.assertEqual(len(statements), 1)

        self.assertEqual(statements[0]["actor"], self.get_compair_actor(self.user))
        self.assertEqual(
            statements[0]["verb"], {"id": "http://activitystrea.ms/schema/1.0/update", "display": {"en-US": "updated"}}
        )
        self.assertEqual(
            statements[0]["object"],
            {
                "id": "https://localhost:8888/app/xapi/criterion/" + self.criterion.uuid,
                "definition": {
                    "type": "http://adlnet.gov/expapi/activities/question",
                    "name": {"en-US": self.criterion.name},
                    "description": {"en-US": self.criterion.description},
                },
                "objectType": "Activity",
            },
        )
        self.assertNotIn("result", statements[0])
        self.assertNotIn("context", statements[0])
Example #22
0
class CoursesAPITests(ACJAPITestCase):
    def setUp(self):
        super(CoursesAPITests, self).setUp()
        self.data = BasicTestData()

    def _verify_course_info(self, course_expected, course_actual):
        self.assertEqual(course_expected.name, course_actual["name"], "Expected course name does not match actual.")
        self.assertEqual(course_expected.id, course_actual["id"], "Expected course id does not match actual.")
        self.assertTrue(course_expected.criteriaandcourses, "Course is missing a criteria")

    def test_get_single_course(self):
        course_api_url = "/api/courses/" + str(self.data.get_course().id)

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test root get course
        with self.login("root"):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test enroled users get course info
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test unenroled user not permitted to get info
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_unauthorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        # Test get invalid course
        with self.login("root"):
            rv = self.client.get("/api/courses/38940450")
            self.assert404(rv)

    def test_get_all_courses(self):
        course_api_url = "/api/courses"

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test only root can get a list of all courses
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login("root"):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json["objects"][0])

    def test_create_course(self):
        course_expected = {"name": "ExpectedCourse1", "description": "Test Course One Description Test"}
        # Test login required
        rv = self.client.post("/api/courses", data=json.dumps(course_expected), content_type="application/json")
        self.assert401(rv)
        # Test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post("/api/courses", data=json.dumps(course_expected), content_type="application/json")
            self.assert403(rv)

        # Test course creation
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post("/api/courses", data=json.dumps(course_expected), content_type="application/json")
            self.assert200(rv)
            # Verify return
            course_actual = rv.json
            self.assertEqual(course_expected["name"], course_actual["name"])
            self.assertEqual(course_expected["description"], course_actual["description"])

            # Verify the course is created in db
            course_in_db = Courses.query.get(course_actual["id"])
            self.assertEqual(course_in_db.name, course_actual["name"])
            self.assertEqual(course_in_db.description, course_actual["description"])

            # create course with criteria
            course = course_expected.copy()
            course["name"] = "ExpectedCourse2"
            course["criteria"] = [{"id": 1}]
            rv = self.client.post("/api/courses", data=json.dumps(course), content_type="application/json")
            self.assert200(rv)
            course_actual = rv.json

            # Verify the course is created in db
            course_in_db = Courses.query.get(course_actual["id"])
            self.assertEqual(len(course_in_db.criteriaandcourses), 1)
            self.assertEqual(course_in_db.criteriaandcourses[0].criteria_id, course["criteria"][0]["id"])

    def test_create_duplicate_course(self):
        with self.login(self.data.get_authorized_instructor().username):
            course_existing = self.data.get_course()
            course_expected = {"name": course_existing.name, "description": course_existing.description}
            rv = self.client.post("/api/courses", data=json.dumps(course_expected), content_type="application/json")
            self.assert400(rv)

    def test_create_course_with_bad_data_format(self):
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(
                "/api/courses", data=json.dumps({"description": "d"}), content_type="application/json"
            )
            self.assert400(rv)

    def test_edit_course(self):
        expected = {"id": self.data.get_course().id, "name": "ExpectedCourse", "description": "Test Description"}
        url = "/api/courses/" + str(self.data.get_course().id)

        # 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 unmatched course id
            rv = self.client.post(
                "/api/courses/" + str(self.data.get_secondary_course().id),
                data=json.dumps(expected),
                content_type="application/json",
            )
            self.assert400(rv)

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

            # test authorized user
            rv = self.client.post(url, data=json.dumps(expected), content_type="application/json")
            self.assert200(rv)
            db.session.expire_all()
            self.assertEqual(expected["id"], rv.json["id"])
            self.assertEqual(expected["name"], rv.json["name"])
            self.assertEqual(expected["description"], rv.json["description"])

            # test add criteria
            course = expected.copy()
            course["criteria"] = [{"id": 1}]
            rv = self.client.post(url, data=json.dumps(course), content_type="application/json")
            self.assert200(rv)

            db.session.expire_all()
            course_in_db = Courses.query.get(course["id"])
            self.assertEqual(len(course_in_db.criteriaandcourses), 1)
            self.assertEqual(course_in_db.criteriaandcourses[0].criteria_id, course["criteria"][0]["id"])

            # test remove criteria
            rv = self.client.post(url, data=json.dumps(expected), content_type="application/json")
            self.assert200(rv)

            # expire all instances in session and force session to query from db
            db.session.expire_all()
            course_in_db = Courses.query.get(course["id"])
            self.assertEqual(len(course_in_db.criteriaandcourses), 0)
Example #23
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = BasicTestData()
     self.user = self.data.authorized_student
     self.criterion = self.data.create_criterion(self.user)
Example #24
0
 def setUp(self):
     super(ComPAIRLearningRecordTestCase, self).setUp()
     self.data = BasicTestData()
     self.user = self.data.authorized_student
     self.setup_session_data(self.user)
Example #25
0
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
        }
Example #26
0
class CoursesAPITests(ComPAIRAPITestCase):
    def setUp(self):
        super(CoursesAPITests, self).setUp()
        self.data = BasicTestData()

    def _verify_course_info(self, course_expected, course_actual):
        self.assertEqual(
            course_expected.name, course_actual['name'],
            "Expected course name does not match actual.")
        self.assertEqual(
            course_expected.uuid, course_actual['id'],
            "Expected course id does not match actual.")
        self.assertEqual(
            course_expected.description, course_actual['description'],
            "Expected course description does not match actual.")
        self.assertEqual(
            course_expected.year, course_actual['year'],
            "Expected course description does not match actual.")
        self.assertEqual(
            course_expected.term, course_actual['term'],
            "Expected course description does not match actual.")
        self.assertEqual(
            course_expected.available, course_actual['available'],
            "Expected course availability does not match actual.")

    def test_get_single_course(self):
        course_api_url = '/api/courses/' + self.data.get_course().uuid

        # Test login required
        rv = self.client.get(course_api_url)
        self.assert401(rv)

        # Test root get course
        with self.login('root'):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test enroled users get course info
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert200(rv)
            self._verify_course_info(self.data.get_course(), rv.json)

        # Test unenroled user not permitted to get info
        with self.login(self.data.get_unauthorized_instructor().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        with self.login(self.data.get_unauthorized_student().username):
            rv = self.client.get(course_api_url)
            self.assert403(rv)

        # Test get invalid course
        with self.login("root"):
            rv = self.client.get('/api/courses/38940450')
            self.assert404(rv)

    def test_create_course(self):
        course_expected = {
            'name': 'ExpectedCourse1',
            'year': 2015,
            'term': 'Winter',
            'start_date': None,
            'end_date': None,
            'description': 'Test Course One Description Test'
        }
        # Test login required
        rv = self.client.post(
            '/api/courses',
            data=json.dumps(course_expected), content_type='application/json')
        self.assert401(rv)
        # Test unauthorized user
        with self.login(self.data.get_authorized_student().username):
            rv = self.client.post(
                '/api/courses',
                data=json.dumps(course_expected), content_type='application/json')
            self.assert403(rv)

        # Test course creation
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(
                '/api/courses',
                data=json.dumps(course_expected), content_type='application/json')
            self.assert200(rv)
            # Verify return
            course_actual = rv.json
            self.assertEqual(course_expected['name'], course_actual['name'])
            self.assertEqual(course_expected['year'], course_actual['year'])
            self.assertEqual(course_expected['term'], course_actual['term'])
            self.assertEqual(course_expected['description'], course_actual['description'])
            self.assertTrue(course_actual['available'])

            # Verify the course is created in db
            course_in_db = Course.query.filter_by(uuid=course_actual['id']).first()
            self.assertEqual(course_in_db.name, course_actual['name'])
            self.assertEqual(course_in_db.year, course_actual['year'])
            self.assertEqual(course_in_db.term, course_actual['term'])
            self.assertEqual(course_in_db.description, course_actual['description'])
            self.assertTrue(course_in_db.available)

            # Verify instructor added to course
            user_course = UserCourse.query \
                .filter_by(
                    user_id=self.data.get_authorized_instructor().id,
                    course_uuid=course_actual['id']
                ) \
                .one_or_none()
            self.assertIsNotNone(user_course)

            # Starts in the future
            now = datetime.datetime.utcnow()
            course_expected['start_date'] = (now + datetime.timedelta(days=7)).isoformat() + 'Z',
            course_expected['end_date'] = None
            rv = self.client.post('/api/courses', data=json.dumps(course_expected), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Ended in the past
            course_expected['start_date'] = None
            course_expected['end_date'] = (now - datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post('/api/courses', data=json.dumps(course_expected), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Is currently available
            course_expected['start_date'] = (now - datetime.timedelta(days=7)).isoformat() + 'Z',
            course_expected['end_date'] = (now + datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post('/api/courses', data=json.dumps(course_expected), content_type='application/json')
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def test_create_course_with_bad_data_format(self):
        with self.login(self.data.get_authorized_instructor().username):
            rv = self.client.post(
                '/api/courses',
                data=json.dumps({'description': 'd'}), content_type='application/json')
            self.assert400(rv)

    def test_edit_course(self):
        expected = {
            'id': self.data.get_course().uuid,
            'name': 'ExpectedCourse',
            'year': 2015,
            'term': 'Winter',
            'start_date': None,
            'end_date': None,
            'description': 'Test Description'
        }
        url = '/api/courses/' + self.data.get_course().uuid

        # 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 unmatched course id
            rv = self.client.post(
                '/api/courses/' + self.data.get_secondary_course().uuid,
                data=json.dumps(expected), content_type='application/json')
            self.assert400(rv)

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

            # test authorized user
            rv = self.client.post(url, data=json.dumps(expected), content_type='application/json')
            self.assert200(rv)
            db.session.expire_all()
            self.assertEqual(expected['id'], rv.json['id'])
            self.assertEqual(expected['name'], rv.json['name'])
            self.assertEqual(expected['description'], rv.json['description'])
            self.assertTrue(rv.json['available'])

            # Starts in the future
            now = datetime.datetime.utcnow()
            expected['start_date'] = (now + datetime.timedelta(days=7)).isoformat() + 'Z',
            expected['end_date'] = None
            rv = self.client.post(url, data=json.dumps(expected), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Ended in the past
            expected['start_date'] = None
            expected['end_date'] = (now - datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post(url, data=json.dumps(expected), content_type='application/json')
            self.assert200(rv)
            self.assertFalse(rv.json['available'])

            # Is currently available
            expected['start_date'] = (now - datetime.timedelta(days=7)).isoformat() + 'Z',
            expected['end_date'] = (now + datetime.timedelta(days=7)).isoformat() + 'Z',
            rv = self.client.post(url, data=json.dumps(expected), content_type='application/json')
            self.assert200(rv)
            self.assertTrue(rv.json['available'])

    def test_delete_course(self):
        course_uuid = self.data.get_course().uuid
        url = '/api/courses/' + course_uuid

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

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

        with self.login(self.data.get_authorized_student().username):
            rv = self.client.delete(url)
            self.assert403(rv)

        with self.login(self.data.get_authorized_ta().username):
            rv = self.client.delete(url)
            self.assert403(rv)

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

            # test deletion by authorized insturctor
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course_uuid, rv.json['id'])

            # test course is deleted
            rv = self.client.delete(url)
            self.assert404(rv)

        course2 = self.data.create_course()
        url = '/api/courses/' + course2.uuid

        with self.login('root'):
            # test deletion by system admin
            rv = self.client.delete(url)
            self.assert200(rv)
            self.assertEqual(course2.uuid, rv.json['id'])

            # test course is deleted
            rv = self.client.delete(url)
            self.assert404(rv)


    def test_duplicate_course_simple(self):
        url = '/api/courses/' + self.data.get_course().uuid + '/duplicate'
        expected = {
            'name': 'duplicate course',
            'year': 2015,
            'term': 'Winter',
            'start_date': None,
            'end_date': None
        }
        # test login required
        rv = self.client.post(url, 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)

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

            # test year missing
            invalid_expected = {
                'term': 'Winter'
            }
            rv = self.client.post('/api/courses/999/duplicate', data=json.dumps(invalid_expected), content_type='application/json')
            self.assert404(rv)

            # test term missing
            invalid_expected = {
                'year': 2015
            }
            rv = self.client.post('/api/courses/999/duplicate', data=json.dumps(invalid_expected), content_type='application/json')
            self.assert404(rv)

            # test authorized user
            original_course = self.data.get_course()
            rv = self.client.post(url, data=json.dumps(expected), content_type='application/json')
            self.assert200(rv)

            # verify course duplicated correctly
            self.assertNotEqual(original_course.uuid, rv.json['id'])
            self.assertEqual(expected['name'], rv.json['name'])
            self.assertEqual(expected['year'], rv.json['year'])
            self.assertEqual(expected['term'], rv.json['term'])
            self.assertEqual(expected['start_date'], rv.json['start_date'])
            self.assertEqual(expected['end_date'], rv.json['end_date'])
            self.assertEqual(original_course.description, rv.json['description'])

            # verify instructor added to duplicate course
            user_course = UserCourse.query \
                .filter_by(
                    user_id=self.data.get_authorized_instructor().id,
                    course_uuid=rv.json['id']
                ) \
                .one_or_none()
            self.assertIsNotNone(user_course)
Example #27
0
 def setUp(self):
     super(UsersAPITests, self).setUp()
     self.data = BasicTestData()
Example #28
0
 def setUp(self):
     super(CoursesAPITests, self).setUp()
     self.data = BasicTestData()
Example #29
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)