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
Example #3
0
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)
Example #4
0
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)
Example #5
0
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)
Example #7
0
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)
Example #8
0
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
Example #12
0
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
Example #15
0
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
Example #16
0
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)
Example #17
0
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)