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") } } }
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' }
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' }
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)
def setUp(self): super(CoursesAPITests, self).setUp() self.data = BasicTestData()
def setUp(self): super(ComPAIRXAPITestCase, self).setUp() self.data = BasicTestData() self.user = self.data.authorized_student self.course = self.data.main_course
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 setUp(self): super(ClassListAPITest, self).setUp() self.data = BasicTestData() self.url = "/api/courses/" + self.data.get_course().uuid + "/users"
def setUp(self): super(LTIConsumersAPITests, self).setUp() self.data = BasicTestData() self.lti_data = LTITestData()
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}
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)
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' }
def setUp(self): super(UsersAPITests, self).setUp() self.data = BasicTestData()
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." )
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)
class ClassListAPITest(ComPAIRAPITestCase): def setUp(self): super(ClassListAPITest, self).setUp() self.data = BasicTestData() self.url = "/api/courses/" + self.data.get_course().uuid + "/users" def test_get_classlist(self): # test login required rv = self.client.get(self.url) self.assert401(rv) # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.get(self.url) self.assert403(rv) expected = [ (self.data.get_authorized_instructor(), '', ''), (self.data.get_authorized_ta(), '', ''), (self.data.get_authorized_student(), '', '')] expected.sort(key=lambda x: x[0].firstname) with self.login(self.data.get_authorized_instructor().username): # test authorized user rv = self.client.get(self.url) self.assert200(rv) self.assertEqual(len(expected), len(rv.json['objects'])) for key, (user, cas_username, group_name) in enumerate(expected): self.assertEqual(user.uuid, rv.json['objects'][key]['id']) # test export csv rv = self.client.get(self.url, headers={'Accept': 'text/csv'}) self.assert200(rv) self.assertEqual('text/csv', rv.content_type) reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',') self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader)) for user, cas_username, group_name in expected: self.assertEqual( [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name], next(reader) ) # test export csv with group names for user_course in self.data.main_course.user_courses: if user_course.user_id == self.data.get_authorized_instructor().id: user_course.group_name = "instructor_group" elif user_course.user_id == self.data.get_authorized_ta().id: user_course.group_name = "ta_group" elif user_course.user_id == self.data.get_authorized_student().id: user_course.group_name = "student_group" db.session.commit() expected = [ (self.data.get_authorized_instructor(), '', "instructor_group"), (self.data.get_authorized_ta(), '', "ta_group"), (self.data.get_authorized_student(), '', "student_group")] expected.sort(key=lambda x: x[0].firstname) rv = self.client.get(self.url, headers={'Accept': 'text/csv'}) self.assert200(rv) self.assertEqual('text/csv', rv.content_type) reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',') self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader)) for user, cas_username, group_name in expected: self.assertEqual( [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name], next(reader) ) # test export csv with cas usernames third_party_student = ThirdPartyUserFactory(user=self.data.get_authorized_student()) third_party_instructor = ThirdPartyUserFactory(user=self.data.get_authorized_instructor()) third_party_ta = ThirdPartyUserFactory(user=self.data.get_authorized_ta()) db.session.commit() expected = [ (self.data.get_authorized_instructor(), third_party_instructor.unique_identifier, "instructor_group"), (self.data.get_authorized_ta(), third_party_ta.unique_identifier, "ta_group"), (self.data.get_authorized_student(), third_party_student.unique_identifier, "student_group")] expected.sort(key=lambda x: x[0].firstname) rv = self.client.get(self.url, headers={'Accept': 'text/csv'}) self.assert200(rv) self.assertEqual('text/csv', rv.content_type) reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',') self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader)) for user, cas_username, group_name in expected: self.assertEqual( [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name], next(reader) ) # test export csv with cas usernames (student has multiple cas_usernames). should use first username third_party_student2 = ThirdPartyUserFactory(user=self.data.get_authorized_student()) third_party_student3 = ThirdPartyUserFactory(user=self.data.get_authorized_student()) third_party_student4 = ThirdPartyUserFactory(user=self.data.get_authorized_student()) db.session.commit() rv = self.client.get(self.url, headers={'Accept': 'text/csv'}) self.assert200(rv) self.assertEqual('text/csv', rv.content_type) reader = csv.reader(rv.data.decode(encoding='UTF-8').splitlines(), delimiter=',') self.assertEqual(['username', 'cas_username', 'student_number', 'firstname', 'lastname', 'email', 'displayname', 'group_name'], next(reader)) for user, cas_username, group_name in expected: self.assertEqual( [user.username, cas_username, user.student_number or '', user.firstname, user.lastname, user.email, user.displayname, group_name], next(reader) ) with self.login(self.data.get_authorized_ta().username): rv = self.client.get(self.url) self.assert200(rv) self.assertEqual(len(expected), len(rv.json['objects'])) for key, (user, cas_username, group_name) in enumerate(expected): self.assertEqual(user.uuid, rv.json['objects'][key]['id']) def test_get_instructor_labels(self): url = self.url + "/instructors/labels" # test login required rv = self.client.get(url) self.assert401(rv) # test dropped instructor - unauthorized with self.login(self.data.get_dropped_instructor().username): rv = self.client.get(url) self.assert403(rv) # test unauthorized instructor with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.data.get_authorized_instructor().username): rv = self.client.get('/api/courses/999/users/instructors/labels') self.assert404(rv) # test success rv = self.client.get(url) self.assert200(rv) labels = rv.json['instructors'] expected = { self.data.get_authorized_ta().uuid: 'Teaching Assistant', self.data.get_authorized_instructor().uuid: 'Instructor' } self.assertEqual(labels, expected) def test_get_students_course(self): url = self.url + "/students" # test login required rv = self.client.get(url) self.assert401(rv) # test dropped instructor - unauthorized with self.login(self.data.get_dropped_instructor().username): rv = self.client.get(url) self.assert403(rv) # test unauthorized instructor with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.get(url) self.assert403(rv) # test invalid course id with self.login(self.data.get_authorized_instructor().username): rv = self.client.get('/api/courses/999/users/students') self.assert404(rv) # test success - instructor rv = self.client.get(url) self.assert200(rv) students = rv.json['objects'] expected = { 'id': self.data.get_authorized_student().uuid, 'name': self.data.get_authorized_student().fullname } self.assertEqual(students[0]['id'], expected['id']) self.assertEqual(students[0]['name'], expected['name']) with self.login(self.data.get_authorized_ta().username): rv = self.client.get(url) self.assert200(rv) students = rv.json['objects'] expected = { 'id': self.data.get_authorized_student().uuid, 'name': self.data.get_authorized_student().fullname } self.assertEqual(students[0]['id'], expected['id']) self.assertEqual(students[0]['name'], expected['name']) # test success - student with self.login(self.data.get_authorized_student().username): rv = self.client.get(url) self.assert200(rv) students = rv.json['objects'] expected = { 'id': self.data.get_authorized_student().uuid, 'name': self.data.get_authorized_student().displayname } self.assertEqual(students[0]['id'], expected['id']) self.assertEqual(students[0]['name'], expected['name'] + ' (You)') def test_enrol_instructor(self): url = self._create_enrol_url(self.url, self.data.get_dropped_instructor().uuid) role = {'course_role': 'Instructor'} # defaults to Instructor # test login required rv = self.client.post( url, data=json.dumps(role), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.post( url, data=json.dumps(role), content_type='application/json') self.assert403(rv) # test invalid course id with self.login(self.data.get_authorized_instructor().username): invalid_url = '/api/courses/999/users/' + self.data.get_dropped_instructor().uuid rv = self.client.post( invalid_url, data=json.dumps(role), content_type='application/json') self.assert404(rv) # test invalid user id invalid_url = self._create_enrol_url(self.url, 999) rv = self.client.post( invalid_url, data=json.dumps(role), content_type='application/json') self.assert404(rv) # test enrolling dropped instructor expected = { 'user_id': self.data.get_dropped_instructor().uuid, 'fullname': self.data.get_dropped_instructor().fullname, 'course_role': CourseRole.instructor.value } rv = self.client.post( url, data=json.dumps(role), content_type='application/json') self.assert200(rv) self.assertEqual(expected, rv.json) # test enrolling new instructor url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid) expected = { 'user_id': self.data.get_unauthorized_instructor().uuid, 'fullname': self.data.get_unauthorized_instructor().fullname, 'course_role': CourseRole.instructor.value } rv = self.client.post( url, data=json.dumps(role), content_type='application/json') self.assert200(rv) self.assertEqual(expected, rv.json) # test enrolling a different role - eg. Student role = {'course_role': CourseRole.teaching_assistant.value } expected = { 'user_id': self.data.get_unauthorized_instructor().uuid, 'fullname': self.data.get_unauthorized_instructor().fullname, 'course_role': CourseRole.teaching_assistant.value } rv = self.client.post( url, data=json.dumps(role), content_type='application/json') self.assert200(rv) self.assertEqual(expected, rv.json) def test_unenrol_instructor(self): url = self._create_enrol_url(self.url, self.data.get_authorized_instructor().uuid) # test login required rv = self.client.delete(url) self.assert401(rv) # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.delete(url) self.assert403(rv) # test invalid course id invalid_url = '/api/courses/999/users/' + self.data.get_authorized_instructor().uuid with self.login(self.data.get_authorized_instructor().username): rv = self.client.delete(invalid_url) self.assert404(rv) # test invalid user id invalid_url = self._create_enrol_url(self.url, 999) rv = self.client.delete(invalid_url) self.assert404(rv) # test existing user not in existing course invalid_url = self._create_enrol_url(self.url, self.data.get_unauthorized_instructor().uuid) rv = self.client.delete(invalid_url) self.assert404(rv) # test success expected = { 'user_id': self.data.get_authorized_instructor().uuid, 'fullname': self.data.get_authorized_instructor().fullname, 'course_role': CourseRole.dropped.value } rv = self.client.delete(url) self.assert200(rv) self.assertEqual(expected, rv.json) def test_import_compair_classlist(self): url = '/api/courses/' + self.data.get_course().uuid + '/users' student = self.data.get_authorized_student() instructor = self.data.get_authorized_instructor() ta = self.data.get_authorized_ta() filename = "classlist.csv" # test login required uploaded_file = io.BytesIO((student.username+",password").encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert401(rv) uploaded_file.close() # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_student().username): uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_ta().username): uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_instructor().username): # test invalid course id invalid_url = '/api/courses/999/users' uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename))) uploaded_file.close() self.assert404(rv) # test invalid file type invalid_filetype = "classlist.png" uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype))) uploaded_file.close() self.assert400(rv) # test no username provided content = "".join([",\n", student.username, ",password,", student.student_number]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual(None, result['invalids'][0]['user']['username']) self.assertEqual('The username is required.', result['invalids'][0]['message']) uploaded_file.close() # test no password provided content = "".join(["nopasswordusername"]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(0, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual("nopasswordusername", result['invalids'][0]['user']['username']) self.assertEqual('The password is required.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate usernames in file content = "".join([student.username, ",password\n", student.username, ",password"]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual(student.username, result['invalids'][0]['user']['username']) self.assertEqual('This username already exists in the file.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate student number in system content = "".join(['username1,password,', student.student_number, "\n", student.username, ',password']) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual("username1", result['invalids'][0]['user']['username']) self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate student number in file content = "".join([ student.username, ",password,", student.student_number, "\n", "username1,password,", student.student_number]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual("username1", result['invalids'][0]['user']['username']) self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message']) uploaded_file.close() # test existing display content = "username1,password,,,," + student.displayname uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() # test authorized instructor - new user uploaded_file = io.BytesIO(b'username2,password') rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() # test authorized instructor - existing user (with password) current_password = student.password new_password = '******' uploaded_file = io.BytesIO((student.username+','+new_password).encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() self.assertEqual(student.password, current_password) self.assertNotEqual(student.password, new_password) student.last_online = None db.session.commit() uploaded_file = io.BytesIO((student.username+','+new_password).encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() self.assertNotEqual(student.password, current_password) self.assertEqual(student.password, new_password) new_password = '******' for set_password in [new_password, '*', '']: uploaded_file = io.BytesIO((student.username+','+set_password).encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() self.assertEqual(student.password, new_password) # test invalid import type app login disabled self.app.config['APP_LOGIN_ENABLED'] = False uploaded_file = io.BytesIO(student.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert400(rv) uploaded_file.close() self.app.config['APP_LOGIN_ENABLED'] = True # test authorized instructor - existing instructor uploaded_file = io.BytesIO(instructor.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(0, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() instructor_enrollment = UserCourse.query \ .filter_by( course_id=self.data.get_course().id, user_id=instructor.id, course_role=CourseRole.instructor ) \ .one_or_none() self.assertIsNotNone(instructor_enrollment) # test authorized instructor - existing teaching assistant uploaded_file = io.BytesIO(ta.username.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(0, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() ta_enrollment = UserCourse.query \ .filter_by( course_id=self.data.get_course().id, user_id=ta.id, course_role=CourseRole.teaching_assistant ) \ .one_or_none() self.assertIsNotNone(ta_enrollment) # test authorized instructor - group enrollment content = "".join([ student.username, ",*,,,,,,group_student\n", instructor.username, ",*,,,,,,group_instructor\n", ta.username, ",*,,,,,,group_ta\n", ]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() user_courses = UserCourse.query \ .filter( UserCourse.course_id == self.data.get_course().id, UserCourse.course_role != CourseRole.dropped ) \ .all() self.assertEqual(len(user_courses), 3) for user_course in user_courses: self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id]) if user_course.user_id == student.id: self.assertEqual(user_course.group_name, 'group_student') elif user_course.user_id == instructor.id: self.assertEqual(user_course.group_name, 'group_instructor') elif user_course.user_id == ta.id: self.assertEqual(user_course.group_name, 'group_ta') # test authorized instructor - group unenrollment content = "".join([ student.username, ",*,,,,,,\n", instructor.username, ",*,,,,,,\n", ta.username, ",*,,,,,,\n", ]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename))) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() user_courses = UserCourse.query \ .filter( UserCourse.course_id == self.data.get_course().id, UserCourse.course_role != CourseRole.dropped ) \ .all() self.assertEqual(len(user_courses), 3) for user_course in user_courses: self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id]) self.assertEqual(user_course.group_name, None) def test_import_cas_classlist(self): url = '/api/courses/' + self.data.get_course().uuid + '/users' student = self.data.get_authorized_student() third_party_student = ThirdPartyUserFactory(user=student) instructor = self.data.get_authorized_instructor() third_party_instructor = ThirdPartyUserFactory(user=instructor) ta = self.data.get_authorized_ta() third_party_ta = ThirdPartyUserFactory(user=ta) db.session.commit() filename = "classlist.csv" # test login required uploaded_file = io.BytesIO((third_party_student.unique_identifier).encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert401(rv) uploaded_file.close() # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_student().username): uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_ta().username): uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert403(rv) uploaded_file.close() with self.login(self.data.get_authorized_instructor().username): # test invalid course id invalid_url = '/api/courses/999/users' uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(invalid_url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) uploaded_file.close() self.assert404(rv) # test invalid file type invalid_filetype = "classlist.png" uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, invalid_filetype), import_type=ThirdPartyType.cas.value)) uploaded_file.close() self.assert400(rv) # test no username provided content = "".join([",\n", third_party_student.unique_identifier, ",", student.student_number]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual(None, result['invalids'][0]['user']['username']) self.assertEqual('The username is required.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate usernames in file content = "".join([third_party_student.unique_identifier, "\n", third_party_student.unique_identifier]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual(third_party_student.unique_identifier, result['invalids'][0]['user']['username']) self.assertEqual('This username already exists in the file.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate student number in system content = "".join(['username1,', student.student_number, "\n", third_party_student.unique_identifier]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual("username1", result['invalids'][0]['user']['username']) self.assertEqual('This student number already exists in the system.', result['invalids'][0]['message']) uploaded_file.close() # test duplicate student number in file content = "".join([ third_party_student.unique_identifier, ",", student.student_number, "\n", "username1,", student.student_number]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(1, len(result['invalids'])) self.assertEqual("username1", result['invalids'][0]['user']['username']) self.assertEqual('This student number already exists in the file.', result['invalids'][0]['message']) uploaded_file.close() # test existing display content = "username1,,,," + student.displayname uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() # test authorized instructor - new user uploaded_file = io.BytesIO(b'username2') rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() # test authorized instructor - existing user uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() # test invalid import type cas login disabled self.app.config['CAS_LOGIN_ENABLED'] = False uploaded_file = io.BytesIO(third_party_student.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert400(rv) uploaded_file.close() self.app.config['CAS_LOGIN_ENABLED'] = True # test authorized instructor - existing instructor uploaded_file = io.BytesIO(third_party_instructor.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(0, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() instructor_enrollment = UserCourse.query \ .filter_by( course_id=self.data.get_course().id, user_id=instructor.id, course_role=CourseRole.instructor ) \ .one_or_none() self.assertIsNotNone(instructor_enrollment) # test authorized instructor - existing teaching assistant uploaded_file = io.BytesIO(third_party_ta.unique_identifier.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(0, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() ta_enrollment = UserCourse.query \ .filter_by( course_id=self.data.get_course().id, user_id=ta.id, course_role=CourseRole.teaching_assistant ) \ .one_or_none() self.assertIsNotNone(ta_enrollment) # test authorized instructor - group enrollment content = "".join([ third_party_student.unique_identifier, ",,,,,,group_student\n", third_party_instructor.unique_identifier, ",,,,,,group_instructor\n", third_party_ta.unique_identifier, ",,,,,,group_ta\n", ]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() user_courses = UserCourse.query \ .filter( UserCourse.course_id == self.data.get_course().id, UserCourse.course_role != CourseRole.dropped ) \ .all() self.assertEqual(len(user_courses), 3) for user_course in user_courses: self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id]) if user_course.user_id == student.id: self.assertEqual(user_course.group_name, 'group_student') elif user_course.user_id == instructor.id: self.assertEqual(user_course.group_name, 'group_instructor') elif user_course.user_id == ta.id: self.assertEqual(user_course.group_name, 'group_ta') # test authorized instructor - group unenrollment content = "".join([ third_party_student.unique_identifier, ",,,,,,\n", third_party_instructor.unique_identifier, ",,,,,,\n", third_party_ta.unique_identifier, ",,,,,,\n", ]) uploaded_file = io.BytesIO(content.encode()) rv = self.client.post(url, data=dict(file=(uploaded_file, filename), import_type=ThirdPartyType.cas.value)) self.assert200(rv) result = rv.json self.assertEqual(1, result['success']) self.assertEqual(0, len(result['invalids'])) uploaded_file.close() user_courses = UserCourse.query \ .filter( UserCourse.course_id == self.data.get_course().id, UserCourse.course_role != CourseRole.dropped ) \ .all() self.assertEqual(len(user_courses), 3) for user_course in user_courses: self.assertIn(user_course.user_id, [student.id, instructor.id, ta.id]) self.assertEqual(user_course.group_name, None) def test_update_course_role_miltiple(self): url = self.url + '/roles' user_ids = [self.data.authorized_instructor.uuid, self.data.authorized_student.uuid, self.data.authorized_ta.uuid] params = { 'ids': user_ids, 'course_role': CourseRole.instructor.value } # test login required rv = self.client.post( url, data=json.dumps(params), content_type='application/json') self.assert401(rv) # test unauthorized user with self.login(self.data.get_unauthorized_instructor().username): rv = self.client.post( url, data=json.dumps(params), content_type='application/json') self.assert403(rv) with self.login(self.data.get_authorized_instructor().username): # test invalid course id rv = self.client.post( '/api/courses/999/users/roles', data=json.dumps(params), content_type='application/json') self.assert404(rv) # test missing user ids missing_ids = params.copy() missing_ids['ids'] = [] rv = self.client.post( url, data=json.dumps(missing_ids), content_type='application/json') self.assert400(rv) # test invalid user ids invalid_ids = params.copy() invalid_ids['ids'] = [self.data.unauthorized_student.uuid] rv = self.client.post( url, data=json.dumps(invalid_ids), content_type='application/json') self.assert400(rv) # cannot change current_user's course role params_self = { 'ids': [self.data.get_authorized_instructor().uuid], 'course_role': CourseRole.teaching_assistant.value } rv = self.client.post( url, data=json.dumps(params_self), content_type='application/json') self.assert400(rv) # test changing role instructor rv = self.client.post( url, data=json.dumps(params), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['course_role'], CourseRole.instructor.value) for user_course in self.data.get_course().user_courses: # ingore changes for current_user if user_course.user_id == self.data.get_authorized_instructor().id: self.assertEqual(user_course.course_role, CourseRole.instructor) # other users should have course role updated elif user_course.user_id in user_ids: self.assertEqual(user_course.course_role, CourseRole.instructor) # test changing teaching assistant params_ta = params.copy() params_ta['course_role'] = CourseRole.teaching_assistant.value rv = self.client.post( url, data=json.dumps(params_ta), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['course_role'], CourseRole.teaching_assistant.value) for user_course in self.data.get_course().user_courses: # ingore changes for current_user if user_course.user_id == self.data.get_authorized_instructor().id: self.assertEqual(user_course.course_role, CourseRole.instructor) # other users should have course role updated elif user_course.user_id in user_ids: self.assertEqual(user_course.course_role, CourseRole.teaching_assistant) # test changing role student params_student = params.copy() params_student['course_role'] = CourseRole.student.value rv = self.client.post( url, data=json.dumps(params_student), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['course_role'], CourseRole.student.value) for user_course in self.data.get_course().user_courses: # ingore changes for current_user if user_course.user_id == self.data.get_authorized_instructor().id: self.assertEqual(user_course.course_role, CourseRole.instructor) # other users should have course role updated elif user_course.user_id in user_ids: self.assertEqual(user_course.course_role, CourseRole.student) # test changing dropped params_dropped = { 'ids': user_ids } rv = self.client.post( url, data=json.dumps(params_dropped), content_type='application/json') self.assert200(rv) self.assertEqual(rv.json['course_role'], CourseRole.dropped.value) for user_course in self.data.get_course().user_courses: # ingore changes for current_user if user_course.user_id == self.data.get_authorized_instructor().id: self.assertEqual(user_course.course_role, CourseRole.instructor) # other users should have course role updated elif user_course.user_id in user_ids: self.assertEqual(user_course.course_role, CourseRole.dropped) def _create_enrol_url(self, url, user_id): return url + '/' + str(user_id)
class 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)
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])
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])
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)
def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = BasicTestData() self.user = self.data.authorized_student self.setup_session_data(self.user)
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 }
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)
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)