Files
notifications-api/tests/app/aws/test_s3.py

227 lines
6.6 KiB
Python
Raw Normal View History

import os
2023-03-03 16:01:12 -05:00
from os import getenv
2021-03-10 13:55:06 +00:00
import pytest
from botocore.exceptions import ClientError
2024-01-05 10:35:14 -08:00
from app.aws.s3 import (
2024-09-10 11:12:43 -07:00
cleanup_old_s3_objects,
2024-01-05 10:35:14 -08:00
file_exists,
2024-08-13 15:32:43 -07:00
get_job_from_s3,
2024-01-18 10:03:35 -08:00
get_personalisation_from_s3,
2024-01-05 10:35:14 -08:00
get_phone_number_from_s3,
get_s3_file,
remove_csv_object,
remove_s3_object,
)
2024-05-23 13:59:51 -07:00
from app.utils import utc_now
2024-09-10 11:12:43 -07:00
from notifications_utils import aware_utcnow
2023-08-29 14:54:30 -07:00
default_access_key = getenv("CSV_AWS_ACCESS_KEY_ID")
default_secret_key = getenv("CSV_AWS_SECRET_ACCESS_KEY")
default_region = getenv("CSV_AWS_REGION")
2023-08-29 14:54:30 -07:00
def single_s3_object_stub(key="foo", last_modified=None):
return {
2023-08-29 14:54:30 -07:00
"ETag": '"d41d8cd98f00b204e9800998ecf8427e"',
"Key": key,
2024-05-23 13:59:51 -07:00
"LastModified": last_modified or utc_now(),
}
2024-09-10 11:12:43 -07:00
def test_cleanup_old_s3_objects(mocker):
mocker.patch("app.aws.s3.get_bucket_name", return_value="Bucket")
mock_s3_client = mocker.Mock()
mocker.patch("app.aws.s3.get_s3_client", return_value=mock_s3_client)
mock_s3_client.list_objects_v2.return_value = {
"Contents": [{"Key": "A", "LastModified": aware_utcnow()}]
}
cleanup_old_s3_objects()
mock_s3_client.list_objects_v2.assert_called_with(Bucket="Bucket")
def test_get_s3_file_makes_correct_call(notify_api, mocker):
2023-08-29 14:54:30 -07:00
get_s3_mock = mocker.patch("app.aws.s3.get_s3_object")
get_s3_file(
"foo-bucket",
"bar-file.txt",
default_access_key,
default_secret_key,
default_region,
)
get_s3_mock.assert_called_with(
2023-08-29 14:54:30 -07:00
"foo-bucket",
"bar-file.txt",
default_access_key,
default_secret_key,
default_region,
)
2024-01-05 10:35:14 -08:00
@pytest.mark.parametrize(
"job, job_id, job_row_number, expected_phone_number",
[
("phone number\r\n+15555555555", "aaa", 0, "15555555555"),
(
2024-01-08 14:31:28 -08:00
"day of week,favorite color,phone number\r\nmonday,green,1.555.111.1111\r\ntuesday,red,+1 (555) 222-2222",
2024-01-05 10:35:14 -08:00
"bbb",
1,
"15552222222",
),
2024-01-08 14:31:28 -08:00
(
"day of week,favorite color,phone number\r\nmonday,green,1.555.111.1111\r\ntuesday,red,+1 (555) 222-2222",
"ccc",
0,
"15551111111",
),
(
"Phone number,name,date,time,address,English,Spanish\r\n15553333333,Tim,10/16,2:00 PM,5678 Tom St.,no,yes",
"ddd",
0,
"15553333333",
),
2024-02-01 12:01:29 -08:00
(
# simulate file saved with utf8withbom
"\\ufeffPHONE NUMBER,Name\r\n5555555550,T 1\r\n5555555551,T 5,3/31/2024\r\n5555555552,T 2",
"eee",
2,
"5555555552",
),
2024-01-05 10:35:14 -08:00
],
)
def test_get_phone_number_from_s3(
mocker, job, job_id, job_row_number, expected_phone_number
):
2024-01-12 07:42:21 -08:00
mocker.patch("app.aws.s3.redis_store")
2024-01-05 10:35:14 -08:00
get_job_mock = mocker.patch("app.aws.s3.get_job_from_s3")
get_job_mock.return_value = job
phone_number = get_phone_number_from_s3("service_id", job_id, job_row_number)
assert phone_number == expected_phone_number
2024-08-13 15:45:39 -07:00
2024-08-13 15:32:43 -07:00
def mock_s3_get_object_slowdown(*args, **kwargs):
error_response = {
2024-08-13 15:45:39 -07:00
"Error": {
"Code": "SlowDown",
"Message": "Reduce your request rate",
2024-08-13 15:32:43 -07:00
}
}
2024-08-13 15:45:39 -07:00
raise ClientError(error_response, "GetObject")
2024-08-13 15:32:43 -07:00
2024-08-16 11:10:10 -07:00
def test_get_job_from_s3_exponential_backoff_on_throttling(mocker):
2024-08-16 09:53:29 -07:00
# We try multiple times to retrieve the job, and if we can't we return None
mock_get_object = mocker.patch(
"app.aws.s3.get_s3_object", side_effect=mock_s3_get_object_slowdown
)
2024-08-16 11:10:10 -07:00
mocker.patch("app.aws.s3.file_exists", return_value=True)
2024-08-16 09:53:29 -07:00
job = get_job_from_s3("service_id", "job_id")
assert job is None
assert mock_get_object.call_count == 4
2024-08-13 15:32:43 -07:00
2024-01-05 10:35:14 -08:00
2024-08-16 11:10:10 -07:00
def test_get_job_from_s3_exponential_backoff_file_not_found(mocker):
mock_get_object = mocker.patch("app.aws.s3.get_s3_object", return_value=None)
mocker.patch("app.aws.s3.file_exists", return_value=False)
job = get_job_from_s3("service_id", "job_id")
assert job is None
assert mock_get_object.call_count == 0
2024-01-18 10:03:35 -08:00
@pytest.mark.parametrize(
"job, job_id, job_row_number, expected_personalisation",
[
("phone number\r\n+15555555555", "aaa", 0, {"phone number": "+15555555555"}),
(
"day of week,favorite color,phone number\r\nmonday,green,1.555.111.1111\r\ntuesday,red,+1 (555) 222-2222",
"bbb",
1,
2024-01-19 08:58:24 -08:00
{
"day of week": "tuesday",
"favorite color": "red",
"phone number": "+1 (555) 222-2222",
},
2024-01-18 10:03:35 -08:00
),
(
"day of week,favorite color,phone number\r\nmonday,green,1.555.111.1111\r\ntuesday,red,+1 (555) 222-2222",
"ccc",
0,
2024-01-19 08:58:24 -08:00
{
"day of week": "monday",
"favorite color": "green",
"phone number": "1.555.111.1111",
},
2024-01-18 10:03:35 -08:00
),
],
)
def test_get_personalisation_from_s3(
mocker, job, job_id, job_row_number, expected_personalisation
):
mocker.patch("app.aws.s3.redis_store")
get_job_mock = mocker.patch("app.aws.s3.get_job_from_s3")
get_job_mock.return_value = job
personalisation = get_personalisation_from_s3("service_id", job_id, job_row_number)
assert personalisation == expected_personalisation
def test_remove_csv_object(notify_api, mocker):
get_s3_mock = mocker.patch("app.aws.s3.get_s3_object")
remove_csv_object("mykey")
get_s3_mock.assert_called_once_with(
os.getenv("CSV_BUCKET_NAME"),
"mykey",
default_access_key,
default_secret_key,
default_region,
)
def test_remove_csv_object_alternate(notify_api, mocker):
get_s3_mock = mocker.patch("app.aws.s3.get_s3_object")
remove_s3_object(
os.getenv("CSV_BUCKET_NAME"),
"mykey",
default_access_key,
default_secret_key,
default_region,
)
get_s3_mock.assert_called_once_with(
os.getenv("CSV_BUCKET_NAME"),
"mykey",
default_access_key,
default_secret_key,
default_region,
)
def test_file_exists_true(notify_api, mocker):
get_s3_mock = mocker.patch("app.aws.s3.get_s3_object")
file_exists(
"mykey",
)
2024-08-16 11:10:10 -07:00
get_s3_mock.assert_called_once()
def test_file_exists_false(notify_api, mocker):
get_s3_mock = mocker.patch("app.aws.s3.get_s3_object")
error_response = {
"Error": {"Code": 500, "Message": "bogus"},
"ResponseMetadata": {"HTTPStatusCode": 500},
}
get_s3_mock.side_effect = ClientError(
error_response=error_response, operation_name="bogus"
)
with pytest.raises(ClientError):
file_exists(
"mykey",
)
2024-08-16 11:10:10 -07:00
get_s3_mock.assert_called_once()