Files
notifications-admin/tests/app/main/views/test_activity.py
T

664 lines
19 KiB
Python
Raw Normal View History

import json
import uuid
2018-07-10 15:49:57 +01:00
from functools import partial
from urllib.parse import parse_qs, urlparse
import pytest
2018-02-20 11:22:17 +00:00
from flask import url_for
from freezegun import freeze_time
2018-04-25 14:12:58 +01:00
from app.main.views.jobs import get_status_filters, get_time_left
2018-10-26 15:58:44 +01:00
from app.models.service import Service
from tests.conftest import (
SERVICE_ONE_ID,
2019-12-19 16:59:07 +00:00
create_active_caseworking_user,
create_active_user_view_permissions,
create_active_user_with_permissions,
create_notifications,
normalize_spaces,
)
2018-02-20 11:22:17 +00:00
@pytest.mark.parametrize(
"user,extra_args,expected_update_endpoint,expected_limit_days,page_title", [
(
2019-12-19 16:59:07 +00:00
create_active_user_view_permissions(),
{'message_type': 'email'},
'/email.json',
7,
'Emails',
),
(
2019-12-19 16:59:07 +00:00
create_active_user_view_permissions(),
{'message_type': 'sms'},
'/sms.json',
7,
'Text messages',
),
(
2019-12-19 16:59:07 +00:00
create_active_caseworking_user(),
{},
'.json',
None,
'Sent messages',
),
]
)
@pytest.mark.parametrize(
"status_argument, expected_api_call", [
(
'',
[
2022-12-05 16:36:34 -05:00
'created', 'pending', 'sending', 'delivered', 'sent', 'failed',
'temporary-failure', 'permanent-failure', 'technical-failure',
'validation-failed'
]
),
(
'sending',
['sending', 'created', 'pending']
),
(
'delivered',
2022-12-05 15:33:44 -05:00
['delivered', 'sent']
),
(
'failed',
[
'failed', 'temporary-failure', 'permanent-failure', 'technical-failure',
'validation-failed'
]
)
]
)
@pytest.mark.parametrize(
"page_argument, expected_page_argument", [
(1, 1),
(22, 22),
(None, 1)
]
)
@pytest.mark.parametrize(
"to_argument, expected_to_argument", [
('', ''),
('+12029000123', '+12029000123'),
('test@example.com', 'test@example.com'),
]
)
2022-11-22 16:33:55 -05:00
@freeze_time('2020-01-01 06:00')
def test_can_show_notifications(
2018-07-13 16:21:22 +01:00
client_request,
service_one,
mock_get_notifications,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
2018-07-30 17:40:32 +01:00
mock_has_no_jobs,
mock_get_no_api_keys,
2018-07-13 16:21:22 +01:00
user,
extra_args,
expected_update_endpoint,
expected_limit_days,
page_title,
status_argument,
expected_api_call,
page_argument,
expected_page_argument,
to_argument,
expected_to_argument,
2018-06-13 14:16:10 +01:00
mocker,
):
2019-12-19 16:59:07 +00:00
client_request.login(user)
2017-06-13 11:05:57 +01:00
if expected_to_argument:
2018-07-13 16:21:22 +01:00
page = client_request.post(
2017-06-13 11:05:57 +01:00
'main.view_notifications',
2019-03-26 12:35:32 +00:00
service_id=SERVICE_ONE_ID,
2017-06-13 11:05:57 +01:00
status=status_argument,
page=page_argument,
2018-07-13 16:21:22 +01:00
_data={
'to': to_argument
},
_expected_status=200,
**extra_args
)
else:
page = client_request.get(
'main.view_notifications',
2019-03-26 12:35:32 +00:00
service_id=SERVICE_ONE_ID,
2018-07-13 16:21:22 +01:00
status=status_argument,
page=page_argument,
**extra_args
)
first_row = page.select_one('tbody tr')
assert normalize_spaces(
first_row.select_one('a.file-list-filename.govuk-link').text
) == (
# Comes from
# https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L436
'2021234567'
)
assert normalize_spaces(
# Were doing str() here not .text to make sure theres no extra
# HTML sneaking in
str(first_row.select_one('.file-list-hint'))
) == (
# Comes from
# https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L271
'template content'
) or (
# Comes from
# https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L273
'template subject'
)
assert normalize_spaces(
first_row.select_one('.table-field-right-aligned .align-with-message-body').text
) == (
'Delivered 1 January at 06:01 UTC'
)
assert page_title in page.h1.text.strip()
path_to_json = page.find("div", {'data-key': 'notifications'})['data-resource']
url = urlparse(path_to_json)
2018-07-13 16:21:22 +01:00
assert url.path == '/services/{}/notifications{}'.format(
2019-03-26 12:35:32 +00:00
SERVICE_ONE_ID,
2018-07-13 16:21:22 +01:00
expected_update_endpoint,
)
query_dict = parse_qs(url.query)
if status_argument:
assert query_dict['status'] == [status_argument]
if expected_page_argument:
assert query_dict['page'] == [str(expected_page_argument)]
assert 'to' not in query_dict
mock_get_notifications.assert_called_with(
limit_days=expected_limit_days,
page=expected_page_argument,
2019-03-26 12:35:32 +00:00
service_id=SERVICE_ONE_ID,
status=expected_api_call,
2018-07-13 16:21:22 +01:00
template_type=list(extra_args.values()),
to=expected_to_argument,
)
2021-12-31 12:08:14 +00:00
json_response = client_request.get_response(
'main.get_notifications_as_json',
service_id=service_one['id'],
2018-07-13 16:21:22 +01:00
status=status_argument,
**extra_args
2021-12-31 12:08:14 +00:00
)
json_content = json.loads(json_response.get_data(as_text=True))
assert json_content.keys() == {'counts', 'notifications', 'service_data_retention_days'}
def test_can_show_notifications_if_data_retention_not_available(
client_request,
mock_get_notifications,
mock_get_service_statistics,
mock_has_no_jobs,
mock_get_no_api_keys,
):
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
status='sending,delivered,failed',
)
assert page.h1.text.strip() == 'Messages'
2018-07-10 15:49:57 +01:00
@pytest.mark.parametrize('user, query_parameters, expected_download_link', [
(
2019-12-19 16:59:07 +00:00
create_active_user_with_permissions(),
2018-07-10 15:49:57 +01:00
{},
partial(
url_for,
'.download_notifications_csv',
message_type=None,
),
),
(
2019-12-19 16:59:07 +00:00
create_active_user_with_permissions(),
2018-07-10 15:49:57 +01:00
{'status': 'failed'},
partial(
url_for,
'.download_notifications_csv',
status='failed'
),
),
(
2019-12-19 16:59:07 +00:00
create_active_user_with_permissions(),
2018-07-10 15:49:57 +01:00
{'message_type': 'sms'},
partial(
url_for,
'.download_notifications_csv',
message_type='sms',
),
),
(
2019-12-19 16:59:07 +00:00
create_active_user_view_permissions(),
2018-07-10 15:49:57 +01:00
{},
partial(
url_for,
'.download_notifications_csv',
),
),
(
2019-12-19 16:59:07 +00:00
create_active_caseworking_user(),
2018-07-10 15:49:57 +01:00
{},
lambda service_id: None,
),
])
def test_link_to_download_notifications(
client_request,
mock_get_notifications,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
2018-07-30 17:40:32 +01:00
mock_has_no_jobs,
mock_get_no_api_keys,
2018-07-10 15:49:57 +01:00
user,
query_parameters,
expected_download_link,
):
2019-12-19 16:59:07 +00:00
client_request.login(user)
2018-07-10 15:49:57 +01:00
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
**query_parameters
)
download_link = page.select_one('a[download=download]')
assert (
download_link['href'] if download_link else None
) == expected_download_link(service_id=SERVICE_ONE_ID)
2018-08-09 17:51:34 +01:00
def test_download_not_available_to_users_without_dashboard(
2018-07-10 15:49:57 +01:00
client_request,
active_caseworking_user,
):
client_request.login(active_caseworking_user)
client_request.get(
'main.download_notifications_csv',
service_id=SERVICE_ONE_ID,
_expected_status=403,
)
def test_shows_message_when_no_notifications(
client_request,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_notifications_with_no_notifications,
mock_get_no_api_keys,
):
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
message_type='sms',
)
assert normalize_spaces(page.select('tbody tr')[0].text) == (
2018-07-10 17:06:06 +01:00
'No messages found (messages are kept for 7 days)'
)
2017-06-13 11:05:57 +01:00
@pytest.mark.parametrize((
'initial_query_arguments,'
'form_post_data,'
'expected_search_box_label,'
2017-06-13 11:05:57 +01:00
'expected_search_box_contents'
), [
(
{},
{},
2020-04-21 13:55:23 +01:00
'Search by recipient',
2020-08-07 10:16:24 +01:00
None,
),
(
{
'message_type': 'sms',
},
2017-06-13 11:05:57 +01:00
{},
'Search by phone number',
2020-08-07 10:16:24 +01:00
None,
),
(
{
'message_type': 'sms',
2017-06-13 11:05:57 +01:00
},
{
'to': '+33(0)5-12-34-56-78',
},
'Search by phone number',
'+33(0)5-12-34-56-78',
),
(
{
'status': 'failed',
'message_type': 'email',
'page': '99',
2017-06-13 11:05:57 +01:00
},
{
'to': 'test@example.com',
},
'Search by email address',
'test@example.com',
),
])
def test_search_recipient_form(
2019-03-26 12:35:32 +00:00
client_request,
mock_get_notifications,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
initial_query_arguments,
2017-06-13 11:05:57 +01:00
form_post_data,
expected_search_box_label,
expected_search_box_contents,
):
2019-03-26 12:35:32 +00:00
page = client_request.post(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
_data=form_post_data,
_expected_status=200,
**initial_query_arguments
2017-06-13 11:05:57 +01:00
)
2017-06-13 11:05:57 +01:00
assert page.find("form")['method'] == 'post'
action_url = page.find("form")['action']
url = urlparse(action_url)
assert url.path == '/services/{}/notifications/{}'.format(
SERVICE_ONE_ID,
initial_query_arguments.get('message_type', '')
).rstrip('/')
query_dict = parse_qs(url.query)
assert query_dict == {}
assert page.select_one('label[for=to]').text.strip() == expected_search_box_label
2017-06-14 16:25:20 +01:00
recipient_inputs = page.select("input[name=to]")
2023-07-26 10:58:53 -07:00
assert (len(recipient_inputs) == 2)
2017-06-14 16:25:20 +01:00
for field in recipient_inputs:
2020-08-07 10:16:24 +01:00
assert field.get("value") == expected_search_box_contents
2020-01-10 15:42:46 +00:00
@pytest.mark.parametrize('message_type, expected_search_box_label', [
2020-04-21 13:55:23 +01:00
(None, 'Search by recipient or reference'),
2020-01-10 15:42:46 +00:00
('sms', 'Search by phone number or reference'),
('email', 'Search by email address or reference'),
])
2020-01-10 15:42:46 +00:00
def test_api_users_are_told_they_can_search_by_reference_when_service_has_api_keys(
client_request,
mocker,
fake_uuid,
mock_get_notifications,
mock_get_service_statistics,
mock_get_service_data_retention,
message_type,
expected_search_box_label,
2020-01-10 15:42:46 +00:00
mock_get_api_keys,
):
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
message_type=message_type,
)
assert page.select_one('label[for=to]').text.strip() == expected_search_box_label
@pytest.mark.parametrize('message_type, expected_search_box_label', [
2020-04-21 13:55:23 +01:00
(None, 'Search by recipient'),
2020-01-10 15:42:46 +00:00
('sms', 'Search by phone number'),
('email', 'Search by email address'),
])
def test_api_users_are_not_told_they_can_search_by_reference_when_service_has_no_api_keys(
client_request,
mocker,
fake_uuid,
mock_get_notifications,
mock_get_service_statistics,
mock_get_service_data_retention,
message_type,
expected_search_box_label,
mock_get_no_api_keys,
):
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
message_type=message_type,
)
assert page.select_one('label[for=to]').text.strip() == expected_search_box_label
def test_should_show_notifications_for_a_service_with_next_previous(
2019-03-26 12:35:32 +00:00
client_request,
service_one,
active_user_with_permissions,
mock_get_notifications_with_previous_next,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
mocker,
):
2019-03-26 12:35:32 +00:00
page = client_request.get(
'main.view_notifications',
service_id=service_one['id'],
message_type='sms',
page=2
2019-03-26 12:35:32 +00:00
)
next_page_link = page.find('a', {'rel': 'next'})
prev_page_link = page.find('a', {'rel': 'previous'})
assert (
url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=3) in
next_page_link['href']
)
assert 'Next page' in next_page_link.text.strip()
assert 'page 3' in next_page_link.text.strip()
assert (
url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=1) in
prev_page_link['href']
)
assert 'Previous page' in prev_page_link.text.strip()
assert 'page 1' in prev_page_link.text.strip()
def test_doesnt_show_pagination_with_search_term(
client_request,
service_one,
active_user_with_permissions,
mock_get_notifications_with_previous_next,
mock_get_service_statistics,
mock_get_service_data_retention,
mock_get_no_api_keys,
mocker,
):
page = client_request.post(
'main.view_notifications',
service_id=service_one['id'],
message_type='sms',
_data={
'to': 'test@example.com',
},
_expected_status=200,
)
assert len(page.select('tbody tr')) == 50
assert not page.find('a', {'rel': 'next'})
assert not page.find('a', {'rel': 'previous'})
assert normalize_spaces(
page.select_one('.table-show-more-link').text
) == (
'Only showing the first 50 messages'
)
@pytest.mark.parametrize(
"job_created_at, expected_message", [
("2016-01-10 11:09:00.000000+00:00", "Data available for 7 days"),
("2016-01-04 11:09:00.000000+00:00", "Data available for 1 day"),
("2016-01-03 11:09:00.000000+00:00", "Data available for 12 hours"),
("2016-01-02 23:59:59.000000+00:00", "Data no longer available")
]
)
@freeze_time("2016-01-10 12:00:00.000000")
def test_time_left(job_created_at, expected_message):
assert get_time_left(job_created_at) == expected_message
STATISTICS = {
'sms': {
'requested': 6,
'failed': 2,
'delivered': 1
}
}
def test_get_status_filters_calculates_stats(client_request):
2018-10-26 12:13:04 +01:00
ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS)
assert {label: count for label, _option, _link, count in ret} == {
'total': 6,
'pending': 3,
'failed': 2,
'delivered': 1
}
def test_get_status_filters_in_right_order(client_request):
2018-10-26 12:13:04 +01:00
ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS)
assert [label for label, _option, _link, _count in ret] == [
'total', 'pending', 'delivered', 'failed'
]
def test_get_status_filters_constructs_links(client_request):
2018-10-26 12:13:04 +01:00
ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS)
link = ret[0][2]
assert link == '/services/foo/notifications/sms?status={}'.format('sending,delivered,failed')
def test_html_contains_notification_id(
2019-03-26 12:35:32 +00:00
client_request,
service_one,
active_user_with_permissions,
mock_get_notifications,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
mocker,
):
2019-03-26 12:35:32 +00:00
page = client_request.get(
'main.view_notifications',
service_id=service_one['id'],
message_type='sms',
2019-03-26 12:35:32 +00:00
status='',
)
2019-03-26 12:35:32 +00:00
notifications = page.tbody.find_all('tr')
for tr in notifications:
assert uuid.UUID(tr.attrs['id'])
2017-06-24 17:29:28 +01:00
def test_html_contains_links_for_failed_notifications(
client_request,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
mocker,
):
notifications = create_notifications(status='technical-failure')
mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications)
response = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
message_type='sms',
status='sending%2Cdelivered%2Cfailed'
)
notifications = response.tbody.find_all('tr')
for tr in notifications:
link_text = tr.find('div', class_='table-field-status-error').find('a').text
assert normalize_spaces(link_text) == 'Technical failure'
@pytest.mark.parametrize('notification_type, expected_row_contents', (
('sms', (
'2021234567 hello & welcome hidden'
)),
('email', (
2022-08-05 00:25:03 -07:00
'example@gsa.gov hidden, hello & welcome'
)),
))
2017-06-24 17:29:28 +01:00
def test_redacts_templates_that_should_be_redacted(
client_request,
mocker,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
notification_type,
expected_row_contents,
2017-06-24 17:29:28 +01:00
):
notifications = create_notifications(
status='technical-failure',
content='hello & welcome ((name))',
subject='((name)), hello & welcome',
2017-06-24 17:29:28 +01:00
personalisation={'name': 'Jo'},
redact_personalisation=True,
template_type=notification_type,
2017-06-24 17:29:28 +01:00
)
mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications)
2017-06-24 17:29:28 +01:00
page = client_request.get(
'main.view_notifications',
service_id=SERVICE_ONE_ID,
message_type=notification_type,
2017-06-24 17:29:28 +01:00
)
assert normalize_spaces(page.select('tbody tr th')[0].text) == (
expected_row_contents
2017-06-24 17:29:28 +01:00
)
@freeze_time("2017-09-27 12:30:00.000000")
@pytest.mark.parametrize(
2018-10-11 10:54:39 +01:00
"message_type, status, expected_hint_status, single_line", [
('email', 'created', 'Sending since 27 September at 12:30 UTC', True),
('email', 'sending', 'Sending since 27 September at 12:30 UTC', True),
('email', 'temporary-failure', 'Inbox not accepting messages right now 27 September at 12:31 UTC', False),
('email', 'permanent-failure', 'Email address does not exist 27 September at 12:31 UTC', False),
('email', 'delivered', 'Delivered 27 September at 12:31 UTC', True),
('sms', 'created', 'Sending since 27 September at 12:30 UTC', True),
('sms', 'sending', 'Sending since 27 September at 12:30 UTC', True),
('sms', 'temporary-failure', 'Phone not accepting messages right now 27 September at 12:31 UTC', False),
('sms', 'permanent-failure', 'Not delivered 27 September at 12:31 UTC', False),
('sms', 'delivered', 'Delivered 27 September at 12:31 UTC', True),
]
)
def test_sending_status_hint_displays_correctly_on_notifications_page(
client_request,
service_one,
mock_get_service_statistics,
2018-12-03 17:19:38 +00:00
mock_get_service_data_retention,
mock_get_no_api_keys,
message_type,
2018-10-11 10:54:39 +01:00
status,
expected_hint_status,
single_line,
mocker
):
notifications = create_notifications(template_type=message_type, status=status)
mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications)
page = client_request.get(
2017-10-18 14:51:26 +01:00
'main.view_notifications',
service_id=service_one['id'],
message_type=message_type
)
2018-10-11 10:54:39 +01:00
assert normalize_spaces(page.select(".table-field-right-aligned")[0].text) == expected_hint_status
assert bool(page.select('.align-with-message-body')) is single_line