def _send_invitation_email(self, section_id): SentEmail.create( section_id=section_id, recipient_uid=get_instructor_uids(section_id=section_id, term_id=self.term_id)[0], template_type='invitation', term_id=self.term_id, )
def _create_emails_sent(): term_id = app.config['CURRENT_TERM_ID'] SentEmail.create( recipient_uids=['8765432'], section_id='28165', template_type='invitation', term_id=term_id, ) std_commit(allow_test_environment=True)
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_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_scheduled_filter(self, client, db, admin_session): """Scheduled filter: Courses with recordings scheduled.""" with test_approvals_workflow(app): 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) # 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 only include courses that were 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_='Scheduled') assert len(api_json) == 1 assert _find_course(api_json=api_json, section_id=section_1_id) assert not _find_course(api_json=api_json, section_id=section_6_id)
def test_not_invited_filter(self, client, db, 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 section_1_instructor_uids = _get_instructor_uids(section_id=section_1_id, term_id=self.term_id) SentEmail.create( section_id=section_1_id, recipient_uids=section_1_instructor_uids, template_type='invitation', term_id=self.term_id, ) # The second course did not receive an invitation BUT it does have approval. invite = SentEmail.get_emails_of_type( section_id=section_4_id, template_type='invitation', term_id=self.term_id, ) assert not invite Approval.create( approved_by_uid=_get_instructor_uids(section_id=section_4_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_4_id, term_id=self.term_id), section_id=section_4_id, term_id=self.term_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) # 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 assert course['label'] == 'BIO 1B, LEC 001'
def test_invited_filter(self, client, db, admin_session): """Invited filter: Course in an eligible room, have received invitation. No approvals. Not scheduled.""" with test_approvals_workflow(app): # First, send invitations SentEmail.create( section_id=section_4_id, recipient_uids=_get_instructor_uids(section_id=section_4_id, term_id=self.term_id), template_type='invitation', term_id=self.term_id, ) section_5_instructor_uids = _get_instructor_uids(section_id=section_5_id, term_id=self.term_id) SentEmail.create( section_id=section_5_id, recipient_uids=section_5_instructor_uids, template_type='invitation', term_id=self.term_id, ) # The section with approval will NOT show up in search results Approval.create( approved_by_uid=section_5_instructor_uids[0], approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=SisSection.get_course(term_id=self.term_id, section_id=section_5_id)['room']['id'], section_id=section_5_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Invited') # 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' # 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 send( self, message, recipient, subject_line, term_id=None, section_id=None, template_type=None, ): if not message or not subject_line or not recipient: app.logger.error( 'Attempted to send a message without required fields: ' f'(recipient={recipient}, subject_line={subject_line}, message={message}' ) return False eb_env = get_eb_environment() prefix = '' if 'prod' in (eb_env or '') else f"[{eb_env or 'diablo-local'}] " subject_line = f'{prefix}{scrub_email_content(subject_line)}' message = scrub_email_content(message) @skip_when_pytest() def _send(): smtp = SMTP(self.bcop_smtp_server, port=self.bcop_smtp_port) # TLS encryption smtp.starttls() smtp.set_debuglevel(app.logger.level == logging.DEBUG) smtp.login(self.bcop_smtp_username, self.bcop_smtp_password) emails_sent_to = set() if app.config['DIABLO_ENV'] == 'test': write_email_to_log(message=message, recipient=recipient, subject_line=subject_line) else: from_address = f"{app.config['EMAIL_COURSE_CAPTURE_SUPPORT_LABEL']} <{app.config['EMAIL_COURSE_CAPTURE_SUPPORT']}>" for email_address in self.get_email_addresses(user=recipient): msg = MIMEMultipart('alternative') msg['From'] = from_address msg['To'] = email_address if app.config['EMAIL_TEST_MODE']: # Append intended recipient email address to verify when testing. intended_email = recipient['email'] msg['Subject'] = f'{subject_line} (To: {intended_email})' else: msg['Subject'] = subject_line # TODO: 'plain' text version of email? msg.attach(MIMEText(message, 'plain')) msg.attach(MIMEText(message, 'html')) # Send smtp.sendmail(from_addr=from_address, to_addrs=email_address, msg=msg.as_string()) emails_sent_to.add(email_address) app.logger.info( f'\'{template_type}\' email sent to {", ".join(list(emails_sent_to))}' ) # Disconnect smtp.quit() # Send emails _send() SentEmail.create( recipient_uid=recipient['uid'], section_id=section_id, template_type=template_type, term_id=term_id or app.config['CURRENT_TERM_ID'], ) return True
def send( self, message, recipients, subject_line, term_id=None, section_id=None, template_type=None, ): @skip_when_pytest() def _send(): # Connect to SMTP server smtp = SMTP(self.bcop_smtp_server, port=self.bcop_smtp_port) # TLS encryption smtp.starttls() smtp.set_debuglevel(app.logger.level == logging.DEBUG) smtp.login(self.bcop_smtp_username, self.bcop_smtp_password) emails_sent_to = set() for recipient in recipients: mock_message = _get_mock_message( recipient['name'], recipient['email'], subject_line, message, ) if app.config['DIABLO_ENV'] == 'test': app.logger.info(mock_message) else: from_address = app.config['EMAIL_DIABLO_SUPPORT'] to_address = self.get_email_address( user=recipient, subject_line=subject_line) msg = MIMEMultipart('alternative') msg['From'] = from_address msg['To'] = to_address msg['Bcc'] = app.config['EMAIL_DIABLO_ADMIN'] if app.config['EMAIL_TEST_MODE']: # Append intended recipient email address to verify when testing. intended_email = recipient['email'] msg['Subject'] = f'{subject_line} (To: {intended_email})' else: msg['Subject'] = subject_line # TODO: 'plain' text version of email? msg.attach(MIMEText(message, 'plain')) msg.attach(MIMEText(message, 'html')) # Send smtp.sendmail(from_addr=from_address, to_addrs=to_address, msg=msg.as_string()) emails_sent_to.add(to_address) app.logger.info( f'{len(recipients)} \'{template_type}\' emails sent to {", ".join(list(emails_sent_to))}' ) # Disconnect smtp.quit() # Send emails _send() SentEmail.create( recipient_uids=[recipient['uid'] for recipient in recipients], section_id=section_id, template_type=template_type, term_id=term_id or app.config['CURRENT_TERM_ID'], )