mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-01 12:18:50 -04:00
merge from main
This commit is contained in:
@@ -22,8 +22,10 @@ from app.aws.s3 import (
|
||||
get_s3_object,
|
||||
get_s3_resource,
|
||||
list_s3_objects,
|
||||
purge_bucket,
|
||||
read_s3_file,
|
||||
remove_csv_object,
|
||||
remove_job_from_s3,
|
||||
remove_s3_object,
|
||||
)
|
||||
from app.clients import AWS_CLIENT_CONFIG
|
||||
@@ -563,3 +565,71 @@ def test_get_s3_object_client_error(mocker):
|
||||
mock_logger.exception.assert_called_once_with(
|
||||
f"Can't retrieve S3 Object from {file_location}"
|
||||
)
|
||||
|
||||
|
||||
def test_purge_bucket(mocker):
|
||||
mock_s3_resource = MagicMock()
|
||||
mock_bucket = MagicMock()
|
||||
mock_s3_resource.Bucket.return_value = mock_bucket
|
||||
mocker.patch("app.aws.s3.get_s3_resource", return_value=mock_s3_resource)
|
||||
|
||||
purge_bucket("my-bucket", "access-key", "secret-key", "region")
|
||||
|
||||
# Assert that the bucket's objects.all().delete() method was called
|
||||
mock_bucket.objects.all.return_value.delete.assert_called_once()
|
||||
|
||||
|
||||
def test_remove_job_from_s3(mocker):
|
||||
mock_get_job_location = mocker.patch("app.aws.s3.get_job_location")
|
||||
mock_remove_s3_object = mocker.patch("app.aws.s3.remove_s3_object")
|
||||
|
||||
mock_get_job_location.return_value = (
|
||||
"test-bucket",
|
||||
"test.csv",
|
||||
"fake-stuff",
|
||||
)
|
||||
|
||||
remove_job_from_s3("service-id-123", "job-id-456")
|
||||
|
||||
mock_get_job_location.assert_called_once_with("service-id-123", "job-id-456")
|
||||
mock_remove_s3_object.assert_called_once_with(
|
||||
"test-bucket",
|
||||
"test.csv",
|
||||
"fake-stuff",
|
||||
)
|
||||
|
||||
|
||||
def test_get_s3_files_handles_exception(mocker):
|
||||
mock_current_app = mocker.patch("app.aws.s3.current_app")
|
||||
mock_current_app.config = {
|
||||
"CSV_UPLOAD_BUCKET": {"bucket": "test-bucket"},
|
||||
"job_cache": {},
|
||||
}
|
||||
|
||||
mock_list_s3_objects = mocker.patch("app.aws.s3.list_s3_objects")
|
||||
mock_list_s3_objects.return_value = ["file1.csv", "file2.csv"]
|
||||
|
||||
mock_get_s3_resource = mocker.patch("app.aws.s3.get_s3_resource")
|
||||
|
||||
# Make the first call succeed, second call should fail.
|
||||
mock_read_s3_file = mocker.patch(
|
||||
"app.aws.s3.read_s3_file", side_effect=[None, Exception("exception here")]
|
||||
)
|
||||
|
||||
mock_thread_pool_executor = mocker.patch("app.aws.s3.ThreadPoolExecutor")
|
||||
mock_executor = mock_thread_pool_executor.return_value.__enter__.return_value
|
||||
|
||||
def mock_map(func, iterable):
|
||||
for item in iterable:
|
||||
func(item)
|
||||
|
||||
mock_executor.map.side_effect = mock_map
|
||||
get_s3_files()
|
||||
|
||||
calls = [
|
||||
mocker.call("test-bucket", "file1.csv", mock_get_s3_resource.return_value),
|
||||
mocker.call("test-bucket", "file2.csv", mock_get_s3_resource.return_value),
|
||||
]
|
||||
mock_read_s3_file.assert_has_calls(calls, any_order=True)
|
||||
|
||||
mock_current_app.logger.exception.assert_called_with("Connection pool issue")
|
||||
|
||||
@@ -583,7 +583,7 @@ def test_batch_insert_with_expired_notifications(mocker):
|
||||
rs.llen.assert_called_once_with("message_queue")
|
||||
rs.rpush.assert_called_once()
|
||||
requeued_notification = json.loads(rs.rpush.call_args[0][1])
|
||||
assert requeued_notification["id"] == 1
|
||||
assert requeued_notification["id"] == '1'
|
||||
|
||||
|
||||
def test_batch_insert_with_malformed_notifications(mocker):
|
||||
|
||||
@@ -31,7 +31,7 @@ def side_effect(filterPattern, logGroupName, startTime, endTime):
|
||||
{
|
||||
"logStreamName": "89db9712-c6d1-49f9-be7c-4caa7ed9efb1",
|
||||
"message": '{"delivery":{"destination":"+1661","phoneCarrier":"ATT Mobility", '
|
||||
'"providerResponse":"Invalid phone number"}}',
|
||||
'"providerResponse":"Invalid phone number", "priceInUSD": "0.00881"}}',
|
||||
"eventId": "37535432778099870001723210579798865345508698025292922880",
|
||||
}
|
||||
]
|
||||
@@ -44,7 +44,7 @@ def side_effect(filterPattern, logGroupName, startTime, endTime):
|
||||
"logStreamName": "89db9712-c6d1-49f9-be7c-4caa7ed9efb1",
|
||||
"timestamp": 1683147017911,
|
||||
"message": '{"delivery":{"destination":"+1661","phoneCarrier":"ATT Mobility",'
|
||||
'"providerResponse":"Phone accepted msg"}}',
|
||||
'"providerResponse":"Phone accepted msg", "priceInUSD": "0.00881"}}',
|
||||
"ingestionTime": 1683147018026,
|
||||
"eventId": "37535432778099870001723210579798865345508698025292922880",
|
||||
}
|
||||
@@ -131,6 +131,7 @@ def test_event_to_db_format_with_missing_fields():
|
||||
"status": "UNKNOWN",
|
||||
"delivery.phoneCarrier": "",
|
||||
"delivery.providerResponse": "",
|
||||
"delivery.priceInUSD": 0.0,
|
||||
"@timestamp": "",
|
||||
}
|
||||
|
||||
@@ -140,7 +141,11 @@ def test_event_to_db_format_with_string_input():
|
||||
{
|
||||
"notification": {"messageId": "67890", "timestamp": "2024-01-01T14:00:00Z"},
|
||||
"status": "FAILED",
|
||||
"delivery": {"phoneCarrier": "Verizon", "providerResponse": "Error"},
|
||||
"delivery": {
|
||||
"phoneCarrier": "Verizon",
|
||||
"providerResponse": "Error",
|
||||
"priceInUSD": "0.00881",
|
||||
},
|
||||
}
|
||||
)
|
||||
result = aws_cloudwatch_client.event_to_db_format(event)
|
||||
@@ -149,5 +154,6 @@ def test_event_to_db_format_with_string_input():
|
||||
"status": "FAILED",
|
||||
"delivery.phoneCarrier": "Verizon",
|
||||
"delivery.providerResponse": "Error",
|
||||
"delivery.priceInUSD": 0.00881,
|
||||
"@timestamp": "2024-01-01T14:00:00Z",
|
||||
}
|
||||
|
||||
@@ -2019,8 +2019,8 @@ def test_notifications_not_yet_sent_return_no_rows(sample_service, notification_
|
||||
def test_update_delivery_receipts(mocker):
|
||||
mock_session = mocker.patch("app.dao.notifications_dao.db.session")
|
||||
receipts = [
|
||||
'{"notification.messageId": "msg1", "delivery.phoneCarrier": "carrier1", "delivery.providerResponse": "resp1", "@timestamp": "2024-01-01T12:00:00"}', # noqa
|
||||
'{"notification.messageId": "msg2", "delivery.phoneCarrier": "carrier2", "delivery.providerResponse": "resp2", "@timestamp": "2024-01-01T13:00:00"}', # noqa
|
||||
'{"notification.messageId": "msg1", "delivery.phoneCarrier": "carrier1", "delivery.providerResponse": "resp1", "@timestamp": "2024-01-01T12:00:00", "delivery.priceInUSD": "0.00881"}', # noqa
|
||||
'{"notification.messageId": "msg2", "delivery.phoneCarrier": "carrier2", "delivery.providerResponse": "resp2", "@timestamp": "2024-01-01T13:00:00", "delivery.priceInUSD": "0.00881"}', # noqa
|
||||
]
|
||||
delivered = True
|
||||
mock_update = MagicMock()
|
||||
@@ -2134,3 +2134,57 @@ def test_sanitize_successful_notification_by_id():
|
||||
"sent_at": ANY,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_dao_get_notifications_by_recipient_or_reference_covers_sms_search_by_reference(
|
||||
notify_db_session,
|
||||
):
|
||||
"""
|
||||
This test:
|
||||
1. Creates a service and an SMS template.
|
||||
2. Creates a notification with a specific client_reference and status=FAILED.
|
||||
3. Calls dao_get_notifications_by_recipient_or_reference with notification_type=SMS,
|
||||
statuses=[FAILED], and a search term = client_reference.
|
||||
4. Confirms the function returns exactly one notification matching that reference.
|
||||
"""
|
||||
|
||||
service = create_service(service_name="Test Service")
|
||||
template = create_template(service=service, template_type=NotificationType.SMS)
|
||||
|
||||
# Instead of matching phone logic, we'll match on client_reference
|
||||
data = {
|
||||
"id": uuid.uuid4(),
|
||||
"to": "1",
|
||||
"normalised_to": "1", # phone is irrelevant here
|
||||
"service_id": service.id,
|
||||
"service": service,
|
||||
"template_id": template.id,
|
||||
"template_version": template.version,
|
||||
"status": NotificationStatus.FAILED,
|
||||
"created_at": utc_now(),
|
||||
"billable_units": 1,
|
||||
"notification_type": template.template_type,
|
||||
"key_type": KeyType.NORMAL,
|
||||
"client_reference": "some-ref", # <--- We'll search for this
|
||||
}
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
# We'll search by this reference instead of a phone number
|
||||
search_term = "some-ref"
|
||||
|
||||
results_page = dao_get_notifications_by_recipient_or_reference(
|
||||
service_id=service.id,
|
||||
search_term=search_term,
|
||||
notification_type=NotificationType.SMS,
|
||||
statuses=[NotificationStatus.FAILED],
|
||||
page=1,
|
||||
page_size=50,
|
||||
)
|
||||
|
||||
# Now we should find exactly one match
|
||||
assert len(results_page.items) == 1, "Should find exactly one matching notification"
|
||||
found = results_page.items[0]
|
||||
assert found.id == notification.id
|
||||
assert found.status == NotificationStatus.FAILED
|
||||
assert found.client_reference == "some-ref"
|
||||
|
||||
@@ -36,7 +36,6 @@ def test_fetch_notification_status_for_service_by_month(notify_db_session):
|
||||
service_2 = create_service(service_name="service_2")
|
||||
|
||||
create_template(service=service_1)
|
||||
create_template(service=service_1, template_type=TemplateType.EMAIL)
|
||||
# not the service being tested
|
||||
create_template(service=service_2)
|
||||
|
||||
@@ -50,11 +49,7 @@ def test_fetch_notification_status_for_service_by_month(notify_db_session):
|
||||
create_notification(
|
||||
service_1.templates[0], created_at=datetime(2018, 1, 1, 1, 1, 0)
|
||||
)
|
||||
create_notification(
|
||||
service_1.templates[1],
|
||||
created_at=datetime(2018, 1, 1, 1, 1, 0),
|
||||
status=NotificationStatus.DELIVERED,
|
||||
)
|
||||
|
||||
create_notification(
|
||||
service_1.templates[0],
|
||||
created_at=datetime(2018, 2, 1, 1, 1, 0),
|
||||
@@ -79,30 +74,25 @@ def test_fetch_notification_status_for_service_by_month(notify_db_session):
|
||||
fetch_notification_status_for_service_by_month(
|
||||
date(2018, 1, 1), date(2018, 2, 28), service_1.id
|
||||
),
|
||||
key=lambda x: (x.month, x.notification_type, x.notification_status),
|
||||
key=lambda x: (x.month, x.notification_status),
|
||||
)
|
||||
|
||||
assert len(results) == 4
|
||||
assert len(results) == 3
|
||||
|
||||
assert results[0].month.date() == date(2018, 1, 1)
|
||||
assert results[0].notification_type == NotificationType.EMAIL
|
||||
assert results[0].notification_status == NotificationStatus.DELIVERED
|
||||
assert results[0].notification_type == NotificationType.SMS
|
||||
assert results[0].notification_status == NotificationStatus.CREATED
|
||||
assert results[0].count == 1
|
||||
|
||||
assert results[1].month.date() == date(2018, 1, 1)
|
||||
assert results[1].notification_type == NotificationType.SMS
|
||||
assert results[1].notification_status == NotificationStatus.CREATED
|
||||
assert results[1].count == 1
|
||||
assert results[1].notification_status == NotificationStatus.DELIVERED
|
||||
assert results[1].count == 14
|
||||
|
||||
assert results[2].month.date() == date(2018, 1, 1)
|
||||
assert results[2].month.date() == date(2018, 2, 1)
|
||||
assert results[2].notification_type == NotificationType.SMS
|
||||
assert results[2].notification_status == NotificationStatus.DELIVERED
|
||||
assert results[2].count == 14
|
||||
|
||||
assert results[3].month.date() == date(2018, 2, 1)
|
||||
assert results[3].notification_type == NotificationType.SMS
|
||||
assert results[3].notification_status == NotificationStatus.DELIVERED
|
||||
assert results[3].count == 1
|
||||
assert results[2].count == 1
|
||||
|
||||
|
||||
def test_fetch_notification_status_for_service_for_day(notify_db_session):
|
||||
|
||||
@@ -831,7 +831,9 @@ def test_get_organization_users_returns_users_for_organization(
|
||||
)
|
||||
|
||||
assert len(response["data"]) == 2
|
||||
assert response["data"][0]["id"] == str(first.id)
|
||||
response_ids = [response["data"][0]["id"], response["data"][1]["id"]]
|
||||
assert str(first.id) in response_ids
|
||||
assert str(second.id) in response_ids
|
||||
|
||||
|
||||
@freeze_time("2019-12-24 13:30")
|
||||
|
||||
@@ -15,6 +15,7 @@ from app.commands import (
|
||||
dump_sms_senders,
|
||||
dump_user_info,
|
||||
fix_billable_units,
|
||||
generate_salt,
|
||||
insert_inbound_numbers_from_file,
|
||||
populate_annual_billing_with_defaults,
|
||||
populate_annual_billing_with_the_previous_years_allowance,
|
||||
@@ -661,3 +662,10 @@ def test_dump_user_info(notify_api, mocker):
|
||||
|
||||
mock_get_user_by_email.assert_called_once_with("john@example.com")
|
||||
mock_open_file.assert_called_once_with("user_download.json", "wb")
|
||||
|
||||
|
||||
def test_generate_salt(notify_api):
|
||||
runner = notify_api.test_cli_runner()
|
||||
result = runner.invoke(generate_salt)
|
||||
assert result.exit_code == 0
|
||||
assert len(result.output.strip()) == 32
|
||||
|
||||
Reference in New Issue
Block a user