Files
notifications-api/tests/app/dao/test_services_dao.py
2024-05-23 13:59:51 -07:00

1583 lines
54 KiB
Python

import uuid
from datetime import datetime, timedelta
from unittest import mock
import pytest
import sqlalchemy
from freezegun import freeze_time
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import NoResultFound
from app import db
from app.dao.inbound_numbers_dao import (
dao_get_available_inbound_numbers,
dao_set_inbound_number_active_flag,
dao_set_inbound_number_to_service,
)
from app.dao.organization_dao import dao_add_service_to_organization
from app.dao.service_permissions_dao import dao_remove_service_permission
from app.dao.service_user_dao import dao_get_service_user, dao_update_service_user
from app.dao.services_dao import (
dao_add_user_to_service,
dao_create_service,
dao_fetch_active_users_for_service,
dao_fetch_all_services,
dao_fetch_all_services_by_user,
dao_fetch_live_services_data,
dao_fetch_service_by_id,
dao_fetch_service_by_id_with_api_keys,
dao_fetch_service_by_inbound_number,
dao_fetch_todays_stats_for_all_services,
dao_fetch_todays_stats_for_service,
dao_find_services_sending_to_tv_numbers,
dao_find_services_with_high_failure_rates,
dao_remove_user_from_service,
dao_resume_service,
dao_suspend_service,
dao_update_service,
delete_service_and_all_associated_db_objects,
get_live_services_with_organization,
get_services_by_partial_name,
)
from app.dao.users_dao import create_user_code, save_model_user
from app.enums import (
CodeType,
KeyType,
NotificationStatus,
NotificationType,
OrganizationType,
PermissionType,
ServicePermissionType,
TemplateType,
)
from app.models import (
ApiKey,
InvitedUser,
Job,
Notification,
NotificationHistory,
Organization,
Permission,
Service,
ServicePermission,
ServiceUser,
Template,
TemplateHistory,
User,
VerifyCode,
user_folder_permissions,
)
from app.utils import utc_now
from tests.app.db import (
create_annual_billing,
create_api_key,
create_ft_billing,
create_inbound_number,
create_invited_user,
create_notification,
create_notification_history,
create_organization,
create_service,
create_service_with_defined_sms_sender,
create_service_with_inbound_number,
create_template,
create_template_folder,
create_user,
)
def test_create_service(notify_db_session):
user = create_user()
assert Service.query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
organization_type=OrganizationType.FEDERAL,
created_by=user,
)
dao_create_service(service, user)
assert Service.query.count() == 1
service_db = Service.query.one()
assert service_db.name == "service_name"
assert service_db.id == service.id
assert service_db.email_from == "email_from"
assert service_db.prefix_sms is True
assert service.active is True
assert user in service_db.users
assert service_db.organization_type == OrganizationType.FEDERAL
assert not service.organization_id
def test_create_service_with_organization(notify_db_session):
user = create_user(email="local.authority@local-authority.gov.uk")
organization = create_organization(
name="Some local authority",
organization_type=OrganizationType.STATE,
domains=["local-authority.gov.uk"],
)
assert Service.query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
organization_type=OrganizationType.FEDERAL,
created_by=user,
)
dao_create_service(service, user)
assert Service.query.count() == 1
service_db = Service.query.one()
organization = Organization.query.get(organization.id)
assert service_db.name == "service_name"
assert service_db.id == service.id
assert service_db.email_from == "email_from"
assert service_db.prefix_sms is True
assert service.active is True
assert user in service_db.users
assert service_db.organization_type == OrganizationType.STATE
assert service.organization_id == organization.id
assert service.organization == organization
def test_fetch_service_by_id_with_api_keys(notify_db_session):
user = create_user(email="local.authority@local-authority.gov.uk")
organization = create_organization(
name="Some local authority",
organization_type=OrganizationType.STATE,
domains=["local-authority.gov.uk"],
)
assert Service.query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
organization_type=OrganizationType.FEDERAL,
created_by=user,
)
dao_create_service(service, user)
assert Service.query.count() == 1
service_db = Service.query.one()
organization = Organization.query.get(organization.id)
assert service_db.name == "service_name"
assert service_db.id == service.id
assert service_db.email_from == "email_from"
assert service_db.prefix_sms is True
assert service.active is True
assert user in service_db.users
assert service_db.organization_type == OrganizationType.STATE
assert service.organization_id == organization.id
assert service.organization == organization
service = dao_fetch_service_by_id_with_api_keys(service.id, False)
assert service is not None
assert service.api_keys is not None
service = dao_fetch_service_by_id_with_api_keys(service.id, True)
assert service is not None
def test_cannot_create_two_services_with_same_name(notify_db_session):
user = create_user()
assert Service.query.count() == 0
service1 = Service(
name="service_name",
email_from="email_from1",
message_limit=1000,
restricted=False,
created_by=user,
)
service2 = Service(
name="service_name",
email_from="email_from2",
message_limit=1000,
restricted=False,
created_by=user,
)
with pytest.raises(IntegrityError) as excinfo:
dao_create_service(service1, user)
dao_create_service(service2, user)
assert 'duplicate key value violates unique constraint "services_name_key"' in str(
excinfo.value
)
def test_cannot_create_two_services_with_same_email_from(notify_db_session):
user = create_user()
assert Service.query.count() == 0
service1 = Service(
name="service_name1",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
service2 = Service(
name="service_name2",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
with pytest.raises(IntegrityError) as excinfo:
dao_create_service(service1, user)
dao_create_service(service2, user)
assert (
'duplicate key value violates unique constraint "services_email_from_key"'
in str(excinfo.value)
)
def test_cannot_create_service_with_no_user(notify_db_session):
user = create_user()
assert Service.query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
with pytest.raises(ValueError) as excinfo:
dao_create_service(service, None)
assert "Can't create a service without a user" in str(excinfo.value)
def test_should_add_user_to_service(notify_db_session):
user = create_user()
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service, user)
assert user in Service.query.first().users
new_user = User(
name="Test User",
email_address="new_user@digital.fake.gov",
password="password",
mobile_number="+12028675309",
)
save_model_user(new_user, validated_email_access=True)
dao_add_user_to_service(service, new_user)
assert new_user in Service.query.first().users
def test_dao_add_user_to_service_sets_folder_permissions(sample_user, sample_service):
folder_1 = create_template_folder(sample_service)
folder_2 = create_template_folder(sample_service)
assert not folder_1.users
assert not folder_2.users
folder_permissions = [str(folder_1.id), str(folder_2.id)]
dao_add_user_to_service(
sample_service, sample_user, folder_permissions=folder_permissions
)
service_user = dao_get_service_user(
user_id=sample_user.id, service_id=sample_service.id
)
assert len(service_user.folders) == 2
assert folder_1 in service_user.folders
assert folder_2 in service_user.folders
def test_dao_add_user_to_service_ignores_folders_which_do_not_exist_when_setting_permissions(
sample_user, sample_service, fake_uuid
):
valid_folder = create_template_folder(sample_service)
folder_permissions = [fake_uuid, str(valid_folder.id)]
dao_add_user_to_service(
sample_service, sample_user, folder_permissions=folder_permissions
)
service_user = dao_get_service_user(sample_user.id, sample_service.id)
assert service_user.folders == [valid_folder]
def test_dao_add_user_to_service_raises_error_if_adding_folder_permissions_for_a_different_service(
sample_service,
):
user = create_user()
other_service = create_service(service_name="other service")
other_service_folder = create_template_folder(other_service)
folder_permissions = [str(other_service_folder.id)]
assert ServiceUser.query.count() == 2
with pytest.raises(IntegrityError) as e:
dao_add_user_to_service(
sample_service, user, folder_permissions=folder_permissions
)
db.session.rollback()
assert (
'insert or update on table "user_folder_permissions" violates foreign key constraint'
in str(e.value)
)
assert ServiceUser.query.count() == 2
def test_should_remove_user_from_service(notify_db_session):
user = create_user()
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service, user)
new_user = User(
name="Test User",
email_address="new_user@digital.fake.gov",
password="password",
mobile_number="+12028675309",
)
save_model_user(new_user, validated_email_access=True)
dao_add_user_to_service(service, new_user)
assert new_user in Service.query.first().users
dao_remove_user_from_service(service, new_user)
assert new_user not in Service.query.first().users
def test_should_remove_user_from_service_exception(notify_db_session):
user = create_user()
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service, user)
new_user = User(
name="Test User",
email_address="new_user@digital.fake.gov",
password="password",
mobile_number="+12028675309",
)
save_model_user(new_user, validated_email_access=True)
wrong_user = User(
name="Wrong User",
email_address="wrong_user@digital.fake.gov",
password="password",
mobile_number="+12028675309",
)
with pytest.raises(expected_exception=Exception):
dao_remove_user_from_service(service, wrong_user)
def test_removing_a_user_from_a_service_deletes_their_permissions(
sample_user, sample_service
):
assert len(Permission.query.all()) == 7
dao_remove_user_from_service(sample_service, sample_user)
assert Permission.query.all() == []
def test_removing_a_user_from_a_service_deletes_their_folder_permissions_for_that_service(
sample_user, sample_service
):
tf1 = create_template_folder(sample_service)
tf2 = create_template_folder(sample_service)
service_2 = create_service(sample_user, service_name="other service")
tf3 = create_template_folder(service_2)
service_user = dao_get_service_user(sample_user.id, sample_service.id)
service_user.folders = [tf1, tf2]
dao_update_service_user(service_user)
service_2_user = dao_get_service_user(sample_user.id, service_2.id)
service_2_user.folders = [tf3]
dao_update_service_user(service_2_user)
dao_remove_user_from_service(sample_service, sample_user)
user_folder_permission = db.session.query(user_folder_permissions).one()
assert user_folder_permission.user_id == service_2_user.user_id
assert user_folder_permission.service_id == service_2_user.service_id
assert user_folder_permission.template_folder_id == tf3.id
def test_get_all_services(notify_db_session):
create_service(service_name="service 1", email_from="service.1")
assert len(dao_fetch_all_services()) == 1
assert dao_fetch_all_services()[0].name == "service 1"
create_service(service_name="service 2", email_from="service.2")
assert len(dao_fetch_all_services()) == 2
assert dao_fetch_all_services()[1].name == "service 2"
def test_get_all_services_should_return_in_created_order(notify_db_session):
create_service(service_name="service 1", email_from="service.1")
create_service(service_name="service 2", email_from="service.2")
create_service(service_name="service 3", email_from="service.3")
create_service(service_name="service 4", email_from="service.4")
assert len(dao_fetch_all_services()) == 4
assert dao_fetch_all_services()[0].name == "service 1"
assert dao_fetch_all_services()[1].name == "service 2"
assert dao_fetch_all_services()[2].name == "service 3"
assert dao_fetch_all_services()[3].name == "service 4"
def test_get_all_services_should_return_empty_list_if_no_services():
assert len(dao_fetch_all_services()) == 0
def test_get_all_services_for_user(notify_db_session):
user = create_user()
create_service(service_name="service 1", user=user, email_from="service.1")
create_service(service_name="service 2", user=user, email_from="service.2")
create_service(service_name="service 3", user=user, email_from="service.3")
assert len(dao_fetch_all_services_by_user(user.id)) == 3
assert dao_fetch_all_services_by_user(user.id)[0].name == "service 1"
assert dao_fetch_all_services_by_user(user.id)[1].name == "service 2"
assert dao_fetch_all_services_by_user(user.id)[2].name == "service 3"
def test_get_services_by_partial_name(notify_db_session):
create_service(service_name="Tadfield Police")
create_service(service_name="Tadfield Air Base")
create_service(service_name="London M25 Management Body")
services_from_db = get_services_by_partial_name("Tadfield")
assert len(services_from_db) == 2
assert sorted([service.name for service in services_from_db]) == [
"Tadfield Air Base",
"Tadfield Police",
]
def test_get_services_by_partial_name_is_case_insensitive(notify_db_session):
create_service(service_name="Tadfield Police")
services_from_db = get_services_by_partial_name("tadfield")
assert services_from_db[0].name == "Tadfield Police"
def test_get_all_user_services_only_returns_services_user_has_access_to(
notify_db_session,
):
user = create_user()
create_service(service_name="service 1", user=user, email_from="service.1")
create_service(service_name="service 2", user=user, email_from="service.2")
service_3 = create_service(
service_name="service 3", user=user, email_from="service.3"
)
new_user = User(
name="Test User",
email_address="new_user@digital.fake.gov",
password="password",
mobile_number="+12028675309",
)
save_model_user(new_user, validated_email_access=True)
dao_add_user_to_service(service_3, new_user)
assert len(dao_fetch_all_services_by_user(user.id)) == 3
assert dao_fetch_all_services_by_user(user.id)[0].name == "service 1"
assert dao_fetch_all_services_by_user(user.id)[1].name == "service 2"
assert dao_fetch_all_services_by_user(user.id)[2].name == "service 3"
assert len(dao_fetch_all_services_by_user(new_user.id)) == 1
assert dao_fetch_all_services_by_user(new_user.id)[0].name == "service 3"
def test_get_all_user_services_should_return_empty_list_if_no_services_for_user(
notify_db_session,
):
user = create_user()
assert len(dao_fetch_all_services_by_user(user.id)) == 0
@freeze_time("2019-04-23T10:00:00")
def test_dao_fetch_live_services_data(sample_user):
org = create_organization(organization_type=OrganizationType.FEDERAL)
service = create_service(go_live_user=sample_user, go_live_at="2014-04-20T10:00:00")
sms_template = create_template(service=service)
service_2 = create_service(
service_name="second",
go_live_at="2017-04-20T10:00:00",
go_live_user=sample_user,
)
service_3 = create_service(service_name="third", go_live_at="2016-04-20T10:00:00")
# below services should be filtered out:
create_service(service_name="restricted", restricted=True)
create_service(service_name="not_active", active=False)
create_service(service_name="not_live", count_as_live=False)
email_template = create_template(service=service, template_type=TemplateType.EMAIL)
dao_add_service_to_organization(service=service, organization_id=org.id)
# two sms billing records for 1st service within current financial year:
create_ft_billing(local_date="2019-04-20", template=sms_template)
create_ft_billing(local_date="2019-04-21", template=sms_template)
# one sms billing record for 1st service from previous financial year, should not appear in the result:
create_ft_billing(local_date="2018-04-20", template=sms_template)
# one email billing record for 1st service within current financial year:
create_ft_billing(local_date="2019-04-20", template=email_template)
# 1st service: billing from 2018 and 2019
create_annual_billing(service.id, 500, 2018)
create_annual_billing(service.id, 100, 2019)
# 2nd service: billing from 2018
create_annual_billing(service_2.id, 300, 2018)
# 3rd service: billing from 2019
create_annual_billing(service_3.id, 200, 2019)
results = dao_fetch_live_services_data()
assert len(results) == 3
# checks the results and that they are ordered by date:
assert results == [
{
"service_id": mock.ANY,
"service_name": "Sample service",
"organization_name": "test_org_1",
"organization_type": OrganizationType.FEDERAL,
"consent_to_research": None,
"contact_name": "Test User",
"contact_email": "notify@digital.fake.gov",
"contact_mobile": "+12028675309",
"live_date": datetime(2014, 4, 20, 10, 0),
"sms_volume_intent": None,
"email_volume_intent": None,
"sms_totals": 2,
"email_totals": 1,
"free_sms_fragment_limit": 100,
},
{
"service_id": mock.ANY,
"service_name": "third",
"organization_name": None,
"consent_to_research": None,
"organization_type": None,
"contact_name": None,
"contact_email": None,
"contact_mobile": None,
"live_date": datetime(2016, 4, 20, 10, 0),
"sms_volume_intent": None,
"email_volume_intent": None,
"sms_totals": 0,
"email_totals": 0,
"free_sms_fragment_limit": 200,
},
{
"service_id": mock.ANY,
"service_name": "second",
"organization_name": None,
"consent_to_research": None,
"contact_name": "Test User",
"contact_email": "notify@digital.fake.gov",
"contact_mobile": "+12028675309",
"live_date": datetime(2017, 4, 20, 10, 0),
"sms_volume_intent": None,
"organization_type": None,
"email_volume_intent": None,
"sms_totals": 0,
"email_totals": 0,
"free_sms_fragment_limit": 300,
},
]
def test_get_service_by_id_returns_none_if_no_service(notify_db_session):
with pytest.raises(NoResultFound) as e:
dao_fetch_service_by_id(str(uuid.uuid4()))
assert "No row was found when one was required" in str(e.value)
def test_get_service_by_id_returns_service(notify_db_session):
service = create_service(service_name="testing", email_from="testing")
assert dao_fetch_service_by_id(service.id).name == "testing"
def test_create_service_returns_service_with_default_permissions(notify_db_session):
service = create_service(
service_name="testing", email_from="testing", service_permissions=None
)
service = dao_fetch_service_by_id(service.id)
_assert_service_permissions(
service.permissions,
(
ServicePermissionType.SMS,
ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
),
)
@pytest.mark.parametrize(
"permission_to_remove, permissions_remaining",
[
(
ServicePermissionType.SMS,
(
ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
),
),
(
ServicePermissionType.EMAIL,
(
ServicePermissionType.SMS,
ServicePermissionType.INTERNATIONAL_SMS,
),
),
],
)
def test_remove_permission_from_service_by_id_returns_service_with_correct_permissions(
notify_db_session, permission_to_remove, permissions_remaining
):
service = create_service(service_permissions=None)
dao_remove_service_permission(
service_id=service.id, permission=permission_to_remove
)
service = dao_fetch_service_by_id(service.id)
_assert_service_permissions(service.permissions, permissions_remaining)
def test_removing_all_permission_returns_service_with_no_permissions(notify_db_session):
service = create_service()
dao_remove_service_permission(
service_id=service.id,
permission=ServicePermissionType.SMS,
)
dao_remove_service_permission(
service_id=service.id,
permission=ServicePermissionType.EMAIL,
)
dao_remove_service_permission(
service_id=service.id,
permission=ServicePermissionType.INTERNATIONAL_SMS,
)
service = dao_fetch_service_by_id(service.id)
assert len(service.permissions) == 0
def test_create_service_creates_a_history_record_with_current_data(notify_db_session):
user = create_user()
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service, user)
assert Service.query.count() == 1
assert Service.get_history_model().query.count() == 1
service_from_db = Service.query.first()
service_history = Service.get_history_model().query.first()
assert service_from_db.id == service_history.id
assert service_from_db.name == service_history.name
assert service_from_db.version == 1
assert service_from_db.version == service_history.version
assert user.id == service_history.created_by_id
assert service_from_db.created_by.id == service_history.created_by_id
def test_update_service_creates_a_history_record_with_current_data(notify_db_session):
user = create_user()
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service, user)
assert Service.query.count() == 1
assert Service.query.first().version == 1
assert Service.get_history_model().query.count() == 1
service.name = "updated_service_name"
dao_update_service(service)
assert Service.query.count() == 1
assert Service.get_history_model().query.count() == 2
service_from_db = Service.query.first()
assert service_from_db.version == 2
assert (
Service.get_history_model().query.filter_by(name="service_name").one().version
== 1
)
assert (
Service.get_history_model()
.query.filter_by(name="updated_service_name")
.one()
.version
== 2
)
def test_update_service_permission_creates_a_history_record_with_current_data(
notify_db_session,
):
user = create_user()
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
service = Service(
name="service_name",
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(
service,
user,
service_permissions=[
ServicePermissionType.SMS,
# ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
],
)
assert Service.query.count() == 1
service.permissions.append(
ServicePermission(service_id=service.id, permission=ServicePermissionType.EMAIL)
)
dao_update_service(service)
assert Service.query.count() == 1
assert Service.get_history_model().query.count() == 2
service_from_db = Service.query.first()
assert service_from_db.version == 2
_assert_service_permissions(
service.permissions,
(
ServicePermissionType.SMS,
ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
),
)
permission = [
p for p in service.permissions if p.permission == ServicePermissionType.SMS
][0]
service.permissions.remove(permission)
dao_update_service(service)
assert Service.query.count() == 1
assert Service.get_history_model().query.count() == 3
service_from_db = Service.query.first()
assert service_from_db.version == 3
_assert_service_permissions(
service.permissions,
(
ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
),
)
history = (
Service.get_history_model()
.query.filter_by(name="service_name")
.order_by("version")
.all()
)
assert len(history) == 3
assert history[2].version == 3
def test_create_service_and_history_is_transactional(notify_db_session):
user = create_user()
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
service = Service(
name=None,
email_from="email_from",
message_limit=1000,
restricted=False,
created_by=user,
)
try:
dao_create_service(service, user)
except sqlalchemy.exc.IntegrityError as seeei:
assert (
'null value in column "name" of relation "services_history" violates not-null constraint'
in str(seeei)
)
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
def test_delete_service_and_associated_objects(notify_db_session):
user = create_user()
organization = create_organization()
service = create_service(
user=user, service_permissions=None, organization=organization
)
create_user_code(user=user, code="somecode", code_type=CodeType.EMAIL)
create_user_code(user=user, code="somecode", code_type=CodeType.SMS)
template = create_template(service=service)
api_key = create_api_key(service=service)
create_notification(template=template, api_key=api_key)
create_invited_user(service=service)
user.organizations = [organization]
assert ServicePermission.query.count() == len(
(
ServicePermissionType.SMS,
ServicePermissionType.EMAIL,
ServicePermissionType.INTERNATIONAL_SMS,
)
)
delete_service_and_all_associated_db_objects(service)
assert VerifyCode.query.count() == 0
assert ApiKey.query.count() == 0
assert ApiKey.get_history_model().query.count() == 0
assert Template.query.count() == 0
assert TemplateHistory.query.count() == 0
assert Job.query.count() == 0
assert Notification.query.count() == 0
assert Permission.query.count() == 0
assert User.query.count() == 0
assert InvitedUser.query.count() == 0
assert Service.query.count() == 0
assert Service.get_history_model().query.count() == 0
assert ServicePermission.query.count() == 0
# the organization hasn't been deleted
assert Organization.query.count() == 1
def test_add_existing_user_to_another_service_doesnot_change_old_permissions(
notify_db_session,
):
user = create_user()
service_one = Service(
name="service_one",
email_from="service_one",
message_limit=1000,
restricted=False,
created_by=user,
)
dao_create_service(service_one, user)
assert user.id == service_one.users[0].id
test_user_permissions = Permission.query.filter_by(
service=service_one, user=user
).all()
assert len(test_user_permissions) == 7
other_user = User(
name="Other Test User",
email_address="other_user@digital.fake.gov",
password="password",
mobile_number="+12028672000",
)
save_model_user(other_user, validated_email_access=True)
service_two = Service(
name="service_two",
email_from="service_two",
message_limit=1000,
restricted=False,
created_by=other_user,
)
dao_create_service(service_two, other_user)
assert other_user.id == service_two.users[0].id
other_user_permissions = Permission.query.filter_by(
service=service_two, user=other_user
).all()
assert len(other_user_permissions) == 7
other_user_service_one_permissions = Permission.query.filter_by(
service=service_one, user=other_user
).all()
assert len(other_user_service_one_permissions) == 0
# adding the other_user to service_one should leave all other_user permissions on service_two intact
permissions = []
for p in [PermissionType.SEND_EMAILS, PermissionType.SEND_TEXTS]:
permissions.append(Permission(permission=p))
dao_add_user_to_service(service_one, other_user, permissions=permissions)
other_user_service_one_permissions = Permission.query.filter_by(
service=service_one, user=other_user
).all()
assert len(other_user_service_one_permissions) == 2
other_user_service_two_permissions = Permission.query.filter_by(
service=service_two, user=other_user
).all()
assert len(other_user_service_two_permissions) == 7
def test_fetch_stats_filters_on_service(notify_db_session):
service_one = create_service()
create_notification(template=create_template(service=service_one))
service_two = Service(
name="service_two",
created_by=service_one.created_by,
email_from="hello",
restricted=False,
message_limit=1000,
)
dao_create_service(service_two, service_one.created_by)
stats = dao_fetch_todays_stats_for_service(service_two.id)
assert len(stats) == 0
def test_fetch_stats_ignores_historical_notification_data(sample_template):
create_notification_history(template=sample_template)
assert Notification.query.count() == 0
assert NotificationHistory.query.count() == 1
stats = dao_fetch_todays_stats_for_service(sample_template.service_id)
assert len(stats) == 0
def test_dao_fetch_todays_stats_for_service(notify_db_session):
service = create_service()
sms_template = create_template(service=service)
email_template = create_template(service=service, template_type=TemplateType.EMAIL)
# two created email, one failed email, and one created sms
create_notification(template=email_template, status=NotificationStatus.CREATED)
create_notification(template=email_template, status=NotificationStatus.CREATED)
create_notification(
template=email_template, status=NotificationStatus.TECHNICAL_FAILURE
)
create_notification(template=sms_template, status=NotificationStatus.CREATED)
stats = dao_fetch_todays_stats_for_service(service.id)
stats = sorted(stats, key=lambda x: (x.notification_type, x.status))
assert len(stats) == 3
assert stats[0].notification_type == NotificationType.EMAIL
assert stats[0].status == NotificationStatus.CREATED
assert stats[0].count == 2
assert stats[1].notification_type == NotificationType.EMAIL
assert stats[1].status == NotificationStatus.TECHNICAL_FAILURE
assert stats[1].count == 1
assert stats[2].notification_type == NotificationType.SMS
assert stats[2].status == NotificationStatus.CREATED
assert stats[2].count == 1
def test_dao_fetch_todays_stats_for_service_should_ignore_test_key(notify_db_session):
service = create_service()
template = create_template(service=service)
live_api_key = create_api_key(service=service, key_type=KeyType.NORMAL)
team_api_key = create_api_key(service=service, key_type=KeyType.TEAM)
test_api_key = create_api_key(service=service, key_type=KeyType.TEST)
# two created email, one failed email, and one created sms
create_notification(
template=template, api_key=live_api_key, key_type=live_api_key.key_type
)
create_notification(
template=template, api_key=test_api_key, key_type=test_api_key.key_type
)
create_notification(
template=template, api_key=team_api_key, key_type=team_api_key.key_type
)
create_notification(template=template)
stats = dao_fetch_todays_stats_for_service(service.id)
assert len(stats) == 1
assert stats[0].notification_type == NotificationType.SMS
assert stats[0].status == NotificationStatus.CREATED
assert stats[0].count == 3
def test_dao_fetch_todays_stats_for_service_only_includes_today(notify_db_session):
template = create_template(service=create_service())
# two created email, one failed email, and one created sms
with freeze_time("2001-01-02T04:59:00"):
# just_before_midnight_yesterday
create_notification(
template=template,
to_field="1",
status=NotificationStatus.DELIVERED,
)
with freeze_time("2001-01-02T05:01:00"):
# just_after_midnight_today
create_notification(
template=template,
to_field="2",
status=NotificationStatus.FAILED,
)
with freeze_time("2001-01-02T12:00:00"):
# right_now
create_notification(
template=template,
to_field="3",
status=NotificationStatus.CREATED,
)
stats = dao_fetch_todays_stats_for_service(template.service_id)
stats = {row.status: row.count for row in stats}
assert stats[NotificationStatus.DELIVERED] == 1
assert stats[NotificationStatus.FAILED] == 1
assert stats[NotificationStatus.CREATED] == 1
@pytest.mark.skip(reason="Need a better way to test variable DST date")
def test_dao_fetch_todays_stats_for_service_only_includes_today_when_clocks_spring_forward(
notify_db_session,
):
template = create_template(service=create_service())
with freeze_time("2021-03-27T23:59:59"):
# just before midnight yesterday in UTC -- not included
create_notification(
template=template,
to_field="1",
status=NotificationStatus.PERMANENT_FAILURE,
)
with freeze_time("2021-03-28T00:01:00"):
# just after midnight yesterday in UTC -- included
create_notification(
template=template,
to_field="2",
status=NotificationStatus.FAILED,
)
with freeze_time("2021-03-28T12:00:00"):
# we have entered BST at this point but had not for the previous two notifications --included
# collect stats for this timestamp
create_notification(
template=template,
to_field="3",
status=NotificationStatus.CREATED,
)
stats = dao_fetch_todays_stats_for_service(template.service_id)
stats = {row.status: row.count for row in stats}
assert NotificationStatus.DELIVERED not in stats
assert stats[NotificationStatus.FAILED] == 1
assert stats[NotificationStatus.CREATED] == 1
assert not stats.get(NotificationStatus.PERMANENT_FAILURE)
assert not stats.get(NotificationStatus.TEMPORARY_FAILURE)
def test_dao_fetch_todays_stats_for_service_only_includes_today_during_bst(
notify_db_session,
):
template = create_template(service=create_service())
with freeze_time("2021-03-28T23:59:59"):
# just before midnight BST -- not included
create_notification(
template=template, to_field="1", status=NotificationStatus.PERMANENT_FAILURE
)
with freeze_time("2021-03-29T04:00:01"):
# just after midnight BST -- included
create_notification(
template=template, to_field="2", status=NotificationStatus.FAILED
)
with freeze_time("2021-03-29T12:00:00"):
# well after midnight BST -- included
# collect stats for this timestamp
create_notification(
template=template, to_field="3", status=NotificationStatus.CREATED
)
stats = dao_fetch_todays_stats_for_service(template.service_id)
stats = {row.status: row.count for row in stats}
assert NotificationStatus.DELIVERED not in stats
assert stats[NotificationStatus.FAILED] == 1
assert stats[NotificationStatus.CREATED] == 1
assert not stats.get(NotificationStatus.PERMANENT_FAILURE)
def test_dao_fetch_todays_stats_for_service_only_includes_today_when_clocks_fall_back(
notify_db_session,
):
template = create_template(service=create_service())
with freeze_time("2021-10-30T22:59:59"):
# just before midnight BST -- not included
create_notification(
template=template, to_field="1", status=NotificationStatus.PERMANENT_FAILURE
)
with freeze_time("2021-10-31T23:00:01"):
# just after midnight BST -- included
create_notification(
template=template, to_field="2", status=NotificationStatus.FAILED
)
# clocks go back to UTC on 31 October at 2am
with freeze_time("2021-10-31T12:00:00"):
# well after midnight -- included
# collect stats for this timestamp
create_notification(
template=template, to_field="3", status=NotificationStatus.CREATED
)
stats = dao_fetch_todays_stats_for_service(template.service_id)
stats = {row.status: row.count for row in stats}
assert NotificationStatus.DELIVERED not in stats
assert stats[NotificationStatus.FAILED] == 1
assert stats[NotificationStatus.CREATED] == 1
assert not stats.get(NotificationStatus.PERMANENT_FAILURE)
def test_dao_fetch_todays_stats_for_service_only_includes_during_utc(notify_db_session):
template = create_template(service=create_service())
with freeze_time("2021-10-30T12:59:59"):
# just before midnight UTC -- not included
create_notification(
template=template, to_field="1", status=NotificationStatus.PERMANENT_FAILURE
)
with freeze_time("2021-10-31T05:00:01"):
# just after midnight UTC -- included
create_notification(
template=template, to_field="2", status=NotificationStatus.FAILED
)
# clocks go back to UTC on 31 October at 2am
with freeze_time("2021-10-31T12:00:00"):
# well after midnight -- included
# collect stats for this timestamp
create_notification(
template=template, to_field="3", status=NotificationStatus.CREATED
)
stats = dao_fetch_todays_stats_for_service(template.service_id)
stats = {row.status: row.count for row in stats}
assert NotificationStatus.DELIVERED not in stats
assert stats[NotificationStatus.FAILED] == 1
assert stats[NotificationStatus.CREATED] == 1
assert not stats.get(NotificationStatus.PERMANENT_FAILURE)
def test_dao_fetch_todays_stats_for_all_services_includes_all_services(
notify_db_session,
):
# two services, each with an email and sms notification
service1 = create_service(service_name="service 1", email_from="service.1")
service2 = create_service(service_name="service 2", email_from="service.2")
template_email_one = create_template(
service=service1, template_type=TemplateType.EMAIL
)
template_sms_one = create_template(service=service1, template_type=TemplateType.SMS)
template_email_two = create_template(
service=service2, template_type=TemplateType.EMAIL
)
template_sms_two = create_template(service=service2, template_type=TemplateType.SMS)
create_notification(template=template_email_one)
create_notification(template=template_sms_one)
create_notification(template=template_email_two)
create_notification(template=template_sms_two)
stats = dao_fetch_todays_stats_for_all_services()
assert len(stats) == 4
# services are ordered by service id; not explicit on email/sms or status
assert stats == sorted(stats, key=lambda x: x.service_id)
def test_dao_fetch_todays_stats_for_all_services_only_includes_today(notify_db_session):
template = create_template(service=create_service())
with freeze_time("2001-01-01T23:59:00"):
# just_before_midnight_yesterday
create_notification(
template=template, to_field="1", status=NotificationStatus.DELIVERED
)
with freeze_time("2001-01-02T05:01:00"):
# just_after_midnight_today
create_notification(
template=template, to_field="2", status=NotificationStatus.FAILED
)
with freeze_time("2001-01-02T12:00:00"):
stats = dao_fetch_todays_stats_for_all_services()
stats = {row.status: row.count for row in stats}
assert NotificationStatus.DELIVERED not in stats
assert stats[NotificationStatus.FAILED] == 1
def test_dao_fetch_todays_stats_for_all_services_groups_correctly(notify_db_session):
service1 = create_service(service_name="service 1", email_from="service.1")
service2 = create_service(service_name="service 2", email_from="service.2")
template_sms = create_template(service=service1)
template_email = create_template(service=service1, template_type=TemplateType.EMAIL)
template_two = create_template(service=service2)
# service1: 2 sms with status "created" and one "failed", and one email
create_notification(template=template_sms)
create_notification(template=template_sms)
create_notification(template=template_sms, status=NotificationStatus.FAILED)
create_notification(template=template_email)
# service2: 1 sms "created"
create_notification(template=template_two)
stats = dao_fetch_todays_stats_for_all_services()
assert len(stats) == 4
assert (
service1.id,
service1.name,
service1.restricted,
service1.active,
service1.created_at,
NotificationType.SMS,
NotificationStatus.CREATED,
2,
) in stats
assert (
service1.id,
service1.name,
service1.restricted,
service1.active,
service1.created_at,
NotificationType.SMS,
NotificationStatus.FAILED,
1,
) in stats
assert (
service1.id,
service1.name,
service1.restricted,
service1.active,
service1.created_at,
NotificationType.EMAIL,
NotificationStatus.CREATED,
1,
) in stats
assert (
service2.id,
service2.name,
service2.restricted,
service2.active,
service2.created_at,
NotificationType.SMS,
NotificationStatus.CREATED,
1,
) in stats
def test_dao_fetch_todays_stats_for_all_services_includes_all_keys_by_default(
notify_db_session,
):
template = create_template(service=create_service())
create_notification(template=template, key_type=KeyType.NORMAL)
create_notification(template=template, key_type=KeyType.TEAM)
create_notification(template=template, key_type=KeyType.TEST)
stats = dao_fetch_todays_stats_for_all_services()
assert len(stats) == 1
assert stats[0].count == 3
def test_dao_fetch_todays_stats_for_all_services_can_exclude_from_test_key(
notify_db_session,
):
template = create_template(service=create_service())
create_notification(template=template, key_type=KeyType.NORMAL)
create_notification(template=template, key_type=KeyType.TEAM)
create_notification(template=template, key_type=KeyType.TEST)
stats = dao_fetch_todays_stats_for_all_services(include_from_test_key=False)
assert len(stats) == 1
assert stats[0].count == 2
@freeze_time("2001-01-01T23:59:00")
def test_dao_suspend_service_with_no_api_keys(notify_db_session):
service = create_service()
dao_suspend_service(service.id)
service = Service.query.get(service.id)
assert not service.active
assert service.name == service.name
assert service.api_keys == []
@freeze_time("2001-01-01T23:59:00")
def test_dao_suspend_service_marks_service_as_inactive_and_expires_api_keys(
notify_db_session,
):
service = create_service()
api_key = create_api_key(service=service)
dao_suspend_service(service.id)
service = Service.query.get(service.id)
assert not service.active
assert service.name == service.name
api_key = ApiKey.query.get(api_key.id)
assert api_key.expiry_date == datetime(2001, 1, 1, 23, 59, 00)
@freeze_time("2001-01-01T23:59:00")
def test_dao_resume_service_marks_service_as_active_and_api_keys_are_still_revoked(
notify_db_session,
):
service = create_service()
api_key = create_api_key(service=service)
dao_suspend_service(service.id)
service = Service.query.get(service.id)
assert not service.active
dao_resume_service(service.id)
assert Service.query.get(service.id).active
api_key = ApiKey.query.get(api_key.id)
assert api_key.expiry_date == datetime(2001, 1, 1, 23, 59, 00)
def test_dao_fetch_active_users_for_service_returns_active_only(notify_db_session):
active_user = create_user(email="active@foo.com", state="active")
pending_user = create_user(email="pending@foo.com", state="pending")
service = create_service(user=active_user)
dao_add_user_to_service(service, pending_user)
users = dao_fetch_active_users_for_service(service.id)
assert len(users) == 1
def test_dao_fetch_service_by_inbound_number_with_inbound_number(notify_db_session):
foo1 = create_service_with_inbound_number(service_name="a", inbound_number="1")
create_service_with_defined_sms_sender(service_name="b", sms_sender_value="2")
create_service_with_defined_sms_sender(service_name="c", sms_sender_value="3")
create_inbound_number("2")
create_inbound_number("3")
service = dao_fetch_service_by_inbound_number("1")
assert foo1.id == service.id
def test_dao_fetch_service_by_inbound_number_with_inbound_number_not_set(
notify_db_session,
):
create_inbound_number("1")
service = dao_fetch_service_by_inbound_number("1")
assert service is None
def test_dao_fetch_service_by_inbound_number_when_inbound_number_set(notify_db_session):
service_1 = create_service_with_inbound_number(inbound_number="1", service_name="a")
create_service(service_name="b")
service = dao_fetch_service_by_inbound_number("1")
assert service.id == service_1.id
def test_dao_fetch_service_by_inbound_number_with_unknown_number(notify_db_session):
create_service_with_inbound_number(inbound_number="1", service_name="a")
service = dao_fetch_service_by_inbound_number("9")
assert service is None
def test_dao_fetch_service_by_inbound_number_with_inactive_number_returns_empty(
notify_db_session,
):
service = create_service_with_inbound_number(inbound_number="1", service_name="a")
dao_set_inbound_number_active_flag(service_id=service.id, active=False)
service = dao_fetch_service_by_inbound_number("1")
assert service is None
def test_dao_allocating_inbound_number_shows_on_service(notify_db_session):
create_service_with_inbound_number()
create_inbound_number(number="07700900003")
inbound_numbers = dao_get_available_inbound_numbers()
service = create_service(service_name="test service")
dao_set_inbound_number_to_service(service.id, inbound_numbers[0])
assert service.inbound_number.number == inbound_numbers[0].number
def _assert_service_permissions(service_permissions, expected):
assert len(service_permissions) == len(expected)
assert set(expected) == set(p.permission for p in service_permissions)
@pytest.mark.skip(
reason="We can't search on recipient if recipient is not kept in the db"
)
@freeze_time("2019-12-02 12:00:00.000000")
def test_dao_find_services_sending_to_tv_numbers(notify_db_session, fake_uuid):
service_1 = create_service(service_name="Service 1", service_id=fake_uuid)
service_3 = create_service(
service_name="Service 3", restricted=True
) # restricted is excluded
service_5 = create_service(
service_name="Service 5", active=False
) # not active is excluded
services = [service_1, service_3, service_5]
tv_number = "447700900001"
normal_number = "447711900001"
normal_number_resembling_tv_number = "447227700900"
for service in services:
template = create_template(service)
for _ in range(0, 5):
create_notification(
template,
normalised_to=tv_number,
status=NotificationStatus.PERMANENT_FAILURE,
)
service_6 = create_service(
service_name="Service 6"
) # notifications too old are excluded
with freeze_time("2019-11-30 15:00:00.000000"):
template_6 = create_template(service_6)
for _ in range(0, 5):
create_notification(
template_6,
normalised_to=tv_number,
status=NotificationStatus.PERMANENT_FAILURE,
)
service_2 = create_service(service_name="Service 2") # below threshold is excluded
template_2 = create_template(service_2)
create_notification(
template_2,
normalised_to=tv_number,
status=NotificationStatus.PERMANENT_FAILURE,
)
for _ in range(0, 5):
# test key type is excluded
create_notification(
template_2,
normalised_to=tv_number,
status=NotificationStatus.PERMANENT_FAILURE,
key_type=KeyType.TEST,
)
for _ in range(0, 5):
# normal numbers are not counted by the query
create_notification(
template_2, normalised_to=normal_number, status=NotificationStatus.DELIVERED
)
create_notification(
template_2,
normalised_to=normal_number_resembling_tv_number,
status=NotificationStatus.DELIVERED,
)
start_date = utc_now() - timedelta(days=1)
end_date = utc_now()
result = dao_find_services_sending_to_tv_numbers(start_date, end_date, threshold=4)
assert len(result) == 1
assert str(result[0].service_id) == fake_uuid
def test_dao_find_services_with_high_failure_rates(notify_db_session, fake_uuid):
service_1 = create_service(service_name="Service 1", service_id=fake_uuid)
service_3 = create_service(
service_name="Service 3", restricted=True
) # restricted is excluded
service_5 = create_service(
service_name="Service 5", active=False
) # not active is excluded
services = [service_1, service_3, service_5]
for service in services:
template = create_template(service)
for _ in range(0, 3):
create_notification(template, status=NotificationStatus.PERMANENT_FAILURE)
create_notification(template, status=NotificationStatus.DELIVERED)
create_notification(template, status=NotificationStatus.SENDING)
create_notification(template, status=NotificationStatus.TEMPORARY_FAILURE)
service_6 = create_service(service_name="Service 6")
with freeze_time("2019-11-30 15:00:00.000000"):
template_6 = create_template(service_6)
for _ in range(0, 4):
create_notification(
template_6,
status=NotificationStatus.PERMANENT_FAILURE,
) # notifications too old are excluded
service_2 = create_service(service_name="Service 2")
template_2 = create_template(service_2)
for _ in range(0, 4):
create_notification(
template_2,
status=NotificationStatus.PERMANENT_FAILURE,
key_type=KeyType.TEST,
) # test key type is excluded
create_notification(
template_2,
status=NotificationStatus.PERMANENT_FAILURE,
) # below threshold is excluded
start_date = utc_now() - timedelta(days=1)
end_date = utc_now()
result = dao_find_services_with_high_failure_rates(
start_date, end_date, threshold=3
)
print(result)
assert len(result) == 1
assert str(result[0].service_id) == fake_uuid
assert result[0].permanent_failure_rate == 0.25
def test_get_live_services_with_organization(sample_organization):
trial_service = create_service(service_name="trial service", restricted=True)
live_service = create_service(service_name="count as live")
live_service_diff_org = create_service(service_name="live service different org")
dont_count_as_live = create_service(
service_name="dont count as live", count_as_live=False
)
inactive_service = create_service(service_name="inactive", active=False)
service_without_org = create_service(service_name="no org")
another_org = create_organization(
name="different org",
)
dao_add_service_to_organization(trial_service, sample_organization.id)
dao_add_service_to_organization(live_service, sample_organization.id)
dao_add_service_to_organization(dont_count_as_live, sample_organization.id)
dao_add_service_to_organization(inactive_service, sample_organization.id)
dao_add_service_to_organization(live_service_diff_org, another_org.id)
services = get_live_services_with_organization()
assert len(services) == 3
assert ([(x.service_name, x.organization_name) for x in services]) == [
(live_service_diff_org.name, another_org.name),
(live_service.name, sample_organization.name),
(service_without_org.name, None),
]