def test_toggle_sms_provider_switches_provider_stores_notify_user_id_in_history( mocker, sample_user, setup_sms_providers_with_history): [inactive_provider, old_provider, alternative_provider] = setup_sms_providers_with_history mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) mocker.patch('app.dao.provider_details_dao.get_alternative_sms_provider', return_value=alternative_provider) dao_toggle_sms_provider(old_provider.identifier) new_provider = get_current_provider('sms') old_provider_from_history = ProviderDetailsHistory.query.filter_by( identifier=old_provider.identifier, version=old_provider.version).order_by( asc(ProviderDetailsHistory.priority)).first() new_provider_from_history = ProviderDetailsHistory.query.filter_by( identifier=new_provider.identifier, version=new_provider.version).order_by( asc(ProviderDetailsHistory.priority)).first() assert old_provider.version == old_provider_from_history.version assert new_provider.version == new_provider_from_history.version assert new_provider_from_history.created_by_id == sample_user.id assert old_provider_from_history.created_by_id == sample_user.id
def test_toggle_sms_provider_switches_provider_stores_notify_user_id_in_history( restore_provider_details, sample_user, mocker ): mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) old_provider = get_current_provider('sms') dao_toggle_sms_provider(old_provider.identifier) new_provider = get_current_provider('sms') old_provider_from_history = ProviderDetailsHistory.query.filter_by( identifier=old_provider.identifier, version=old_provider.version ).order_by( asc(ProviderDetailsHistory.priority) ).first() new_provider_from_history = ProviderDetailsHistory.query.filter_by( identifier=new_provider.identifier, version=new_provider.version ).order_by( asc(ProviderDetailsHistory.priority) ).first() assert old_provider.version == old_provider_from_history.version assert new_provider.version == new_provider_from_history.version assert new_provider_from_history.created_by_id == sample_user.id assert old_provider_from_history.created_by_id == sample_user.id
def switch_current_sms_provider_on_slow_delivery(): """ Switch providers if at least 30% of notifications took more than four minutes to be delivered in the last ten minutes. Search from the time we last switched to the current provider. """ if not current_app.config['SWITCH_SLOW_SMS_PROVIDER_ENABLED']: current_app.logger.info("Feature SWITCH_SLOW_SMS_PROVIDER is Diabled.") return current_provider = get_current_provider('sms') # TODO: If no provider changes the below lines throws error: # TypeError: '>' not supported between instances of 'NoneType' and 'datetime.datetime' if current_provider.updated_at > datetime.utcnow() - timedelta(minutes=10): current_app.logger.info( "Slow delivery notifications provider switched less than 10 minutes ago." ) return slow_delivery_notifications = is_delivery_slow_for_provider( provider=current_provider.identifier, threshold=0.3, created_at=datetime.utcnow() - timedelta(minutes=10), delivery_time=timedelta(minutes=4), ) if slow_delivery_notifications: current_app.logger.warning( 'Slow delivery notifications detected for provider {}'.format( current_provider.identifier)) dao_toggle_sms_provider(current_provider.identifier)
def switch_current_sms_provider_on_slow_delivery(): """ Switch providers if there are at least two slow delivery notifications (more than four minutes) in the last ten minutes. Search from the time we last switched to the current provider. """ functional_test_provider_service_id = current_app.config.get( 'FUNCTIONAL_TEST_PROVIDER_SERVICE_ID') functional_test_provider_template_id = current_app.config.get( 'FUNCTIONAL_TEST_PROVIDER_SMS_TEMPLATE_ID') if functional_test_provider_service_id and functional_test_provider_template_id: current_provider = get_current_provider('sms') sent_at = datetime.utcnow() - timedelta(minutes=10) if current_provider.updated_at is not None: sent_at = max(sent_at, current_provider.updated_at) slow_delivery_notifications = is_delivery_slow_for_provider( provider=current_provider.identifier, threshold=2, sent_at=sent_at, delivery_time=timedelta(minutes=4), service_id=functional_test_provider_service_id, template_id=functional_test_provider_template_id) if slow_delivery_notifications: current_app.logger.warning( 'Slow delivery notifications detected for provider {}'.format( current_provider.identifier)) dao_toggle_sms_provider(current_provider.identifier)
def test_toggle_sms_provider_switches_provider(mocker, restore_provider_details, current_sms_provider, sample_user): mocker.patch("app.provider_details.switch_providers.get_user_by_id", return_value=sample_user) dao_toggle_sms_provider(current_sms_provider.identifier) new_provider = get_current_provider("sms") old_starting_provider = get_provider_details_by_identifier(current_sms_provider.identifier) assert new_provider.identifier != old_starting_provider.identifier assert new_provider.priority < old_starting_provider.priority
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == "created": provider = provider_to_use( SMS_TYPE, notification.id, notification.international, notification.reply_to_text, ) template_dict = dao_get_template_by_id( notification.template_id, notification.template_version).__dict__ template = SMSMessageTemplate( template_dict, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = send_sms_response(provider.get_name(), notification.to) update_notification_to_sending(notification, provider) else: try: reference = provider.send_sms( to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text, ) except Exception as e: notification.billable_units = template.fragment_count dao_update_notification(notification) dao_toggle_sms_provider(provider.name) raise e else: notification.reference = reference notification.billable_units = template.fragment_count update_notification_to_sending(notification, provider) # Record StatsD stats to compute SLOs statsd_client.timing_with_dates("sms.total-time", notification.sent_at, notification.created_at) statsd_key = f"sms.process_type-{template_dict['process_type']}" statsd_client.timing_with_dates(statsd_key, notification.sent_at, notification.created_at) statsd_client.incr(statsd_key)
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(SMS_TYPE, notification.id, notification.international) current_app.logger.debug( "Starting sending SMS {} to provider at {}".format( notification.id, datetime.utcnow())) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.billable_units = 0 update_notification(notification, provider) try: send_sms_response(provider.get_name(), str(notification.id), notification.to) except HTTPError: # when we retry, we only do anything if the notification is in created - it's currently in sending, # so set it back so that we actually attempt the callback again notification.sent_at = None notification.sent_by = None notification.status = NOTIFICATION_CREATED dao_update_notification(notification) raise else: try: provider.send_sms(to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text) except Exception as e: dao_toggle_sms_provider(provider.name) raise e else: notification.billable_units = template.fragment_count update_notification(notification, provider, notification.international) current_app.logger.debug("SMS {} sent to provider {} at {}".format( notification.id, provider.get_name(), notification.sent_at)) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)
def test_toggle_sms_provider_switches_provider_stores_notify_user_id(restore_provider_details, sample_user, mocker): mocker.patch("app.provider_details.switch_providers.get_user_by_id", return_value=sample_user) current_provider = get_current_provider("sms") dao_toggle_sms_provider(current_provider.identifier) new_provider = get_current_provider("sms") assert current_provider.identifier != new_provider.identifier assert new_provider.created_by.id == sample_user.id assert new_provider.created_by_id == sample_user.id
def test_toggle_sms_provider_should_not_switch_provider_if_no_alternate_provider( mocker): mocker.patch('app.dao.provider_details_dao.get_alternative_sms_provider', return_value=None) mock_dao_switch_sms_provider_to_provider_with_identifier = mocker.patch( 'app.dao.provider_details_dao.dao_switch_sms_provider_to_provider_with_identifier' ) dao_toggle_sms_provider('some-identifier') mock_dao_switch_sms_provider_to_provider_with_identifier.assert_not_called( )
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': provider = provider_to_use(SMS_TYPE, notification, notification.international) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.reference = create_uuid() update_notification_to_sending(notification, provider) send_sms_response(provider.get_name(), str(notification.id), notification.to, notification.reference) else: try: reference = provider.send_sms( to=validate_and_format_phone_number( notification.to, international=notification.international), content=str(template), reference=str(notification.id), sender=notification.reply_to_text) except Exception as e: notification.billable_units = template.fragment_count dao_update_notification(notification) dao_toggle_sms_provider(provider.name) raise e else: notification.billable_units = template.fragment_count notification.reference = reference update_notification_to_sending(notification, provider) current_app.logger.info( f"Saved provider reference: {reference} for notification id: {notification.id}" ) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)
def test_toggle_sms_provider_switches_provider(mocker, sample_user, setup_sms_providers): [inactive_provider, old_provider, alternative_provider] = setup_sms_providers mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) mocker.patch('app.dao.provider_details_dao.get_alternative_sms_provider', return_value=alternative_provider) dao_toggle_sms_provider(old_provider.identifier) new_provider = get_current_provider('sms') assert new_provider.identifier != old_provider.identifier assert new_provider.priority < old_provider.priority
def test_toggle_sms_provider_switches_when_provider_priorities_are_equal(mocker, restore_provider_details, sample_user): mocker.patch("app.provider_details.switch_providers.get_user_by_id", return_value=sample_user) current_provider = get_current_provider("sms") new_provider = get_alternative_sms_provider(current_provider.identifier) current_provider.priority = new_provider.priority dao_update_provider_details(current_provider) dao_toggle_sms_provider(current_provider.identifier) old_starting_provider = get_provider_details_by_identifier(current_provider.identifier) assert new_provider.identifier != old_starting_provider.identifier assert new_provider.priority < old_starting_provider.priority
def test_toggle_sms_provider_switches_provider_stores_notify_user_id( mocker, sample_user, setup_sms_providers): [inactive_provider, current_provider, alternative_provider] = setup_sms_providers mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) mocker.patch('app.dao.provider_details_dao.get_alternative_sms_provider', return_value=alternative_provider) dao_toggle_sms_provider(current_provider.identifier) new_provider = get_current_provider('sms') assert current_provider.identifier != new_provider.identifier assert new_provider.created_by.id == sample_user.id assert new_provider.created_by_id == sample_user.id
def test_toggle_sms_provider_updates_provider_history( mocker, sample_user, setup_sms_providers_with_history ): [inactive_provider, current_provider, alternative_provider] = setup_sms_providers_with_history mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) mocker.patch('app.dao.provider_details_dao.get_alternative_sms_provider', return_value=alternative_provider) current_provider_history = dao_get_provider_versions(current_provider.id) dao_toggle_sms_provider(current_provider.identifier) updated_provider_history_rows = dao_get_provider_versions(current_provider.id) assert len(updated_provider_history_rows) - len(current_provider_history) == 1 assert updated_provider_history_rows[0].version - current_provider_history[0].version == 1
def test_toggle_sms_provider_updates_provider_history( mocker, restore_provider_details, current_sms_provider, with_active_telstra_provider, sample_user): mocker.patch('app.provider_details.switch_providers.get_user_by_id', return_value=sample_user) provider_history_rows = ProviderDetailsHistory.query.filter( ProviderDetailsHistory.id == current_sms_provider.id).order_by( desc(ProviderDetailsHistory.version)).all() dao_toggle_sms_provider(current_sms_provider.identifier) updated_provider_history_rows = ProviderDetailsHistory.query.filter( ProviderDetailsHistory.id == current_sms_provider.id).order_by( desc(ProviderDetailsHistory.version)).all() assert len(updated_provider_history_rows) - len(provider_history_rows) == 1 assert updated_provider_history_rows[0].version - provider_history_rows[ 0].version == 1
def switch_current_sms_provider_on_slow_delivery(): """ Switch providers if at least 30% of notifications took more than four minutes to be delivered in the last ten minutes. Search from the time we last switched to the current provider. """ current_provider = get_current_provider("sms") if current_provider.updated_at > datetime.utcnow() - timedelta(minutes=10): current_app.logger.info( "Slow delivery notifications provider switched less than 10 minutes ago." ) return slow_delivery_notifications = is_delivery_slow_for_provider( provider=current_provider.identifier, threshold=0.3, created_at=datetime.utcnow() - timedelta(minutes=10), delivery_time=timedelta(minutes=4), ) if slow_delivery_notifications: current_app.logger.warning( "Slow delivery notifications detected for provider {}".format( current_provider.identifier)) dao_toggle_sms_provider(current_provider.identifier)
def send_sms_to_provider(notification): service = notification.service if not service.active: technical_failure(notification=notification) return if notification.status == 'created': # TODO: issue is that this does not get the provider based on who owns # the inbound number. The notification.reply_to_text below is the phone # number that we should send from, but we need to look at that and see # who the provider is. # TODO: now that we do get the right provider, the issue is that the # reply to text could be different because the service is able to choose # the sender when sending a message. So we need to check if the sender # ID that was chosen is also an inbound number. provider = None preferred_provider = get_preferred_sms_provider(service) if preferred_provider: provider = get_sms_provider_client(preferred_provider, notification.id) else: provider = provider_to_use(SMS_TYPE, notification.id, notification.international) current_app.logger.debug( "Starting sending SMS {} to provider at {}".format( notification.id, datetime.utcnow())) template_model = dao_get_template_by_id(notification.template_id, notification.template_version) template = SMSMessageTemplate( template_model.__dict__, values=notification.personalisation, prefix=service.name, show_prefix=service.prefix_sms, ) if service.research_mode or notification.key_type == KEY_TYPE_TEST: notification.billable_units = 0 update_notification(notification, provider) try: send_sms_response(provider.get_name(), str(notification.id), notification.to) except HTTPError: # when we retry, we only do anything if the notification is in created - it's currently in sending, # so set it back so that we actually attempt the callback again notification.sent_at = None notification.sent_by = None notification.status = NOTIFICATION_CREATED dao_update_notification(notification) raise else: status = None try: reference, status = provider.send_sms( to=notification.normalised_to, content=str(template), reference=str(notification.id), sender=notification.reply_to_text) notification.reference = reference notification.billable_units = template.fragment_count # An international notification (i.e. a text message with an # abroad recipient phone number) instantly get marked as "sent". # It might later get marked as "delivered" when the provider # status callback is triggered. if notification.international: status = NOTIFICATION_SENT except Exception as e: dao_toggle_sms_provider(provider.name) raise e else: update_notification(notification, provider, status=status) current_app.logger.debug("SMS {} sent to provider {} at {}".format( notification.id, provider.get_name(), notification.sent_at)) delta_milliseconds = (datetime.utcnow() - notification.created_at).total_seconds() * 1000 statsd_client.timing("sms.total-time", delta_milliseconds)