mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-23 17:01:35 -05:00
349 lines
13 KiB
Python
349 lines
13 KiB
Python
import pytest
|
|
|
|
from datetime import datetime, timedelta
|
|
from freezegun import freeze_time
|
|
from sqlalchemy.sql import desc
|
|
|
|
from app.models import ProviderDetails, ProviderDetailsHistory
|
|
from app import clients
|
|
from app.dao.provider_details_dao import (
|
|
get_alternative_sms_provider,
|
|
get_provider_details_by_identifier,
|
|
get_provider_details_by_notification_type,
|
|
dao_update_provider_details,
|
|
dao_get_provider_stats,
|
|
dao_reduce_sms_provider_priority,
|
|
dao_adjust_provider_priority_back_to_resting_points,
|
|
_adjust_provider_priority,
|
|
_get_sms_providers_for_update,
|
|
)
|
|
from tests.app.db import (
|
|
create_ft_billing,
|
|
create_service,
|
|
create_template,
|
|
)
|
|
from tests.conftest import set_config
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def set_provider_resting_points(notify_api):
|
|
with set_config(notify_api, 'SMS_PROVIDER_RESTING_POINTS', {'mmg': 60, 'firetext': 40}):
|
|
yield
|
|
|
|
|
|
def set_primary_sms_provider(identifier):
|
|
primary_provider = get_provider_details_by_identifier(identifier)
|
|
secondary_provider = get_provider_details_by_identifier(get_alternative_sms_provider(identifier))
|
|
|
|
primary_provider.priority = 10
|
|
secondary_provider.priority = 20
|
|
|
|
dao_update_provider_details(primary_provider)
|
|
dao_update_provider_details(secondary_provider)
|
|
|
|
|
|
def test_can_get_sms_non_international_providers(notify_db_session):
|
|
sms_providers = get_provider_details_by_notification_type('sms')
|
|
assert len(sms_providers) == 2
|
|
assert all('sms' == prov.notification_type for prov in sms_providers)
|
|
|
|
|
|
def test_can_get_sms_international_providers(notify_db_session):
|
|
sms_providers = get_provider_details_by_notification_type('sms', True)
|
|
assert len(sms_providers) == 1
|
|
assert all('sms' == prov.notification_type for prov in sms_providers)
|
|
assert all(prov.supports_international for prov in sms_providers)
|
|
|
|
|
|
def test_can_get_sms_providers_in_order_of_priority(notify_db_session):
|
|
providers = get_provider_details_by_notification_type('sms', False)
|
|
|
|
assert providers[0].priority < providers[1].priority
|
|
|
|
|
|
def test_can_get_email_providers_in_order_of_priority(notify_db_session):
|
|
providers = get_provider_details_by_notification_type('email')
|
|
|
|
assert providers[0].identifier == "ses"
|
|
|
|
|
|
def test_can_get_email_providers(notify_db_session):
|
|
assert len(get_provider_details_by_notification_type('email')) == 1
|
|
types = [provider.notification_type for provider in get_provider_details_by_notification_type('email')]
|
|
assert all('email' == notification_type for notification_type in types)
|
|
|
|
|
|
def test_should_not_error_if_any_provider_in_code_not_in_database(restore_provider_details):
|
|
ProviderDetails.query.filter_by(identifier='mmg').delete()
|
|
|
|
assert clients.get_sms_client('mmg')
|
|
|
|
|
|
@freeze_time('2000-01-01T00:00:00')
|
|
def test_update_adds_history(restore_provider_details):
|
|
ses = ProviderDetails.query.filter(ProviderDetails.identifier == 'ses').one()
|
|
ses_history = ProviderDetailsHistory.query.filter(ProviderDetailsHistory.id == ses.id).one()
|
|
|
|
assert ses.version == 1
|
|
assert ses_history.version == 1
|
|
assert ses.updated_at is None
|
|
|
|
ses.active = False
|
|
|
|
dao_update_provider_details(ses)
|
|
|
|
assert not ses.active
|
|
assert ses.updated_at == datetime(2000, 1, 1, 0, 0, 0)
|
|
|
|
ses_history = ProviderDetailsHistory.query.filter(
|
|
ProviderDetailsHistory.id == ses.id
|
|
).order_by(
|
|
ProviderDetailsHistory.version
|
|
).all()
|
|
|
|
assert ses_history[0].active
|
|
assert ses_history[0].version == 1
|
|
assert ses_history[0].updated_at is None
|
|
|
|
assert not ses_history[1].active
|
|
assert ses_history[1].version == 2
|
|
assert ses_history[1].updated_at == datetime(2000, 1, 1, 0, 0, 0)
|
|
|
|
|
|
def test_update_sms_provider_to_inactive_sets_inactive(restore_provider_details):
|
|
mmg = get_provider_details_by_identifier('mmg')
|
|
|
|
mmg.active = False
|
|
dao_update_provider_details(mmg)
|
|
|
|
assert not mmg.active
|
|
|
|
|
|
@pytest.mark.parametrize('identifier, expected', [
|
|
('firetext', 'mmg'),
|
|
('mmg', 'firetext'),
|
|
])
|
|
def test_get_alternative_sms_provider_returns_expected_provider(identifier, expected):
|
|
assert get_alternative_sms_provider(identifier) == expected
|
|
|
|
|
|
def test_get_alternative_sms_provider_fails_if_unrecognised():
|
|
with pytest.raises(ValueError):
|
|
get_alternative_sms_provider('ses')
|
|
|
|
|
|
@freeze_time('2016-01-01 00:30')
|
|
def test_adjust_provider_priority_sets_priority(
|
|
restore_provider_details,
|
|
notify_user,
|
|
mmg_provider,
|
|
):
|
|
# need to update these manually to avoid triggering the `onupdate` clause of the updated_at column
|
|
ProviderDetails.query.filter(ProviderDetails.identifier == 'mmg').update({'updated_at': datetime.min})
|
|
|
|
_adjust_provider_priority(mmg_provider, 50)
|
|
|
|
assert mmg_provider.updated_at == datetime.utcnow()
|
|
assert mmg_provider.created_by.id == notify_user.id
|
|
assert mmg_provider.priority == 50
|
|
|
|
|
|
@freeze_time('2016-01-01 00:30')
|
|
def test_adjust_provider_priority_adds_history(
|
|
restore_provider_details,
|
|
notify_user,
|
|
mmg_provider,
|
|
):
|
|
# need to update these manually to avoid triggering the `onupdate` clause of the updated_at column
|
|
ProviderDetails.query.filter(ProviderDetails.identifier == 'mmg').update({'updated_at': datetime.min})
|
|
|
|
old_provider_history_rows = ProviderDetailsHistory.query.filter(
|
|
ProviderDetailsHistory.id == mmg_provider.id
|
|
).order_by(
|
|
desc(ProviderDetailsHistory.version)
|
|
).all()
|
|
|
|
_adjust_provider_priority(mmg_provider, 50)
|
|
|
|
updated_provider_history_rows = ProviderDetailsHistory.query.filter(
|
|
ProviderDetailsHistory.id == mmg_provider.id
|
|
).order_by(
|
|
desc(ProviderDetailsHistory.version)
|
|
).all()
|
|
|
|
assert len(updated_provider_history_rows) - len(old_provider_history_rows) == 1
|
|
assert updated_provider_history_rows[0].version - old_provider_history_rows[0].version == 1
|
|
assert updated_provider_history_rows[0].priority == 50
|
|
|
|
|
|
@freeze_time('2016-01-01 01:00')
|
|
def test_get_sms_providers_for_update_returns_providers(restore_provider_details):
|
|
sixty_one_minutes_ago = datetime(2015, 12, 31, 23, 59)
|
|
ProviderDetails.query.filter(ProviderDetails.identifier == 'mmg').update({'updated_at': sixty_one_minutes_ago})
|
|
ProviderDetails.query.filter(ProviderDetails.identifier == 'firetext').update({'updated_at': None})
|
|
|
|
resp = _get_sms_providers_for_update(timedelta(hours=1))
|
|
|
|
assert {p.identifier for p in resp} == {'mmg', 'firetext'}
|
|
|
|
|
|
@freeze_time('2016-01-01 01:00')
|
|
def test_get_sms_providers_for_update_returns_nothing_if_recent_updates(restore_provider_details):
|
|
fifty_nine_minutes_ago = datetime(2016, 1, 1, 0, 1)
|
|
ProviderDetails.query.filter(ProviderDetails.identifier == 'mmg').update({'updated_at': fifty_nine_minutes_ago})
|
|
|
|
resp = _get_sms_providers_for_update(timedelta(hours=1))
|
|
|
|
assert not resp
|
|
|
|
|
|
@pytest.mark.parametrize(['starting_priorities', 'expected_priorities'], [
|
|
({'mmg': 50, 'firetext': 50}, {'mmg': 40, 'firetext': 60}),
|
|
({'mmg': 0, 'firetext': 20}, {'mmg': 0, 'firetext': 30}), # lower bound respected
|
|
({'mmg': 50, 'firetext': 100}, {'mmg': 40, 'firetext': 100}), # upper bound respected
|
|
|
|
# document what happens if they have unexpected values outside of the 0 - 100 range (due to manual setting from
|
|
# the admin app). the code never causes further issues, but sometimes doesn't actively reset the vaues to 0-100.
|
|
({'mmg': 150, 'firetext': 50}, {'mmg': 140, 'firetext': 60}),
|
|
({'mmg': 50, 'firetext': 150}, {'mmg': 40, 'firetext': 100}),
|
|
|
|
({'mmg': -100, 'firetext': 50}, {'mmg': 0, 'firetext': 60}),
|
|
({'mmg': 50, 'firetext': -100}, {'mmg': 40, 'firetext': -90}),
|
|
])
|
|
def test_reduce_sms_provider_priority_adjusts_provider_priorities(
|
|
mocker,
|
|
restore_provider_details,
|
|
notify_user,
|
|
starting_priorities,
|
|
expected_priorities,
|
|
):
|
|
mock_adjust = mocker.patch('app.dao.provider_details_dao._adjust_provider_priority')
|
|
|
|
mmg = get_provider_details_by_identifier('mmg')
|
|
firetext = get_provider_details_by_identifier('firetext')
|
|
|
|
mmg.priority = starting_priorities['mmg']
|
|
firetext.priority = starting_priorities['firetext']
|
|
# need to update these manually to avoid triggering the `onupdate` clause of the updated_at column
|
|
ProviderDetails.query.filter(ProviderDetails.notification_type == 'sms').update({'updated_at': datetime.min})
|
|
|
|
# switch away from mmg. currently both 50/50
|
|
dao_reduce_sms_provider_priority('mmg', time_threshold=timedelta(minutes=10))
|
|
|
|
mock_adjust.assert_any_call(firetext, expected_priorities['firetext'])
|
|
mock_adjust.assert_any_call(mmg, expected_priorities['mmg'])
|
|
|
|
|
|
def test_reduce_sms_provider_priority_does_nothing_if_providers_have_recently_changed(
|
|
mocker,
|
|
restore_provider_details,
|
|
):
|
|
mock_get_providers = mocker.patch('app.dao.provider_details_dao._get_sms_providers_for_update', return_value=None)
|
|
mock_adjust = mocker.patch('app.dao.provider_details_dao._adjust_provider_priority')
|
|
|
|
dao_reduce_sms_provider_priority('firetext', time_threshold=timedelta(minutes=5))
|
|
|
|
mock_get_providers.assert_called_once_with(timedelta(minutes=5))
|
|
assert mock_adjust.called is False
|
|
|
|
|
|
@pytest.mark.parametrize('existing_mmg, existing_firetext, new_mmg, new_firetext', [
|
|
(50, 50, 60, 40), # not just 50/50 - 60/40 specifically
|
|
(65, 35, 60, 40), # doesn't overshoot if there's less than 10 difference
|
|
(0, 100, 10, 90), # only adjusts by 10
|
|
(100, 100, 90, 90), # it tries to fix weird data - it will reduce both if needs be
|
|
])
|
|
def test_adjust_provider_priority_back_to_resting_points_updates_all_providers(
|
|
restore_provider_details,
|
|
mocker,
|
|
existing_mmg,
|
|
existing_firetext,
|
|
new_mmg,
|
|
new_firetext
|
|
):
|
|
mmg = get_provider_details_by_identifier('mmg')
|
|
firetext = get_provider_details_by_identifier('firetext')
|
|
mmg.priority = existing_mmg
|
|
firetext.priority = existing_firetext
|
|
|
|
mock_adjust = mocker.patch('app.dao.provider_details_dao._adjust_provider_priority')
|
|
mock_get_providers = mocker.patch('app.dao.provider_details_dao._get_sms_providers_for_update', return_value=[
|
|
mmg, firetext
|
|
])
|
|
|
|
dao_adjust_provider_priority_back_to_resting_points()
|
|
|
|
mock_get_providers.assert_called_once_with(timedelta(hours=1))
|
|
mock_adjust.assert_any_call(mmg, new_mmg)
|
|
mock_adjust.assert_any_call(firetext, new_firetext)
|
|
|
|
|
|
def test_adjust_provider_priority_back_to_resting_points_does_nothing_if_theyre_already_at_right_values(
|
|
restore_provider_details,
|
|
mocker,
|
|
):
|
|
mmg = get_provider_details_by_identifier('mmg')
|
|
firetext = get_provider_details_by_identifier('firetext')
|
|
mmg.priority = 60
|
|
firetext.priority = 40
|
|
|
|
mock_adjust = mocker.patch('app.dao.provider_details_dao._adjust_provider_priority')
|
|
mocker.patch('app.dao.provider_details_dao._get_sms_providers_for_update', return_value=[mmg, firetext])
|
|
|
|
dao_adjust_provider_priority_back_to_resting_points()
|
|
|
|
assert mock_adjust.called is False
|
|
|
|
|
|
def test_adjust_provider_priority_back_to_resting_points_does_nothing_if_no_providers_to_update(
|
|
restore_provider_details,
|
|
mocker,
|
|
):
|
|
mock_adjust = mocker.patch('app.dao.provider_details_dao._adjust_provider_priority')
|
|
mocker.patch('app.dao.provider_details_dao._get_sms_providers_for_update', return_value=[])
|
|
|
|
dao_adjust_provider_priority_back_to_resting_points()
|
|
|
|
assert mock_adjust.called is False
|
|
|
|
|
|
@freeze_time('2018-06-28 12:00')
|
|
def test_dao_get_provider_stats(notify_db_session):
|
|
service_1 = create_service(service_name='1')
|
|
service_2 = create_service(service_name='2')
|
|
sms_template_1 = create_template(service_1, 'sms')
|
|
sms_template_2 = create_template(service_2, 'sms')
|
|
|
|
create_ft_billing('2017-06-05', sms_template_2, provider='firetext', billable_unit=4)
|
|
create_ft_billing('2018-05-31', sms_template_1, provider='mmg', billable_unit=1)
|
|
create_ft_billing('2018-06-01', sms_template_1, provider='mmg',
|
|
rate_multiplier=2, billable_unit=1)
|
|
create_ft_billing('2018-06-03', sms_template_2, provider='firetext', billable_unit=4)
|
|
create_ft_billing('2018-06-15', sms_template_1, provider='firetext', billable_unit=1)
|
|
create_ft_billing('2018-06-28', sms_template_2, provider='mmg', billable_unit=2)
|
|
|
|
result = dao_get_provider_stats()
|
|
|
|
assert len(result) == 4
|
|
|
|
assert result[0].identifier == 'ses'
|
|
assert result[0].display_name == 'AWS SES'
|
|
assert result[0].created_by_name is None
|
|
assert result[0].current_month_billable_sms == 0
|
|
|
|
assert result[1].identifier == 'firetext'
|
|
assert result[1].notification_type == 'sms'
|
|
assert result[1].supports_international is False
|
|
assert result[1].active is True
|
|
assert result[1].current_month_billable_sms == 5
|
|
|
|
assert result[2].identifier == 'mmg'
|
|
assert result[2].display_name == 'MMG'
|
|
assert result[2].supports_international is True
|
|
assert result[2].active is True
|
|
assert result[2].current_month_billable_sms == 4
|
|
|
|
assert result[3].identifier == 'dvla'
|
|
assert result[3].current_month_billable_sms == 0
|
|
assert result[3].supports_international is False
|