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 upsert(cls, rows): now = utc_now().strftime('%Y-%m-%dT%H:%M:%S+00') count_per_chunk = 10000 for chunk in range(0, len(rows), count_per_chunk): rows_subset = rows[chunk:chunk + count_per_chunk] query = """ INSERT INTO instructors ( created_at, dept_code, email, first_name, last_name, uid, updated_at ) SELECT created_at, dept_code, email, first_name, last_name, uid, updated_at FROM json_populate_recordset(null::instructors, :json_dumps) ON CONFLICT(uid) DO UPDATE SET dept_code = EXCLUDED.dept_code, email = EXCLUDED.email, first_name = EXCLUDED.first_name, last_name = EXCLUDED.last_name; """ data = [{ 'created_at': now, 'dept_code': row['dept_code'], 'email': row['email'], 'first_name': row['first_name'], 'last_name': row['last_name'], 'uid': row['uid'], 'updated_at': now, } for row in rows_subset] db.session.execute(query, {'json_dumps': json.dumps(data)})
def test_queued_email_for_admin(self): """Certain email template types are for admin recipients only.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 22287 email_template_type = 'admin_alert_room_change' QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect email to admin email address emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) + 1 sent_email = next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) assert sent_email json_ = sent_email.to_api_json() assert json_['recipientUids'] == [app.config['EMAIL_DIABLO_ADMIN_UID']] assert json_['sectionId'] == section_id assert json_['templateType'] == email_template_type assert json_['termId'] == term_id assert json_['sentAt']
def test_course_has_opted_out(self): """Do not send email to courses that have opted out.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 28602 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) email_template_type = 'invitation' QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect no emails sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) assert not next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None)
def test_course_has_opted_out(self): """Do not send email to courses that have opted out.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 50000 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) email_template_type = 'invitation' recipient = { 'name': 'William Peter Blatty', 'uid': '10001', } QueuedEmail.create(section_id, email_template_type, term_id, recipient=recipient) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect no emails sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) assert not next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None)
def test_send_invitation_emails(self): """Send all email in 'queued_emails' table.""" def _emails_sent(section_id): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] courses = [ { 'sectionId': 28602, 'instructorUids': ['234567', '8765432'], }, { 'sectionId': 28165, 'instructorUids': ['8765432'], }, ] email_template_type = 'invitation' for course in courses: QueuedEmail.create(course['sectionId'], email_template_type, term_id) std_commit(allow_test_environment=True) def _get_emails_to_courses(): emails_sent_ = [] for course_ in courses: emails_sent_.extend(_emails_sent(course_['sectionId'])) return emails_sent_ before = utc_now() emails_sent_before = _get_emails_to_courses() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect one email per instructor emails_sent_after = _get_emails_to_courses() assert len(emails_sent_after) == len(emails_sent_before) + 2 def _find_email(section_id): return next((e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) for course in courses: sent_email = _find_email(section_id=course['sectionId']) assert sent_email json_ = sent_email.to_api_json() assert set(json_['recipientUids']) == set(course['instructorUids']) assert json_['sectionId'] == course['sectionId'] assert json_['templateType'] == email_template_type assert json_['termId'] == term_id assert json_['sentAt']
def test_send_invitation_emails(self): """Send all email in 'queued_emails' table.""" term_id = app.config['CURRENT_TERM_ID'] courses = SisSection.get_courses(section_ids=[50000, 50001], term_id=term_id) email_template_type = 'invitation' for course in courses: for instructor in course['instructors']: QueuedEmail.create(course['sectionId'], email_template_type, term_id, recipient=instructor) std_commit(allow_test_environment=True) def _get_emails_to_courses(): emails_sent = [] for c in courses: emails_sent.extend( _get_emails_sent( email_template_type=email_template_type, section_id=c['sectionId'], term_id=term_id, ), ) return emails_sent before = utc_now() emails_sent_before = _get_emails_to_courses() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect one email per instructor emails_sent_after = _get_emails_to_courses() assert len(emails_sent_after) == len(emails_sent_before) + 3 def _find_email(section_id, uid): return next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before and uid == e.recipient_uid), None) for course in courses: for instructor in course['instructors']: sent_email = _find_email(section_id=course['sectionId'], uid=instructor['uid']) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == instructor['uid'] assert email_json['sectionId'] == course['sectionId'] assert email_json['templateType'] == email_template_type assert email_json['termId'] == term_id assert email_json['sentAt']
def _run(self): kaltura = Kaltura() for blackout in Blackout.all_blackouts(): if blackout.end_date < utc_now(): app.logger.info(f'Removing past blackout: {blackout}') Blackout.delete_blackout(blackout.id) else: events = kaltura.get_events_in_date_range(end_date=blackout.end_date, start_date=blackout.start_date) for event in events: created_by_diablo = CREATED_BY_DIABLO_TAG in event['tags'] if created_by_diablo and not represents_recording_series(event): kaltura.delete(event['id']) app.logger.info(f"'Event {event['summary']} deleted per {blackout}.")
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_queued_email_for_admin(self): """Certain email template types are for admin recipients only.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 50005 email_template_type = 'admin_alert_room_change' recipient = { 'name': 'Course Capture Admin', 'uid': app.config['EMAIL_DIABLO_ADMIN_UID'], } QueuedEmail.create(section_id, email_template_type, term_id, recipient=recipient) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect email to admin email address emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) + 1 sent_email = next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == app.config[ 'EMAIL_DIABLO_ADMIN_UID'] assert email_json['sectionId'] == section_id assert email_json['templateType'] == email_template_type assert email_json['termId'] == term_id assert email_json['sentAt']
def refresh(cls, sis_sections, term_id): db.session.execute( cls.__table__.delete().where(cls.term_id == term_id)) now = utc_now().strftime('%Y-%m-%dT%H:%M:%S+00') count_per_chunk = 10000 for chunk in range(0, len(sis_sections), count_per_chunk): rows_subset = sis_sections[chunk:chunk + count_per_chunk] query = """ INSERT INTO sis_sections ( allowed_units, course_name, course_title, created_at, instruction_format, instructor_name, instructor_role_code, instructor_uid, is_primary, meeting_days, meeting_end_date, meeting_end_time, meeting_location, meeting_start_date, meeting_start_time, section_id, section_num, term_id ) SELECT allowed_units, course_name, course_title, created_at, instruction_format, instructor_name, instructor_role_code, instructor_uid, is_primary, meeting_days, meeting_end_date, meeting_end_time, meeting_location, meeting_start_date, meeting_start_time, section_id, section_num, term_id FROM json_populate_recordset(null::sis_sections, :json_dumps) """ data = [{ 'allowed_units': row['allowed_units'], 'course_name': row['course_name'], 'course_title': row['course_title'], 'created_at': now, 'instruction_format': row['instruction_format'], 'instructor_name': row['instructor_name'], 'instructor_role_code': row['instructor_role_code'], 'instructor_uid': row['instructor_uid'], 'is_primary': row['is_primary'], 'meeting_days': row['meeting_days'], 'meeting_end_date': row['meeting_end_date'], 'meeting_end_time': row['meeting_end_time'], 'meeting_location': row['meeting_location'], 'meeting_start_date': row['meeting_start_date'], 'meeting_start_time': row['meeting_start_time'], 'section_id': int(row['section_id']), 'section_num': row['section_num'], 'term_id': int(row['term_id']), } for row in rows_subset] db.session.execute(query, {'json_dumps': json.dumps(data)})
def _save_courses(sis_sections): now = utc_now().strftime('%Y-%m-%dT%H:%M:%S+00') query = """ INSERT INTO sis_sections ( allowed_units, course_name, course_title, created_at, deleted_at, instruction_format, instructor_name, instructor_role_code, instructor_uid, is_primary, meeting_days, meeting_end_date, meeting_end_time, meeting_location, meeting_start_date, meeting_start_time, section_id, section_num, term_id ) SELECT allowed_units, course_name, course_title, created_at, deleted_at, instruction_format, instructor_name, instructor_role_code, instructor_uid, is_primary::BOOLEAN, meeting_days, meeting_end_date::TIMESTAMP, meeting_end_time, meeting_location, meeting_start_date::TIMESTAMP, meeting_start_time, section_id::INTEGER, section_num, term_id::INTEGER FROM json_populate_recordset(null::sis_sections, :json_dumps) """ data = [{ 'allowed_units': row['allowed_units'], 'course_name': row['course_name'], 'course_title': row['course_title'], 'created_at': now, 'deleted_at': now if row.get('is_deleted') else None, 'instruction_format': row['instruction_format'], 'instructor_name': row['instructor_name'], 'instructor_role_code': row['instructor_role_code'], 'instructor_uid': row['instructor_uid'], 'is_primary': row['is_primary'], 'meeting_days': row['meeting_days'], 'meeting_end_date': row['meeting_end_date'], 'meeting_end_time': row['meeting_end_time'], 'meeting_location': row['meeting_location'], 'meeting_start_date': row['meeting_start_date'], 'meeting_start_time': row['meeting_start_time'], 'section_id': int(row['section_id']), 'section_num': row['section_num'], 'term_id': int(row['term_id']), } for row in sis_sections] db.session.execute(query, {'json_dumps': json.dumps(data)})
def delete(cls, uid): now = utc_now() user = cls.query.filter_by(uid=uid).first() user.deleted_at = now std_commit() return user
from diablo.models.email_template import EmailTemplate from diablo.models.job import Job from diablo.models.room import Room from diablo.models.sis_section import SisSection from flask import current_app as app from sqlalchemy.sql import text from tests.util import simply_yield _test_users = [ { 'uid': '90001', 'deleted_at': None, }, { 'uid': '90002', 'deleted_at': utc_now(), }, ] def clear(): with open(app.config['BASE_DIR'] + '/scripts/db/drop_schema.sql', 'r') as ddlfile: db.session().execute(text(ddlfile.read())) std_commit() def load(create_test_data=True): cache.clear() _load_schemas() if create_test_data: