2025-01-13 10:00:18 -08:00
import json
2021-03-10 13:55:06 +00:00
from collections import namedtuple
2024-05-23 13:59:51 -07:00
from datetime import timedelta
2021-07-08 14:10:58 +01:00
from unittest import mock
2025-01-13 10:00:18 -08:00
from unittest . mock import ANY , MagicMock , call
2017-06-07 16:31:51 +01:00
2018-11-15 17:24:37 +00:00
import pytest
2017-06-07 16:31:51 +01:00
2016-06-20 13:33:53 +01:00
from app . celery import scheduled_tasks
2017-01-27 12:30:56 +00:00
from app . celery . scheduled_tasks import (
2025-01-13 10:00:18 -08:00
batch_insert_notifications ,
2021-03-10 13:55:06 +00:00
check_for_missing_rows_in_completed_jobs ,
check_for_services_with_high_failure_rates_or_sending_to_tv_numbers ,
2017-10-16 12:32:44 +01:00
check_job_status ,
2017-01-27 12:30:56 +00:00
delete_verify_codes ,
2023-11-10 16:14:03 -05:00
expire_or_delete_invitations ,
2025-01-13 11:37:54 -08:00
process_delivery_receipts ,
2019-06-11 13:16:34 +01:00
replay_created_notifications ,
2021-03-10 13:55:06 +00:00
run_scheduled_jobs ,
2017-11-09 14:13:42 +00:00
)
2023-03-02 20:20:31 -05:00
from app . config import QueueNames , Test
2016-08-24 17:03:56 +01:00
from app . dao . jobs_dao import dao_get_job_by_id
2024-02-09 12:24:09 -05:00
from app . enums import JobStatus , NotificationStatus , TemplateType
2024-05-23 13:59:51 -07:00
from app . utils import utc_now
2024-05-16 10:17:45 -04:00
from notifications_utils . clients . zendesk . zendesk_client import NotifySupportTicket
2019-11-05 16:47:00 +00:00
from tests . app import load_example_csv
2022-10-05 01:12:35 +00:00
from tests . app . db import create_job , create_notification , create_template
2016-06-20 13:33:53 +01:00
2024-12-27 10:28:08 -08:00
CHECK_JOB_STATUS_TOO_OLD_MINUTES = 241
2016-06-20 13:33:53 +01:00
2023-08-23 10:35:43 -07:00
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 "
)
2016-06-20 13:33:53 +01:00
delete_verify_codes ( )
2023-08-23 10:35:43 -07:00
assert (
scheduled_tasks . delete_codes_older_created_more_than_a_day_ago . call_count == 1
)
2016-06-20 13:33:53 +01:00
2023-11-10 16:14:03 -05:00
def test_should_call_expire_or_delete_invotations_on_expire_or_delete_invitations_task (
2023-08-23 10:35:43 -07:00
notify_db_session , mocker
) :
mocker . patch (
2023-11-07 15:28:27 -05:00
" app.celery.scheduled_tasks.expire_invitations_created_more_than_two_days_ago "
2023-08-23 10:35:43 -07:00
)
2023-11-07 15:28:27 -05:00
expire_or_delete_invitations ( )
2023-08-23 10:35:43 -07:00
assert (
2023-11-07 15:28:27 -05:00
scheduled_tasks . expire_invitations_created_more_than_two_days_ago . call_count
2023-08-23 10:35:43 -07:00
== 1
)
2016-06-20 13:33:53 +01:00
2019-10-30 10:51:07 +00:00
def test_should_update_scheduled_jobs_and_put_on_queue ( mocker , sample_template ) :
2023-08-23 10:35:43 -07:00
mocked = mocker . patch ( " app.celery.tasks.process_job.apply_async " )
2016-08-24 17:03:56 +01:00
2024-05-23 13:59:51 -07:00
one_minute_in_the_past = utc_now ( ) - timedelta ( minutes = 1 )
2023-08-23 10:35:43 -07:00
job = create_job (
2024-02-13 11:04:02 -05:00
sample_template ,
job_status = JobStatus . SCHEDULED ,
scheduled_for = one_minute_in_the_past ,
2023-08-23 10:35:43 -07:00
)
2016-08-24 17:03:56 +01:00
run_scheduled_jobs ( )
updated_job = dao_get_job_by_id ( job . id )
2024-02-09 12:24:09 -05:00
assert updated_job . job_status == JobStatus . PENDING
2017-05-25 10:51:49 +01:00
mocked . assert_called_with ( [ str ( job . id ) ] , queue = " job-tasks " )
2016-08-24 17:03:56 +01:00
2019-10-30 10:51:07 +00:00
def test_should_update_all_scheduled_jobs_and_put_on_queue ( sample_template , mocker ) :
2023-08-23 10:35:43 -07:00
mocked = mocker . patch ( " app.celery.tasks.process_job.apply_async " )
2016-08-24 17:03:56 +01:00
2024-05-23 13:59:51 -07:00
one_minute_in_the_past = utc_now ( ) - timedelta ( minutes = 1 )
ten_minutes_in_the_past = utc_now ( ) - timedelta ( minutes = 10 )
twenty_minutes_in_the_past = utc_now ( ) - timedelta ( minutes = 20 )
2023-08-23 10:35:43 -07:00
job_1 = create_job (
2024-02-13 11:04:02 -05:00
sample_template ,
job_status = JobStatus . SCHEDULED ,
scheduled_for = one_minute_in_the_past ,
2023-08-23 10:35:43 -07:00
)
job_2 = create_job (
2024-02-13 11:04:02 -05:00
sample_template ,
job_status = JobStatus . SCHEDULED ,
scheduled_for = ten_minutes_in_the_past ,
2023-08-23 10:35:43 -07:00
)
job_3 = create_job (
sample_template ,
2024-02-09 12:24:09 -05:00
job_status = JobStatus . SCHEDULED ,
2023-08-23 10:35:43 -07:00
scheduled_for = twenty_minutes_in_the_past ,
)
2016-08-24 17:03:56 +01:00
run_scheduled_jobs ( )
2024-02-09 12:24:09 -05:00
assert dao_get_job_by_id ( job_1 . id ) . job_status == JobStatus . PENDING
assert dao_get_job_by_id ( job_2 . id ) . job_status == JobStatus . PENDING
assert dao_get_job_by_id ( job_2 . id ) . job_status == JobStatus . PENDING
2016-08-24 17:03:56 +01:00
2023-08-23 10:35:43 -07:00
mocked . assert_has_calls (
[
call ( [ str ( job_3 . id ) ] , queue = " job-tasks " ) ,
call ( [ str ( job_2 . id ) ] , queue = " job-tasks " ) ,
call ( [ str ( job_1 . id ) ] , queue = " job-tasks " ) ,
]
)
2016-09-07 15:36:59 +01:00
2020-07-22 17:00:20 +01:00
def test_check_job_status_task_calls_process_incomplete_jobs ( mocker , sample_template ) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2023-08-23 10:35:43 -07:00
)
2017-10-12 16:21:08 +01:00
create_notification ( template = sample_template , job = job )
2020-07-22 17:00:20 +01:00
check_job_status ( )
2017-10-12 16:21:08 +01:00
2023-08-23 10:35:43 -07:00
mock_celery . assert_called_once_with ( [ [ str ( job . id ) ] ] , queue = QueueNames . JOBS )
2017-10-13 16:46:17 +01:00
2017-10-12 16:21:08 +01:00
2020-07-22 17:00:20 +01:00
def test_check_job_status_task_calls_process_incomplete_jobs_when_scheduled_job_is_not_complete (
mocker , sample_template
) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2023-08-23 10:35:43 -07:00
)
2020-07-22 17:00:20 +01:00
check_job_status ( )
2017-10-12 16:21:08 +01:00
2023-08-23 10:35:43 -07:00
mock_celery . assert_called_once_with ( [ [ str ( job . id ) ] ] , queue = QueueNames . JOBS )
2017-10-13 16:46:17 +01:00
2017-10-12 16:21:08 +01:00
2021-03-17 14:53:34 +00:00
def test_check_job_status_task_calls_process_incomplete_jobs_for_pending_scheduled_jobs (
mocker , sample_template
) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . PENDING ,
2023-08-23 10:35:43 -07:00
)
2021-03-17 14:53:34 +00:00
check_job_status ( )
2023-08-23 10:35:43 -07:00
mock_celery . assert_called_once_with ( [ [ str ( job . id ) ] ] , queue = QueueNames . JOBS )
2021-03-17 14:53:34 +00:00
def test_check_job_status_task_does_not_call_process_incomplete_jobs_for_non_scheduled_pending_jobs (
mocker ,
sample_template ,
) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
2021-03-17 14:53:34 +00:00
create_job (
template = sample_template ,
notification_count = 3 ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) - timedelta ( hours = 2 ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . PENDING ,
2021-03-17 14:53:34 +00:00
)
check_job_status ( )
assert not mock_celery . called
2023-08-23 10:35:43 -07:00
def test_check_job_status_task_calls_process_incomplete_jobs_for_multiple_jobs (
mocker , sample_template
) :
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2023-08-23 10:35:43 -07:00
)
job_2 = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2023-08-23 10:35:43 -07:00
)
2020-07-22 17:00:20 +01:00
check_job_status ( )
2017-10-12 16:21:08 +01:00
2017-10-13 16:46:17 +01:00
mock_celery . assert_called_once_with (
2023-08-23 10:35:43 -07:00
[ [ str ( job . id ) , str ( job_2 . id ) ] ] , queue = QueueNames . JOBS
2017-10-13 16:46:17 +01:00
)
2017-11-09 10:32:39 +00:00
2018-03-09 16:30:50 +00:00
def test_check_job_status_task_only_sends_old_tasks ( mocker , sample_template ) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
2018-03-09 16:30:50 +00:00
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2018-03-09 16:30:50 +00:00
)
2020-07-22 17:00:20 +01:00
create_job (
2018-03-09 16:30:50 +00:00
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( minutes = 300 ) ,
processing_started = utc_now ( ) - timedelta ( minutes = 239 ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2018-03-09 16:30:50 +00:00
)
2021-03-17 14:53:34 +00:00
create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( minutes = 300 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = 239 ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . PENDING ,
2021-03-17 14:53:34 +00:00
)
2020-07-22 17:00:20 +01:00
check_job_status ( )
2018-03-09 16:30:50 +00:00
2021-03-17 14:53:34 +00:00
# jobs 2 and 3 were created less than 30 minutes ago, so are not sent to Celery task
2023-08-23 10:35:43 -07:00
mock_celery . assert_called_once_with ( [ [ str ( job . id ) ] ] , queue = QueueNames . JOBS )
2018-03-09 16:30:50 +00:00
2018-03-09 16:34:47 +00:00
def test_check_job_status_task_sets_jobs_to_error ( mocker , sample_template ) :
2023-08-23 10:35:43 -07:00
mock_celery = mocker . patch ( " app.celery.tasks.process_incomplete_jobs.apply_async " )
2018-03-09 16:34:47 +00:00
job = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2018-03-09 16:34:47 +00:00
)
job_2 = create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( minutes = 300 ) ,
processing_started = utc_now ( ) - timedelta ( minutes = 239 ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . IN_PROGRESS ,
2018-03-09 16:34:47 +00:00
)
2020-07-22 17:00:20 +01:00
check_job_status ( )
2018-03-09 16:34:47 +00:00
# job 2 not in celery task
2023-08-23 10:35:43 -07:00
mock_celery . assert_called_once_with ( [ [ str ( job . id ) ] ] , queue = QueueNames . JOBS )
2024-01-16 14:46:17 -05:00
assert job . job_status == JobStatus . ERROR
assert job_2 . job_status == JobStatus . IN_PROGRESS
2018-03-09 16:34:47 +00:00
2018-03-23 15:38:35 +00:00
def test_replay_created_notifications ( notify_db_session , sample_service , mocker ) :
2023-08-23 10:35:43 -07:00
email_delivery_queue = mocker . patch (
" app.celery.provider_tasks.deliver_email.apply_async "
)
sms_delivery_queue = mocker . patch (
" app.celery.provider_tasks.deliver_sms.apply_async "
)
2018-03-23 15:38:35 +00:00
2024-02-13 11:04:02 -05:00
sms_template = create_template (
service = sample_service , template_type = TemplateType . SMS
)
email_template = create_template (
service = sample_service , template_type = TemplateType . EMAIL
)
2019-11-21 15:51:27 +00:00
older_than = ( 60 * 60 ) + ( 60 * 15 ) # 1 hour 15 minutes
2018-03-23 15:38:35 +00:00
# notifications expected to be resent
2023-08-23 10:35:43 -07:00
old_sms = create_notification (
template = sms_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) - timedelta ( seconds = older_than ) ,
2024-02-09 12:24:09 -05:00
status = NotificationStatus . CREATED ,
2023-08-23 10:35:43 -07:00
)
old_email = create_notification (
template = email_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) - timedelta ( seconds = older_than ) ,
2024-02-09 12:24:09 -05:00
status = NotificationStatus . CREATED ,
2023-08-23 10:35:43 -07:00
)
2018-03-23 15:38:35 +00:00
# notifications that are not to be resent
2023-08-23 10:35:43 -07:00
create_notification (
template = sms_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) - timedelta ( seconds = older_than ) ,
2024-02-09 12:24:09 -05:00
status = NotificationStatus . SENDING ,
2023-08-23 10:35:43 -07:00
)
create_notification (
template = email_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) - timedelta ( seconds = older_than ) ,
2024-02-09 12:24:09 -05:00
status = NotificationStatus . DELIVERED ,
2023-08-23 10:35:43 -07:00
)
create_notification (
2024-02-13 11:04:02 -05:00
template = sms_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) ,
2024-02-13 11:04:02 -05:00
status = NotificationStatus . CREATED ,
2023-08-23 10:35:43 -07:00
)
create_notification (
2024-02-13 11:04:02 -05:00
template = email_template ,
2024-05-23 13:59:51 -07:00
created_at = utc_now ( ) ,
2024-02-13 11:04:02 -05:00
status = NotificationStatus . CREATED ,
2023-08-23 10:35:43 -07:00
)
2018-03-23 15:38:35 +00:00
replay_created_notifications ( )
2023-08-23 10:35:43 -07:00
email_delivery_queue . assert_called_once_with (
2025-01-13 13:35:40 -08:00
[ str ( old_email . id ) ] , queue = " send-email-tasks " , countdown = 60
2023-08-23 10:35:43 -07:00
)
sms_delivery_queue . assert_called_once_with (
2025-01-13 13:35:40 -08:00
[ str ( old_sms . id ) ] , queue = " send-sms-tasks " , countdown = 60
2023-08-23 10:35:43 -07:00
)
2019-01-14 17:22:41 +00:00
def test_check_job_status_task_does_not_raise_error ( sample_template ) :
create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( hours = 5 ) ,
scheduled_for = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2023-08-23 10:35:43 -07:00
)
2019-01-14 17:22:41 +00:00
create_job (
template = sample_template ,
notification_count = 3 ,
2024-12-27 10:28:08 -08:00
created_at = utc_now ( ) - timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
processing_started = utc_now ( )
- timedelta ( minutes = CHECK_JOB_STATUS_TOO_OLD_MINUTES ) ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2023-08-23 10:35:43 -07:00
)
2019-01-14 17:22:41 +00:00
check_job_status ( )
2019-06-11 13:16:34 +01:00
2023-08-23 10:35:43 -07:00
@pytest.mark.parametrize (
" offset " ,
(
timedelta ( days = 1 ) ,
pytest . param ( timedelta ( hours = 23 , minutes = 59 ) , marks = pytest . mark . xfail ) ,
pytest . param ( timedelta ( minutes = 20 ) , marks = pytest . mark . xfail ) ,
timedelta ( minutes = 19 ) ,
) ,
)
2020-09-26 11:06:44 +01:00
def test_check_for_missing_rows_in_completed_jobs_ignores_old_and_new_jobs (
mocker ,
sample_email_template ,
offset ,
) :
2023-08-23 10:35:43 -07:00
mocker . patch (
" app.celery.tasks.s3.get_job_and_metadata_from_s3 " ,
return_value = ( load_example_csv ( " multiple_email " ) , { " sender_id " : None } ) ,
)
mocker . patch ( " app.encryption.encrypt " , return_value = " something_encrypted " )
process_row = mocker . patch ( " app.celery.scheduled_tasks.process_row " )
2020-09-26 11:06:44 +01:00
job = create_job (
template = sample_email_template ,
notification_count = 5 ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2024-05-23 13:59:51 -07:00
processing_finished = utc_now ( ) - offset ,
2020-09-26 11:06:44 +01:00
)
for i in range ( 0 , 4 ) :
create_notification ( job = job , job_row_number = i )
check_for_missing_rows_in_completed_jobs ( )
assert process_row . called is False
2019-11-05 16:47:00 +00:00
def test_check_for_missing_rows_in_completed_jobs ( mocker , sample_email_template ) :
2023-08-23 10:35:43 -07:00
mocker . patch (
" app.celery.tasks.s3.get_job_and_metadata_from_s3 " ,
return_value = ( load_example_csv ( " multiple_email " ) , { " sender_id " : None } ) ,
)
mocker . patch ( " app.encryption.encrypt " , return_value = " something_encrypted " )
process_row = mocker . patch ( " app.celery.scheduled_tasks.process_row " )
job = create_job (
template = sample_email_template ,
notification_count = 5 ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2024-05-23 13:59:51 -07:00
processing_finished = utc_now ( ) - timedelta ( minutes = 20 ) ,
2023-08-23 10:35:43 -07:00
)
2019-11-05 16:47:00 +00:00
for i in range ( 0 , 4 ) :
create_notification ( job = job , job_row_number = i )
check_for_missing_rows_in_completed_jobs ( )
process_row . assert_called_once_with (
2019-11-15 15:32:16 +00:00
mock . ANY , mock . ANY , job , job . service , sender_id = None
2019-11-05 16:47:00 +00:00
)
2023-08-23 10:35:43 -07:00
def test_check_for_missing_rows_in_completed_jobs_calls_save_email (
mocker , sample_email_template
) :
mocker . patch (
" app.celery.tasks.s3.get_job_and_metadata_from_s3 " ,
return_value = ( load_example_csv ( " multiple_email " ) , { " sender_id " : None } ) ,
)
save_email_task = mocker . patch ( " app.celery.tasks.save_email.apply_async " )
mocker . patch ( " app.encryption.encrypt " , return_value = " something_encrypted " )
mocker . patch ( " app.celery.tasks.create_uuid " , return_value = " uuid " )
2019-11-05 16:47:00 +00:00
2023-08-23 10:35:43 -07:00
job = create_job (
template = sample_email_template ,
notification_count = 5 ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2024-05-23 13:59:51 -07:00
processing_finished = utc_now ( ) - timedelta ( minutes = 20 ) ,
2023-08-23 10:35:43 -07:00
)
2019-11-05 16:47:00 +00:00
for i in range ( 0 , 4 ) :
create_notification ( job = job , job_row_number = i )
check_for_missing_rows_in_completed_jobs ( )
save_email_task . assert_called_once_with (
(
str ( job . service_id ) ,
" uuid " ,
" something_encrypted " ,
) ,
{ } ,
2023-08-23 10:35:43 -07:00
queue = " database-tasks " ,
2024-12-04 08:04:34 -08:00
expires = ANY ,
2019-11-05 16:47:00 +00:00
)
2019-11-15 15:32:16 +00:00
2023-08-23 10:35:43 -07:00
def test_check_for_missing_rows_in_completed_jobs_uses_sender_id (
mocker , sample_email_template , fake_uuid
) :
mocker . patch (
" app.celery.tasks.s3.get_job_and_metadata_from_s3 " ,
return_value = ( load_example_csv ( " multiple_email " ) , { " sender_id " : fake_uuid } ) ,
)
mock_process_row = mocker . patch ( " app.celery.scheduled_tasks.process_row " )
2019-11-15 15:32:16 +00:00
2023-08-23 10:35:43 -07:00
job = create_job (
template = sample_email_template ,
notification_count = 5 ,
2024-01-16 14:46:17 -05:00
job_status = JobStatus . FINISHED ,
2024-05-23 13:59:51 -07:00
processing_finished = utc_now ( ) - timedelta ( minutes = 20 ) ,
2023-08-23 10:35:43 -07:00
)
2019-11-15 15:32:16 +00:00
for i in range ( 0 , 4 ) :
create_notification ( job = job , job_row_number = i )
check_for_missing_rows_in_completed_jobs ( )
mock_process_row . assert_called_once_with (
mock . ANY , mock . ANY , job , job . service , sender_id = fake_uuid
)
2019-11-29 21:24:17 +00:00
2019-12-03 10:26:59 +00:00
MockServicesSendingToTVNumbers = namedtuple (
2023-08-23 10:35:43 -07:00
" ServicesSendingToTVNumbers " ,
2019-12-03 10:26:59 +00:00
[
2023-08-23 10:35:43 -07:00
" service_id " ,
" notification_count " ,
] ,
2019-12-03 10:26:59 +00:00
)
2019-12-05 16:07:06 +00:00
MockServicesWithHighFailureRate = namedtuple (
2023-08-23 10:35:43 -07:00
" ServicesWithHighFailureRate " ,
2019-12-05 16:07:06 +00:00
[
2023-08-23 10:35:43 -07:00
" service_id " ,
" permanent_failure_rate " ,
] ,
2019-12-05 16:07:06 +00:00
)
2019-12-03 10:26:59 +00:00
2023-08-23 10:35:43 -07:00
@pytest.mark.parametrize (
" failure_rates, sms_to_tv_numbers, expected_message " ,
2019-11-29 21:24:17 +00:00
[
2023-08-23 10:35:43 -07:00
[
[ MockServicesWithHighFailureRate ( " 123 " , 0.3 ) ] ,
[ ] ,
" 1 service(s) have had high permanent-failure rates for sms messages in last "
" 24 hours: \n service: {} /services/ {} failure rate: 0.3, \n " . format (
Test . ADMIN_BASE_URL , " 123 "
) ,
] ,
[
[ ] ,
[ MockServicesSendingToTVNumbers ( " 123 " , 300 ) ] ,
" 1 service(s) have sent over 500 sms messages to tv numbers in last 24 hours: \n "
" service: {} /services/ {} count of sms to tv numbers: 300, \n " . format (
Test . ADMIN_BASE_URL , " 123 "
) ,
] ,
2019-12-03 10:26:59 +00:00
] ,
2023-08-23 10:35:43 -07:00
)
2019-11-29 21:24:17 +00:00
def test_check_for_services_with_high_failure_rates_or_sending_to_tv_numbers (
2019-12-03 10:26:59 +00:00
mocker , notify_db_session , failure_rates , sms_to_tv_numbers , expected_message
2019-11-29 21:24:17 +00:00
) :
2023-08-23 10:35:43 -07:00
mock_logger = mocker . patch ( " app.celery.tasks.current_app.logger.warning " )
mock_create_ticket = mocker . spy ( NotifySupportTicket , " __init__ " )
2021-09-23 16:35:12 +01:00
mock_send_ticket_to_zendesk = mocker . patch (
2023-08-23 10:35:43 -07:00
" app.celery.scheduled_tasks.zendesk_client.send_ticket_to_zendesk " ,
2021-09-23 16:35:12 +01:00
autospec = True ,
)
2019-11-29 21:24:17 +00:00
mock_failure_rates = mocker . patch (
2023-08-23 10:35:43 -07:00
" app.celery.scheduled_tasks.dao_find_services_with_high_failure_rates " ,
return_value = failure_rates ,
2019-11-29 21:24:17 +00:00
)
2019-12-03 10:26:59 +00:00
mock_sms_to_tv_numbers = mocker . patch (
2023-08-23 10:35:43 -07:00
" app.celery.scheduled_tasks.dao_find_services_sending_to_tv_numbers " ,
return_value = sms_to_tv_numbers ,
2019-12-03 10:26:59 +00:00
)
2019-11-29 21:24:17 +00:00
2019-12-17 11:47:23 +00:00
zendesk_actions = " \n You can find instructions for this ticket in our manual: \n https://github.com/alphagov/notifications-manuals/wiki/Support-Runbook#Deal-with-services-with-high-failure-rates-or-sending-sms-to-tv-numbers " # noqa
2019-12-03 16:18:07 +00:00
2019-11-29 21:24:17 +00:00
check_for_services_with_high_failure_rates_or_sending_to_tv_numbers ( )
assert mock_failure_rates . called
2019-12-03 10:26:59 +00:00
assert mock_sms_to_tv_numbers . called
2019-11-29 21:24:17 +00:00
mock_logger . assert_called_once_with ( expected_message )
mock_create_ticket . assert_called_with (
2021-09-23 16:35:12 +01:00
ANY ,
2019-12-03 16:18:07 +00:00
message = expected_message + zendesk_actions ,
2019-11-29 21:24:17 +00:00
subject = " [test] High failure rates for sms spotted for services " ,
2023-08-23 10:35:43 -07:00
ticket_type = " incident " ,
technical_ticket = True ,
2019-11-29 21:24:17 +00:00
)
2021-09-23 16:35:12 +01:00
mock_send_ticket_to_zendesk . assert_called_once ( )
2025-01-13 10:00:18 -08:00
def test_batch_insert_with_valid_notifications ( mocker ) :
mocker . patch ( " app.celery.scheduled_tasks.dao_batch_insert_notifications " )
rs = MagicMock ( )
mocker . patch ( " app.celery.scheduled_tasks.redis_store " , rs )
notifications = [
{ " id " : 1 , " notification_status " : " pending " } ,
{ " id " : 2 , " notification_status " : " pending " } ,
]
serialized_notifications = [ json . dumps ( n ) . encode ( " utf-8 " ) for n in notifications ]
pipeline_mock = MagicMock ( )
rs . pipeline . return_value . __enter__ . return_value = pipeline_mock
rs . llen . return_value = len ( notifications )
rs . lpop . side_effect = serialized_notifications
batch_insert_notifications ( )
rs . llen . assert_called_once_with ( " message_queue " )
rs . lpop . assert_called_with ( " message_queue " )
2025-01-13 10:48:19 -08:00
def test_batch_insert_with_expired_notifications ( mocker ) :
expired_time = utc_now ( ) - timedelta ( minutes = 2 )
mocker . patch (
" app.celery.scheduled_tasks.dao_batch_insert_notifications " ,
side_effect = Exception ( " DB Error " ) ,
)
rs = MagicMock ( )
mocker . patch ( " app.celery.scheduled_tasks.redis_store " , rs )
notifications = [
{
" id " : 1 ,
" notification_status " : " pending " ,
" created_at " : utc_now ( ) . isoformat ( ) ,
} ,
{
" id " : 2 ,
" notification_status " : " pending " ,
" created_at " : expired_time . isoformat ( ) ,
} ,
]
serialized_notifications = [ json . dumps ( n ) . encode ( " utf-8 " ) for n in notifications ]
pipeline_mock = MagicMock ( )
rs . pipeline . return_value . __enter__ . return_value = pipeline_mock
rs . llen . return_value = len ( notifications )
rs . lpop . side_effect = serialized_notifications
batch_insert_notifications ( )
rs . llen . assert_called_once_with ( " message_queue " )
rs . rpush . assert_called_once ( )
requeued_notification = json . loads ( rs . rpush . call_args [ 0 ] [ 1 ] )
2025-03-12 14:42:20 -07:00
assert requeued_notification [ " id " ] == " 1 "
2025-01-13 10:48:19 -08:00
def test_batch_insert_with_malformed_notifications ( mocker ) :
rs = MagicMock ( )
mocker . patch ( " app.celery.scheduled_tasks.redis_store " , rs )
malformed_data = b " not_a_valid_json "
pipeline_mock = MagicMock ( )
rs . pipeline . return_value . __enter__ . return_value = pipeline_mock
rs . llen . return_value = 1
rs . lpop . side_effect = [ malformed_data ]
with pytest . raises ( json . JSONDecodeError ) :
batch_insert_notifications ( )
rs . llen . assert_called_once_with ( " message_queue " )
rs . rpush . assert_not_called ( )
2025-01-13 11:37:54 -08:00
def test_process_delivery_receipts_success ( mocker ) :
dao_update_mock = mocker . patch (
2025-01-13 11:59:07 -08:00
" app.celery.scheduled_tasks.dao_update_delivery_receipts "
2025-01-13 11:37:54 -08:00
)
2025-01-13 13:21:34 -08:00
cloudwatch_mock = mocker . patch ( " app.celery.scheduled_tasks.AwsCloudwatchClient " )
cloudwatch_mock . return_value . check_delivery_receipts . return_value = (
range ( 2000 ) ,
range ( 500 ) ,
2025-01-13 11:37:54 -08:00
)
current_app_mock = mocker . patch ( " app.celery.scheduled_tasks.current_app " )
current_app_mock . return_value = MagicMock ( )
processor = MagicMock ( )
processor . process_delivery_receipts = process_delivery_receipts
2025-01-13 11:47:25 -08:00
processor . retry = MagicMock ( )
2025-01-13 11:37:54 -08:00
processor . process_delivery_receipts ( )
assert dao_update_mock . call_count == 3
dao_update_mock . assert_any_call ( list ( range ( 1000 ) ) , True )
dao_update_mock . assert_any_call ( list ( range ( 1000 , 2000 ) ) , True )
2025-01-13 13:12:09 -08:00
dao_update_mock . assert_any_call ( list ( range ( 500 ) ) , False )
2025-01-13 11:47:25 -08:00
processor . retry . assert_not_called ( )