def test_enabling_peer_feedback(test_client, session, describe, admin_user, logged_in): with describe('setup'), logged_in(admin_user): course = helpers.create_course(test_client) assignment = helpers.create_assignment(test_client, course, state='open') user_with_perm = helpers.create_user_with_perms( session, [CPerm.can_edit_peer_feedback_settings], course) user_without_perm = helpers.create_user_with_perms(session, [], course) with describe('User without perm cannot enable'), logged_in( user_without_perm): helpers.enable_peer_feedback(test_client, assignment, err=403) with describe('User with perm can enable'), logged_in(user_with_perm): helpers.enable_peer_feedback(test_client, assignment) with describe('Amount should be >= 1'), logged_in(user_with_perm): helpers.enable_peer_feedback(test_client, assignment, amount=-1, err=400) helpers.enable_peer_feedback(test_client, assignment, amount=0, err=400) helpers.enable_peer_feedback(test_client, assignment, amount=110) with describe('Time should be > 0'), logged_in(user_with_perm): helpers.enable_peer_feedback(test_client, assignment, days=-1, err=400) helpers.enable_peer_feedback(test_client, assignment, days=0, err=400) helpers.enable_peer_feedback(test_client, assignment, days=0.005)
def test_create_lti_provider( test_client, describe, lms, iss, session, logged_in ): with describe('setup'): admin_user = helpers.create_user_with_perms( session, [], [], [GPerm.can_manage_lti_providers] ) normal_user = helpers.create_user_with_perms(session, [], [], []) data = {'lms': lms, 'iss': iss, 'intended_use': 'AA'} with describe('admin can create a provider'), logged_in(admin_user): prov = test_client.req( 'post', '/api/v1/lti1.3/providers/', 200, data=data ) test_client.req('get', '/api/v1/lti1.3/providers/', 200, result=[prov]) with describe('cannot create provider without iss'), logged_in(admin_user): test_client.req( 'post', '/api/v1/lti1.3/providers/', 400, data={**data, 'iss': ''} ) test_client.req('get', '/api/v1/lti1.3/providers/', 200, result=[prov]) with describe('normal user cannot create a provider'): with logged_in(normal_user): test_client.req( 'post', '/api/v1/lti1.3/providers/', 403, data=data ) with logged_in(admin_user): # Should not be a new one created test_client.req( 'get', '/api/v1/lti1.3/providers/', 200, result=[prov] )
def test_submit_with_small_group( test_client, session, logged_in, teacher_user, course, error_template, assignment ): user_no_group = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) user_with_group = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) with logged_in(teacher_user): g_set = create_group_set(test_client, course.id, 1, 1) test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'group_set_id': g_set['id']} ) g1 = create_group( test_client, g_set['id'], [user_with_group.id], ) with logged_in(user_no_group): # Can submit without group, you are simply the author (not a group) sub = create_submission(test_client, assignment.id) assert sub['user']['id'] == user_no_group.id with logged_in(user_with_group): # But when submitting as a user in a group the group should still be # the author sub = create_submission(test_client, assignment.id) assert sub['user']['group']['id'] == g1['id']
def test_change_name_of_group(test_client, logged_in, error_template, prog_course, session, teacher_user): new_name = f'NEW_NAME-{uuid.uuid4()}' def check_name(name): res = g1 if name is None else {**g1, 'name': name} with logged_in(teacher_user): return test_client.req('get', f'/api/v1/groups/{g1["id"]}', 200, result=res) u1 = create_user_with_perms(session, [CPerm.can_edit_own_groups], prog_course) u2 = create_user_with_perms(session, [CPerm.can_edit_own_groups], prog_course) u3 = create_user_with_perms(session, [CPerm.can_edit_own_groups], prog_course) with logged_in(teacher_user): g_set = create_group_set(test_client, prog_course.id, 2, 4) g1 = create_group( test_client, g_set['id'], [u1.id, u2.id], ) with logged_in(u3): test_client.req('post', f'/api/v1/groups/{g1["id"]}/name', 403, data={'name': new_name}) check_name(None) with logged_in(u1): # u1 is member so it can change the name old_name = g1['name'] test_client.req('post', f'/api/v1/groups/{g1["id"]}/name', 200, data={'name': new_name}) check_name(new_name) # Reset back to old name test_client.req('post', f'/api/v1/groups/{g1["id"]}/name', 200, data={'name': old_name}) check_name(old_name) test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/name', 400, data={'name': 'sh'}, # This name is too short result=error_template) check_name(None)
def test_get_all_extended_courses(ta_user, test_client, logged_in, session): with logged_in(ta_user): res = test_client.req( 'get', f'/api/v1/courses/', 200, query={'extended': 'true'}, result=list, ) assert len(res) == 3 for item in res: assert 'assignments' in item assert isinstance(item['assignments'], list) assert 'group_sets' in item assert isinstance(item['group_sets'], list) u = create_user_with_perms(session, [], res) with logged_in(u): test_client.req('get', '/api/v1/courses/', 200, query={'extended': 'true'}, result=[{ **c, 'assignments': [], 'role': str, } for c in res])
def make_user(): return create_user_with_perms( session, [ CPerm.can_edit_own_groups, CPerm.can_submit_own_work, CPerm.can_see_assignments ], prog_course )
def test_add_test_student_to_group( session, test_client, logged_in, assignment, teacher_user, error_template, describe, monkeypatch_celery ): c_id = assignment.course.id with logged_in(teacher_user): g_set = create_group_set(test_client, c_id, 1, 4) res = create_submission( test_client, assignment.id, is_test_submission=True, ) test_student = res['user'] with describe('new group with a test student cannot be created'): res = test_client.req( 'post', f'/api/v1/group_sets/{g_set["id"]}/group', 400, result=error_template, data={ 'member_ids': [test_student['id']], }, ) with describe( 'new group with a test student and other students cannot be created' ): u1 = create_user_with_perms( session, [CPerm.can_edit_own_groups], assignment.course ) res = test_client.req( 'post', f'/api/v1/group_sets/{g_set["id"]}/group', 400, result=error_template, data={ 'member_ids': [test_student['id'], u1.id], }, ) with describe('test student can not be added to existing group'): g1 = create_group( test_client, g_set['id'], [], ) res = test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/member', 400, result=error_template, data={ 'username': test_student['username'], }, )
def test_list_groups( test_client, logged_in, bs_course, error_template, teacher_user, assignment, session ): def make_empty_user(): return create_user_with_perms(session, [], bs_course) user_others_too = create_user_with_perms( session, [CPerm.can_view_others_groups], bs_course, ) user_no_perm = make_empty_user() with logged_in(teacher_user): group_set = create_group_set(test_client, bs_course.id, 2, 4) empty_groups = [ create_group(test_client, group_set['id'], []) for _ in range(4) ] other_user_groups = [ create_group( test_client, group_set['id'], [make_empty_user().id, make_empty_user().id] ) ] user_no_perm_group = [ create_group( test_client, group_set['id'], [ make_empty_user().id, make_empty_user().id, user_no_perm.id, make_empty_user().id, make_empty_user().id, ] ) ] with logged_in(user_no_perm): test_client.req( 'get', f'/api/v1/group_sets/{group_set["id"]}/groups/', 200, result=empty_groups + user_no_perm_group ) with logged_in(user_others_too): test_client.req( 'get', f'/api/v1/group_sets/{group_set["id"]}/groups/', 200, result=empty_groups + other_user_groups + user_no_perm_group )
def test_reset_password_without_perm(test_client, session, error_template, describe): with describe('setup'): user = create_user_with_perms(session, [], [], gperms=[]) with describe('Cannot get reset password link'): test_client.req( 'patch', '/api/v1/login?type=reset_email', 403, data={'username': user.username}, result={ **error_template, 'message': re.compile('^This user.*necessary.*reset.*own password'), })
def test_creating_webhooks(basic, test_client, logged_in, describe, session): with describe('setup'): course, assig, teacher, student = basic url = f'/api/v1/assignments/{assig.id}/webhook_settings' other_user = helpers.create_user_with_perms(session, [], []) with describe('Posting twice results in the same hook twice'), logged_in( student): student_webhook = test_client.req('post', f'{url}?webhook_type=git', 200, result={ 'id': str, 'public_key': str, 'assignment_id': assig.id, 'user_id': student.id, 'secret': str, 'default_branch': '', }) assert test_client.req('post', f'{url}?webhook_type=git', 200) == student_webhook with describe('A different users gets different data'), logged_in(teacher): teacher_webhook = test_client.req('post', f'{url}?webhook_type=git', 200) assert all(v != teacher_webhook[k] for k, v in student_webhook.items() if k not in ['assignment_id', 'default_branch']) assert student_webhook['assignment_id'] == teacher_webhook[ 'assignment_id'] with describe('Webhook type should exist'), logged_in(teacher): test_client.req('post', url, 404) test_client.req('post', f'{url}?webhook_type=nope', 404) with describe('Can submit for other users'), logged_in(teacher): test_client.req('post', f'{url}?webhook_type=git&author={student.username}', 200) == student_webhook with describe('Users not in the course cannot hand in using it'): with logged_in(teacher): test_client.req( 'post', f'{url}?webhook_type=git&author={other_user.username}', 403) with logged_in(other_user): test_client.req('post', f'{url}?webhook_type=git', 403)
def test_timeout_jwt_token(test_client, session, describe, logged_in, app): with describe('setup'): user = create_user_with_perms(session, [], []) with describe('cannot use the same token far in the future'): with logged_in(user): # Can within timeout test_client.req('get', '/api/v1/login', 200) future = datetime.datetime.utcnow() + datetime.timedelta(weeks=8) with freezegun.freeze_time(future): test_client.req('get', '/api/v1/login', 401) with describe('cannot use a garbage jwt token'), app.app_context(): test_client.req( 'get', '/api/v1/login', 401, headers={'Authorization': f'Bearer jdalksfjakldfjlkadjsdlkf'})
def test_send_email_as_user(describe, session, stubmailer): with describe('setup'): user = helpers.create_user_with_perms(session, [], []) task_result = m.TaskResult(user) session.add(task_result) session.commit() task_result2 = m.TaskResult(user) session.add(task_result2) session.commit() with describe('can send the first time'): psef.tasks._send_email_as_user_1([user.id], 'd', 'b', task_result.id.hex, user.id) assert stubmailer.was_called assert m.TaskResult.query.get( task_result.id).state == m.TaskResultState.finished with describe('Second call should not send'): psef.tasks._send_email_as_user_1([user.id], 'd', 'b', task_result.id.hex, user.id) assert not stubmailer.was_called assert m.TaskResult.query.get( task_result.id).state == m.TaskResultState.finished with describe('Should not crash for non existing task id'): psef.tasks._send_email_as_user_1([user.id], 'd', 'b', uuid.uuid4().hex, user.id) assert not stubmailer.was_called assert m.TaskResult.query.get( task_result.id).state == m.TaskResultState.finished with describe('task result should indicate failure when function crashes'): psef.tasks._send_email_as_user_1([user.id], 'd', 'b', task_result2.id.hex, -user.id) assert not stubmailer.was_called assert m.TaskResult.query.get( task_result2.id).state == m.TaskResultState.crashed
def test_course_snippets(error_template, logged_in, test_client, session, prog_course, prolog_course): url_base = f'/api/v1/courses/{prog_course.id}' teacher_user = create_user_with_perms(session, [ CPerm.can_manage_course_snippets, CPerm.can_view_course_snippets, ], [prog_course, prolog_course]) ta_user = create_user_with_perms(session, [ CPerm.can_view_course_snippets, ], [prog_course, prolog_course]) student_user = create_user_with_perms(session, [], [prog_course, prolog_course]) snips = [] with logged_in(teacher_user): # Create snippets for i in range(2): snips.append({ 'key': f'snippet_key{i}', 'value': f'snippet_value{i}', }) test_client.req( 'put', f'{url_base}/snippet', 201, data=snips[-1], result={ 'id': int, **snips[-1] }, ) snips = test_client.req( 'get', f'{url_base}/snippets/', 200, result=[{ 'id': int, **snip } for snip in snips], ) # Change value by putting snippet with existing key snips[0]['value'] = 'newvalue' test_client.req( 'put', f'{url_base}/snippet', 201, data={ 'key': snips[0]['key'], 'value': snips[0]['value'] }, result=snips[0], ) test_client.req( 'get', f'{url_base}/snippets/', 200, result=snips, ) # Change key, patch by id snips[0]['key'] = 'newkey' test_client.req( 'patch', f'{url_base}/snippets/{snips[0]["id"]}', 204, data={ 'key': snips[0]['key'], 'value': snips[0]['value'] }, ) test_client.req( 'get', f'{url_base}/snippets/', 200, result=snips, ) # Check that duplicate keys raise an error test_client.req( 'patch', f'{url_base}/snippets/{snips[0]["id"]}', 400, data={ 'key': snips[1]['key'], 'value': snips[0]['value'] }, result=error_template, ) # Delete existing snippet test_client.req( 'delete', f'{url_base}/snippets/{snips[0]["id"]}', 204, ) snips = test_client.req( 'get', f'{url_base}/snippets/', 200, result=snips[1:], ) # Shouldn't be able to change other course's snippets # This should return a 404 to minimize leaking information res = test_client.req( 'patch', f'/api/v1/courses/{prolog_course.id}/snippets/{snips[0]["id"]}', 404, data=snips[0], result=error_template, ) res = test_client.req( 'delete', f'/api/v1/courses/{prolog_course.id}/snippets/{snips[0]["id"]}', 404, result=error_template, ) with logged_in(ta_user): # TA user should only be able to view snippets test_client.req( 'get', f'{url_base}/snippets/', 200, result=snips, ) # But they may not edit them. test_client.req( 'put', f'{url_base}/snippet', 403, result=error_template, ) test_client.req( 'patch', f'{url_base}/snippets/{snips[0]["id"]}', 403, data={ 'key': snips[0]['key'], 'value': 'new value' }, result=error_template, ) test_client.req( 'delete', f'{url_base}/snippets/{snips[0]["id"]}', 403, result=error_template, ) with logged_in(student_user): # Student users may not use course snippets test_client.req( 'get', f'{url_base}/snippets/', 403, result=error_template, ) # They can also not edit them test_client.req( 'put', f'{url_base}/snippet', 403, result=error_template, ) test_client.req( 'patch', f'{url_base}/snippets/{snips[0]["id"]}', 403, data={ 'key': snips[0]['key'], 'value': 'new value' }, result=error_template, ) test_client.req( 'delete', f'{url_base}/snippets/{snips[0]["id"]}', 403, result=error_template, )
def test_submit_with_group( test_client, session, logged_in, teacher_user, course, error_template, assignment, monkeypatch_celery ): user_full_group = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) user_empty_group = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) user_no_group = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) user_no_perms = create_user_with_perms( session, [CPerm.can_see_assignments], course ) with logged_in(teacher_user): g_set = create_group_set(test_client, course.id, 2, 4) test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'group_set_id': g_set['id']} ) g1 = create_group( test_client, g_set['id'], [user_full_group.id, user_no_perms.id], ) create_group(test_client, g_set['id'], [user_empty_group.id]) with logged_in(user_empty_group): err = create_submission(test_client, assignment.id, err=400) assert 'enough members' in err['message'] with logged_in(user_no_perms): # Can't submit even if some users in the group can err = create_submission(test_client, assignment.id, err=403) with logged_in(user_no_group): # Can't submit as a user without group as the minimum size is 2 err = create_submission(test_client, assignment.id, err=404) assert 'group was found' in err['message'] with logged_in(user_full_group): # User can submit submission sub = create_submission(test_client, assignment.id) # Make sure submission is done as the group, not as the user assert sub['user']['group']['id'] == g1['id'] # Make sure the user can see the just submitted submission test_client.req( 'get', f'/api/v1/assignments/{assignment.id}/submissions/?extended', 200, result=[sub] ) test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}?extended', 200, result=sub ) files = test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/', 200 ) while 'entries' in files: files = files['entries'][0] code_id = files['id'] response = test_client.get(f'/api/v1/code/{code_id}') assert response.status_code == 200 with logged_in(teacher_user): test_client.req( 'put', f'/api/v1/code/{code_id}/comments/3', 204, data={'comment': 'Lekker gewerkt pik!'} ) # Set state to done so the student can see its feedback test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'state': 'done'} ) with logged_in(user_full_group): # Make sure we can see comments on the files test_client.req( 'get', f'/api/v1/code/{code_id}?type=feedback', 200, result={'3': {'line': 3, 'msg': 'Lekker gewerkt pik!'}} ) with logged_in(teacher_user): # Reset state back test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'state': 'open'} )
def test_add_user_to_group( test_client, session, logged_in, teacher_user, prog_course, error_template ): user_only_own = create_user_with_perms( session, [CPerm.can_edit_own_groups], prog_course ) user_other_too = create_user_with_perms( session, [CPerm.can_edit_others_groups], prog_course ) nobody = create_user_with_perms(session, [], prog_course) with logged_in(teacher_user): g_set = create_group_set(test_client, prog_course.id, 2, 4) g1 = create_group( test_client, g_set['id'], [create_user_with_perms(session, [], prog_course).id], ) g2 = create_group(test_client, g_set['id'], []) with logged_in(user_only_own) as u: # Can add to group with members not including the user. g1 = test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/member', 200, data={'username': u.username}, ) # Cannot add other user to group test_client.req( 'post', f'/api/v1/groups/{g2["id"]}/member', 403, data={'username': nobody.username} ) with logged_in(user_other_too) as u: # Can add to empty group g2 = test_client.req( 'post', f'/api/v1/groups/{g2["id"]}/member', 200, data={'username': u.username}, ) # Can other user to a group test_client.req( 'post', f'/api/v1/groups/{g2["id"]}/member', 200, data={'username': nobody.username} ) # Cannot add virtual user to a group test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/member', 400, data={ 'username': m.Group.query.get(g2['id']).virtual_user.username } ) # user_only_own is already in g1 so we can't add this user to any group # too. for g in [g1, g2]: err = test_client.req( 'post', f'/api/v1/groups/{g["id"]}/member', 400, data={ 'username': user_only_own.username, }, result=error_template, ) assert 'already in a group' in err['message'] # Fill group g1 for _ in range(2): test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/member', 200, data={ 'username': create_user_with_perms(session, [], prog_course).username } ) # Group is full so we can't add another user test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/member', 400, data={ 'username': create_user_with_perms(session, [], prog_course).username }, result=error_template )
def test_delete_group( test_client, logged_in, prog_course, session, teacher_user, error_template, app, monkeypatch_celery ): user_only_own = create_user_with_perms( session, [ CPerm.can_edit_own_groups, CPerm.can_submit_own_work, CPerm.can_see_assignments ], prog_course ) user_other_too = create_user_with_perms( session, [CPerm.can_edit_others_groups, CPerm.can_see_assignments], prog_course ) with logged_in(teacher_user): assig_id = [ a for a in test_client. req('get', f'/api/v1/courses/{prog_course.id}/assignments/', 200) if a['state'] == 'submitting' ][0]['id'] with logged_in(teacher_user): group_set = create_group_set(test_client, prog_course.id, 2, 4) test_client.req( 'patch', f'/api/v1/assignments/{assig_id}', 200, data={'group_set_id': group_set['id']} ) group_own_id = create_group( test_client, group_set["id"], [ create_user_with_perms(session, [], prog_course).id, user_only_own.id, ] )['id'] group_others_id = create_group( test_client, group_set["id"], [ create_user_with_perms(session, [], prog_course).id, create_user_with_perms(session, [], prog_course).id, ] )['id'] group_empty_id = create_group(test_client, group_set["id"], [])['id'] with logged_in(user_only_own): sub = create_submission(test_client, assig_id) assert sub['user']['group'] with logged_in(user_only_own): # Cannot delete with submission test_client.req( 'delete', f'/api/v1/groups/{group_own_id}', 400, result=error_template ) # Cannot delete non empty group of other members test_client.req( 'delete', f'/api/v1/groups/{group_others_id}', 403, result=error_template ) # Can delete empty group test_client.req('delete', f'/api/v1/groups/{group_empty_id}', 204) with logged_in(user_other_too): # Cannot delete with submission test_client.req( 'delete', f'/api/v1/groups/{group_own_id}', 400, result=error_template ) # Can delete non empty group of other members test_client.req('delete', f'/api/v1/groups/{group_others_id}', 204) with logged_in(teacher_user): test_client.req('delete', f'/api/v1/submissions/{sub["id"]}', 204) with logged_in(user_only_own): # Can delete now submission has been deleted test_client.req('delete', f'/api/v1/groups/{group_own_id}', 204)
def test_delete_group_set( test_client, logged_in, teacher_user, user_with_perms, prog_course, session, request, error_template ): assigs = list(prog_course.assignments)[:2] assert len(assigs) == 2 with logged_in(teacher_user): group_set = create_group_set(test_client, prog_course.id, 2, 4) for assig in assigs: test_client.req( 'patch', f'/api/v1/assignments/{assig.id}', 200, data={'group_set_id': group_set['id']} ) groups = [ create_group( test_client, group_set["id"], [create_user_with_perms(session, [], prog_course).id] ) for _ in range(2) ] perm_err = request.node.get_closest_marker('perm_error') has_err = bool(perm_err) if perm_err: status = 403 else: status = 400 with logged_in(user_with_perms): test_client.req( 'delete', f'/api/v1/group_sets/{group_set["id"]}', status, result=error_template ) with logged_in(teacher_user): test_client.req( 'get', f'/api/v1/group_sets/{group_set["id"]}', 200, ) for assig in assigs: test_client.req( 'patch', f'/api/v1/assignments/{assig.id}', 200, data={'group_set_id': None} ) with logged_in(user_with_perms): # Still fails because there are groups test_client.req( 'delete', f'/api/v1/group_sets/{group_set["id"]}', status, result=error_template ) with logged_in(teacher_user): for group in groups: test_client.req( 'delete', f'/api/v1/groups/{group["id"]}', 204, ) # Should now work as there are no groups anymore if perm_err: status = 403 else: status = 204 with logged_in(user_with_perms): test_client.req( 'delete', f'/api/v1/group_sets/{group_set["id"]}', status, result=error_template if has_err else None ) with logged_in(teacher_user): if not has_err: test_client.req( 'get', f'/api/v1/group_sets/{group_set["id"]}', 404, )
def test_update_group_set( test_client, logged_in, bs_course, prog_course, request, error_template, teacher_user, ta_user, assignment, session ): with logged_in(teacher_user): group_set = test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 1, 'maximum_size': 3, }, status_code=200, ) # Update should be done in place group_set = test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 3, 'maximum_size': 10, 'id': group_set['id'], }, status_code=200, result={ 'id': group_set['id'], 'minimum_size': 3, 'maximum_size': 10, 'assignment_ids': [], }, ) test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'group_set_id': group_set['id']} ) group_set['assignment_ids'].append(assignment.id) test_client.req( 'get', f'/api/v1/courses/{bs_course.id}/group_sets/', 200, result=[group_set] ) # You should not be able to change the course id test_client.req( 'put', f'/api/v1/courses/{prog_course.id}/group_sets/', data={ 'minimum_size': 3, 'maximum_size': 10, 'id': group_set['id'], }, status_code=400, result=error_template, ) # The group set now has a group with 3 members user = create_user_with_perms(session, [], bs_course) group = test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [teacher_user.id, ta_user.id, user.id]}, status_code=200 ) test_client.req( 'get', f'/api/v1/assignments/{assignment.id}/groups/{group["id"]}/member_states/', 200, result={ str(teacher_user.id): True, str(ta_user.id): True, str(user.id): True, } ) # You should not be able to set the max size to lower than 3 test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 1, 'maximum_size': 2, 'id': group_set['id'], }, status_code=400, result=error_template, ) # You SHOULD be able to set the min size to higher than 3 as there was # no submission by any group test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 4, 'maximum_size': 10, 'id': group_set['id'], }, status_code=200, result={ 'minimum_size': 4, 'maximum_size': 10, 'id': group_set['id'], 'assignment_ids': [assignment.id], } ) # Reset back to minimum size of 3 test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 3, 'maximum_size': 4, 'id': group_set['id'], }, status_code=200, ) test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'group_set_id': group_set['id']} ) submission = create_submission(test_client, assignment.id) assert submission['user']['group'] assert submission['user']['group']['id'] == group['id'] # Now you should not be able to set the min size to higher than 3 as # there IS a submission by a group. test_client.req( 'put', f'/api/v1/courses/{bs_course.id}/group_sets/', data={ 'minimum_size': 4, 'maximum_size': 10, 'id': group_set['id'], }, status_code=400, result=error_template, ) # You should also not be able to disconnect the group set for this # assignment test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 400, data={'group_set_id': None}, result=error_template, ) new_group_set = create_group_set(test_client, prog_course.id, 2, 4) # You should also not be able to change the group set for this # assignment test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 400, data={'group_set_id': new_group_set['id']}, result=error_template, )
def make_empty_user(): return create_user_with_perms(session, [], bs_course)
def test_update_lti_provider( test_client, describe, lms, iss, session, logged_in ): with describe('setup'): admin_user = helpers.create_user_with_perms( session, [], [], [GPerm.can_manage_lti_providers] ) normal_user = helpers.create_user_with_perms(session, [], [], []) with logged_in(admin_user): prov = test_client.req( 'post', '/api/v1/lti1.3/providers/', 200, data={'lms': lms, 'iss': iss, 'intended_use': 'AA'}, ) url = f'/api/v1/lti1.3/providers/{helpers.get_id(prov)}' with describe('admin can update a provider'), logged_in(admin_user): prov = test_client.req('patch', url, 200, data={'client_id': 'aa'}) test_client.req('get', '/api/v1/lti1.3/providers/', 200, result=[prov]) with describe('normal user cannot update a provider' ), logged_in(normal_user): test_client.req( 'patch', url + '?secret=aa', 403, data={'auth_token_url': 'err'} ) with logged_in(admin_user): test_client.req( 'get', '/api/v1/lti1.3/providers/', 200, result=[prov] ) with describe('normal user can update provider with secret' ), logged_in(normal_user): test_client.req( 'patch', f'{url}?secret={prov["edit_secret"]}', 200, data={'auth_token_url': 'DEP 1'}, result={ **prov, 'auth_token_url': 'DEP 1', 'edit_secret': None, }, ) with logged_in(admin_user): prov, = test_client.req( 'get', '/api/v1/lti1.3/providers/', 200, result=[{**prov, 'auth_token_url': 'DEP 1'}] ) with describe('cannot finalize before all info is filled in' ), logged_in(admin_user): test_client.req('patch', url, 400, data={'finalize': True}) with describe('Can also update the iss'), logged_in(admin_user): test_client.req( 'patch', url, 200, data={'iss': 'NEW ISS!!'}, result={'__allow_extra__': True, 'iss': 'NEW ISS!!'}, ) with describe('cannot edit after it is finalized'), logged_in(admin_user): prov = test_client.req( 'patch', url, 200, data={ 'client_id': 'id', 'auth_token_url': 'c', 'auth_login_url': 'd', 'key_set_url': 'asdf', 'finalize': True, }, result={'__allow_extra__': True, 'finalized': True} ) test_client.req('patch', url, 403, data={'client_id': 'e'}) test_client.req('get', url, 200, result=prov)
def test_create_extended_group( test_client, logged_in, user_with_perms, prog_course, request, error_template, teacher_user, session, bs_course ): with logged_in(teacher_user): group_set = create_group_set(test_client, prog_course.id, 2, 4) perm_err = request.node.get_closest_marker('perm_error') has_err = bool(perm_err) if perm_err: status = 403 else: status = 200 with logged_in(user_with_perms) as user: user1 = create_user_with_perms(session, [], prog_course) user2 = create_user_with_perms(session, [], prog_course) group = test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [user1.id]}, status_code=status, result=error_template if has_err else { 'id': int, 'name': str, 'members': list, 'virtual_user': dict, 'created_at': str, 'group_set_id': group_set['id'], } ) if not has_err: assert len(group['members']) == 1 assert group['members'][0]['id'] == user1.id group = test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [user.id, user2.id]}, status_code=status, result=error_template if has_err else { 'id': int, 'name': str, 'members': list, 'virtual_user': dict, 'created_at': str, 'group_set_id': group_set['id'], } ) if not has_err: assert len(group['members']) == 2 if has_err: return with logged_in(teacher_user) as user: user_not_in_course = create_user_with_perms(session, [], bs_course) # We should be able to create a normal group test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [user.id]}, status_code=200 ) # But not with users that are not in this course test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [user_not_in_course.id]}, status_code=400, result=error_template ) # We can't be in a group twice test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [user.id]}, status_code=400 ) # We can't add users that don't exist test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [10000000]}, status_code=404 ) test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'member_ids': [None]}, status_code=400, result=error_template ) test_client.req( 'post', f'/api/v1/group_sets/{group_set["id"]}/group', data={'name': ['not', 'a', 'string']}, status_code=400, result=error_template )
def test_seeing_teacher_revision_in_group( test_client, session, logged_in, teacher_user, course, error_template, assignment, describe ): with describe('setup'): user_other_group = create_user_with_perms( session, [ CPerm.can_submit_own_work, CPerm.can_view_own_teacher_files, CPerm.can_see_assignments ], course ) user_with_perm = create_user_with_perms( session, [ CPerm.can_submit_own_work, CPerm.can_view_own_teacher_files, CPerm.can_see_assignments ], course ) user_without_perm = create_user_with_perms( session, [CPerm.can_submit_own_work, CPerm.can_see_assignments], course ) with logged_in(teacher_user): g_set = create_group_set(test_client, course.id, 1, 2) test_client.req( 'patch', f'/api/v1/assignments/{assignment.id}', 200, data={'group_set_id': g_set['id']} ) g1 = create_group( test_client, g_set['id'], [user_with_perm, user_without_perm], ) with logged_in(user_with_perm): sub = create_submission(test_client, assignment.id) # Submission is by the group assert sub['user']['group']['id'] == g1['id'] student_files = test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/', 200 ) with describe('Cannot view teacher files while assig is open' ), logged_in(user_with_perm): test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher', 403, result=error_template, ) with describe('Can see teacher files after assig is done' ), logged_in(user_with_perm): assignment.set_state_with_string('done') session.commit() test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher', 200, result=student_files, ) # This marks all files as from the student revision, i.e. there are no # teacher files. m.File.query.filter( m.File.work_id == sub['id'], m.File.parent_id.isnot(None) ).update({'fileowner': m.FileOwner.student}) test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher', 200, result={ **student_files, 'entries': [], } ) with describe( 'User without permission to see own teacher files gets an error' ), logged_in(user_without_perm): test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher', 403, result=error_template, ) with describe('User from other group cannot see teacher files' ), logged_in(user_other_group): test_client.req( 'get', f'/api/v1/submissions/{sub["id"]}/files/?owner=teacher', 403, result=error_template, )
def test_add_feedback(request, logged_in, test_client, session, data, error_template, admin_user, mail_functions, describe, tomorrow): with describe('setup'), logged_in(admin_user): assignment = helpers.create_assignment(test_client, state='open', deadline=tomorrow) course = assignment['course'] teacher = admin_user student = helpers.create_user_with_perms(session, [CPerm.can_submit_own_work], course) work = helpers.create_submission(test_client, assignment, for_user=student) data_err = request.node.get_closest_marker('data_error') is not None code_id = session.query(m.File.id).filter( m.File.work_id == work['id'], m.File.parent_id.isnot(None), m.File.name != '__init__', ).first()[0] if data_err: code = 400 else: code = 204 def get_result(has_author=True): if data_err: return {} msg = data.get('expected_result', data['comment']) res = { '0': { 'line': 0, 'msg': msg, 'author': { 'id': teacher.id, '__allow_extra__': True }, } } if not has_author: res['0'].pop('author') return res with describe('teacher can place comments'), logged_in(teacher): test_client.req('put', f'/api/v1/code/{code_id}/comments/0', code, data=data, result=None if code == 204 else error_template) if data_err: assert not mail_functions.any_mails() return assert not mail_functions.any_mails(), 'Student should not be mailed' res = test_client.req('get', f'/api/v1/code/{code_id}', 200, query={'type': 'feedback'}, result=get_result()) data = { 'comment': 'bye', 'expected_result': 'bye', } test_client.req( 'put', f'/api/v1/code/{code_id}/comments/0', code, data=data, ) assert not mail_functions.any_mails(), 'No mails for updates' test_client.req('get', f'/api/v1/code/{code_id}', 200, query={'type': 'feedback'}, result=get_result()) with describe( 'Students cannot place comments if they do not have the permission' ), logged_in(student): test_client.req('put', f'/api/v1/code/{code_id}/comments/1', 403, data=data) with describe('Students can get comments when assignment is done'): with logged_in(student): test_client.req( 'get', f'/api/v1/code/{code_id}', 200, query={'type': 'feedback'}, result={}, ) # We don't have the permission to see the notification test_client.req('get', '/api/v1/notifications/', 200, result={'notifications': []}) test_client.req('get', '/api/v1/notifications/?has_unread', 200, result={'has_unread': False}) with logged_in(teacher): test_client.req( 'patch', f'/api/v1/assignments/{assignment["id"]}', 200, data={'state': 'done'}, ) assert not mail_functions.any_mails(), ( 'Should not send old notifications when state changes') with logged_in(student): test_client.req( 'get', f'/api/v1/code/{code_id}', 200, query={'type': 'feedback'}, result=get_result(has_author=False), ) # Now we do have this permission test_client.req('get', '/api/v1/notifications/', 200, result={ 'notifications': [{ 'id': int, 'reasons': [['author', str]], '__allow_extra__': True, 'read': False, 'file_id': code_id, }] }) test_client.req('get', '/api/v1/notifications/?has_unread', 200, result={'has_unread': True}) with describe('Cannot get or update feedback after deleting submission' ), logged_in(teacher): test_client.req('delete', f'/api/v1/submissions/{work["id"]}', 204) test_client.req( 'get', f'/api/v1/code/{code_id}', 404, query={'type': 'feedback'}, result=error_template, ) test_client.req( 'put', f'/api/v1/code/{code_id}/comments/0', 404, data={'comment': 'Any value'}, result=error_template, ) with describe('After delete notification should be gone'), logged_in( student): test_client.req('get', '/api/v1/notifications/', 200, result={'notifications': []}) test_client.req('get', '/api/v1/notifications/?has_unread', 200, result={'has_unread': False})
def test_get_assignment_all_feedback(named_user, request, logged_in, test_client, assignment_real_works, session, error_template, ta_user, monkeypatch_celery, teacher_user): assignment, work = assignment_real_works assig_id = assignment.id perm_err = request.node.get_closest_marker('perm_error') only_own_subs = request.node.get_closest_marker('only_own') code_id = session.query(m.File.id).filter( m.File.work_id == work['id'], m.File.parent != None, # NOQA m.File.name != '__init__', ).first()[0] with logged_in(ta_user): res = test_client.req('patch', f'/api/v1/submissions/{work["id"]}', 200, data={ 'grade': 5, 'feedback': 'Niet zo goed\0' }, result=dict) test_client.req( 'put', f'/api/v1/code/{code_id}/comments/0', 204, data={'comment': 'for line 0'}, ) test_client.req( 'put', f'/api/v1/code/{code_id}/comments/1', 204, data={'comment': 'for line - 1'}, ) with logged_in(teacher_user): test_client.req('post', f'/api/v1/assignments/{assignment.id}/linter', 200, data={ 'name': 'Flake8', 'cfg': '' }) def match_res(res, only_own_subs=only_own_subs): general = 'Niet zo goed' user = ['test.py:1:1: for line 0', 'test.py:2:1: for line - 1'] assert len(res) == 1 if only_own_subs else 3 linter = None for key, val in res.items(): if key == str(work['id']): assert val['user'] == user assert val['general'] == general assert len(val['linter']) >= 1 else: assert not val['user'] assert val['general'] == '' assert len(val['linter']) >= 1 if linter is None: linter = val['linter'] else: assert linter == val['linter'] with logged_in(named_user): if perm_err: code = perm_err.kwargs['error'] else: code = 200 if only_own_subs: ex_res = { str(work["id"]): { 'user': [], 'linter': [], 'general': '' } } elif code == 200: ex_res = dict else: ex_res = error_template res = test_client.req( 'get', f'/api/v1/assignments/{assig_id}/feedbacks/', code, result=ex_res, ) if not (perm_err or only_own_subs): match_res(res) assig = session.query(m.Assignment).get(assig_id) assig.state = m.AssignmentStateEnum.done session.commit() with logged_in(named_user): res = test_client.req( 'get', f'/api/v1/assignments/{assig_id}/feedbacks/', code, result=dict if code == 200 else error_template, ) if not perm_err: match_res(res) with logged_in( helpers.create_user_with_perms(session, [CPerm.can_see_others_work], assig.course)): res = test_client.req( 'get', f'/api/v1/assignments/{assig_id}/feedbacks/', 200, result=dict, ) if not perm_err: match_res(res, only_own_subs=False)
def user_with_perms(session, request, course_name): course = session.query(m.Course).filter_by(name=course_name).one() yield create_user_with_perms(session, request.param, course)
def test_impersonate(logged_in, describe, test_client, session): with describe('setup'): admin_password = '******' user_password = '******' with_perm = create_user_with_perms( session, [], [], [GlobalPermission.can_impersonate_users]) no_perm = create_user_with_perms(session, [], [], []) with_perm.password = admin_password no_perm.password = admin_password active = create_user_with_perms(session, [], []) not_active = create_user_with_perms(session, [], []) not_active.active = False active.password = user_password not_active.password = user_password session.commit() with describe('no perm cannot impersonate'), logged_in(no_perm): test_client.req('post', '/api/v1/login?impersonate', 403, data={ 'username': active.username, 'own_password': admin_password, }) test_client.req('post', '/api/v1/login?impersonate', 403, data={ 'username': active.username, 'own_password': user_password, }) with describe('with perm can impersonate'), logged_in(with_perm): # Does not work with users password test_client.req('post', '/api/v1/login?impersonate', 403, data={ 'username': active.username, 'own_password': user_password }) # Does work with own password test_client.req('post', '/api/v1/login?impersonate', 200, data={ 'username': active.username, 'own_password': admin_password }, result={ 'user': { '__allow_extra__': True, 'username': active.username, 'email': active.email, }, 'access_token': str, }) with describe('cannot impersonate non active user'), logged_in(with_perm): test_client.req('post', '/api/v1/login?impersonate', 404, data={ 'username': not_active.username, 'own_password': admin_password }) with describe('cannot impersonate while not logged in'): test_client.req('post', '/api/v1/login?impersonate', 401, data={ 'username': active.username, 'own_password': admin_password })
def test_enabling_login_links( describe, test_client, logged_in, admin_user, yesterday, session, stub_function, app, monkeypatch, tomorrow, error_template ): with describe('setup'), logged_in(admin_user): send_mail_stub = stub_function(psef.mail, 'send_login_link_mail') assig = helpers.create_assignment(test_client) assig_id = helpers.get_id(assig) course = helpers.get_id(assig['course']) url = f'/api/v1/assignments/{assig_id}?no_course_in_assignment=t' teacher = helpers.create_user_with_perms( session, [ CPerm.can_see_assignments, CPerm.can_see_hidden_assignments, CPerm.can_view_analytics, CPerm.can_edit_assignment_info, ], course ) no_perm = helpers.create_user_with_perms( session, [ CPerm.can_see_assignments, CPerm.can_see_hidden_assignments, CPerm.can_view_analytics, ], course ) # Make sure there are users to email for _ in range(10): helpers.create_user_with_role(session, 'Student', course) with describe('cannot enable login links for wrong kind' ), logged_in(teacher): test_client.req('patch', url, 409, data={'send_login_links': True}) _, rv = test_client.req( 'patch', url, 200, data={ 'deadline': yesterday.isoformat(), 'available_at': (yesterday - timedelta(minutes=1)).isoformat(), 'kind': 'exam', 'send_login_links': True, }, result={ '__allow_extra__': True, 'send_login_links': True, }, include_response=True, ) warning = rv.headers['warning'] assert re.match(r'.*deadline.*expired.*not send', warning) assert not send_mail_stub.called with describe('cannot enable login links for wrong kind' ), logged_in(teacher): test_client.req('patch', url, 409, data={'kind': 'normal'}) assert not send_mail_stub.called with describe('cannot change login links with incorrect perms' ), logged_in(no_perm): test_client.req('patch', url, 403, data={'send_login_links': False}) assert not send_mail_stub.called with describe('Setting again does nothing'), logged_in(teacher): old_token = m.Assignment.query.get(assig_id).send_login_links_token test_client.req( 'patch', url, 200, data={ 'send_login_links': True, 'available_at': (yesterday - timedelta(minutes=1)).isoformat(), } ) new_token = m.Assignment.query.get(assig_id).send_login_links_token assert new_token == old_token assert not send_mail_stub.called with describe('Changing available at reschedules links' ), logged_in(teacher): old_token = m.Assignment.query.get(assig_id).send_login_links_token test_client.req( 'patch', url, 200, data={ 'send_login_links': True, 'available_at': (yesterday - timedelta(minutes=2)).isoformat(), } ) new_token = m.Assignment.query.get(assig_id).send_login_links_token assert new_token != old_token assert new_token is not None assert not send_mail_stub.called with describe('Disabling clears token'), logged_in(teacher): test_client.req('patch', url, 200, data={'send_login_links': False}) new_token = m.Assignment.query.get(assig_id).send_login_links_token assert new_token is None assert not send_mail_stub.called with describe('Cannot enable login links with large availability window' ), logged_in(teacher): psef.site_settings.Opt.EXAM_LOGIN_MAX_LENGTH.set_and_commit_value( 'P1D' ) test_client.req( 'patch', url, 409, data={ 'send_login_links': True, 'deadline': tomorrow.isoformat(), 'available_at': yesterday.isoformat(), }, result={ **error_template, 'message': re.compile( 'Login links can only be enabled if the deadline is at' ' most 24 hours after.*' ) } )
def test_change_name_of_group( test_client, logged_in, error_template, prog_course, session, teacher_user ): new_name = f'NEW_NAME-{uuid.uuid4()}' def check_name(name): name_to_check = g1['name'] if name is None else name with logged_in(teacher_user): assert g_model.get_readable_name() == f'group "{name_to_check}"' return test_client.req( 'get', f'/api/v1/groups/{g1["id"]}', 200, result={**g1, 'name': name_to_check} ) u1 = create_user_with_perms( session, [CPerm.can_edit_own_groups], prog_course ) u2 = create_user_with_perms( session, [CPerm.can_edit_own_groups], prog_course ) u3 = create_user_with_perms( session, [CPerm.can_edit_own_groups], prog_course ) with logged_in(teacher_user): g_set = create_group_set(test_client, prog_course.id, 2, 4) g1 = create_group( test_client, g_set['id'], [u1.id, u2.id], ) g_model = LocalProxy( lambda: m.User.query.get(g1['virtual_user']['id']) ) with logged_in(u3): test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/name', 403, data={'name': new_name} ) check_name(None) with logged_in(u1): # u1 is member so it can change the name old_name = g1['name'] test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/name', 200, data={'name': new_name} ) check_name(new_name) # Reset back to old name test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/name', 200, data={'name': old_name} ) check_name(old_name) test_client.req( 'post', f'/api/v1/groups/{g1["id"]}/name', 400, data={'name': 'sh'}, # This name is too short result=error_template ) check_name(None)