mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-11 02:23:32 -04:00
Merge branch 'master' into fix-team-recipient-check
This commit is contained in:
@@ -210,7 +210,7 @@ def process_virus_scan_passed(self, filename):
|
|||||||
try:
|
try:
|
||||||
billable_units = _get_page_count(notification, old_pdf)
|
billable_units = _get_page_count(notification, old_pdf)
|
||||||
except PdfReadError:
|
except PdfReadError:
|
||||||
_move_invalid_letter_and_update_status(notification.reference, filename, scan_pdf_object)
|
_move_invalid_letter_and_update_status(notification, filename, scan_pdf_object)
|
||||||
return
|
return
|
||||||
|
|
||||||
new_pdf = _sanitise_precompiled_pdf(self, notification, old_pdf)
|
new_pdf = _sanitise_precompiled_pdf(self, notification, old_pdf)
|
||||||
@@ -222,7 +222,7 @@ def process_virus_scan_passed(self, filename):
|
|||||||
|
|
||||||
if not new_pdf:
|
if not new_pdf:
|
||||||
current_app.logger.info('Invalid precompiled pdf received {} ({})'.format(notification.id, filename))
|
current_app.logger.info('Invalid precompiled pdf received {} ({})'.format(notification.id, filename))
|
||||||
_move_invalid_letter_and_update_status(notification.reference, filename, scan_pdf_object)
|
_move_invalid_letter_and_update_status(notification, filename, scan_pdf_object)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
@@ -230,6 +230,7 @@ def process_virus_scan_passed(self, filename):
|
|||||||
|
|
||||||
current_app.logger.info('notification id {} ({}) sanitised and ready to send'.format(notification.id, filename))
|
current_app.logger.info('notification id {} ({}) sanitised and ready to send'.format(notification.id, filename))
|
||||||
|
|
||||||
|
try:
|
||||||
_upload_pdf_to_test_or_live_pdf_bucket(
|
_upload_pdf_to_test_or_live_pdf_bucket(
|
||||||
new_pdf,
|
new_pdf,
|
||||||
filename,
|
filename,
|
||||||
@@ -240,8 +241,12 @@ def process_virus_scan_passed(self, filename):
|
|||||||
status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED,
|
status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED,
|
||||||
billable_units=billable_units
|
billable_units=billable_units
|
||||||
)
|
)
|
||||||
|
|
||||||
scan_pdf_object.delete()
|
scan_pdf_object.delete()
|
||||||
|
except BotoClientError:
|
||||||
|
current_app.logger.exception(
|
||||||
|
"Error uploading letter to live pdf bucket for notification: {}".format(notification.id)
|
||||||
|
)
|
||||||
|
update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
|
||||||
|
|
||||||
|
|
||||||
def _get_page_count(notification, old_pdf):
|
def _get_page_count(notification, old_pdf):
|
||||||
@@ -255,14 +260,20 @@ def _get_page_count(notification, old_pdf):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def _move_invalid_letter_and_update_status(notification_reference, filename, scan_pdf_object):
|
def _move_invalid_letter_and_update_status(notification, filename, scan_pdf_object):
|
||||||
|
try:
|
||||||
move_scan_to_invalid_pdf_bucket(filename)
|
move_scan_to_invalid_pdf_bucket(filename)
|
||||||
scan_pdf_object.delete()
|
scan_pdf_object.delete()
|
||||||
|
|
||||||
update_letter_pdf_status(
|
update_letter_pdf_status(
|
||||||
reference=notification_reference,
|
reference=notification.reference,
|
||||||
status=NOTIFICATION_VALIDATION_FAILED,
|
status=NOTIFICATION_VALIDATION_FAILED,
|
||||||
billable_units=0)
|
billable_units=0)
|
||||||
|
except BotoClientError:
|
||||||
|
current_app.logger.exception(
|
||||||
|
"Error when moving letter with id {} to invalid PDF bucket".format(notification.id)
|
||||||
|
)
|
||||||
|
update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
|
||||||
|
|
||||||
|
|
||||||
def _upload_pdf_to_test_or_live_pdf_bucket(pdf_data, filename, is_test_letter):
|
def _upload_pdf_to_test_or_live_pdf_bucket(pdf_data, filename, is_test_letter):
|
||||||
|
|||||||
@@ -150,11 +150,17 @@ def timeout_notifications():
|
|||||||
@notify_celery.task(name='send-daily-performance-platform-stats')
|
@notify_celery.task(name='send-daily-performance-platform-stats')
|
||||||
@cronitor('send-daily-performance-platform-stats')
|
@cronitor('send-daily-performance-platform-stats')
|
||||||
@statsd(namespace="tasks")
|
@statsd(namespace="tasks")
|
||||||
def send_daily_performance_platform_stats():
|
def send_daily_performance_platform_stats(date=None):
|
||||||
|
# date is a string in the format of "YYYY-MM-DD"
|
||||||
|
if date is None:
|
||||||
|
date = (datetime.utcnow() - timedelta(days=1)).date()
|
||||||
|
else:
|
||||||
|
date = datetime.strptime(date, "%Y-%m-%d").date()
|
||||||
|
|
||||||
if performance_platform_client.active:
|
if performance_platform_client.active:
|
||||||
yesterday = datetime.utcnow() - timedelta(days=1)
|
|
||||||
send_total_sent_notifications_to_performance_platform(bst_date=yesterday.date())
|
send_total_sent_notifications_to_performance_platform(bst_date=date)
|
||||||
processing_time.send_processing_time_to_performance_platform()
|
processing_time.send_processing_time_to_performance_platform(bst_date=date)
|
||||||
|
|
||||||
|
|
||||||
def send_total_sent_notifications_to_performance_platform(bst_date):
|
def send_total_sent_notifications_to_performance_platform(bst_date):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from notifications_utils.statsd_decorators import statsd
|
|||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from app import notify_celery
|
from app import notify_celery, zendesk_client
|
||||||
from app.celery.tasks import process_job
|
from app.celery.tasks import process_job
|
||||||
from app.config import QueueNames, TaskNames
|
from app.config import QueueNames, TaskNames
|
||||||
from app.dao.invited_org_user_dao import delete_org_invitations_created_more_than_two_days_ago
|
from app.dao.invited_org_user_dao import delete_org_invitations_created_more_than_two_days_ago
|
||||||
@@ -19,7 +19,9 @@ from app.dao.notifications_dao import (
|
|||||||
is_delivery_slow_for_provider,
|
is_delivery_slow_for_provider,
|
||||||
dao_get_scheduled_notifications,
|
dao_get_scheduled_notifications,
|
||||||
set_scheduled_notification_to_processed,
|
set_scheduled_notification_to_processed,
|
||||||
notifications_not_yet_sent
|
notifications_not_yet_sent,
|
||||||
|
dao_precompiled_letters_still_pending_virus_check,
|
||||||
|
dao_old_letters_with_created_status,
|
||||||
)
|
)
|
||||||
from app.dao.provider_details_dao import (
|
from app.dao.provider_details_dao import (
|
||||||
get_current_provider,
|
get_current_provider,
|
||||||
@@ -178,3 +180,45 @@ def replay_created_notifications():
|
|||||||
|
|
||||||
for n in notifications_to_resend:
|
for n in notifications_to_resend:
|
||||||
send_notification_to_queue(notification=n, research_mode=n.service.research_mode)
|
send_notification_to_queue(notification=n, research_mode=n.service.research_mode)
|
||||||
|
|
||||||
|
|
||||||
|
@notify_celery.task(name='check-precompiled-letter-state')
|
||||||
|
@statsd(namespace="tasks")
|
||||||
|
def check_precompiled_letter_state():
|
||||||
|
letters = dao_precompiled_letters_still_pending_virus_check()
|
||||||
|
|
||||||
|
if len(letters) > 0:
|
||||||
|
letter_ids = [str(letter.id) for letter in letters]
|
||||||
|
|
||||||
|
msg = "{} precompiled letters have been pending-virus-check for over 90 minutes. " \
|
||||||
|
"Notifications: {}".format(len(letters), letter_ids)
|
||||||
|
|
||||||
|
current_app.logger.exception(msg)
|
||||||
|
|
||||||
|
if current_app.config['NOTIFY_ENVIRONMENT'] in ['live', 'production', 'test']:
|
||||||
|
zendesk_client.create_ticket(
|
||||||
|
subject="[{}] Letters still pending virus check".format(current_app.config['NOTIFY_ENVIRONMENT']),
|
||||||
|
message=msg,
|
||||||
|
ticket_type=zendesk_client.TYPE_INCIDENT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@notify_celery.task(name='check-templated-letter-state')
|
||||||
|
@statsd(namespace="tasks")
|
||||||
|
def check_templated_letter_state():
|
||||||
|
letters = dao_old_letters_with_created_status()
|
||||||
|
|
||||||
|
if len(letters) > 0:
|
||||||
|
letter_ids = [str(letter.id) for letter in letters]
|
||||||
|
|
||||||
|
msg = "{} letters were created before 17.30 yesterday and still have 'created' status. " \
|
||||||
|
"Notifications: {}".format(len(letters), letter_ids)
|
||||||
|
|
||||||
|
current_app.logger.exception(msg)
|
||||||
|
|
||||||
|
if current_app.config['NOTIFY_ENVIRONMENT'] in ['live', 'production', 'test']:
|
||||||
|
zendesk_client.create_ticket(
|
||||||
|
subject="[{}] Letters still in 'created' status".format(current_app.config['NOTIFY_ENVIRONMENT']),
|
||||||
|
message=msg,
|
||||||
|
ticket_type=zendesk_client.TYPE_INCIDENT
|
||||||
|
)
|
||||||
|
|||||||
@@ -259,6 +259,11 @@ class Config(object):
|
|||||||
# since we mark jobs as archived
|
# since we mark jobs as archived
|
||||||
'options': {'queue': QueueNames.PERIODIC},
|
'options': {'queue': QueueNames.PERIODIC},
|
||||||
},
|
},
|
||||||
|
'check-templated-letter-state': {
|
||||||
|
'task': 'check-templated-letter-state',
|
||||||
|
'schedule': crontab(day_of_week='mon-fri', hour=9, minute=0),
|
||||||
|
'options': {'queue', QueueNames.PERIODIC}
|
||||||
|
},
|
||||||
'raise-alert-if-letter-notifications-still-sending': {
|
'raise-alert-if-letter-notifications-still-sending': {
|
||||||
'task': 'raise-alert-if-letter-notifications-still-sending',
|
'task': 'raise-alert-if-letter-notifications-still-sending',
|
||||||
'schedule': crontab(hour=16, minute=30),
|
'schedule': crontab(hour=16, minute=30),
|
||||||
@@ -276,6 +281,11 @@ class Config(object):
|
|||||||
'schedule': crontab(hour=23, minute=00),
|
'schedule': crontab(hour=23, minute=00),
|
||||||
'options': {'queue': QueueNames.PERIODIC}
|
'options': {'queue': QueueNames.PERIODIC}
|
||||||
},
|
},
|
||||||
|
'check-precompiled-letter-state': {
|
||||||
|
'task': 'check-precompiled-letter-state',
|
||||||
|
'schedule': crontab(day_of_week='mon-fri', hour='9,15', minute=0),
|
||||||
|
'options': {'queue', QueueNames.PERIODIC}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
CELERY_QUEUES = []
|
CELERY_QUEUES = []
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from notifications_utils.recipients import (
|
|||||||
try_validate_and_format_phone_number
|
try_validate_and_format_phone_number
|
||||||
)
|
)
|
||||||
from notifications_utils.statsd_decorators import statsd
|
from notifications_utils.statsd_decorators import statsd
|
||||||
from notifications_utils.timezones import convert_utc_to_bst
|
from notifications_utils.timezones import convert_bst_to_utc, convert_utc_to_bst
|
||||||
from sqlalchemy import (desc, func, asc)
|
from sqlalchemy import (desc, func, asc)
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
@@ -692,6 +692,32 @@ def notifications_not_yet_sent(should_be_sending_after_seconds, notification_typ
|
|||||||
return notifications
|
return notifications
|
||||||
|
|
||||||
|
|
||||||
|
def dao_old_letters_with_created_status():
|
||||||
|
yesterday_bst = convert_utc_to_bst(datetime.utcnow()) - timedelta(days=1)
|
||||||
|
last_processing_deadline = yesterday_bst.replace(hour=17, minute=30, second=0, microsecond=0)
|
||||||
|
|
||||||
|
notifications = Notification.query.filter(
|
||||||
|
Notification.created_at < convert_bst_to_utc(last_processing_deadline),
|
||||||
|
Notification.notification_type == LETTER_TYPE,
|
||||||
|
Notification.status == NOTIFICATION_CREATED
|
||||||
|
).order_by(
|
||||||
|
Notification.created_at
|
||||||
|
).all()
|
||||||
|
return notifications
|
||||||
|
|
||||||
|
|
||||||
|
def dao_precompiled_letters_still_pending_virus_check():
|
||||||
|
ninety_minutes_ago = datetime.utcnow() - timedelta(seconds=5400)
|
||||||
|
|
||||||
|
notifications = Notification.query.filter(
|
||||||
|
Notification.created_at < ninety_minutes_ago,
|
||||||
|
Notification.status == NOTIFICATION_PENDING_VIRUS_CHECK
|
||||||
|
).order_by(
|
||||||
|
Notification.created_at
|
||||||
|
).all()
|
||||||
|
return notifications
|
||||||
|
|
||||||
|
|
||||||
def guess_notification_type(search_term):
|
def guess_notification_type(search_term):
|
||||||
if set(search_term) & set(string.ascii_letters + '@'):
|
if set(search_term) & set(string.ascii_letters + '@'):
|
||||||
return EMAIL_TYPE
|
return EMAIL_TYPE
|
||||||
|
|||||||
@@ -344,6 +344,8 @@ class Organisation(db.Model):
|
|||||||
db.ForeignKey('users.id'),
|
db.ForeignKey('users.id'),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
agreement_signed_on_behalf_of_name = db.Column(db.String(255), nullable=True)
|
||||||
|
agreement_signed_on_behalf_of_email_address = db.Column(db.String(255), nullable=True)
|
||||||
agreement_signed_version = db.Column(db.Float, nullable=True)
|
agreement_signed_version = db.Column(db.Float, nullable=True)
|
||||||
crown = db.Column(db.Boolean, nullable=True)
|
crown = db.Column(db.Boolean, nullable=True)
|
||||||
organisation_type = db.Column(db.String(255), nullable=True)
|
organisation_type = db.Column(db.String(255), nullable=True)
|
||||||
@@ -367,6 +369,13 @@ class Organisation(db.Model):
|
|||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def live_services(self):
|
||||||
|
return [
|
||||||
|
service for service in self.services
|
||||||
|
if service.active and not service.restricted
|
||||||
|
]
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
return {
|
return {
|
||||||
"id": str(self.id),
|
"id": str(self.id),
|
||||||
@@ -379,11 +388,14 @@ class Organisation(db.Model):
|
|||||||
"agreement_signed": self.agreement_signed,
|
"agreement_signed": self.agreement_signed,
|
||||||
"agreement_signed_at": self.agreement_signed_at,
|
"agreement_signed_at": self.agreement_signed_at,
|
||||||
"agreement_signed_by_id": self.agreement_signed_by_id,
|
"agreement_signed_by_id": self.agreement_signed_by_id,
|
||||||
|
"agreement_signed_on_behalf_of_name": self.agreement_signed_on_behalf_of_name,
|
||||||
|
"agreement_signed_on_behalf_of_email_address": self.agreement_signed_on_behalf_of_email_address,
|
||||||
"agreement_signed_version": self.agreement_signed_version,
|
"agreement_signed_version": self.agreement_signed_version,
|
||||||
"domains": [
|
"domains": [
|
||||||
domain.domain for domain in self.domains
|
domain.domain for domain in self.domains
|
||||||
],
|
],
|
||||||
"request_to_go_live_notes": self.request_to_go_live_notes,
|
"request_to_go_live_notes": self.request_to_go_live_notes,
|
||||||
|
"count_of_live_services": len(self.live_services),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
from datetime import datetime
|
from datetime import timedelta
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc
|
from app.utils import get_london_midnight_in_utc
|
||||||
from app.dao.notifications_dao import dao_get_total_notifications_sent_per_day_for_performance_platform
|
from app.dao.notifications_dao import dao_get_total_notifications_sent_per_day_for_performance_platform
|
||||||
from app import performance_platform_client
|
from app import performance_platform_client
|
||||||
|
|
||||||
|
|
||||||
def send_processing_time_to_performance_platform():
|
def send_processing_time_to_performance_platform(bst_date):
|
||||||
today = datetime.utcnow()
|
start_time = get_london_midnight_in_utc(bst_date)
|
||||||
start_time = get_midnight_for_day_before(today)
|
end_time = get_london_midnight_in_utc(bst_date + timedelta(days=1))
|
||||||
end_time = get_london_midnight_in_utc(today)
|
|
||||||
|
|
||||||
send_processing_time_for_start_and_end(start_time, end_time)
|
send_processing_time_for_start_and_end(start_time, end_time)
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -515,7 +515,8 @@ def get_orgs_and_services(user):
|
|||||||
}
|
}
|
||||||
for service in org.services
|
for service in org.services
|
||||||
if service.active and service in user.services
|
if service.active and service in user.services
|
||||||
]
|
],
|
||||||
|
'count_of_live_services': len(org.live_services),
|
||||||
}
|
}
|
||||||
for org in user.organisations if org.active
|
for org in user.organisations if org.active
|
||||||
],
|
],
|
||||||
@@ -534,5 +535,14 @@ def get_orgs_and_services(user):
|
|||||||
service.organisation not in user.organisations
|
service.organisation not in user.organisations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
],
|
||||||
|
'services': [
|
||||||
|
{
|
||||||
|
'id': service.id,
|
||||||
|
'name': service.name,
|
||||||
|
'restricted': service.restricted,
|
||||||
|
'organisation': service.organisation.id if service.organisation else None,
|
||||||
|
}
|
||||||
|
for service in user.services if service.active
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ applications:
|
|||||||
MMG_INBOUND_SMS_AUTH: '{{ MMG_INBOUND_SMS_AUTH | tojson }}'
|
MMG_INBOUND_SMS_AUTH: '{{ MMG_INBOUND_SMS_AUTH | tojson }}'
|
||||||
MMG_INBOUND_SMS_USERNAME: '{{ MMG_INBOUND_SMS_USERNAME | tojson }}'
|
MMG_INBOUND_SMS_USERNAME: '{{ MMG_INBOUND_SMS_USERNAME | tojson }}'
|
||||||
|
|
||||||
|
FIRETEXT_URL: '{{ FIRETEXT_URL }}'
|
||||||
FIRETEXT_API_KEY: '{{ FIRETEXT_API_KEY }}'
|
FIRETEXT_API_KEY: '{{ FIRETEXT_API_KEY }}'
|
||||||
LOADTESTING_API_KEY: '{{ LOADTESTING_API_KEY }}'
|
LOADTESTING_API_KEY: '{{ LOADTESTING_API_KEY }}'
|
||||||
FIRETEXT_INBOUND_SMS_AUTH: '{{ FIRETEXT_INBOUND_SMS_AUTH | tojson }}'
|
FIRETEXT_INBOUND_SMS_AUTH: '{{ FIRETEXT_INBOUND_SMS_AUTH | tojson }}'
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Revision ID: 0296_agreement_signed_by_person
|
||||||
|
Revises: 0295_api_key_constraint
|
||||||
|
Create Date: 2019-06-13 16:40:32.982607
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
revision = '0296_agreement_signed_by_person'
|
||||||
|
down_revision = '0295_api_key_constraint'
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('organisation', sa.Column('agreement_signed_on_behalf_of_email_address', sa.String(length=255), nullable=True))
|
||||||
|
op.add_column('organisation', sa.Column('agreement_signed_on_behalf_of_name', sa.String(length=255), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_column('organisation', 'agreement_signed_on_behalf_of_name')
|
||||||
|
op.drop_column('organisation', 'agreement_signed_on_behalf_of_email_address')
|
||||||
@@ -23,6 +23,7 @@ from app.celery.letters_pdf_tasks import (
|
|||||||
process_virus_scan_failed,
|
process_virus_scan_failed,
|
||||||
process_virus_scan_error,
|
process_virus_scan_error,
|
||||||
replay_letters_in_error,
|
replay_letters_in_error,
|
||||||
|
_move_invalid_letter_and_update_status,
|
||||||
_sanitise_precompiled_pdf
|
_sanitise_precompiled_pdf
|
||||||
)
|
)
|
||||||
from app.letters.utils import get_letter_pdf_filename, ScanErrorType
|
from app.letters.utils import get_letter_pdf_filename, ScanErrorType
|
||||||
@@ -46,6 +47,10 @@ from tests.conftest import set_config_values
|
|||||||
|
|
||||||
def test_should_have_decorated_tasks_functions():
|
def test_should_have_decorated_tasks_functions():
|
||||||
assert create_letters_pdf.__wrapped__.__name__ == 'create_letters_pdf'
|
assert create_letters_pdf.__wrapped__.__name__ == 'create_letters_pdf'
|
||||||
|
assert collate_letter_pdfs_for_day.__wrapped__.__name__ == 'collate_letter_pdfs_for_day'
|
||||||
|
assert process_virus_scan_passed.__wrapped__.__name__ == 'process_virus_scan_passed'
|
||||||
|
assert process_virus_scan_failed.__wrapped__.__name__ == 'process_virus_scan_failed'
|
||||||
|
assert process_virus_scan_error.__wrapped__.__name__ == 'process_virus_scan_error'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('personalisation', [{'name': 'test'}, None])
|
@pytest.mark.parametrize('personalisation', [{'name': 'test'}, None])
|
||||||
@@ -517,6 +522,67 @@ def test_process_letter_task_check_virus_scan_passed_when_file_cannot_be_opened(
|
|||||||
assert sample_letter_notification.billable_units == 0
|
assert sample_letter_notification.billable_units == 0
|
||||||
|
|
||||||
|
|
||||||
|
@mock_s3
|
||||||
|
def test_process_virus_scan_passed_logs_error_and_sets_tech_failure_if_s3_error_uploading_to_live_bucket(
|
||||||
|
mocker,
|
||||||
|
sample_letter_notification,
|
||||||
|
):
|
||||||
|
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
|
||||||
|
|
||||||
|
sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
|
||||||
|
filename = 'NOTIFY.{}'.format(sample_letter_notification.reference)
|
||||||
|
|
||||||
|
source_bucket_name = current_app.config['LETTERS_SCAN_BUCKET_NAME']
|
||||||
|
conn = boto3.resource('s3', region_name='eu-west-1')
|
||||||
|
conn.create_bucket(Bucket=source_bucket_name)
|
||||||
|
|
||||||
|
s3 = boto3.client('s3', region_name='eu-west-1')
|
||||||
|
s3.put_object(Bucket=source_bucket_name, Key=filename, Body=b'pdf_content')
|
||||||
|
|
||||||
|
mocker.patch('app.celery.letters_pdf_tasks._get_page_count', return_value=1)
|
||||||
|
mocker.patch('app.celery.letters_pdf_tasks._sanitise_precompiled_pdf', return_value=b'pdf_content')
|
||||||
|
|
||||||
|
error_response = {
|
||||||
|
'Error': {
|
||||||
|
'Code': 'InvalidParameterValue',
|
||||||
|
'Message': 'some error message from amazon',
|
||||||
|
'Type': 'Sender'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mocker.patch('app.celery.letters_pdf_tasks._upload_pdf_to_test_or_live_pdf_bucket',
|
||||||
|
side_effect=ClientError(error_response, 'operation_name'))
|
||||||
|
|
||||||
|
process_virus_scan_passed(filename)
|
||||||
|
|
||||||
|
assert sample_letter_notification.status == NOTIFICATION_TECHNICAL_FAILURE
|
||||||
|
mock_logger.assert_called_once_with(
|
||||||
|
'Error uploading letter to live pdf bucket for notification: {}'.format(sample_letter_notification.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_invalid_letter_and_update_status_logs_error_and_sets_tech_failure_state_if_s3_error(
|
||||||
|
mocker,
|
||||||
|
sample_letter_notification,
|
||||||
|
):
|
||||||
|
error_response = {
|
||||||
|
'Error': {
|
||||||
|
'Code': 'InvalidParameterValue',
|
||||||
|
'Message': 'some error message from amazon',
|
||||||
|
'Type': 'Sender'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mocker.patch('app.celery.letters_pdf_tasks.move_scan_to_invalid_pdf_bucket',
|
||||||
|
side_effect=ClientError(error_response, 'operation_name'))
|
||||||
|
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
|
||||||
|
|
||||||
|
_move_invalid_letter_and_update_status(sample_letter_notification, 'filename', mocker.Mock())
|
||||||
|
|
||||||
|
assert sample_letter_notification.status == NOTIFICATION_TECHNICAL_FAILURE
|
||||||
|
mock_logger.assert_called_once_with(
|
||||||
|
'Error when moving letter with id {} to invalid PDF bucket'.format(sample_letter_notification.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_process_letter_task_check_virus_scan_failed(sample_letter_notification, mocker):
|
def test_process_letter_task_check_virus_scan_failed(sample_letter_notification, mocker):
|
||||||
filename = 'NOTIFY.{}'.format(sample_letter_notification.reference)
|
filename = 'NOTIFY.{}'.format(sample_letter_notification.reference)
|
||||||
sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
|
sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from app.celery.scheduled_tasks import (
|
|||||||
run_scheduled_jobs,
|
run_scheduled_jobs,
|
||||||
send_scheduled_notifications,
|
send_scheduled_notifications,
|
||||||
switch_current_sms_provider_on_slow_delivery,
|
switch_current_sms_provider_on_slow_delivery,
|
||||||
replay_created_notifications
|
replay_created_notifications,
|
||||||
|
check_precompiled_letter_state,
|
||||||
|
check_templated_letter_state,
|
||||||
)
|
)
|
||||||
from app.config import QueueNames, TaskNames
|
from app.config import QueueNames, TaskNames
|
||||||
from app.dao.jobs_dao import dao_get_job_by_id
|
from app.dao.jobs_dao import dao_get_job_by_id
|
||||||
@@ -26,6 +28,8 @@ from app.models import (
|
|||||||
JOB_STATUS_IN_PROGRESS,
|
JOB_STATUS_IN_PROGRESS,
|
||||||
JOB_STATUS_ERROR,
|
JOB_STATUS_ERROR,
|
||||||
JOB_STATUS_FINISHED,
|
JOB_STATUS_FINISHED,
|
||||||
|
NOTIFICATION_DELIVERED,
|
||||||
|
NOTIFICATION_PENDING_VIRUS_CHECK,
|
||||||
)
|
)
|
||||||
from app.v2.errors import JobIncompleteError
|
from app.v2.errors import JobIncompleteError
|
||||||
|
|
||||||
@@ -58,7 +62,7 @@ def prepare_current_provider(restore_provider_details):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def test_should_call_delete_codes_on_delete_verify_codes_task(notify_api, mocker):
|
def test_should_call_delete_codes_on_delete_verify_codes_task(notify_db_session, mocker):
|
||||||
mocker.patch('app.celery.scheduled_tasks.delete_codes_older_created_more_than_a_day_ago')
|
mocker.patch('app.celery.scheduled_tasks.delete_codes_older_created_more_than_a_day_ago')
|
||||||
delete_verify_codes()
|
delete_verify_codes()
|
||||||
assert scheduled_tasks.delete_codes_older_created_more_than_a_day_ago.call_count == 1
|
assert scheduled_tasks.delete_codes_older_created_more_than_a_day_ago.call_count == 1
|
||||||
@@ -334,3 +338,84 @@ def test_check_job_status_task_does_not_raise_error(sample_template):
|
|||||||
job_status=JOB_STATUS_FINISHED)
|
job_status=JOB_STATUS_FINISHED)
|
||||||
|
|
||||||
check_job_status()
|
check_job_status()
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2019-05-30 14:00:00")
|
||||||
|
def test_check_precompiled_letter_state(mocker, sample_letter_template):
|
||||||
|
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
|
||||||
|
mock_create_ticket = mocker.patch('app.celery.nightly_tasks.zendesk_client.create_ticket')
|
||||||
|
|
||||||
|
create_notification(template=sample_letter_template,
|
||||||
|
status=NOTIFICATION_PENDING_VIRUS_CHECK,
|
||||||
|
created_at=datetime.utcnow() - timedelta(seconds=5400))
|
||||||
|
create_notification(template=sample_letter_template,
|
||||||
|
status=NOTIFICATION_DELIVERED,
|
||||||
|
created_at=datetime.utcnow() - timedelta(seconds=6000))
|
||||||
|
noti_1 = create_notification(template=sample_letter_template,
|
||||||
|
status=NOTIFICATION_PENDING_VIRUS_CHECK,
|
||||||
|
created_at=datetime.utcnow() - timedelta(seconds=5401))
|
||||||
|
noti_2 = create_notification(template=sample_letter_template,
|
||||||
|
status=NOTIFICATION_PENDING_VIRUS_CHECK,
|
||||||
|
created_at=datetime.utcnow() - timedelta(seconds=70000))
|
||||||
|
|
||||||
|
check_precompiled_letter_state()
|
||||||
|
|
||||||
|
message = "2 precompiled letters have been pending-virus-check for over 90 minutes. " \
|
||||||
|
"Notifications: ['{}', '{}']".format(noti_2.id, noti_1.id)
|
||||||
|
|
||||||
|
mock_logger.assert_called_once_with(message)
|
||||||
|
mock_create_ticket.assert_called_with(
|
||||||
|
message=message,
|
||||||
|
subject='[test] Letters still pending virus check',
|
||||||
|
ticket_type='incident'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2019-05-30 14:00:00")
|
||||||
|
def test_check_templated_letter_state_during_bst(mocker, sample_letter_template):
|
||||||
|
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
|
||||||
|
mock_create_ticket = mocker.patch('app.celery.nightly_tasks.zendesk_client.create_ticket')
|
||||||
|
|
||||||
|
noti_1 = create_notification(template=sample_letter_template, created_at=datetime(2019, 5, 1, 12, 0))
|
||||||
|
noti_2 = create_notification(template=sample_letter_template, created_at=datetime(2019, 5, 29, 16, 29))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 5, 29, 16, 30))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 5, 29, 17, 29))
|
||||||
|
create_notification(template=sample_letter_template, status='delivered', created_at=datetime(2019, 5, 28, 10, 0))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 5, 30, 10, 0))
|
||||||
|
|
||||||
|
check_templated_letter_state()
|
||||||
|
|
||||||
|
message = "2 letters were created before 17.30 yesterday and still have 'created' status. " \
|
||||||
|
"Notifications: ['{}', '{}']".format(noti_1.id, noti_2.id)
|
||||||
|
|
||||||
|
mock_logger.assert_called_once_with(message)
|
||||||
|
mock_create_ticket.assert_called_with(
|
||||||
|
message=message,
|
||||||
|
subject="[test] Letters still in 'created' status",
|
||||||
|
ticket_type='incident'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2019-01-30 14:00:00")
|
||||||
|
def test_check_templated_letter_state_during_utc(mocker, sample_letter_template):
|
||||||
|
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
|
||||||
|
mock_create_ticket = mocker.patch('app.celery.nightly_tasks.zendesk_client.create_ticket')
|
||||||
|
|
||||||
|
noti_1 = create_notification(template=sample_letter_template, created_at=datetime(2018, 12, 1, 12, 0))
|
||||||
|
noti_2 = create_notification(template=sample_letter_template, created_at=datetime(2019, 1, 29, 17, 29))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 1, 29, 17, 30))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 1, 29, 18, 29))
|
||||||
|
create_notification(template=sample_letter_template, status='delivered', created_at=datetime(2019, 1, 29, 10, 0))
|
||||||
|
create_notification(template=sample_letter_template, created_at=datetime(2019, 1, 30, 10, 0))
|
||||||
|
|
||||||
|
check_templated_letter_state()
|
||||||
|
|
||||||
|
message = "2 letters were created before 17.30 yesterday and still have 'created' status. " \
|
||||||
|
"Notifications: ['{}', '{}']".format(noti_1.id, noti_2.id)
|
||||||
|
|
||||||
|
mock_logger.assert_called_once_with(message)
|
||||||
|
mock_create_ticket.assert_called_with(
|
||||||
|
message=message,
|
||||||
|
subject="[test] Letters still in 'created' status",
|
||||||
|
ticket_type='incident'
|
||||||
|
)
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ def test_get_all_organisations(admin_request, notify_db_session):
|
|||||||
assert len(response) == 2
|
assert len(response) == 2
|
||||||
assert response[0]['name'] == 'active org'
|
assert response[0]['name'] == 'active org'
|
||||||
assert response[0]['active'] is True
|
assert response[0]['active'] is True
|
||||||
|
assert response[0]['count_of_live_services'] == 0
|
||||||
assert response[1]['name'] == 'inactive org'
|
assert response[1]['name'] == 'inactive org'
|
||||||
assert response[1]['active'] is False
|
assert response[1]['active'] is False
|
||||||
|
assert response[1]['count_of_live_services'] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_get_organisation_by_id(admin_request, notify_db_session):
|
def test_get_organisation_by_id(admin_request, notify_db_session):
|
||||||
@@ -49,10 +51,13 @@ def test_get_organisation_by_id(admin_request, notify_db_session):
|
|||||||
'agreement_signed_at',
|
'agreement_signed_at',
|
||||||
'agreement_signed_by_id',
|
'agreement_signed_by_id',
|
||||||
'agreement_signed_version',
|
'agreement_signed_version',
|
||||||
|
'agreement_signed_on_behalf_of_name',
|
||||||
|
'agreement_signed_on_behalf_of_email_address',
|
||||||
'letter_branding_id',
|
'letter_branding_id',
|
||||||
'email_branding_id',
|
'email_branding_id',
|
||||||
'domains',
|
'domains',
|
||||||
'request_to_go_live_notes',
|
'request_to_go_live_notes',
|
||||||
|
'count_of_live_services',
|
||||||
}
|
}
|
||||||
assert response['id'] == str(org.id)
|
assert response['id'] == str(org.id)
|
||||||
assert response['name'] == 'test_org_1'
|
assert response['name'] == 'test_org_1'
|
||||||
@@ -66,6 +71,9 @@ def test_get_organisation_by_id(admin_request, notify_db_session):
|
|||||||
assert response['email_branding_id'] is None
|
assert response['email_branding_id'] is None
|
||||||
assert response['domains'] == []
|
assert response['domains'] == []
|
||||||
assert response['request_to_go_live_notes'] is None
|
assert response['request_to_go_live_notes'] is None
|
||||||
|
assert response['count_of_live_services'] == 0
|
||||||
|
assert response['agreement_signed_on_behalf_of_name'] is None
|
||||||
|
assert response['agreement_signed_on_behalf_of_email_address'] is None
|
||||||
|
|
||||||
|
|
||||||
def test_get_organisation_by_id_returns_domains(admin_request, notify_db_session):
|
def test_get_organisation_by_id_returns_domains(admin_request, notify_db_session):
|
||||||
@@ -193,6 +201,8 @@ def test_post_update_organisation_updates_fields(
|
|||||||
'active': False,
|
'active': False,
|
||||||
'agreement_signed': agreement_signed,
|
'agreement_signed': agreement_signed,
|
||||||
'crown': crown,
|
'crown': crown,
|
||||||
|
'agreement_signed_on_behalf_of_name': 'Firstname Lastname',
|
||||||
|
'agreement_signed_on_behalf_of_email_address': 'test@example.com',
|
||||||
}
|
}
|
||||||
assert org.agreement_signed is None
|
assert org.agreement_signed is None
|
||||||
assert org.crown is None
|
assert org.crown is None
|
||||||
@@ -213,6 +223,8 @@ def test_post_update_organisation_updates_fields(
|
|||||||
assert organisation[0].agreement_signed == agreement_signed
|
assert organisation[0].agreement_signed == agreement_signed
|
||||||
assert organisation[0].crown == crown
|
assert organisation[0].crown == crown
|
||||||
assert organisation[0].domains == []
|
assert organisation[0].domains == []
|
||||||
|
assert organisation[0].agreement_signed_on_behalf_of_name == 'Firstname Lastname'
|
||||||
|
assert organisation[0].agreement_signed_on_behalf_of_email_address == 'test@example.com'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('domain_list', (
|
@pytest.mark.parametrize('domain_list', (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, date
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ def test_send_processing_time_to_performance_platform_generates_correct_calls(mo
|
|||||||
create_notification(sample_template, created_at=created_at, sent_at=created_at + timedelta(seconds=15))
|
create_notification(sample_template, created_at=created_at, sent_at=created_at + timedelta(seconds=15))
|
||||||
create_notification(sample_template, created_at=datetime.utcnow() - timedelta(days=2))
|
create_notification(sample_template, created_at=datetime.utcnow() - timedelta(days=2))
|
||||||
|
|
||||||
send_processing_time_to_performance_platform()
|
send_processing_time_to_performance_platform(date(2016, 10, 17))
|
||||||
|
|
||||||
send_mock.assert_any_call(datetime(2016, 10, 16, 23, 0), 'messages-total', 2)
|
send_mock.assert_any_call(datetime(2016, 10, 16, 23, 0), 'messages-total', 2)
|
||||||
send_mock.assert_any_call(datetime(2016, 10, 16, 23, 0), 'messages-within-10-secs', 1)
|
send_mock.assert_any_call(datetime(2016, 10, 16, 23, 0), 'messages-within-10-secs', 1)
|
||||||
|
|||||||
+82
-19
@@ -835,8 +835,12 @@ def test_get_orgs_and_services_nests_services(admin_request, sample_user):
|
|||||||
|
|
||||||
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
||||||
|
|
||||||
assert resp == {
|
assert set(resp.keys()) == {
|
||||||
'organisations': [
|
'organisations',
|
||||||
|
'services_without_organisations',
|
||||||
|
'services',
|
||||||
|
}
|
||||||
|
assert resp['organisations'] == [
|
||||||
{
|
{
|
||||||
'name': org1.name,
|
'name': org1.name,
|
||||||
'id': str(org1.id),
|
'id': str(org1.id),
|
||||||
@@ -851,22 +855,43 @@ def test_get_orgs_and_services_nests_services(admin_request, sample_user):
|
|||||||
'id': str(service2.id),
|
'id': str(service2.id),
|
||||||
'restricted': False,
|
'restricted': False,
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
'count_of_live_services': 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': org2.name,
|
'name': org2.name,
|
||||||
'id': str(org2.id),
|
'id': str(org2.id),
|
||||||
'services': []
|
'services': [],
|
||||||
}
|
'count_of_live_services': 0,
|
||||||
],
|
},
|
||||||
'services_without_organisations': [
|
]
|
||||||
|
assert resp['services_without_organisations'] == [
|
||||||
{
|
{
|
||||||
'name': service3.name,
|
'name': service3.name,
|
||||||
'id': str(service3.id),
|
'id': str(service3.id),
|
||||||
'restricted': False,
|
'restricted': False,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
assert resp['services'] == [
|
||||||
|
{
|
||||||
|
'name': service1.name,
|
||||||
|
'id': str(service1.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': str(org1.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': service2.name,
|
||||||
|
'id': str(service2.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': str(org1.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': service3.name,
|
||||||
|
'id': str(service3.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_get_orgs_and_services_only_returns_active(admin_request, sample_user):
|
def test_get_orgs_and_services_only_returns_active(admin_request, sample_user):
|
||||||
@@ -890,8 +915,12 @@ def test_get_orgs_and_services_only_returns_active(admin_request, sample_user):
|
|||||||
|
|
||||||
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
||||||
|
|
||||||
assert resp == {
|
assert set(resp.keys()) == {
|
||||||
'organisations': [
|
'organisations',
|
||||||
|
'services_without_organisations',
|
||||||
|
'services',
|
||||||
|
}
|
||||||
|
assert resp['organisations'] == [
|
||||||
{
|
{
|
||||||
'name': org1.name,
|
'name': org1.name,
|
||||||
'id': str(org1.id),
|
'id': str(org1.id),
|
||||||
@@ -901,17 +930,37 @@ def test_get_orgs_and_services_only_returns_active(admin_request, sample_user):
|
|||||||
'id': str(service1.id),
|
'id': str(service1.id),
|
||||||
'restricted': False,
|
'restricted': False,
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
'services_without_organisations': [
|
'count_of_live_services': 1,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert resp['services_without_organisations'] == [
|
||||||
{
|
{
|
||||||
'name': service4.name,
|
'name': service4.name,
|
||||||
'id': str(service4.id),
|
'id': str(service4.id),
|
||||||
'restricted': False,
|
'restricted': False,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
assert resp['services'] == [
|
||||||
|
{
|
||||||
|
'name': service1.name,
|
||||||
|
'id': str(service1.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': str(org1.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': service3.name,
|
||||||
|
'id': str(service3.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': str(org2.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': service4.name,
|
||||||
|
'id': str(service4.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_get_orgs_and_services_only_shows_users_orgs_and_services(admin_request, sample_user):
|
def test_get_orgs_and_services_only_shows_users_orgs_and_services(admin_request, sample_user):
|
||||||
@@ -932,23 +981,37 @@ def test_get_orgs_and_services_only_shows_users_orgs_and_services(admin_request,
|
|||||||
|
|
||||||
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
resp = admin_request.get('user.get_organisations_and_services_for_user', user_id=sample_user.id)
|
||||||
|
|
||||||
assert resp == {
|
assert set(resp.keys()) == {
|
||||||
'organisations': [
|
'organisations',
|
||||||
|
'services_without_organisations',
|
||||||
|
'services',
|
||||||
|
}
|
||||||
|
assert resp['organisations'] == [
|
||||||
{
|
{
|
||||||
'name': org2.name,
|
'name': org2.name,
|
||||||
'id': str(org2.id),
|
'id': str(org2.id),
|
||||||
'services': []
|
'services': [],
|
||||||
|
'count_of_live_services': 0,
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
# service1 belongs to org1, but the user doesn't know about org1
|
# service1 belongs to org1, but the user doesn't know about org1
|
||||||
'services_without_organisations': [
|
assert resp['services_without_organisations'] == [
|
||||||
{
|
{
|
||||||
'name': service1.name,
|
'name': service1.name,
|
||||||
'id': str(service1.id),
|
'id': str(service1.id),
|
||||||
'restricted': False,
|
'restricted': False,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
# 'services' always returns the org_id no matter whether the user
|
||||||
|
# belongs to that org or not
|
||||||
|
assert resp['services'] == [
|
||||||
|
{
|
||||||
|
'name': service1.name,
|
||||||
|
'id': str(service1.id),
|
||||||
|
'restricted': False,
|
||||||
|
'organisation': str(org1.id),
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_find_users_by_email_finds_user_by_partial_email(notify_db, client):
|
def test_find_users_by_email_finds_user_by_partial_email(notify_db, client):
|
||||||
|
|||||||
Reference in New Issue
Block a user