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_switches_provider( notify_db_session, mocker, restore_provider_details, sample_user, starting_priorities, expected_priorities, ): mocker.patch('app.dao.provider_details_dao.get_user_by_id', return_value=sample_user) 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)) assert firetext.priority == expected_priorities['firetext'] assert mmg.priority == expected_priorities['mmg'] assert mmg.created_by is sample_user assert firetext.created_by is sample_user
def sms_providers(notify_db): """ In production we randomly choose which provider to use based on their priority. To guarantee tests run the same each time, make sure we always choose mmg. You'll need to override them in your tests if you wish to do something different. """ get_provider_details_by_identifier('mmg').priority = 100 get_provider_details_by_identifier('firetext').priority = 0
def get_sms_provider_client(provider, notification_id): try: get_provider_details_by_identifier(provider) except NoResultFound: raise Exception( f"Could not find {provider} provider when trying to send notification {notification_id}" ) return clients.get_client_by_name_and_type(provider, SMS_TYPE)
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_provider_to_use_should_only_return_active_providers(mocker, restore_provider_details): mmg = get_provider_details_by_identifier('mmg') firetext = get_provider_details_by_identifier('firetext') mmg.active = False mock_choices = mocker.patch('app.delivery.send_to_providers.random.choices', return_value=[firetext]) ret = send_to_providers.provider_to_use('sms') mock_choices.assert_called_once_with([firetext], weights=[0]) assert ret.get_name() == 'firetext'
def test_should_send_sms_to_international_providers( sample_template, sample_user, mocker ): mocker.patch('app.mmg_client.send_sms') mocker.patch('app.firetext_client.send_sms') # set firetext to active get_provider_details_by_identifier('firetext').priority = 100 get_provider_details_by_identifier('mmg').priority = 0 notification_uk = create_notification( template=sample_template, to_field="+447234123999", personalisation={"name": "Jo"}, status='created', international=False, reply_to_text=sample_template.service.get_default_sms_sender() ) notification_international = create_notification( template=sample_template, to_field="+6011-17224412", personalisation={"name": "Jo"}, status='created', international=True, reply_to_text=sample_template.service.get_default_sms_sender() ) send_to_providers.send_sms_to_provider( notification_uk ) firetext_client.send_sms.assert_called_once_with( to="447234123999", content=ANY, reference=str(notification_uk.id), sender=current_app.config['FROM_NUMBER'] ) send_to_providers.send_sms_to_provider( notification_international ) mmg_client.send_sms.assert_called_once_with( to="601117224412", content=ANY, reference=str(notification_international.id), sender=current_app.config['FROM_NUMBER'] ) assert notification_uk.status == 'sending' assert notification_uk.sent_by == 'firetext' assert notification_international.status == 'sent' assert notification_international.sent_by == 'mmg'
def test_provider_to_use_should_return_random_provider(mocker, notify_db_session): mmg = get_provider_details_by_identifier('mmg') firetext = get_provider_details_by_identifier('firetext') mmg.priority = 25 firetext.priority = 75 mock_choices = mocker.patch('app.delivery.send_to_providers.random.choices', return_value=[mmg]) ret = send_to_providers.provider_to_use('sms', international=False) mock_choices.assert_called_once_with([mmg, firetext], weights=[25, 75]) assert ret.get_name() == 'mmg'
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_provider_to_use_raises_if_no_active_providers( mocker, restore_provider_details): mmg = get_provider_details_by_identifier('mmg') mmg.active = False with pytest.raises(Exception): send_to_providers.provider_to_use('sms', international=True)
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
def test_reduce_sms_provider_priority_adds_rows_to_history_table( mocker, restore_provider_details, sample_user): mocker.patch('app.dao.provider_details_dao.get_user_by_id', return_value=sample_user) mmg = get_provider_details_by_identifier('mmg') # 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}) provider_history_rows = ProviderDetailsHistory.query.filter( ProviderDetailsHistory.id == mmg.id).order_by( desc(ProviderDetailsHistory.version)).all() dao_reduce_sms_provider_priority(mmg.identifier, time_threshold=timedelta(minutes=10)) updated_provider_history_rows = ProviderDetailsHistory.query.filter( ProviderDetailsHistory.id == mmg.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 assert updated_provider_history_rows[0].priority == 90
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", sms_template_2, service_1, provider="mmg", billable_unit=4) create_ft_billing("2018-05-31", "sms", sms_template_1, service_1, provider="sns", billable_unit=1) create_ft_billing( "2018-06-01", "sms", sms_template_1, service_1, provider="sns", rate_multiplier=2, billable_unit=1, ) create_ft_billing("2018-06-03", "sms", sms_template_2, service_1, provider="mmg", billable_unit=4) create_ft_billing("2018-06-15", "sms", sms_template_1, service_2, provider="mmg", billable_unit=1) create_ft_billing("2018-06-28", "sms", sms_template_2, service_2, provider="sns", billable_unit=2) provider = get_provider_details_by_identifier("pinpoint") provider.priority = 50 dao_update_provider_details(provider) result = dao_get_provider_stats() assert len(result) == 7 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 == "sns" assert result[1].display_name == "AWS SNS" assert result[1].supports_international is False assert result[1].active is True assert result[1].current_month_billable_sms == 4 assert result[2].identifier == "mmg" assert result[2].notification_type == "sms" assert result[2].supports_international is True assert result[2].active is False assert result[2].current_month_billable_sms == 5 assert result[3].identifier == "firetext" assert result[3].active is False assert result[3].current_month_billable_sms == 0 assert result[4].identifier == "loadtesting" assert result[4].active is False assert result[4].current_month_billable_sms == 0 assert result[4].supports_international is False assert result[5].identifier == "pinpoint" assert result[5].notification_type == "sms" assert result[5].supports_international is False assert result[5].active is False assert result[5].current_month_billable_sms == 0
def test_provider_to_use_should_only_return_mmg_for_international(mocker, notify_db_session): mmg = get_provider_details_by_identifier('mmg') mock_choices = mocker.patch('app.delivery.send_to_providers.random.choices', return_value=[mmg]) ret = send_to_providers.provider_to_use('sms', international=True) mock_choices.assert_called_once_with([mmg], weights=[100]) assert ret.get_name() == 'mmg'
def with_active_telstra_provider(): # Simulate the Telstra provider actually being active. # This is required because at the time of writing Telstra is not currently # active in the DB but we have some behaviour that we want to test that # relies on being able to switch providers to another active provider. telstra = get_provider_details_by_identifier('telstra') telstra.active = True dao_update_provider_details(telstra)
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 test_provider_details_schema_returns_user_details( mocker, sample_user, restore_provider_details): from app.schemas import provider_details_schema current_sms_provider = get_provider_details_by_identifier('mmg') current_sms_provider.created_by = sample_user data = provider_details_schema.dump(current_sms_provider).data assert sorted(data['created_by'].keys()) == sorted( ['id', 'email_address', 'name'])
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_switch_current_sms_provider_on_slow_delivery_switches_when_one_provider_is_slow( mocker, restore_provider_details, ): is_slow_dict = {'mmg': False, 'firetext': True} mock_is_slow = mocker.patch('app.celery.scheduled_tasks.is_delivery_slow_for_providers', return_value=is_slow_dict) mock_reduce = mocker.patch('app.celery.scheduled_tasks.dao_reduce_sms_provider_priority') # updated_at times are older than the 10 minute window get_provider_details_by_identifier('mmg').updated_at = datetime(2017, 5, 1, 13, 49) get_provider_details_by_identifier('firetext').updated_at = None switch_current_sms_provider_on_slow_delivery() mock_is_slow.assert_called_once_with( threshold=0.3, created_at=datetime(2017, 5, 1, 13, 50), delivery_time=timedelta(minutes=4) ) mock_reduce.assert_called_once_with('firetext', time_threshold=timedelta(minutes=10))
def test_switch_current_sms_provider_on_slow_delivery_does_nothing_if_no_need( mocker, restore_provider_details, is_slow_dict): mocker.patch('app.celery.scheduled_tasks.is_delivery_slow_for_providers', return_value=is_slow_dict) mock_reduce = mocker.patch( 'app.celery.scheduled_tasks.dao_reduce_sms_provider_priority') get_provider_details_by_identifier('mmg').updated_at = datetime( 2017, 5, 1, 13, 51) switch_current_sms_provider_on_slow_delivery() assert mock_reduce.called is False
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', sms_template_2, service_1, provider='mmg', billable_unit=4) create_ft_billing('2018-05-31', 'sms', sms_template_1, service_1, provider='sns', billable_unit=1) create_ft_billing('2018-06-01', 'sms', sms_template_1, service_1, provider='sns', rate_multiplier=2, billable_unit=1) create_ft_billing('2018-06-03', 'sms', sms_template_2, service_1, provider='mmg', billable_unit=4) create_ft_billing('2018-06-15', 'sms', sms_template_1, service_2, provider='mmg', billable_unit=1) create_ft_billing('2018-06-28', 'sms', sms_template_2, service_2, provider='sns', billable_unit=2) provider = get_provider_details_by_identifier('pinpoint') provider.priority = 50 dao_update_provider_details(provider) result = dao_get_provider_stats() assert len(result) == 7 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 == 'sns' assert result[1].display_name == 'AWS SNS' assert result[1].supports_international is False assert result[1].active is True assert result[1].current_month_billable_sms == 4 assert result[2].identifier == 'mmg' assert result[2].notification_type == 'sms' assert result[2].supports_international is True assert result[2].active is True assert result[2].current_month_billable_sms == 5 assert result[3].identifier == 'firetext' assert result[3].current_month_billable_sms == 0 assert result[4].identifier == 'loadtesting' assert result[4].current_month_billable_sms == 0 assert result[4].supports_international is False assert result[5].identifier == 'pinpoint' assert result[5].notification_type == 'sms' assert result[5].supports_international is False assert result[5].active is True assert result[5].current_month_billable_sms == 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_get_provider_details_in_type_and_identifier_order(client, notify_db): provider = get_provider_details_by_identifier("pinpoint") provider.priority = 50 dao_update_provider_details(provider) response = client.get("/provider-details", headers=[create_authorization_header()]) assert response.status_code == 200 json_resp = json.loads(response.get_data(as_text=True))["provider_details"] assert len(json_resp) == 7 assert json_resp[0]["identifier"] == "ses" assert json_resp[1]["identifier"] == "sns" assert json_resp[2]["identifier"] == "mmg" assert json_resp[3]["identifier"] == "firetext" assert json_resp[4]["identifier"] == "loadtesting" assert json_resp[5]["identifier"] == "pinpoint" assert json_resp[6]["identifier"] == "dvla"
def test_get_provider_details_in_type_and_identifier_order(client, notify_db): provider = get_provider_details_by_identifier('pinpoint') provider.priority = 50 dao_update_provider_details(provider) response = client.get('/provider-details', headers=[create_authorization_header()]) assert response.status_code == 200 json_resp = json.loads(response.get_data(as_text=True))['provider_details'] assert len(json_resp) == 7 assert json_resp[0]['identifier'] == 'ses' assert json_resp[1]['identifier'] == 'sns' assert json_resp[2]['identifier'] == 'mmg' assert json_resp[3]['identifier'] == 'firetext' assert json_resp[4]['identifier'] == 'loadtesting' assert json_resp[5]['identifier'] == 'pinpoint' assert json_resp[6]['identifier'] == 'dvla'
def test_provider_details_history_schema_returns_user_details( mocker, sample_user, restore_provider_details, ): from app.schemas import provider_details_schema current_sms_provider = get_provider_details_by_identifier('mmg') current_sms_provider.created_by_id = sample_user.id data = provider_details_schema.dump(current_sms_provider).data dao_update_provider_details(current_sms_provider) current_sms_provider_in_history = ProviderDetailsHistory.query.filter( ProviderDetailsHistory.id == current_sms_provider.id).order_by( desc(ProviderDetailsHistory.version)).first() data = provider_details_schema.dump(current_sms_provider_in_history).data assert sorted(data['created_by'].keys()) == sorted( ['id', 'email_address', 'name'])
def firetext_provider(): return get_provider_details_by_identifier('firetext')
def twilio_provider(): return get_provider_details_by_identifier('twilio')
def mmg_provider(): return get_provider_details_by_identifier('mmg')
def ses_provider(): return get_provider_details_by_identifier('ses')
def with_active_sap_provider(): # Simulate the SAP provider actually being active. sap = get_provider_details_by_identifier('sap') sap.active = True dao_update_provider_details(sap)