def test_delete_submission_of_group( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.GRADE_UPDATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade' ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='done', deadline=tomorrow ) # Create some users and let them have individual submissions g_user1 = helpers.create_lti1p3_user(session, lti1p3_provider) g_user2 = helpers.create_lti1p3_user(session, lti1p3_provider) for user in [g_user1, g_user2]: course_conn.maybe_add_user_to_course(user, ['Learner']) sub = helpers.create_submission(test_client, assig, for_user=user) helpers.to_db_object(sub, m.Work).set_grade( 5.0, m.User.resolve(admin_user) ) # Place the users in a group, with a submission with a different grade # than their individual submission. gset = helpers.create_group_set(test_client, course, 1, 2, [assig]) helpers.create_group(test_client, gset, [g_user1, g_user2]) gsub = helpers.create_submission(test_client, assig, for_user=g_user2) helpers.to_db_object(gsub, m.Work).set_grade( 6.0, m.User.resolve(admin_user) ) session.commit() with describe( 'deleting group submission passes back individual submissions' ): with logged_in(admin_user): test_client.req( 'delete', f'/api/v1/submissions/{helpers.get_id(gsub)}', 204 ) assert stub_passback.called_amount == 2 # The grade passed back is that of their individual submission assert stub_passback.args[0][0].get_score_given() == 5.0 assert stub_passback.args[1][0].get_score_given() == 5.0
def test_passback_single_submission( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) signal = watch_signal( signals.GRADE_UPDATED, clear_all_but=[m.LTI1p3Provider._passback_submission] ) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade', raise_pylti1p3_exc ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='done', deadline=tomorrow ) user = helpers.create_lti1p3_user(session, lti1p3_provider) course_conn.maybe_add_user_to_course(user, []) older_sub, newest_sub = [ helpers.to_db_object( helpers.create_submission(test_client, assig, for_user=user), m.Work ) for _ in range(2) ] with describe('calling directly with non existing assignment is a noop'): m.LTI1p3Provider._passback_submission((newest_sub.id, 100000)) assert not stub_passback.called with describe('changing grade of non newest sub does not passback'): older_sub.set_grade(5.0, admin_user) assert signal.was_send_once assert not stub_passback.called with describe('changing grade on newest sub passes the new grade back'): newest_sub.set_grade(9.5, m.User.resolve(admin_user)) assert signal.was_send_once assert stub_passback.called_amount == 1 assert stub_passback.args[0][0].get_score_given() == 9.5 with describe('unsetting grade should passback something without grade'): newest_sub.set_grade(None, m.User.resolve(admin_user)) assert signal.was_send_once assert stub_passback.called_amount == 1 assert stub_passback.args[0][0].get_score_given() is None assert stub_passback.args[0][0].get_activity_progress() == 'Submitted'
def test_passback_with_bonus_points( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) signal = watch_signal( signals.GRADE_UPDATED, clear_all_but=[m.LTI1p3Provider._passback_submission] ) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade', raise_pylti1p3_exc ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='done', deadline=tomorrow ) test_client.req( 'patch', f'/api/v1/assignments/{helpers.get_id(assig)}', 200, data={'max_grade': 15} ) user = helpers.create_lti1p3_user(session, lti1p3_provider) course_conn.maybe_add_user_to_course(user, []) sub = helpers.to_db_object( helpers.create_submission(test_client, assig, for_user=user), m.Work ) with describe('can passback bonus points'), logged_in(admin_user): test_client.req( 'patch', f'/api/v1/submissions/{helpers.get_id(sub)}', 200, data={'grade': 14}, ) assert signal.was_send_once assert stub_passback.called_amount == 1 assert stub_passback.args[0][0].get_score_given() == 14 assert stub_passback.args[0][0].get_score_maximum() == 10
def test_failing_passback( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.GRADE_UPDATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade', raise_pylti1p3_exc ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='done', deadline=tomorrow ) user = helpers.create_lti1p3_user(session, lti1p3_provider) course_conn.maybe_add_user_to_course(user, []) sub = helpers.to_db_object( helpers.create_submission(test_client, assig, for_user=user), m.Work ) sub.set_grade(10.0, m.User.resolve(admin_user)) session.commit() hist = m.GradeHistory.query.filter_by(work=sub).one() assert hist assert not hist.passed_back with describe('failing passback should not update history'): m.LTI1p3Provider._passback_grades(assig.id) assert stub_passback.called hist = m.GradeHistory.query.filter_by(work=sub).one() assert hist assert not hist.passed_back
def test_passback_for_new_student( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): signal = watch_signal( signals.USER_ADDED_TO_COURSE, flush_db=True, clear_all_but=[m.LTI1p3Provider._passback_new_user] ) stub_function( pylti1p3.names_roles.NamesRolesProvisioningService, 'get_members', lambda: [], ) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade' ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig1 = helpers.create_lti1p3_assignment( session, course, deadline=tomorrow ) assig2 = helpers.create_lti1p3_assignment( session, course, deadline=tomorrow ) user = helpers.create_lti1p3_user(session, lti1p3_provider) with describe('non enrolled student should not do anything'): assert not user.is_enrolled(course) m.LTI1p3Provider._passback_new_user((user.id, course.id, None)) assert not stub_passback.called with describe('enrolling student should passback the grades'): course_conn.maybe_add_user_to_course(user, []) assert signal.was_send_once assert stub_passback.called_amount == 2
def test_passing_back_all_grades( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow, make_function_spy ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.GRADE_UPDATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) watch_signal(signals.WORK_DELETED, clear_all_but=[]) signal = watch_signal( signals.ASSIGNMENT_STATE_CHANGED, clear_all_but=[m.LTI1p3Provider._passback_grades] ) stub_get_acccess_token = stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = make_function_spy( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade', pass_self=True ) # Make a session with a response that returns json when the `json` # method is called req_session = requests_stubs.session_maker()() req_session.Response.json = lambda _=None: {} stub_requests_post = stub_function(requests, 'post', req_session.post) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='hidden', deadline=tomorrow ) user1 = helpers.create_lti1p3_user(session, lti1p3_provider) user2 = helpers.create_lti1p3_user(session, lti1p3_provider) user3 = helpers.create_lti1p3_user(session, lti1p3_provider) all_students = [user1, user2, user3] for u in all_students: course_conn.maybe_add_user_to_course(u, ['Learner']) lti_user_ids = [ m.UserLTIProvider.query.filter_by(user=u).one().lti_user_id for u in all_students ] gset = helpers.create_group_set(test_client, course, 1, 2, [assig]) helpers.create_group(test_client, gset, [user1, user3]) sub = helpers.to_db_object( helpers.create_submission(test_client, assig, for_user=user1), m.Work ) sub.set_grade(2.5, admin_user) # Make sure user2 does not have a non deleted submission user2_sub = helpers.create_submission( test_client, assig, for_user=user2 ) test_client.req( 'delete', f'/api/v1/submissions/{helpers.get_id(user2_sub)}', 204 ) with describe('changing assignment state to "open" does not passback'): assig.set_state_with_string('open') assert signal.was_send_once assert not stub_passback.called assert not stub_get_acccess_token.called with describe('changing to done does passback'): assig.set_state_with_string('done') assert signal.was_send_once assert stub_passback.called_amount == len(all_students) # Calls should be cached assert stub_get_acccess_token.called_amount == 1 p1, p2, p3 = stub_passback.args assert p1[0] != p2[0] != p3[0] assert p1[0].get_score_given() == 2.5 assert p2[0].get_score_given() == 2.5 assert {p1[0].get_user_id(), p2[0].get_user_id()} == {lti_user_ids[0], lti_user_ids[2]} # Does not have a submission assert p3[0].get_score_given() is None assert p3[0].get_user_id() == lti_user_ids[1] with describe('toggling to open and done should do a new passback'): assig.set_state_with_string('open') assert signal.was_send_once assert not stub_passback.called assert not stub_get_acccess_token.called assig.set_state_with_string('done') assert signal.was_send_n_times(2) assert stub_passback.called # access token should still be cached assert not stub_get_acccess_token.called
def test_delete_submission_passback( lti1p3_provider, describe, logged_in, admin_user, watch_signal, stub_function, test_client, session, tomorrow ): with describe('setup'), logged_in(admin_user): watch_signal(signals.WORK_CREATED, clear_all_but=[]) watch_signal(signals.GRADE_UPDATED, clear_all_but=[]) watch_signal(signals.USER_ADDED_TO_COURSE, clear_all_but=[]) stub_function( pylti1p3.service_connector.ServiceConnector, 'get_access_token', lambda: '' ) stub_passback = stub_function( pylti1p3.assignments_grades.AssignmentsGradesService, 'put_grade' ) course, course_conn = helpers.create_lti1p3_course( test_client, session, lti1p3_provider ) assig = helpers.create_lti1p3_assignment( session, course, state='done', deadline=tomorrow ) user = helpers.create_lti1p3_user(session, lti1p3_provider) lti_user_id = m.UserLTIProvider.query.filter_by( user=m.User.resolve(user) ).one().lti_user_id course_conn.maybe_add_user_to_course(user, ['Learner']) sub_oldest, sub_older, sub_middle, sub_newest = [ helpers.to_db_object( helpers.create_submission(test_client, assig, for_user=user), m.Work ) for _ in range(4) ] signal = watch_signal( signals.WORK_DELETED, clear_all_but=[m.LTI1p3Provider._delete_submission] ) def do_delete(sub): with logged_in(admin_user): test_client.req( 'delete', f'/api/v1/submissions/{helpers.get_id(sub)}', 204 ) with describe('Delete non newest'): do_delete(sub_older) assert signal.was_send_once assert not stub_passback.called with describe('Calling method for non existing work simply does nothing'): m.LTI1p3Provider._delete_submission(1000000) assert not stub_passback.called with describe('Calling method for non deleted work does nothing'): m.LTI1p3Provider._delete_submission(helpers.get_id(sub_newest)) assert not stub_passback.called with describe('Delete newest should passback grade of new newest'): sub_middle.set_grade(5.0, m.User.resolve(admin_user)) session.commit() # We should have removed the grade_updated signal assert not stub_passback.called do_delete(sub_newest) assert signal.was_send_once assert stub_passback.called_amount == 1 grade, = stub_passback.all_args[0].values() assert grade.get_score_given() == 5.0 assert grade.get_user_id() == lti_user_id with describe('Deleting new newest should passback next non deleted'): sub_older.set_grade(6.0, m.User.resolve(admin_user)) sub_oldest.set_grade(8.0, m.User.resolve(admin_user)) session.commit() assert not m.GradeHistory.query.filter_by(work=sub_oldest ).one().passed_back do_delete(sub_middle) assert signal.was_send_once assert stub_passback.called_amount == 1 grade, = stub_passback.all_args[0].values() # Should passback oldest as we deleted older in an earlier block assert grade.get_score_given() == 8.0 assert grade.get_user_id() == lti_user_id # Should update history assert m.GradeHistory.query.filter_by(work=sub_oldest ).one().passed_back with describe('Deleting without any existing submission should passback'): do_delete(sub_oldest) assert signal.was_send_once assert stub_passback.called_amount == 1 grade, = stub_passback.all_args[0].values() assert grade.get_score_given() is None assert grade.get_grading_progress() == 'NotReady' assert grade.get_user_id() == lti_user_id with describe('Passing back deleted sub should do nothing'): lti1p3_provider._passback_grade( assignment=assig, sub=sub_newest, timestamp=DatetimeWithTimezone.utcnow() ) assert not stub_passback.called