mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-19 14:09:20 -04:00
Merge main
This commit is contained in:
@@ -3,9 +3,11 @@ import json
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from flask import url_for
|
||||
from flask import Flask, url_for
|
||||
from flask_socketio import SocketIOTestClient
|
||||
from freezegun import freeze_time
|
||||
|
||||
from app import create_app, socketio
|
||||
from app.main.views.dashboard import (
|
||||
aggregate_notifications_stats,
|
||||
aggregate_status_types,
|
||||
@@ -23,6 +25,7 @@ from tests import (
|
||||
from tests.conftest import (
|
||||
ORGANISATION_ID,
|
||||
SERVICE_ONE_ID,
|
||||
SERVICE_TWO_ID,
|
||||
create_active_caseworking_user,
|
||||
create_active_user_view_permissions,
|
||||
normalize_spaces,
|
||||
@@ -1875,3 +1878,76 @@ def test_service_dashboard_shows_batched_jobs(
|
||||
assert job_table_body is not None
|
||||
|
||||
assert len(rows) == 1
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def app_with_socketio():
|
||||
app = Flask("app")
|
||||
create_app(app)
|
||||
return app, socketio
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service_id", "date_range", "expected_call_args"),
|
||||
[
|
||||
(
|
||||
SERVICE_ONE_ID,
|
||||
{"start_date": "2024-01-01", "days": 7},
|
||||
{"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7}
|
||||
),
|
||||
(
|
||||
SERVICE_TWO_ID,
|
||||
{"start_date": "2023-06-01", "days": 7},
|
||||
{"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7}
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_fetch_daily_stats(
|
||||
app_with_socketio, mocker,
|
||||
service_id,
|
||||
date_range,
|
||||
expected_call_args
|
||||
):
|
||||
app, socketio = app_with_socketio
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.dashboard.get_stats_date_range",
|
||||
return_value=date_range
|
||||
)
|
||||
|
||||
mock_service_api = mocker.patch(
|
||||
"app.service_api_client.get_service_notification_statistics_by_day",
|
||||
return_value={
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||
"sms": {"delivered": 0, "failure": 1, "requested": 1}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
client = SocketIOTestClient(app, socketio)
|
||||
try:
|
||||
connected = client.is_connected()
|
||||
assert connected, "Client should be connected"
|
||||
|
||||
client.emit('fetch_daily_stats', service_id)
|
||||
|
||||
received = client.get_received()
|
||||
assert received, "Should receive a response message"
|
||||
assert received[0]['name'] == 'daily_stats_update'
|
||||
assert received[0]['args'][0] == {
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||
"sms": {"delivered": 0, "failure": 1, "requested": 1}
|
||||
},
|
||||
}
|
||||
|
||||
mock_service_api.assert_called_once_with(
|
||||
service_id,
|
||||
start_date=expected_call_args["start_date"],
|
||||
days=expected_call_args["days"]
|
||||
)
|
||||
finally:
|
||||
client.disconnect()
|
||||
disconnected = not client.is_connected()
|
||||
assert disconnected, "Client should be disconnected"
|
||||
|
||||
@@ -179,26 +179,6 @@ def test_old_static_pages_redirect(client_request, view, expected_view):
|
||||
)
|
||||
|
||||
|
||||
def test_message_status_page_contains_message_status_ids(client_request):
|
||||
# The 'email-statuses' and 'sms-statuses' id are linked to when we display a message status,
|
||||
# so this test ensures we don't accidentally remove them
|
||||
page = client_request.get("main.message_status")
|
||||
|
||||
# email-statuses is commented out in view
|
||||
# assert page.find(id='email-statuses')
|
||||
assert page.find(id="text-message-statuses")
|
||||
|
||||
|
||||
def test_message_status_page_contains_link_to_support(client_request):
|
||||
page = client_request.get("main.message_status")
|
||||
sms_status_table = page.find(id="text-message-statuses").findNext("tbody")
|
||||
|
||||
temp_fail_details_cell = sms_status_table.select_one(
|
||||
"tr:nth-child(4) > td:nth-child(2)"
|
||||
)
|
||||
assert temp_fail_details_cell.find("a").attrs["href"] == url_for("main.support")
|
||||
|
||||
|
||||
def test_old_using_notify_page(client_request):
|
||||
client_request.get("main.using_notify", _expected_status=410)
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ def test_should_return_200_when_email_is_not_gov_uk(
|
||||
"email_address",
|
||||
[
|
||||
"notfound@example.gsa.gov",
|
||||
"example@lsquo.net",
|
||||
"example@lsquo.si.edu",
|
||||
],
|
||||
)
|
||||
def test_should_add_user_details_to_session(
|
||||
@@ -401,6 +401,26 @@ def test_check_invited_user_email_address_doesnt_match_expected(mocker):
|
||||
mock_abort.assert_called_once_with(403)
|
||||
|
||||
|
||||
def test_check_user_email_address_fails_if_not_government_address(mocker):
|
||||
mock_flash = mocker.patch("app.main.views.register.flash")
|
||||
mock_abort = mocker.patch("app.main.views.register.abort")
|
||||
|
||||
check_invited_user_email_address_matches_expected(
|
||||
"fake@fake.bogus", "Fake@Fake.BOGUS"
|
||||
)
|
||||
mock_flash.assert_called_once_with("You must use a government email address.")
|
||||
mock_abort.assert_called_once_with(403)
|
||||
|
||||
|
||||
def test_check_user_email_address_succeeds_if_government_address(mocker):
|
||||
mock_flash = mocker.patch("app.main.views.register.flash")
|
||||
mock_abort = mocker.patch("app.main.views.register.abort")
|
||||
|
||||
check_invited_user_email_address_matches_expected("fake@fake.mil", "Fake@Fake.MIL")
|
||||
mock_flash.assert_not_called()
|
||||
mock_abort.assert_not_called()
|
||||
|
||||
|
||||
def decode_invite_data(state):
|
||||
state = state.encode("utf8")
|
||||
state = base64.b64decode(state)
|
||||
|
||||
@@ -6,6 +6,7 @@ from io import BytesIO
|
||||
from itertools import repeat
|
||||
from os import path
|
||||
from random import randbytes
|
||||
from unittest.mock import ANY
|
||||
from uuid import uuid4
|
||||
from zipfile import BadZipFile
|
||||
|
||||
@@ -17,7 +18,11 @@ from xlrd.xldate import XLDateAmbiguous, XLDateError, XLDateNegative, XLDateTooL
|
||||
|
||||
from notifications_utils.recipients import RecipientCSV
|
||||
from notifications_utils.template import SMSPreviewTemplate
|
||||
from tests import validate_route_permission, validate_route_permission_with_client
|
||||
from tests import (
|
||||
sample_uuid,
|
||||
validate_route_permission,
|
||||
validate_route_permission_with_client,
|
||||
)
|
||||
from tests.conftest import (
|
||||
SERVICE_ONE_ID,
|
||||
create_active_caseworking_user,
|
||||
@@ -434,10 +439,15 @@ def test_upload_files_in_different_formats(
|
||||
service_one,
|
||||
mocker,
|
||||
mock_get_service_template,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_upload,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mock_s3_set_metadata = mocker.patch(
|
||||
"app.main.views.send.set_metadata_on_csv_upload"
|
||||
)
|
||||
|
||||
mock_s3_upload = mocker.patch("app.main.views.send.s3upload")
|
||||
|
||||
with open(filename, "rb") as uploaded:
|
||||
page = client_request.post(
|
||||
"main.send_messages",
|
||||
@@ -456,7 +466,7 @@ def test_upload_files_in_different_formats(
|
||||
"202 205 8823,Still Not Pete,Crimson,Pear"
|
||||
)
|
||||
mock_s3_set_metadata.assert_called_once_with(
|
||||
SERVICE_ONE_ID, fake_uuid, original_file_name=filename
|
||||
SERVICE_ONE_ID, ANY, original_file_name=filename
|
||||
)
|
||||
else:
|
||||
assert not mock_s3_upload.called
|
||||
@@ -470,13 +480,16 @@ def test_send_messages_sanitises_and_truncates_file_name_for_metadata(
|
||||
service_one,
|
||||
mocker,
|
||||
mock_get_service_template_with_placeholders,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_s3_download,
|
||||
mock_get_job_doesnt_exist,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mock_s3_set_metadata = mocker.patch(
|
||||
"app.main.views.send.set_metadata_on_csv_upload"
|
||||
)
|
||||
|
||||
mocker.patch("app.main.views.send.s3upload")
|
||||
|
||||
filename = f"😁{'a' * 2000}.csv"
|
||||
|
||||
client_request.post(
|
||||
@@ -577,15 +590,20 @@ def test_upload_csv_file_with_errors_shows_check_page_with_errors(
|
||||
service_one,
|
||||
mocker,
|
||||
mock_get_service_template_with_placeholders,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="""
|
||||
@@ -624,15 +642,21 @@ def test_upload_csv_file_with_empty_message_shows_check_page_with_errors(
|
||||
service_one,
|
||||
mocker,
|
||||
mock_get_empty_service_template_with_optional_placeholder,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="""
|
||||
@@ -676,15 +700,20 @@ def test_upload_csv_file_with_very_long_placeholder_shows_check_page_with_errors
|
||||
service_one,
|
||||
mocker,
|
||||
mock_get_service_template_with_placeholders,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
big_placeholder = " ".join(["not ok"] * 402)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
@@ -811,9 +840,6 @@ def test_upload_csv_file_with_missing_columns_shows_error(
|
||||
client_request,
|
||||
mocker,
|
||||
mock_get_service_template_with_placeholders,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
@@ -823,6 +849,15 @@ def test_upload_csv_file_with_missing_columns_shows_error(
|
||||
file_contents,
|
||||
expected_error,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
|
||||
mocker.patch("app.main.views.send.s3download", return_value=file_contents)
|
||||
|
||||
page = client_request.post(
|
||||
@@ -885,10 +920,13 @@ def test_upload_csv_size_too_big(
|
||||
def test_upload_valid_csv_redirects_to_check_page(
|
||||
client_request,
|
||||
mock_get_service_template_with_placeholders,
|
||||
mock_s3_upload,
|
||||
mock_s3_set_metadata,
|
||||
fake_uuid,
|
||||
mocker,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
client_request.post(
|
||||
"main.send_messages",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
@@ -937,13 +975,16 @@ def test_upload_valid_csv_shows_preview_and_table(
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
fake_uuid,
|
||||
extra_args,
|
||||
expected_link_in_first_row,
|
||||
expected_message,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
with client_request.session_transaction() as session:
|
||||
session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}}
|
||||
|
||||
@@ -1031,8 +1072,12 @@ def test_show_all_columns_if_there_are_duplicate_recipient_columns(
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
fake_uuid,
|
||||
mock_s3_get_metadata,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
with client_request.session_transaction() as session:
|
||||
session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}}
|
||||
|
||||
@@ -1080,12 +1125,17 @@ def test_404_for_previewing_a_row_out_of_range(
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
fake_uuid,
|
||||
row_index,
|
||||
expected_status,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
with client_request.session_transaction() as session:
|
||||
session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}}
|
||||
|
||||
@@ -1523,7 +1573,6 @@ def test_send_one_off_redirects_to_start_if_you_skip_steps(
|
||||
client_request,
|
||||
service_one,
|
||||
fake_uuid,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_has_no_jobs,
|
||||
@@ -1559,7 +1608,6 @@ def test_send_one_off_redirects_to_start_if_index_out_of_bounds_and_some_placeho
|
||||
service_one,
|
||||
fake_uuid,
|
||||
mock_get_service_email_template,
|
||||
mock_s3_download,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_has_no_jobs,
|
||||
@@ -1628,7 +1676,6 @@ def test_send_one_off_email_to_self_without_placeholders_redirects_to_check_page
|
||||
mocker,
|
||||
service_one,
|
||||
mock_get_service_email_template_without_placeholders,
|
||||
mock_s3_upload,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_has_no_jobs,
|
||||
@@ -1835,13 +1882,20 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers(
|
||||
mock_get_live_service,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
service_one,
|
||||
fake_uuid,
|
||||
mock_s3_upload,
|
||||
mocker,
|
||||
):
|
||||
|
||||
mock_s3_set_metadata = mocker.patch(
|
||||
"app.main.views.send.set_metadata_on_csv_upload"
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="\n".join(
|
||||
@@ -1895,9 +1949,6 @@ def test_upload_csvfile_with_international_validates(
|
||||
api_user_active,
|
||||
client_request,
|
||||
mock_get_service_template,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_upload,
|
||||
mock_has_permissions,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
@@ -1908,6 +1959,14 @@ def test_upload_csvfile_with_international_validates(
|
||||
should_allow_international,
|
||||
service_one,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
if international_sms_permission:
|
||||
service_one["permissions"] += ("sms", "international_sms")
|
||||
mocker.patch(
|
||||
@@ -1942,15 +2001,20 @@ def test_test_message_can_only_be_sent_now(
|
||||
mocker,
|
||||
service_one,
|
||||
mock_get_service_template,
|
||||
mock_s3_download,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
mock_s3_download,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
content = client_request.get(
|
||||
"main.check_messages",
|
||||
service_id=service_one["id"],
|
||||
@@ -1972,8 +2036,12 @@ def test_preview_button_is_correctly_labelled(
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
fake_uuid,
|
||||
mock_s3_get_metadata,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="\n".join(["phone_number"] + (["2028670123"] * 1000)),
|
||||
@@ -2058,7 +2126,6 @@ def test_route_permissions(
|
||||
mock_get_jobs,
|
||||
mock_get_notifications,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
fake_uuid,
|
||||
route,
|
||||
response_code,
|
||||
@@ -2092,8 +2159,8 @@ def test_route_permissions_send_check_notifications(
|
||||
response_code,
|
||||
method,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
with client_request.session_transaction() as session:
|
||||
session["recipient"] = "2028675301"
|
||||
session["placeholders"] = {"name": "a"}
|
||||
@@ -2173,8 +2240,6 @@ def test_check_messages_back_link(
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_download,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
fake_uuid,
|
||||
mocker,
|
||||
template_type,
|
||||
@@ -2182,6 +2247,13 @@ def test_check_messages_back_link(
|
||||
extra_args,
|
||||
expected_url,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
content = "Hi there ((name))" if has_placeholders else "Hi there"
|
||||
template_data = create_template(
|
||||
template_id=fake_uuid, template_type=template_type, content=content
|
||||
@@ -2236,8 +2308,12 @@ def test_check_messages_shows_too_many_messages_errors(
|
||||
fake_uuid,
|
||||
num_requested,
|
||||
expected_msg,
|
||||
mock_s3_get_metadata,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
# csv with 100 phone numbers
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
@@ -2281,7 +2357,6 @@ def test_check_messages_shows_too_many_messages_errors(
|
||||
|
||||
def test_check_messages_shows_trial_mode_error(
|
||||
client_request,
|
||||
mock_s3_get_metadata,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_template,
|
||||
mock_has_permissions,
|
||||
@@ -2291,6 +2366,11 @@ def test_check_messages_shows_trial_mode_error(
|
||||
fake_uuid,
|
||||
mocker,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value=("phone number,\n2028675209"), # Not in team
|
||||
@@ -2426,8 +2506,12 @@ def test_check_messages_column_error_doesnt_show_optional_columns(
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="\n".join(
|
||||
@@ -2468,10 +2552,17 @@ def test_check_messages_adds_sender_id_in_session_to_metadata(
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
fake_uuid,
|
||||
):
|
||||
|
||||
mock_s3_set_metadata = mocker.patch(
|
||||
"app.main.views.send.set_metadata_on_csv_upload"
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download", return_value=("phone number,\n2028675209")
|
||||
)
|
||||
@@ -2508,11 +2599,15 @@ def test_check_messages_shows_over_max_row_error(
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
mock_get_jobs,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_download,
|
||||
fake_uuid,
|
||||
mocker,
|
||||
):
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mock_recipients = mocker.patch("app.main.views.send.RecipientCSV").return_value
|
||||
mock_recipients.max_rows = 11111
|
||||
mock_recipients.__len__.return_value = 99999
|
||||
@@ -2662,8 +2757,8 @@ def test_send_notification_submits_data(
|
||||
expected_personalisation,
|
||||
mocker,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
with client_request.session_transaction() as session:
|
||||
session["recipient"] = recipient
|
||||
session["placeholders"] = placeholders
|
||||
@@ -2690,8 +2785,8 @@ def test_send_notification_clears_session(
|
||||
mock_get_service_template,
|
||||
mocker,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
with client_request.session_transaction() as session:
|
||||
session["recipient"] = "2028675301"
|
||||
session["placeholders"] = {"a": "b"}
|
||||
@@ -2752,8 +2847,8 @@ def test_send_notification_redirects_to_view_page(
|
||||
extra_redirect_args,
|
||||
mocker,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
with client_request.session_transaction() as session:
|
||||
session["recipient"] = "2028675301"
|
||||
session["placeholders"] = {"a": "b"}
|
||||
@@ -2812,8 +2907,9 @@ def test_send_notification_shows_error_if_400(
|
||||
exception_msg,
|
||||
expected_h1,
|
||||
expected_err_details,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
|
||||
class MockHTTPError(HTTPError):
|
||||
message = exception_msg
|
||||
|
||||
@@ -2851,8 +2947,9 @@ def test_send_notification_shows_email_error_in_trial_mode(
|
||||
mocker,
|
||||
mock_get_service_email_template,
|
||||
mock_create_job,
|
||||
mock_s3_upload,
|
||||
):
|
||||
mocker.patch("app.main.views.send.s3upload", return_value=sample_uuid())
|
||||
|
||||
class MockHTTPError(HTTPError):
|
||||
message = TRIAL_MODE_MSG
|
||||
status_code = 400
|
||||
@@ -2897,9 +2994,6 @@ def test_reply_to_is_previewed_if_chosen(
|
||||
client_request,
|
||||
mocker,
|
||||
mock_get_service_email_template,
|
||||
mock_s3_download,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
@@ -2910,6 +3004,8 @@ def test_reply_to_is_previewed_if_chosen(
|
||||
extra_args,
|
||||
reply_to_address,
|
||||
):
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="""
|
||||
@@ -2952,9 +3048,6 @@ def test_sms_sender_is_previewed(
|
||||
client_request,
|
||||
mocker,
|
||||
mock_get_service_template,
|
||||
mock_s3_download,
|
||||
mock_s3_get_metadata,
|
||||
mock_s3_set_metadata,
|
||||
mock_get_users_by_service,
|
||||
mock_get_service_statistics,
|
||||
mock_get_job_doesnt_exist,
|
||||
@@ -2965,6 +3058,13 @@ def test_sms_sender_is_previewed(
|
||||
extra_args,
|
||||
sms_sender,
|
||||
):
|
||||
|
||||
mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": "example.csv"},
|
||||
)
|
||||
mocker.patch(
|
||||
"app.main.views.send.s3download",
|
||||
return_value="""
|
||||
|
||||
@@ -1843,14 +1843,6 @@ def mock_get_users_by_service(mocker):
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_s3_upload(mocker):
|
||||
def _upload(service_id, filedata):
|
||||
return sample_uuid()
|
||||
|
||||
return mocker.patch("app.main.views.send.s3upload", side_effect=_upload)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_s3_download(mocker):
|
||||
def _download(service_id, upload_id):
|
||||
@@ -1863,21 +1855,6 @@ def mock_s3_download(mocker):
|
||||
return mocker.patch("app.main.views.send.s3download", side_effect=_download)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_s3_get_metadata(mocker):
|
||||
def _get_metadata(service_id, upload_id):
|
||||
return {"original_file_name": "example.csv"}
|
||||
|
||||
return mocker.patch(
|
||||
"app.main.views.send.get_csv_metadata", side_effect=_get_metadata
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_s3_set_metadata(mocker):
|
||||
return mocker.patch("app.main.views.send.set_metadata_on_csv_upload")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sample_invite(mocker, service_one):
|
||||
id_ = USER_ONE_ID
|
||||
|
||||
@@ -191,7 +191,7 @@ def handle_no_existing_template_case(page):
|
||||
in content
|
||||
)
|
||||
assert "12025555555" in content
|
||||
assert "one-off-e2e_test_user" in content
|
||||
assert "one-off-" in content
|
||||
os.remove("download_test_file")
|
||||
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import io
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from notifications_utils.clients.antivirus.antivirus_client import (
|
||||
AntivirusClient,
|
||||
AntivirusError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def antivirus(app, mocker):
|
||||
client = AntivirusClient()
|
||||
app.config["ANTIVIRUS_API_HOST"] = "https://antivirus"
|
||||
app.config["ANTIVIRUS_API_KEY"] = "test-antivirus-key"
|
||||
client.init_app(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_scan_document(antivirus, rmock):
|
||||
document = io.BytesIO(b"filecontents")
|
||||
rmock.request(
|
||||
"POST",
|
||||
"https://antivirus/scan",
|
||||
json={"ok": True},
|
||||
request_headers={
|
||||
"Authorization": "Bearer test-antivirus-key",
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
resp = antivirus.scan(document)
|
||||
|
||||
assert resp
|
||||
assert "filecontents" in rmock.last_request.text
|
||||
assert document.tell() == 0
|
||||
|
||||
|
||||
def test_should_raise_for_status(antivirus, rmock):
|
||||
with pytest.raises(AntivirusError) as excinfo:
|
||||
_test_one_statement_for_status(antivirus, rmock)
|
||||
|
||||
assert excinfo.value.message == "Antivirus error"
|
||||
assert excinfo.value.status_code == 400
|
||||
|
||||
|
||||
def _test_one_statement_for_status(antivirus, rmock):
|
||||
rmock.request(
|
||||
"POST",
|
||||
"https://antivirus/scan",
|
||||
json={"error": "Antivirus error"},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
antivirus.scan(io.BytesIO(b"document"))
|
||||
|
||||
|
||||
def test_should_raise_for_connection_errors(antivirus, rmock):
|
||||
with pytest.raises(AntivirusError) as excinfo:
|
||||
_test_one_statement_for_connection_errors(antivirus, rmock)
|
||||
|
||||
assert excinfo.value.message == "connection error"
|
||||
assert excinfo.value.status_code == 503
|
||||
|
||||
|
||||
def _test_one_statement_for_connection_errors(antivirus, rmock):
|
||||
rmock.request(
|
||||
"POST", "https://antivirus/scan", exc=requests.exceptions.ConnectTimeout
|
||||
)
|
||||
antivirus.scan(io.BytesIO(b"document"))
|
||||
@@ -1,88 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from notifications_utils.clients.encryption.encryption_client import (
|
||||
Encryption,
|
||||
EncryptionError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def encryption_client(app):
|
||||
client = Encryption()
|
||||
|
||||
app.config["SECRET_KEY"] = "test-notify-secret-key"
|
||||
app.config["DANGEROUS_SALT"] = "test-notify-salt"
|
||||
|
||||
client.init_app(app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def test_should_ensure_shared_salt_security(app):
|
||||
client = Encryption()
|
||||
app.config["SECRET_KEY"] = "test-notify-secret-key"
|
||||
app.config["DANGEROUS_SALT"] = "too-short"
|
||||
with pytest.raises(EncryptionError):
|
||||
client.init_app(app)
|
||||
|
||||
|
||||
def test_should_ensure_custom_salt_security(encryption_client):
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.encrypt("this", salt="too-short")
|
||||
|
||||
|
||||
def test_should_encrypt_strings(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
assert encrypted != "this"
|
||||
assert isinstance(encrypted, str)
|
||||
|
||||
|
||||
def test_should_encrypt_dicts(encryption_client):
|
||||
to_encrypt = {"hello": "world"}
|
||||
encrypted = encryption_client.encrypt(to_encrypt)
|
||||
assert encrypted != to_encrypt
|
||||
assert encryption_client.decrypt(encrypted) == to_encrypt
|
||||
|
||||
|
||||
def test_encryption_is_nondeterministic(encryption_client):
|
||||
first_run = encryption_client.encrypt("this")
|
||||
second_run = encryption_client.encrypt("this")
|
||||
assert first_run != second_run
|
||||
|
||||
|
||||
def test_should_decrypt_content(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
assert encryption_client.decrypt(encrypted) == "this"
|
||||
|
||||
|
||||
def test_should_decrypt_content_with_custom_salt(encryption_client):
|
||||
salt = "different-salt-value"
|
||||
encrypted = encryption_client.encrypt("this", salt=salt)
|
||||
assert encryption_client.decrypt(encrypted, salt=salt) == "this"
|
||||
|
||||
|
||||
def test_should_verify_decryption(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.decrypt(encrypted, salt="different-salt-value")
|
||||
|
||||
|
||||
def test_should_sign_and_serialize_string(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
assert signed != "this"
|
||||
|
||||
|
||||
def test_should_verify_signature_and_deserialize_string(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
assert encryption_client.verify_signature(signed) == "this"
|
||||
|
||||
|
||||
def test_should_raise_encryption_error_on_bad_salt(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.verify_signature(signed, salt="different-salt-value")
|
||||
|
||||
|
||||
def test_should_sign_and_serialize_json(encryption_client):
|
||||
signed = encryption_client.sign({"this": "that"})
|
||||
assert encryption_client.verify_signature(signed) == {"this": "that"}
|
||||
@@ -1,227 +0,0 @@
|
||||
from base64 import b64decode
|
||||
|
||||
import pytest
|
||||
|
||||
from notifications_utils.clients.zendesk.zendesk_client import (
|
||||
NotifySupportTicket,
|
||||
ZendeskClient,
|
||||
ZendeskError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def zendesk_client(app):
|
||||
client = ZendeskClient()
|
||||
|
||||
app.config["ZENDESK_API_KEY"] = "testkey"
|
||||
|
||||
client.init_app(app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def test_zendesk_client_send_ticket_to_zendesk(zendesk_client, app, mocker, rmock):
|
||||
rmock.request(
|
||||
"POST",
|
||||
ZendeskClient.ZENDESK_TICKET_URL,
|
||||
status_code=201,
|
||||
json={
|
||||
"ticket": {
|
||||
"id": 12345,
|
||||
"subject": "Something is wrong",
|
||||
}
|
||||
},
|
||||
)
|
||||
mock_logger = mocker.patch.object(app.logger, "info")
|
||||
|
||||
ticket = NotifySupportTicket("subject", "message", "incident")
|
||||
zendesk_client.send_ticket_to_zendesk(ticket)
|
||||
|
||||
assert rmock.last_request.headers["Authorization"][:6] == "Basic "
|
||||
b64_auth = rmock.last_request.headers["Authorization"][6:]
|
||||
assert (
|
||||
b64decode(b64_auth.encode()).decode()
|
||||
== "zd-api-notify@digital.cabinet-office.gov.uk/token:testkey"
|
||||
)
|
||||
assert rmock.last_request.json() == ticket.request_data
|
||||
mock_logger.assert_called_once_with("Zendesk create ticket 12345 succeeded")
|
||||
|
||||
|
||||
def test_zendesk_client_send_ticket_to_zendesk_error(
|
||||
zendesk_client, app, mocker, rmock
|
||||
):
|
||||
rmock.request(
|
||||
"POST", ZendeskClient.ZENDESK_TICKET_URL, status_code=401, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
mock_logger = mocker.patch.object(app.logger, "error")
|
||||
|
||||
ticket = NotifySupportTicket("subject", "message", "incident")
|
||||
|
||||
with pytest.raises(ZendeskError):
|
||||
zendesk_client.send_ticket_to_zendesk(ticket)
|
||||
|
||||
mock_logger.assert_called_with(
|
||||
"Zendesk create ticket request failed with 401 '{'foo': 'bar'}'"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("p1_arg", "expected_tags", "expected_priority"),
|
||||
[
|
||||
(
|
||||
{},
|
||||
["govuk_notify_support"],
|
||||
"normal",
|
||||
),
|
||||
(
|
||||
{
|
||||
"p1": False,
|
||||
},
|
||||
["govuk_notify_support"],
|
||||
"normal",
|
||||
),
|
||||
(
|
||||
{
|
||||
"p1": True,
|
||||
},
|
||||
["govuk_notify_emergency"],
|
||||
"urgent",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_notify_support_ticket_request_data(p1_arg, expected_tags, expected_priority):
|
||||
notify_ticket_form = NotifySupportTicket("subject", "message", "question", **p1_arg)
|
||||
|
||||
assert notify_ticket_form.request_data == {
|
||||
"ticket": {
|
||||
"subject": "subject",
|
||||
"comment": {
|
||||
"body": "message",
|
||||
"public": True,
|
||||
},
|
||||
"group_id": NotifySupportTicket.NOTIFY_GROUP_ID,
|
||||
"organization_id": NotifySupportTicket.NOTIFY_ORG_ID,
|
||||
"ticket_form_id": NotifySupportTicket.NOTIFY_TICKET_FORM_ID,
|
||||
"priority": expected_priority,
|
||||
"tags": expected_tags,
|
||||
"type": "question",
|
||||
"custom_fields": [
|
||||
{"id": "1900000744994", "value": "notify_ticket_type_non_technical"},
|
||||
{"id": "360022836500", "value": []},
|
||||
{"id": "360022943959", "value": None},
|
||||
{"id": "360022943979", "value": None},
|
||||
{"id": "1900000745014", "value": None},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_notify_support_ticket_request_data_with_message_hidden_from_requester():
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "problem", requester_sees_message_content=False
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["comment"]["public"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "zendesk_name"), [("Name", "Name"), (None, "(no name supplied)")]
|
||||
)
|
||||
def test_notify_support_ticket_request_data_with_user_name_and_email(
|
||||
name, zendesk_name
|
||||
):
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", user_name=name, user_email="user@example.com"
|
||||
)
|
||||
|
||||
assert (
|
||||
notify_ticket_form.request_data["ticket"]["requester"]["email"]
|
||||
== "user@example.com"
|
||||
)
|
||||
assert (
|
||||
notify_ticket_form.request_data["ticket"]["requester"]["name"] == zendesk_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"custom_fields",
|
||||
"tech_ticket_tag",
|
||||
"categories",
|
||||
"org_id",
|
||||
"org_type",
|
||||
"service_id",
|
||||
),
|
||||
[
|
||||
(
|
||||
{"technical_ticket": True},
|
||||
"notify_ticket_type_technical",
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"technical_ticket": False},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"ticket_categories": ["notify_billing", "notify_bug"]},
|
||||
"notify_ticket_type_non_technical",
|
||||
["notify_billing", "notify_bug"],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"org_id": "1234", "org_type": "local"},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
"1234",
|
||||
"notify_org_type_local",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"service_id": "abcd", "org_type": "nhs"},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
None,
|
||||
"notify_org_type_nhs",
|
||||
"abcd",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_notify_support_ticket_request_data_custom_fields(
|
||||
custom_fields,
|
||||
tech_ticket_tag,
|
||||
categories,
|
||||
org_id,
|
||||
org_type,
|
||||
service_id,
|
||||
):
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", **custom_fields
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["custom_fields"] == [
|
||||
{"id": "1900000744994", "value": tech_ticket_tag},
|
||||
{"id": "360022836500", "value": categories},
|
||||
{"id": "360022943959", "value": org_id},
|
||||
{"id": "360022943979", "value": org_type},
|
||||
{"id": "1900000745014", "value": service_id},
|
||||
]
|
||||
|
||||
|
||||
def test_notify_support_ticket_request_data_email_ccs():
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", email_ccs=["someone@example.com"]
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["email_ccs"] == [
|
||||
{"user_email": "someone@example.com", "action": "put"},
|
||||
]
|
||||
@@ -49,3 +49,13 @@ def test_base_json_formatter_contains_service_id():
|
||||
== "message to log"
|
||||
)
|
||||
assert service_id_filter.filter(record).service_id == "no-service-id"
|
||||
|
||||
|
||||
def test_scrub():
|
||||
result = logging.scrub(
|
||||
"This is a message with 17775554324, and also 18884449323 and also 17775554324"
|
||||
)
|
||||
assert (
|
||||
result
|
||||
== "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324"
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ from urllib.parse import parse_qs
|
||||
|
||||
import botocore
|
||||
import pytest
|
||||
from moto import mock_aws
|
||||
|
||||
from notifications_utils.s3 import S3ObjectNotFound, s3download, s3upload
|
||||
|
||||
@@ -12,6 +13,7 @@ location = "some_file_location"
|
||||
content_type = "binary/octet-stream"
|
||||
|
||||
|
||||
@mock_aws
|
||||
def test_s3upload_save_file_to_bucket(mocker):
|
||||
mocked = mocker.patch("notifications_utils.s3.Session.resource")
|
||||
s3upload(
|
||||
|
||||
Reference in New Issue
Block a user