def test_course_with_partial_approval(self, client, admin_session): """Course with two instructors and one approval.""" with test_approvals_workflow(app): # If course has approvals but not scheduled then it will show up in the feed. approved_by_uid = get_instructor_uids(section_id=section_1_id, term_id=self.term_id)[0] room_id = Room.get_room_id(section_id=section_1_id, term_id=self.term_id) Approval.create( approved_by_uid=approved_by_uid, approver_type_='instructor', publish_type_='kaltura_my_media', recording_type_='presentation_audio', room_id=room_id, section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = api_get_course( client, term_id=self.term_id, section_id=section_1_id, ) assert [i['uid'] for i in api_json['instructors']] == ['10001', '10002'] approvals = api_json['approvals'] assert len(approvals) == 1 assert approved_by_uid == approvals[0]['approvedBy']['uid'] assert api_json['approvalStatus'] == 'Partially Approved' assert api_json['schedulingStatus'] == 'Not Scheduled' assert api_json['meetings']['eligible'][0]['room']['id'] == room_id assert api_json['meetings']['eligible'][0]['room']['location'] == 'Barrows 106'
def test_not_invited_filter(self, client, admin_session): """Not-invited filter: Courses in eligible rooms, never sent an invitation. No approval. Not scheduled.""" with test_approvals_workflow(app): # The first course gets an invitation self._send_invitation_email(section_1_id) # The second course did not receive an invitation BUT it does have approval. invite = SentEmail.get_emails_of_type( section_ids=[section_4_id], template_type='invitation', term_id=self.term_id, ) assert not invite self._create_approval(section_4_id) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Not Invited') assert not _find_course(api_json=api_json, section_id=section_1_id) assert not _find_course(api_json=api_json, section_id=section_4_id) # Zero instructors is acceptable assert _find_course(api_json=api_json, section_id=eligible_course_with_no_instructors) # Third course is in enabled room and has not received an invite. Therefore, it is in the feed. assert _is_course_in_enabled_room(section_id=section_3_id, term_id=self.term_id) course = _find_course(api_json=api_json, section_id=section_3_id) assert course['approvalStatus'] == 'Not Invited' assert course['schedulingStatus'] == 'Not Scheduled' assert course['label'] == 'BIO 1B, LEC 001'
def test_invited_filter(self, client, admin_session): """Invited filter: Course in an eligible room, have received invitation. No approvals. Not scheduled.""" with test_approvals_workflow(app): # Course with approval is NOT expected in results self._send_invitation_email(section_5_id) self._create_approval(section_5_id) # Course in ineligible room is NOT expected in results self._send_invitation_email(section_2_id) # Course in eligible room eligible_section_id = section_4_id self._send_invitation_email(eligible_section_id) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Invited') assert len(api_json) == 1 assert api_json[0]['sectionId'] == eligible_section_id # Section with ZERO approvals will show up in search results course = _find_course(api_json=api_json, section_id=section_4_id) assert course assert course['label'] == 'CHEM C110L, LAB 001' assert course['approvalStatus'] == 'Invited' assert course['schedulingStatus'] == 'Not Scheduled' # The section with approval will NOT show up in search results assert not _find_course(api_json=api_json, section_id=section_5_id)
def test_admin_approval(self): """Course is scheduled for recording if an admin user has approved.""" with test_approvals_workflow(app): section_id = 50005 term_id = app.config['CURRENT_TERM_ID'] course = SisSection.get_course(section_id=section_id, term_id=term_id) instructors = course['instructors'] assert len(instructors) == 2 # Verify that course is not scheduled assert Scheduled.get_scheduled(section_id=section_id, term_id=term_id) is None Approval.create( approved_by_uid=admin_uid, approver_type_='admin', course_display_name=course['label'], publish_type_='kaltura_my_media', recording_type_='presentation_audio', room_id=Room.find_room('Barker 101').id, section_id=section_id, term_id=term_id, ) KalturaJob(simply_yield).run() std_commit(allow_test_environment=True) # Admin approval is all we need. assert Scheduled.get_scheduled(section_id=section_id, term_id=term_id)
def test_scheduled_filter(self, client, admin_session): """Scheduled filter: Courses with recordings scheduled.""" with test_approvals_workflow(app): # Send invites for section_id in [section_1_id, section_6_id]: self._send_invitation_email(section_id) self._create_approval(section_id) # Feed will only include courses that were scheduled. mock_scheduled( section_id=section_1_id, term_id=self.term_id, ) # Deleted records will be ignored mock_scheduled( section_id=section_2_id, term_id=self.term_id, ) Scheduled.delete(section_id=section_2_id, term_id=self.term_id) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Scheduled') assert len(api_json) == 1 course = _find_course(api_json=api_json, section_id=section_1_id) assert course['approvalStatus'] == 'Partially Approved' assert course['schedulingStatus'] == 'Scheduled' assert not _find_course(api_json=api_json, section_id=section_6_id)
def test_has_obsolete_meeting_dates(self, client, admin_session): """Admins can see meeting date changes that might disrupt scheduled recordings.""" with test_approvals_workflow(app): meeting = get_eligible_meeting(section_id=section_1_id, term_id=self.term_id) obsolete_meeting_end_date = '2020-04-01' assert meeting['endDate'] != obsolete_meeting_end_date Scheduled.create( instructor_uids=get_instructor_uids(term_id=self.term_id, section_id=section_1_id), kaltura_schedule_id=random.randint(1, 10), meeting_days=meeting['days'], meeting_end_date=obsolete_meeting_end_date, meeting_end_time=meeting['endTime'], meeting_start_date=meeting['startDate'], meeting_start_time=meeting['startTime'], publish_type_='kaltura_my_media', recording_type_='presentation_audio', room_id=Room.get_room_id(section_id=section_1_id, term_id=self.term_id), section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_course_changes(client, term_id=self.term_id) course = _find_course(api_json=api_json, section_id=section_1_id) assert course assert course['scheduled']['hasObsoleteRoom'] is False assert course['scheduled']['hasObsoleteDates'] is True assert course['scheduled']['hasObsoleteTimes'] is False assert course['scheduled']['hasObsoleteInstructors'] is False
def test_admin_approval(self): """Course is scheduled for recording if an admin user has approved.""" with test_approvals_workflow(app): section_id = 22287 term_id = app.config['CURRENT_TERM_ID'] course = SisSection.get_course(section_id=section_id, term_id=term_id) instructors = course['instructors'] assert len(instructors) == 2 # Verify that course is not scheduled assert Scheduled.get_scheduled(section_id=section_id, term_id=term_id) is None Approval.create( approved_by_uid=admin_uid, approver_type_='admin', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=Room.find_room('Barker 101').id, section_id=section_id, term_id=term_id, ) KalturaJob(app.app_context).run() std_commit(allow_test_environment=True) # Admin approval is all we need. assert Scheduled.get_scheduled(section_id=section_id, term_id=term_id)
def test_has_instructors(self, client, admin_session): """Admins can see instructor changes that might disrupt scheduled recordings.""" with test_approvals_workflow(app): meeting_days, meeting_start_time, meeting_end_time = SisSection.get_meeting_times( term_id=self.term_id, section_id=section_3_id, ) instructor_uids = SisSection.get_instructor_uids(term_id=self.term_id, section_id=section_3_id) Scheduled.create( cross_listed_section_ids=[], instructor_uids=instructor_uids + ['999999'], meeting_days=meeting_days, meeting_start_time=meeting_start_time, meeting_end_time=meeting_end_time, publish_type_='canvas', recording_type_='presenter_audio', room_id=Room.get_room_id(section_id=section_3_id, term_id=self.term_id), section_id=section_3_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_course_changes(client, term_id=self.term_id) course = _find_course(api_json=api_json, section_id=section_3_id) assert course assert course['scheduled']['hasObsoleteRoom'] is False assert course['scheduled']['hasObsoleteMeetingTimes'] is False assert course['scheduled']['hasObsoleteInstructors'] is True
def test_opt_out_cross_listings(self, client, admin_session): """If a section opts out then its cross-listings are automatically opted out.""" with test_approvals_workflow(app): # First, opt out cross_listed_section_ids = [28475, 27950, 32827] self._api_opt_out_update( client, term_id=self.term_id, section_id=cross_listed_section_ids[-1], opt_out=True, ) section_ids_opted_out = CoursePreference.get_section_ids_opted_out(term_id=self.term_id) for section_id in cross_listed_section_ids: assert section_id in section_ids_opted_out std_commit(allow_test_environment=True) # Opt back in self._api_opt_out_update( client, term_id=self.term_id, section_id=cross_listed_section_ids[0], opt_out=False, ) section_ids_opted_out = CoursePreference.get_section_ids_opted_out(term_id=self.term_id) for section_id in cross_listed_section_ids: assert section_id not in section_ids_opted_out
def test_has_obsolete_instructors(self, client, admin_session): """Admins can see instructor changes that might disrupt scheduled recordings.""" with test_approvals_workflow(app): meeting = get_eligible_meeting(section_id=section_1_id, term_id=self.term_id) instructor_uids = get_instructor_uids(term_id=self.term_id, section_id=section_1_id) # Course has multiple instructors; we will schedule using only one instructor UID. assert len(instructor_uids) > 1 scheduled_with_uid = instructor_uids[0] Scheduled.create( instructor_uids=[scheduled_with_uid], kaltura_schedule_id=random.randint(1, 10), meeting_days=meeting['days'], meeting_end_date=get_recording_end_date(meeting), meeting_end_time=meeting['endTime'], meeting_start_date=get_recording_start_date(meeting, return_today_if_past_start=True), meeting_start_time=meeting['startTime'], publish_type_='kaltura_my_media', recording_type_='presenter_audio', room_id=Room.get_room_id(section_id=section_1_id, term_id=self.term_id), section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_course_changes(client, term_id=self.term_id) course = _find_course(api_json=api_json, section_id=section_1_id) assert course assert course['scheduled']['hasObsoleteRoom'] is False assert course['scheduled']['hasObsoleteDates'] is False assert course['scheduled']['hasObsoleteTimes'] is False assert course['scheduled']['hasObsoleteInstructors'] is True assert len(course['instructors']) == 2 assert len(course['scheduled']['instructors']) == 1 assert course['scheduled']['instructors'][0]['uid'] == scheduled_with_uid
def test_course_with_partial_approval(self, client, db, admin_session): """Course with two instructors and one approval.""" with test_approvals_workflow(app): # If course has approvals but not scheduled then it will show up in the feed. approved_by_uid = _get_instructor_uids(section_id=section_1_id, term_id=self.term_id)[0] room_id = Room.get_room_id(section_id=section_1_id, term_id=self.term_id) Approval.create( approved_by_uid=approved_by_uid, approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=room_id, section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = api_get_course( client, term_id=self.term_id, section_id=section_1_id, ) assert [i['uid'] for i in api_json['instructors']] == ['234567', '8765432'] approvals = api_json['approvals'] assert len(approvals) == 1 assert approved_by_uid == approvals[0]['approvedBy']['uid'] assert api_json['room']['id'] == room_id assert api_json['room']['location'] == 'Barrows 106'
def test_authorized_unschedule_scheduled(self, client, admin_session): with test_approvals_workflow(app): api_approve( client, publish_type='kaltura_my_media', recording_type='presentation_audio', section_id=section_1_id, ) mock_scheduled( section_id=section_1_id, term_id=self.term_id, ) course = api_get_course(client, term_id=self.term_id, section_id=section_1_id) assert len(course['approvals']) == 1 assert course['scheduled'] is not None assert course['hasNecessaryApprovals'] is True assert course['hasOptedOut'] is False assert course['schedulingStatus'] == 'Scheduled' response = self._api_unschedule( client, term_id=self.term_id, section_id=section_1_id, ) assert len(response['approvals']) == 0 assert response['scheduled'] is None assert response['hasNecessaryApprovals'] is False assert response['hasOptedOut'] is True assert response['schedulingStatus'] == 'Not Scheduled'
def _assert_schedule_is_obsolete( self, expect_obsolete_dates, expect_obsolete_times, meeting, override_days=None, override_end_date=None, override_end_time=None, override_start_date=None, override_start_time=None, ): with test_approvals_workflow(app): with override_config(app, 'CURRENT_TERM_RECORDINGS_BEGIN', meeting['startDate']): with override_config(app, 'CURRENT_TERM_RECORDINGS_END', meeting['endDate']): mock_scheduled( meeting=meeting, override_days=override_days, override_end_date=override_end_date, override_end_time=override_end_time, override_start_date=override_start_date, override_start_time=override_start_time, section_id=self.section_id, term_id=self.term_id, ) course = SisSection.get_course(section_id=self.section_id, term_id=self.term_id) scheduled = course['scheduled'] assert are_scheduled_dates_obsolete(meeting=meeting, scheduled=scheduled) is expect_obsolete_dates assert are_scheduled_times_obsolete(meeting=meeting, scheduled=scheduled) is expect_obsolete_times
def test_admin_alert_multiple_meeting_patterns(self): """Emails admin if course is scheduled with weird start/end dates.""" with test_approvals_workflow(app): with enabled_job(job_key=AdminEmailsJob.key()): term_id = app.config['CURRENT_TERM_ID'] section_id = 50014 room_id = Room.find_room('Barker 101').id # The course has two instructors. instructor_uid = get_instructor_uids(section_id=section_id, term_id=term_id)[0] approval = Approval.create( approved_by_uid=instructor_uid, approver_type_='instructor', publish_type_='kaltura_my_media', recording_type_='presenter_audio', room_id=room_id, section_id=section_id, term_id=term_id, ) # Uh oh! Only one of them has been scheduled. meeting = get_eligible_meeting(section_id=section_id, term_id=term_id) Scheduled.create( instructor_uids=[instructor_uid], kaltura_schedule_id=random.randint(1, 10), meeting_days=meeting['days'], meeting_end_date=get_recording_end_date(meeting), meeting_end_time=meeting['endTime'], meeting_start_date=get_recording_start_date( meeting, return_today_if_past_start=True), meeting_start_time=meeting['startTime'], publish_type_=approval.publish_type, recording_type_=approval.recording_type, room_id=room_id, section_id=section_id, term_id=term_id, ) courses = SisSection.get_courses_scheduled_nonstandard_dates( term_id=term_id) course = next( (c for c in courses if c['sectionId'] == section_id), None) assert course # Message queued but not sent. admin_uid = app.config['EMAIL_DIABLO_ADMIN_UID'] AdminEmailsJob(simply_yield).run() queued_messages = QueuedEmail.query.filter_by( section_id=section_id).all() assert len(queued_messages) == 1 for queued_message in queued_messages: assert '2020-08-26 to 2020-10-02' in queued_message.message # Message sent. QueuedEmailsJob(simply_yield).run() emails_sent = SentEmail.get_emails_sent_to(uid=admin_uid) assert len(emails_sent) == 1 assert emails_sent[ 0].template_type == 'admin_alert_multiple_meeting_patterns' assert emails_sent[0].section_id == section_id
def test_approval_by_instructors(self, app, client, fake_auth): """Instructor can submit approval if s/he is teaching the requested course.""" with test_approvals_workflow(app): instructor_uids = get_instructor_uids(section_id=section_1_id, term_id=self.term_id) fake_auth.login(instructor_uids[0]) api_approve( client, publish_type='kaltura_my_media', recording_type='presentation_audio', section_id=section_1_id, ) std_commit(allow_test_environment=True) fake_auth.login(instructor_uids[1]) api_approve( client, publish_type='kaltura_media_gallery', recording_type='presentation_audio', section_id=section_1_id, ) std_commit(allow_test_environment=True) QueuedEmailsJob(app.app_context).run() # First instructor was notified 1) that second instructor needed to approve; 2) that second instructor made changes. emails_sent = SentEmail.get_emails_sent_to(instructor_uids[0]) assert len(emails_sent) == 2 for email in emails_sent: assert email.section_id == section_1_id assert email.term_id == self.term_id assert emails_sent[0].template_type == 'waiting_for_approval' assert emails_sent[1].template_type == 'notify_instructor_of_changes' # Second instructor received no notifications. assert len(SentEmail.get_emails_sent_to(instructor_uids[1])) == 0 fake_auth.login(admin_uid) api_json = api_get_course( client, term_id=self.term_id, section_id=section_1_id, ) assert api_json['meetings']['eligible'][0]['room']['location'] == 'Barrows 106' instructor_uids = [i['uid'] for i in api_json['instructors']] assert instructor_uids == instructor_uids approvals_ = api_json['approvals'] assert len(approvals_) == 2 assert approvals_[0]['approvedBy']['uid'] == instructor_uids[0] assert approvals_[0]['publishType'] == 'kaltura_my_media' assert approvals_[1]['approvedBy']['uid'] == instructor_uids[1] assert approvals_[1]['publishType'] == 'kaltura_media_gallery' assert approvals_[1]['recordingType'] == 'presentation_audio' assert approvals_[1]['recordingTypeName'] == 'Presentation and Audio' assert api_json['hasNecessaryApprovals'] is True assert api_json['scheduled'] is None
def test_alert_admin_of_instructor_change(self): """Emails admin when a scheduled course gets a new instructor.""" with test_approvals_workflow(app): with enabled_job(job_key=AdminEmailsJob.key()): term_id = app.config['CURRENT_TERM_ID'] section_id = 50005 room_id = Room.find_room('Barker 101').id # The course has two instructors. instructor_1_uid, instructor_2_uid = get_instructor_uids( section_id=section_id, term_id=term_id) approval = Approval.create( approved_by_uid=instructor_1_uid, approver_type_='instructor', course_display_name= f'term_id:{term_id} section_id:{section_id}', publish_type_='kaltura_my_media', recording_type_='presenter_audio', room_id=room_id, section_id=section_id, term_id=term_id, ) # Uh oh! Only one of them has been scheduled. meeting = get_eligible_meeting(section_id=section_id, term_id=term_id) Scheduled.create( course_display_name= f'term_id:{term_id} section_id:{section_id}', instructor_uids=[instructor_1_uid], kaltura_schedule_id=random.randint(1, 10), meeting_days=meeting['days'], meeting_end_date=get_recording_end_date(meeting), meeting_end_time=meeting['endTime'], meeting_start_date=get_recording_start_date( meeting, return_today_if_past_start=True), meeting_start_time=meeting['startTime'], publish_type_=approval.publish_type, recording_type_=approval.recording_type, room_id=room_id, section_id=section_id, term_id=term_id, ) admin_uid = app.config['EMAIL_DIABLO_ADMIN_UID'] email_count = _get_email_count(admin_uid) # Message queued but not sent. AdminEmailsJob(simply_yield).run() assert _get_email_count(admin_uid) == email_count queued_messages = QueuedEmail.query.filter_by( template_type='admin_alert_instructor_change').all() assert len(queued_messages) == 1 for snippet in [ 'LAW 23', 'Old instructor(s) Regan MacNeil', 'New instructor(s) Regan MacNeil, Burke Dennings' ]: assert snippet in queued_messages[0].message # Message sent. QueuedEmailsJob(simply_yield).run() assert _get_email_count(admin_uid) == email_count + 1
def test_room_change_no_longer_eligible(self, db_session): section_id = 50004 term_id = app.config['CURRENT_TERM_ID'] def _move_course(meeting_location): db.session.execute( text( 'UPDATE sis_sections SET meeting_location = :meeting_location WHERE term_id = :term_id AND section_id = :section_id' ), { 'meeting_location': meeting_location, 'section_id': section_id, 'term_id': term_id, }, ) with enabled_job(job_key=InstructorEmailsJob.key()): with test_approvals_workflow(app): course = SisSection.get_course(section_id=section_id, term_id=term_id) eligible_meetings = course.get('meetings', {}).get('eligible', []) assert len(eligible_meetings) == 1 original_room = eligible_meetings[0]['room'] assert original_room['location'] == 'Li Ka Shing 145' # Schedule _schedule(original_room['id'], section_id) _run_instructor_emails_job() _assert_email_count(0, section_id, 'room_change_no_longer_eligible') # Move course to some other eligible room. _move_course('Barker 101') _run_instructor_emails_job() _assert_email_count(0, section_id, 'room_change_no_longer_eligible') # Move course to an ineligible room. ineligible_room = 'Wheeler 150' _move_course(ineligible_room) _run_instructor_emails_job() _assert_email_count(1, section_id, 'room_change_no_longer_eligible') # Move course back to its original location _move_course(original_room['location']) # Finally, let's pretend the course is scheduled to a room that was previously eligible. Scheduled.delete(section_id=section_id, term_id=term_id) _schedule(Room.find_room(ineligible_room).id, section_id) _run_instructor_emails_job() # Expect email. _assert_email_count(2, section_id, 'room_change_no_longer_eligible') Scheduled.delete(section_id=section_id, term_id=term_id)
def test_admin_alert_date_change(self, db_session): with enabled_job(job_key=AdminEmailsJob.key()): admin_uid = app.config['EMAIL_DIABLO_ADMIN_UID'] term_id = app.config['CURRENT_TERM_ID'] section_id = 50004 meeting = get_eligible_meeting(section_id=section_id, term_id=term_id) with test_approvals_workflow(app): with override_config(app, 'CURRENT_TERM_RECORDINGS_BEGIN', meeting['startDate']): with override_config(app, 'CURRENT_TERM_RECORDINGS_END', meeting['endDate']): def _run_jobs(): AdminEmailsJob(simply_yield).run() QueuedEmailsJob(simply_yield).run() def _schedule(): mock_scheduled( meeting=meeting, override_end_time='16:59', override_start_time='08:00', section_id=section_id, term_id=term_id, ) course = SisSection.get_course( section_id=section_id, term_id=term_id) scheduled = course['scheduled'] assert are_scheduled_dates_obsolete( meeting=meeting, scheduled=scheduled) is False assert are_scheduled_times_obsolete( meeting=meeting, scheduled=scheduled) is True def _assert_alert_count(count): emails_sent = SentEmail.get_emails_sent_to( uid=admin_uid) assert len(emails_sent) == count assert emails_sent[0].section_id == section_id assert emails_sent[ 0].template_type == 'admin_alert_date_change' # First time scheduled. _schedule() _run_jobs() _assert_alert_count(1) # Unschedule and schedule a second time. Scheduled.delete(section_id=section_id, term_id=term_id) _schedule() _run_jobs() # Another alert is emailed to admin because it is a new schedule. _assert_alert_count(2) # Run jobs again and expect no alerts. _run_jobs() _assert_alert_count(2)
def test_approval_by_admin(self, client, admin_session): """Admins can schedule recordings on behalf of any eligible course.""" with test_approvals_workflow(app): api_json = api_approve( client, publish_type='kaltura_my_media', recording_type='presentation_audio', section_id=section_1_id, ) std_commit(allow_test_environment=True) assert api_json['hasNecessaryApprovals'] is True assert api_json['scheduled'] is None
def test_invite_new_instructors(self, app, db_session): """Invite all assigned instructors who haven't yet received an invitation.""" term_id = app.config['CURRENT_TERM_ID'] with test_approvals_workflow(app): # The job creates many new invitations. timestamp = utc_now() # Emails are queued but not sent. InvitationJob(simply_yield).run() assert len(_get_invitations_since(term_id, timestamp)) == 0 # Emails are sent. We have more emails than courses since some courses have multiple instructors. QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 14 # Each eligible course has an invitation. eligible_courses = [ c for c in SisSection.get_courses(term_id=term_id) if len(c['meetings']['eligible']) == 1 ] assert len(eligible_courses) == 11 for course in eligible_courses: for i in course['instructors']: sent_email = next((e for e in invitations if e.section_id == course['sectionId'] and i['uid'] == e.recipient_uid), None) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == i['uid'] assert email_json['sectionId'] == course['sectionId'] assert email_json['templateType'] == 'invitation' assert email_json['termId'] == term_id assert email_json['sentAt'] # Add an instructor. section = SisSection.query.filter_by(term_id=term_id, section_id=50002).first() db_session.expunge(section) make_transient(section) section.id = None section.instructor_uid = '10008' db_session.add(section) std_commit(allow_test_environment=True) # Re-run the job. An email is sent to the new instructor only. timestamp = utc_now() InvitationJob(simply_yield).run() QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 1 invitation = invitations[0].to_api_json() assert invitation['sectionId'] == 50002 assert invitation['recipientUid'] == '10008'
def test_email_alert_when_canceled_course(self, db_session): term_id = app.config['CURRENT_TERM_ID'] with enabled_job(job_key=InstructorEmailsJob.key()): with test_approvals_workflow(app): course = SisSection.get_course(section_id=deleted_section_id, term_id=term_id, include_deleted=True) room = course.get('meetings', {}).get('eligible', [])[0]['room'] _schedule(room['id'], deleted_section_id) _run_instructor_emails_job() _assert_email_count(1, deleted_section_id, 'room_change_no_longer_eligible')
def test_instructor_already_approved(self, client, fake_auth): """Instructor can submit his/her approval only once.""" with test_approvals_workflow(app): instructor_uids = get_instructor_uids(section_id=section_1_id, term_id=self.term_id) fake_auth.login(instructor_uids[0]) for expected_status_code in [200, 403]: api_approve( client, publish_type='kaltura_my_media', recording_type='presentation_audio', section_id=section_1_id, expected_status_code=expected_status_code, )
def test_do_not_email_filter(self, client, db, admin_session): """Do Not Email filter: Courses in eligible room; "opt out" is true; all stages of approval; not scheduled.""" with test_approvals_workflow(app): # Send invites them opt_out. for section_id in (section_1_id, section_in_ineligible_room, section_3_id, section_4_id): CoursePreference.update_opt_out(section_id=section_id, term_id=self.term_id, opt_out=True) in_enabled_room = _is_course_in_enabled_room(section_id=section_id, term_id=self.term_id) if section_id == section_in_ineligible_room: # Courses in ineligible rooms will be excluded from the feed. assert not in_enabled_room else: assert in_enabled_room SentEmail.create( section_id=section_id, recipient_uids=_get_instructor_uids(section_id=section_id, term_id=self.term_id), template_type='invitation', term_id=self.term_id, ) # If course has approvals but not scheduled then it will show up in the feed. Approval.create( approved_by_uid=_get_instructor_uids(section_id=section_1_id, term_id=self.term_id)[0], approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=Room.get_room_id(section_id=section_1_id, term_id=self.term_id), section_id=section_1_id, term_id=self.term_id, ) # Feed will exclude scheduled. _schedule_recordings( section_id=section_3_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Do Not Email') for section_id in (section_1_id, section_4_id): # The 'Do Not Email' course is in the feed assert _find_course(api_json=api_json, section_id=section_id) for section_id in (section_3_id, section_in_ineligible_room): # Excluded courses assert not _find_course(api_json=api_json, section_id=section_id)
def test_alert_admin_of_room_change(self, db_session): """Emails admin when a scheduled course gets a room change.""" with test_approvals_workflow(app): with enabled_job(job_key=AdminEmailsJob.key()): term_id = app.config['CURRENT_TERM_ID'] section_id = 50004 approved_by_uid = '10004' the_old_room = 'Wheeler 150' scheduled_in_room = Room.find_room(the_old_room) approval = Approval.create( approved_by_uid=approved_by_uid, approver_type_='instructor', publish_type_='kaltura_media_gallery', recording_type_='presenter_audio', room_id=scheduled_in_room.id, section_id=section_id, term_id=term_id, ) meeting = get_eligible_meeting(section_id=section_id, term_id=term_id) Scheduled.create( instructor_uids=get_instructor_uids(term_id=term_id, section_id=section_id), kaltura_schedule_id=random.randint(1, 10), meeting_days=meeting['days'], meeting_end_date=get_recording_end_date(meeting), meeting_end_time=meeting['endTime'], meeting_start_date=get_recording_start_date( meeting, return_today_if_past_start=True), meeting_start_time=meeting['startTime'], publish_type_=approval.publish_type, recording_type_=approval.recording_type, room_id=scheduled_in_room.id, section_id=section_id, term_id=term_id, ) admin_uid = app.config['EMAIL_DIABLO_ADMIN_UID'] # Message queued, then sent. AdminEmailsJob(simply_yield).run() QueuedEmailsJob(simply_yield).run() emails_sent = SentEmail.get_emails_sent_to(uid=admin_uid) assert len(emails_sent) == 1 assert emails_sent[0].section_id == section_id assert emails_sent[ 0].template_type == 'admin_alert_room_change'
def test_all_filter(self, client, admin_session): """The 'all' filter returns all courses in eligible rooms.""" with test_approvals_workflow(app): # Put courses in a few different states. for section_id in [section_1_id, section_6_id]: self._send_invitation_email(section_id) self._create_approval(section_id) mock_scheduled( section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) # We gotta catch 'em all. api_json = self._api_courses(client, term_id=self.term_id, filter_='All') assert len(api_json) == 11 for section_id in [section_1_id, section_3_id, section_4_id, section_5_id, section_6_id]: assert _find_course(api_json=api_json, section_id=section_id) assert not _find_course(api_json=api_json, section_id=section_in_ineligible_room)
def test_has_necessary_approvals_when_cross_listed(self, client, fake_auth): """If section X and Y are cross-listed then hasNecessaryApprovals is false until the Y instructor approves.""" with test_approvals_workflow(app): def _approve(instructor_uid, expected_has_necessary): fake_auth.login(instructor_uid) api_json = api_approve( client, publish_type='kaltura_my_media', recording_type='presentation_audio', section_id=50012, ) std_commit(allow_test_environment=True) assert api_json['hasNecessaryApprovals'] is expected_has_necessary, f'instructor_uid: {instructor_uid}' _approve(instructor_uid='10009', expected_has_necessary=False) # Log out client.get('/api/auth/logout') _approve(instructor_uid='10010', expected_has_necessary=True)
def test_authorized(self, client, admin_session): """Only admins can toggle the do-not-email preference of any given course.""" with test_approvals_workflow(app): section_ids_opted_out = CoursePreference.get_section_ids_opted_out(term_id=self.term_id) previously_opted_out = section_1_id not in section_ids_opted_out opt_out = not previously_opted_out self._api_opt_out_update( client, term_id=self.term_id, section_id=section_1_id, opt_out=opt_out, ) std_commit(allow_test_environment=True) section_ids_opted_out = CoursePreference.get_section_ids_opted_out(term_id=self.term_id) if opt_out: assert section_1_id in section_ids_opted_out else: assert section_1_id not in section_ids_opted_out
def test_partially_approved_filter(self, client, db, admin_session): """Partially approved: Eligible, invited course with 1+ approvals, but not ALL instructors have approved.""" with test_approvals_workflow(app): # Assert multiple instructors section_1_instructor_uids = _get_instructor_uids(section_id=section_1_id, term_id=self.term_id) section_6_instructor_uids = _get_instructor_uids(section_id=section_6_id, term_id=self.term_id) assert len(section_1_instructor_uids) > 1 assert len(section_6_instructor_uids) > 1 # Send invites courses = [ {'section_id': section_1_id, 'instructor_uids': section_1_instructor_uids}, {'section_id': section_6_id, 'instructor_uids': section_6_instructor_uids}, ] for course in courses: SentEmail.create( section_id=course['section_id'], recipient_uids=course['instructor_uids'], template_type='invitation', term_id=self.term_id, ) Approval.create( approved_by_uid=course['instructor_uids'][0], approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=Room.get_room_id(section_id=course['section_id'], term_id=self.term_id), section_id=course['section_id'], term_id=self.term_id, ) # Feed will include both scheduled and not scheduled. _schedule_recordings( section_id=section_1_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Partially Approved') assert len(api_json) == 2 assert _find_course(api_json=api_json, section_id=section_1_id) course = _find_course(api_json=api_json, section_id=section_6_id) assert course assert course['label'] == 'LAW 23, LEC 002'
def test_course_opted_out(self, app): """Do not send email to courses that have opted out.""" term_id = app.config['CURRENT_TERM_ID'] with test_approvals_workflow(app): section_id = 50006 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) std_commit(allow_test_environment=True) timestamp = utc_now() # Emails are queued but not sent. InvitationJob(simply_yield).run() assert len(_get_invitations_since(term_id, timestamp)) == 0 # Emails are sent. QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 15 assert not next( (e for e in invitations if e.section_id == section_id), None)
def test_alert_admin_of_instructor_change(self): """Emails admin when a scheduled course gets a new instructor.""" with test_approvals_workflow(app): term_id = app.config['CURRENT_TERM_ID'] section_id = 22287 approved_by_uid = '8765432' room_id = Room.find_room('Barker 101').id approval = Approval.create( approved_by_uid=approved_by_uid, approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presenter_audio', room_id=room_id, section_id=section_id, term_id=term_id, ) meeting_days, meeting_start_time, meeting_end_time = SisSection.get_meeting_times( term_id=term_id, section_id=section_id, ) Scheduled.create( cross_listed_section_ids=approval.cross_listed_section_ids, instructor_uids=SisSection.get_instructor_uids( term_id=term_id, section_id=section_id), meeting_days=meeting_days, meeting_start_time=meeting_start_time, meeting_end_time=meeting_end_time, publish_type_=approval.publish_type, recording_type_=approval.recording_type, room_id=room_id, section_id=section_id, term_id=term_id, ) admin_uid = app.config['EMAIL_DIABLO_ADMIN_UID'] email_count = _get_email_count(admin_uid) std_commit(allow_test_environment=True) AdminEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) assert _get_email_count(admin_uid) > email_count