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_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_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 _schedule(): mock_scheduled( override_room_id=Room.find_room('Barker 101').id, section_id=section_id, term_id=term_id, ) course = SisSection.get_course(section_id=section_id, term_id=term_id) assert course['scheduled']['hasObsoleteRoom'] is True
def approve(): term_id = app.config['CURRENT_TERM_ID'] term_name = term_name_for_sis_id(term_id) params = request.get_json() publish_type = params.get('publishType') recording_type = params.get('recordingType') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id) if section_id else None if not course or publish_type not in get_all_publish_types() or recording_type not in get_all_recording_types(): raise BadRequestError('One or more required params are missing or invalid') if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]: raise ForbiddenRequestError('Sorry, request unauthorized') if Approval.get_approval(approved_by_uid=current_user.uid, section_id=section_id, term_id=term_id): raise ForbiddenRequestError(f'You have already approved recording of {course["courseName"]}, {term_name}') meetings = course.get('meetings', {}).get('eligible', []) if len(meetings) != 1: raise BadRequestError('Unique eligible meeting pattern not found for course') meeting = meetings[0] location = meeting and meeting.get('location') room = Room.find_room(location=location) if not room: raise BadRequestError(f'{location} is not eligible for Course Capture.') previous_approvals = Approval.get_approvals_per_section_ids(section_ids=[section_id], term_id=term_id) approval = Approval.create( approved_by_uid=current_user.uid, approver_type_='admin' if current_user.is_admin else 'instructor', course_display_name=course['label'], publish_type_=publish_type, recording_type_=recording_type, room_id=room.id, section_id=section_id, term_id=term_id, ) if previous_approvals: # Compare the current approval with preferences submitted in previous approval previous_approval = previous_approvals[-1] if (approval.publish_type, approval.recording_type) != (previous_approval.publish_type, previous_approval.recording_type): notify_instructors_of_changes(course, approval, previous_approvals) all_approvals = previous_approvals + [approval] if len(course['instructors']) > len(all_approvals): approval_uids = [a.approved_by_uid for a in all_approvals] pending_instructors = [i for i in course['instructors'] if i['uid'] not in approval_uids] last_approver = next((i for i in course['instructors'] if i['uid'] == approval.approved_by_uid), None) if last_approver: notify_instructor_waiting_for_approval(course, last_approver, pending_instructors) return tolerant_jsonify(_after_approval(course=SisSection.get_course(term_id, section_id)))
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_recording_type_options(self, client, admin_session): """Available recording types determined by values of capability and is_auditorium.""" expected = { 'Barker 101': ['presenter_presentation_audio'], 'Barrows 106': ['presentation_audio'], 'Li Ka Shing 145': ALL_RECORDING_TYPES.keys(), } for location, expected_types in expected.items(): room = Room.find_room(location=location) api_json = self._api_room(client, room.id) actual_types = api_json['recordingTypeOptions'].keys() assert list(actual_types) == list(expected_types)
def test_screencast_and_video_auditorium(self, client, admin_session): """All recording types are available for Auditorium with 'screencast_and_video' capability.""" location = 'Li Ka Shing 145' room = Room.find_room(location=location) assert room api_json = self._api_room(client, room.id) assert api_json['id'] == room.id assert api_json['capabilityName'] == 'Screencast + Video' assert api_json['isAuditorium'] is True assert api_json['kalturaResourceId'] == 678 assert api_json['location'] == location assert api_json['recordingTypeOptions'] == ALL_RECORDING_TYPES # Feed includes courses but room-per-course would be redundant assert len(api_json['courses']) > 0 assert 'room' not in api_json['courses'][0]
def test_recording_type_options(self, client, admin_session): """Available recording types determined by values of capability and is_auditorium.""" expected = { 'Barker 101': ['presenter_presentation_audio'], "O'Brien 212": ['presentation_audio'], 'Li Ka Shing 145': [ 'presenter_presentation_audio_with_operator', 'presenter_presentation_audio', ], } for location, expected_options in expected.items(): room = Room.find_room(location=location) api_json = self._api_room(client, room.id) assert list( api_json['recordingTypeOptions'].keys()) == expected_options
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_has_obsolete_room(self, client, admin_session): """Admins can see room changes that might disrupt scheduled recordings.""" course = SisSection.get_course(term_id=self.term_id, section_id=section_2_id) actual_room_id = course['room']['id'] obsolete_room = Room.find_room('Barker 101') assert obsolete_room assert actual_room_id != obsolete_room.id _schedule_recordings( section_id=section_2_id, term_id=self.term_id, room_id=obsolete_room.id, ) api_json = self._api_course_changes(client, term_id=self.term_id) course = _find_course(api_json=api_json, section_id=section_2_id) assert course assert course['scheduled']['hasObsoleteRoom'] is True assert course['scheduled']['hasObsoleteMeetingTimes'] is False assert course['scheduled']['hasObsoleteInstructors'] is False
def refresh_rooms(): locations = SisSection.get_distinct_meeting_locations() existing_locations = Room.get_all_locations() new_locations = [ location for location in locations if location not in existing_locations ] if new_locations: app.logger.info(f'Creating {len(new_locations)} new rooms') for location in new_locations: Room.create(location=location) kaltura_resource_ids_per_room = {} for resource in Kaltura().get_schedule_resources(): room = Room.find_room(location=resource['name']) if room: kaltura_resource_ids_per_room[room.id] = resource['id'] if kaltura_resource_ids_per_room: Room.update_kaltura_resource_mappings(kaltura_resource_ids_per_room)
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
def approve(): term_id = app.config['CURRENT_TERM_ID'] term_name = term_name_for_sis_id(term_id) params = request.get_json() publish_type = params.get('publishType') recording_type = params.get('recordingType') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id) if section_id else None if not course or publish_type not in get_all_publish_types() or recording_type not in get_all_recording_types(): raise BadRequestError('One or more required params are missing or invalid') if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]: raise ForbiddenRequestError('Sorry, request unauthorized') if Approval.get_approval(approved_by_uid=current_user.uid, section_id=section_id, term_id=term_id): raise ForbiddenRequestError(f'You have already approved recording of {course["courseName"]}, {term_name}') location = course['meetingLocation'] room = Room.find_room(location=location) if not room: raise BadRequestError(f'{location} is not eligible for Course Capture.') previous_approvals = Approval.get_approvals_per_section_ids(section_ids=[section_id], term_id=term_id) approval = Approval.create( approved_by_uid=current_user.uid, approver_type_='admin' if current_user.is_admin else 'instructor', cross_listed_section_ids=[c['sectionId'] for c in course['crossListings']], publish_type_=publish_type, recording_type_=recording_type, room_id=room.id, section_id=section_id, term_id=term_id, ) _notify_instructors_of_approval( approval=approval, course=course, previous_approvals=previous_approvals, ) return tolerant_jsonify(SisSection.get_course(term_id, section_id))
def test_alert_admin_of_room_change(self): """Emails admin when a scheduled course gets a room change.""" with test_approvals_workflow(app): term_id = app.config['CURRENT_TERM_ID'] section_id = 26094 approved_by_uid = '6789' 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', cross_listed_section_ids=[], publish_type_='kaltura_media_gallery', recording_type_='presenter_audio', room_id=scheduled_in_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=scheduled_in_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) AdminEmailsJob(app.app_context).run() assert _get_email_count(admin_uid) == email_count + 1
def _get_api_json(cls, uid=None): calnet_profile = None email_address = None is_active = False is_admin = False courses = [] if uid: calnet_profile = calnet.get_calnet_user_for_uid(app, uid) is_active = not calnet_profile.get('isExpiredPerLdap', True) if is_active: email_address = calnet_profile.get( 'campusEmail') or calnet_profile.get('email') is_admin = AdminUser.is_admin(uid) courses = SisSection.get_courses_per_instructor_uid( term_id=app.config['CURRENT_TERM_ID'], instructor_uid=uid, ) for course in courses: if course['meetingLocation']: room = Room.find_room(course['meetingLocation']) course['room'] = room and room.to_api_json() is_active = is_admin or bool(courses) return { **(calnet_profile or {}), **{ 'id': uid, 'emailAddress': email_address, 'isActive': is_active, 'isAdmin': is_admin, 'isAnonymous': not is_active, 'isAuthenticated': is_active, 'isTeaching': bool(courses), 'courses': courses, 'uid': uid, }, }
def test_cross_listed_course(self): """Recordings will be scheduled if and only if all instructor(s) approve.""" with test_approvals_workflow(app): section_id = 50012 term_id = app.config['CURRENT_TERM_ID'] email_template_type = 'recordings_scheduled' def _get_emails_sent(): return SentEmail.get_emails_of_type( section_ids=[section_id], template_type=email_template_type, term_id=term_id, ) emails_sent = _get_emails_sent() KalturaJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect no emails sent during action above assert _get_emails_sent() == emails_sent course = SisSection.get_course(section_id=section_id, term_id=term_id) instructors = course['instructors'] cross_listings = course['crossListings'] room_id = Room.find_room( course['meetings']['eligible'][0]['location']).id assert len(instructors) == 2 assert room_id == Room.find_room("O'Brien 212").id assert len(cross_listings) == 1 assert len(course['canvasCourseSites']) == 2 # This course requires two (2) approvals. approvals = [ Approval.create( approved_by_uid=instructors[0]['uid'], approver_type_='instructor', course_display_name=course['label'], publish_type_='kaltura_my_media', recording_type_='presentation_audio', room_id=room_id, section_id=section_id, term_id=term_id, ), ] """If we have insufficient approvals then do nothing.""" email_count = _get_emails_sent() KalturaJob(simply_yield).run() std_commit(allow_test_environment=True) assert _get_emails_sent() == email_count # The second approval final_approval = Approval.create( approved_by_uid=instructors[1]['uid'], approver_type_='instructor', course_display_name=course['label'], publish_type_='kaltura_media_gallery', recording_type_='presenter_presentation_audio', room_id=room_id, section_id=section_id, term_id=term_id, ) approvals.append(final_approval) """If a course is scheduled for recording then email is sent to its instructor(s).""" email_count = len(_get_emails_sent()) KalturaJob(simply_yield).run() std_commit(allow_test_environment=True) # Verify publish and recording types scheduled = Scheduled.get_scheduled(term_id=term_id, section_id=section_id) assert scheduled.publish_type == final_approval.publish_type assert scheduled.recording_type == final_approval.recording_type assert scheduled.section_id == section_id assert scheduled.term_id == term_id # Verify emails sent QueuedEmailsJob(app.app_context).run() emails_sent = _get_emails_sent() assert len(emails_sent) == email_count + 2 assert [ emails_sent[-1].recipient_uid, emails_sent[-2].recipient_uid ] == ['10009', '10010'] email_sent = emails_sent[-1] assert email_sent.section_id == section_id assert email_sent.template_type == 'recordings_scheduled' assert email_sent.term_id == term_id """If recordings were already scheduled then do nothing, send no email.""" email_count = len(_get_emails_sent()) KalturaJob(simply_yield).run() assert len(_get_emails_sent()) == email_count
def test_scheduling_of_recordings(self): """If a course is scheduled for recording then email is sent to its instructor(s).""" with test_approvals_workflow(app): section_id = 22287 term_id = app.config['CURRENT_TERM_ID'] email_template_type = 'recordings_scheduled' def _get_emails_sent(): return SentEmail.get_emails_of_type( section_id=section_id, template_type=email_template_type, term_id=term_id, ) emails_sent = _get_emails_sent() KalturaJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect no emails sent during action above assert _get_emails_sent() == emails_sent course = SisSection.get_course(section_id=section_id, term_id=term_id) instructors = course['instructors'] assert len(instructors) == 2 room_id = Room.find_room(course['meetingLocation']).id # This course requires two (2) approvals. approvals = [ Approval.create( approved_by_uid=instructors[0]['uid'], approver_type_='instructor', 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, ), ] """If we have insufficient approvals then do nothing.""" email_count = _get_emails_sent() KalturaJob(app.app_context).run() std_commit(allow_test_environment=True) assert _get_emails_sent() == email_count # The second approval final_approval = Approval.create( approved_by_uid=instructors[1]['uid'], approver_type_='instructor', cross_listed_section_ids=[], publish_type_='kaltura_media_gallery', recording_type_='presenter_presentation_audio', room_id=room_id, section_id=section_id, term_id=term_id, ) approvals.append(final_approval) """If a course is scheduled for recording then email is sent to its instructor(s).""" email_count = len(_get_emails_sent()) KalturaJob(app.app_context).run() std_commit(allow_test_environment=True) # Verify publish and recording types scheduled = Scheduled.get_scheduled(term_id=term_id, section_id=section_id) assert scheduled.publish_type == final_approval.publish_type assert scheduled.recording_type == final_approval.recording_type assert scheduled.section_id == section_id assert scheduled.term_id == term_id # Verify emails sent emails_sent = _get_emails_sent() assert len(emails_sent) == email_count + 1 email_sent = emails_sent[-1].to_api_json() assert set(email_sent['recipientUids']) == {'98765', '87654'} assert email_sent['sectionId'] == section_id assert email_sent['templateType'] == 'recordings_scheduled' assert email_sent['termId'] == term_id assert email_sent['sentAt'] """If recordings were already scheduled then do nothing, send no email.""" email_count = len(_get_emails_sent()) KalturaJob(app.app_context).run() assert len(_get_emails_sent()) == email_count