mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-21 10:54:49 -05:00
It’s not clear where 103 is coming from unless you know that the code is doing the slightly unintuitive thing of displaying the max of all return values.
957 lines
36 KiB
Python
957 lines
36 KiB
Python
import datetime
|
||
import re
|
||
import uuid
|
||
from functools import partial
|
||
from unittest.mock import ANY, call
|
||
|
||
import pyexcel
|
||
import pytest
|
||
from bs4 import BeautifulSoup
|
||
from flask import url_for
|
||
from freezegun import freeze_time
|
||
|
||
from app.main.views.platform_admin import (
|
||
create_global_stats,
|
||
format_stats_by_service,
|
||
get_tech_failure_status_box_data,
|
||
is_over_threshold,
|
||
sum_service_usage,
|
||
)
|
||
from tests import service_json
|
||
from tests.conftest import SERVICE_ONE_ID, SERVICE_TWO_ID, normalize_spaces
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint', [
|
||
'main.platform_admin',
|
||
'main.live_services',
|
||
'main.trial_services',
|
||
])
|
||
def test_should_redirect_if_not_logged_in(
|
||
client,
|
||
endpoint
|
||
):
|
||
response = client.get(url_for(endpoint))
|
||
assert response.status_code == 302
|
||
assert response.location == url_for('main.sign_in', next=url_for(endpoint), _external=True)
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint', [
|
||
'main.platform_admin',
|
||
'main.platform_admin_splash_page',
|
||
'main.live_services',
|
||
'main.trial_services',
|
||
])
|
||
def test_should_403_if_not_platform_admin(
|
||
client_request,
|
||
endpoint,
|
||
):
|
||
client_request.get(endpoint, _expected_status=403)
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint, expected_services_shown', [
|
||
('main.live_services', 1),
|
||
('main.trial_services', 1),
|
||
])
|
||
def test_should_render_platform_admin_page(
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
endpoint,
|
||
expected_services_shown
|
||
):
|
||
response = platform_admin_client.get(url_for(endpoint))
|
||
assert response.status_code == 200
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
assert [
|
||
normalize_spaces(column.text)
|
||
for column in page.select('tbody tr')[1].select('td')
|
||
] == [
|
||
'0 emails sent', '0 text messages sent', '0 letters sent',
|
||
]
|
||
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
||
'include_from_test_key': True,
|
||
'only_active': False})
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint', [
|
||
'main.live_services',
|
||
'main.trial_services',
|
||
])
|
||
@pytest.mark.parametrize('partial_url_for, inc', [
|
||
(partial(url_for), True),
|
||
(partial(url_for, include_from_test_key='y', start_date='', end_date=''), True),
|
||
(partial(url_for, start_date='', end_date=''), False),
|
||
])
|
||
def test_live_trial_services_toggle_including_from_test_key(
|
||
partial_url_for,
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
endpoint,
|
||
inc
|
||
):
|
||
response = platform_admin_client.get(partial_url_for(endpoint))
|
||
|
||
assert response.status_code == 200
|
||
mock_get_detailed_services.assert_called_once_with({
|
||
'detailed': True,
|
||
'only_active': False,
|
||
'include_from_test_key': inc,
|
||
})
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint', [
|
||
'main.live_services',
|
||
'main.trial_services'
|
||
])
|
||
def test_live_trial_services_with_date_filter(
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
endpoint
|
||
):
|
||
response = platform_admin_client.get(url_for(endpoint, start_date='2016-12-20', end_date='2016-12-28'))
|
||
|
||
assert response.status_code == 200
|
||
resp_data = response.get_data(as_text=True)
|
||
assert 'Platform admin' in resp_data
|
||
mock_get_detailed_services.assert_called_once_with({
|
||
'include_from_test_key': False,
|
||
'end_date': datetime.date(2016, 12, 28),
|
||
'start_date': datetime.date(2016, 12, 20),
|
||
'detailed': True,
|
||
'only_active': False,
|
||
})
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint, expected_big_numbers', [
|
||
(
|
||
'main.live_services', (
|
||
'55 emails sent 5 failed – 5.0%',
|
||
'110 text messages sent 10 failed – 5.0%',
|
||
'15 letters sent 3 failed – 20.0%'
|
||
),
|
||
),
|
||
(
|
||
'main.trial_services', (
|
||
'6 emails sent 1 failed – 10.0%',
|
||
'11 text messages sent 1 failed – 5.0%',
|
||
'30 letters sent 10 failed – 33.3%'
|
||
),
|
||
),
|
||
])
|
||
def test_should_show_total_on_live_trial_services_pages(
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
endpoint,
|
||
fake_uuid,
|
||
expected_big_numbers,
|
||
):
|
||
services = [
|
||
service_json(fake_uuid, 'My Service 1', [], restricted=False),
|
||
service_json(fake_uuid, 'My Service 2', [], restricted=True),
|
||
]
|
||
services[0]['statistics'] = create_stats(
|
||
emails_requested=100,
|
||
emails_delivered=50,
|
||
emails_failed=5,
|
||
sms_requested=200,
|
||
sms_delivered=100,
|
||
sms_failed=10,
|
||
letters_requested=15,
|
||
letters_delivered=12,
|
||
letters_failed=3
|
||
)
|
||
|
||
services[1]['statistics'] = create_stats(
|
||
emails_requested=10,
|
||
emails_delivered=5,
|
||
emails_failed=1,
|
||
sms_requested=20,
|
||
sms_delivered=10,
|
||
sms_failed=1,
|
||
letters_requested=30,
|
||
letters_delivered=20,
|
||
letters_failed=10
|
||
)
|
||
|
||
mock_get_detailed_services.return_value = {'data': services}
|
||
|
||
response = platform_admin_client.get(url_for(endpoint))
|
||
assert response.status_code == 200
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
assert (
|
||
normalize_spaces(page.select('.big-number-with-status')[0].text),
|
||
normalize_spaces(page.select('.big-number-with-status')[1].text),
|
||
normalize_spaces(page.select('.big-number-with-status')[2].text),
|
||
) == expected_big_numbers
|
||
|
||
|
||
def test_create_global_stats_sets_failure_rates(fake_uuid):
|
||
services = [
|
||
service_json(fake_uuid, 'a', []),
|
||
service_json(fake_uuid, 'b', [])
|
||
]
|
||
services[0]['statistics'] = create_stats(
|
||
emails_requested=1,
|
||
emails_delivered=1,
|
||
emails_failed=0,
|
||
)
|
||
services[1]['statistics'] = create_stats(
|
||
emails_requested=2,
|
||
emails_delivered=1,
|
||
emails_failed=1,
|
||
)
|
||
|
||
stats = create_global_stats(services)
|
||
assert stats == {
|
||
'email': {
|
||
'delivered': 2,
|
||
'failed': 1,
|
||
'requested': 3,
|
||
'failure_rate': '33.3'
|
||
},
|
||
'sms': {
|
||
'delivered': 0,
|
||
'failed': 0,
|
||
'requested': 0,
|
||
'failure_rate': '0'
|
||
},
|
||
'letter': {
|
||
'delivered': 0,
|
||
'failed': 0,
|
||
'requested': 0,
|
||
'failure_rate': '0'
|
||
}
|
||
}
|
||
|
||
|
||
def create_stats(
|
||
emails_requested=0,
|
||
emails_delivered=0,
|
||
emails_failed=0,
|
||
sms_requested=0,
|
||
sms_delivered=0,
|
||
sms_failed=0,
|
||
letters_requested=0,
|
||
letters_delivered=0,
|
||
letters_failed=0
|
||
):
|
||
return {
|
||
'sms': {
|
||
'requested': sms_requested,
|
||
'delivered': sms_delivered,
|
||
'failed': sms_failed,
|
||
},
|
||
'email': {
|
||
'requested': emails_requested,
|
||
'delivered': emails_delivered,
|
||
'failed': emails_failed,
|
||
},
|
||
'letter': {
|
||
'requested': letters_requested,
|
||
'delivered': letters_delivered,
|
||
'failed': letters_failed,
|
||
},
|
||
}
|
||
|
||
|
||
def test_format_stats_by_service_returns_correct_values(fake_uuid):
|
||
services = [service_json(fake_uuid, 'a', [])]
|
||
services[0]['statistics'] = create_stats(
|
||
emails_requested=10,
|
||
emails_delivered=3,
|
||
emails_failed=5,
|
||
sms_requested=50,
|
||
sms_delivered=7,
|
||
sms_failed=11,
|
||
letters_requested=40,
|
||
letters_delivered=20,
|
||
letters_failed=7
|
||
)
|
||
|
||
ret = list(format_stats_by_service(services))
|
||
assert len(ret) == 1
|
||
|
||
assert ret[0]['stats']['email']['requested'] == 10
|
||
assert ret[0]['stats']['email']['delivered'] == 3
|
||
assert ret[0]['stats']['email']['failed'] == 5
|
||
|
||
assert ret[0]['stats']['sms']['requested'] == 50
|
||
assert ret[0]['stats']['sms']['delivered'] == 7
|
||
assert ret[0]['stats']['sms']['failed'] == 11
|
||
|
||
assert ret[0]['stats']['letter']['requested'] == 40
|
||
assert ret[0]['stats']['letter']['delivered'] == 20
|
||
assert ret[0]['stats']['letter']['failed'] == 7
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint, restricted, research_mode', [
|
||
('main.trial_services', True, False),
|
||
('main.live_services', False, False)
|
||
])
|
||
def test_should_show_email_and_sms_stats_for_all_service_types(
|
||
endpoint,
|
||
restricted,
|
||
research_mode,
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
fake_uuid,
|
||
):
|
||
services = [service_json(fake_uuid, 'My Service', [], restricted=restricted, research_mode=research_mode)]
|
||
services[0]['statistics'] = create_stats(
|
||
emails_requested=10,
|
||
emails_delivered=3,
|
||
emails_failed=5,
|
||
sms_requested=50,
|
||
sms_delivered=7,
|
||
sms_failed=11
|
||
)
|
||
|
||
mock_get_detailed_services.return_value = {'data': services}
|
||
response = platform_admin_client.get(url_for(endpoint))
|
||
|
||
assert response.status_code == 200
|
||
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
||
'include_from_test_key': True,
|
||
'only_active': ANY})
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
|
||
table_body = page.find_all('table')[0].find_all('tbody')[0]
|
||
service_row_group = table_body.find_all('tbody')[0].find_all('tr')
|
||
email_stats = service_row_group[1].select('.big-number-number')[0]
|
||
sms_stats = service_row_group[1].select('.big-number-number')[1]
|
||
|
||
assert normalize_spaces(email_stats.text) == '10'
|
||
assert normalize_spaces(sms_stats.text) == '50'
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint, restricted', [
|
||
('main.live_services', False),
|
||
('main.trial_services', True)
|
||
], ids=['live', 'trial'])
|
||
def test_should_show_archived_services_last(
|
||
endpoint,
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
restricted,
|
||
):
|
||
services = [
|
||
service_json(name='C', restricted=restricted, active=False, created_at='2002-02-02 12:00:00'),
|
||
service_json(name='B', restricted=restricted, active=True, created_at='2001-01-01 12:00:00'),
|
||
service_json(name='A', restricted=restricted, active=True, created_at='2003-03-03 12:00:00'),
|
||
]
|
||
services[0]['statistics'] = create_stats()
|
||
services[1]['statistics'] = create_stats()
|
||
services[2]['statistics'] = create_stats()
|
||
|
||
mock_get_detailed_services.return_value = {'data': services}
|
||
response = platform_admin_client.get(url_for(endpoint))
|
||
|
||
assert response.status_code == 200
|
||
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
||
'include_from_test_key': True,
|
||
'only_active': ANY})
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
|
||
table_body = page.find_all('table')[0].find_all('tbody')[0]
|
||
services = [service.tr for service in table_body.find_all('tbody')]
|
||
assert len(services) == 3
|
||
assert normalize_spaces(services[0].td.text) == 'A'
|
||
assert normalize_spaces(services[1].td.text) == 'B'
|
||
assert normalize_spaces(services[2].td.text) == 'C Archived'
|
||
|
||
|
||
@pytest.mark.parametrize('endpoint, restricted, research_mode', [
|
||
('main.trial_services', True, False),
|
||
('main.live_services', False, False)
|
||
])
|
||
def test_should_order_services_by_usage_with_inactive_last(
|
||
endpoint,
|
||
restricted,
|
||
research_mode,
|
||
platform_admin_client,
|
||
mock_get_detailed_services,
|
||
fake_uuid,
|
||
):
|
||
services = [
|
||
service_json(fake_uuid, 'My Service 1', [], restricted=restricted, research_mode=research_mode),
|
||
service_json(fake_uuid, 'My Service 2', [], restricted=restricted, research_mode=research_mode),
|
||
service_json(fake_uuid, 'My Service 3', [], restricted=restricted, research_mode=research_mode, active=False)
|
||
]
|
||
services[0]['statistics'] = create_stats(
|
||
emails_requested=100,
|
||
emails_delivered=25,
|
||
emails_failed=25,
|
||
sms_requested=100,
|
||
sms_delivered=25,
|
||
sms_failed=25
|
||
)
|
||
|
||
services[1]['statistics'] = create_stats(
|
||
emails_requested=200,
|
||
emails_delivered=50,
|
||
emails_failed=50,
|
||
sms_requested=200,
|
||
sms_delivered=50,
|
||
sms_failed=50
|
||
)
|
||
|
||
services[2]['statistics'] = create_stats(
|
||
emails_requested=200,
|
||
emails_delivered=50,
|
||
emails_failed=50,
|
||
sms_requested=200,
|
||
sms_delivered=50,
|
||
sms_failed=50
|
||
)
|
||
|
||
mock_get_detailed_services.return_value = {'data': services}
|
||
response = platform_admin_client.get(url_for(endpoint))
|
||
|
||
assert response.status_code == 200
|
||
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
||
'include_from_test_key': True,
|
||
'only_active': ANY})
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
|
||
table_body = page.find_all('table')[0].find_all('tbody')[0]
|
||
services = [service.tr for service in table_body.find_all('tbody')]
|
||
assert len(services) == 3
|
||
assert normalize_spaces(services[0].td.text) == 'My Service 2'
|
||
assert normalize_spaces(services[1].td.text) == 'My Service 1'
|
||
assert normalize_spaces(services[2].td.text) == 'My Service 3 Archived'
|
||
|
||
|
||
def test_sum_service_usage_is_sum_of_all_activity(fake_uuid):
|
||
service = service_json(fake_uuid, 'My Service 1')
|
||
service['statistics'] = create_stats(
|
||
emails_requested=100,
|
||
emails_delivered=25,
|
||
emails_failed=25,
|
||
sms_requested=100,
|
||
sms_delivered=25,
|
||
sms_failed=25
|
||
)
|
||
assert sum_service_usage(service) == 200
|
||
|
||
|
||
def test_sum_service_usage_with_zeros(fake_uuid):
|
||
service = service_json(fake_uuid, 'My Service 1')
|
||
service['statistics'] = create_stats(
|
||
emails_requested=0,
|
||
emails_delivered=0,
|
||
emails_failed=25,
|
||
sms_requested=0,
|
||
sms_delivered=0,
|
||
sms_failed=0
|
||
)
|
||
assert sum_service_usage(service) == 0
|
||
|
||
|
||
def test_platform_admin_list_complaints(
|
||
platform_admin_client,
|
||
mocker
|
||
):
|
||
complaint = {
|
||
'id': str(uuid.uuid4()),
|
||
'notification_id': str(uuid.uuid4()),
|
||
'service_id': str(uuid.uuid4()),
|
||
'service_name': 'Sample service',
|
||
'ses_feedback_id': 'Some ses id',
|
||
'complaint_type': 'abuse',
|
||
'complaint_date': '2018-06-05T13:50:30.012354',
|
||
'created_at': '2018-06-05T13:50:30.012354',
|
||
}
|
||
mock = mocker.patch('app.complaint_api_client.get_all_complaints',
|
||
return_value={'complaints': [complaint], 'links': {}})
|
||
|
||
response = platform_admin_client.get(url_for('main.platform_admin_list_complaints'))
|
||
|
||
assert response.status_code == 200
|
||
resp_data = response.get_data(as_text=True)
|
||
assert 'Email complaints' in resp_data
|
||
assert mock.called
|
||
|
||
|
||
def test_should_show_complaints_with_next_previous(platform_admin_client, mocker, service_one, fake_uuid):
|
||
|
||
api_response = {
|
||
'complaints': [{'complaint_date': None,
|
||
'complaint_type': None,
|
||
'created_at': '2017-12-18T05:00:00.000000Z',
|
||
'id': fake_uuid,
|
||
'notification_id': fake_uuid,
|
||
'service_id': service_one['id'],
|
||
'service_name': service_one['name'],
|
||
'ses_feedback_id': 'None'}],
|
||
'links': {'last': '/complaint?page=3', 'next': '/complaint?page=3', 'prev': '/complaint?page=1'}
|
||
}
|
||
|
||
mocker.patch('app.complaint_api_client.get_all_complaints', return_value=api_response)
|
||
|
||
response = platform_admin_client.get(url_for('main.platform_admin_list_complaints', page=2))
|
||
|
||
assert response.status_code == 200
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
|
||
next_page_link = page.find('a', {'rel': 'next'})
|
||
prev_page_link = page.find('a', {'rel': 'previous'})
|
||
assert (url_for('main.platform_admin_list_complaints', 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.platform_admin_list_complaints', 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_platform_admin_list_complaints_returns_404_with_invalid_page(platform_admin_client, mocker):
|
||
|
||
mocker.patch('app.complaint_api_client.get_all_complaints', return_value={'complaints': [], 'links': {}})
|
||
|
||
response = platform_admin_client.get(url_for('main.platform_admin_list_complaints', page='invalid'))
|
||
|
||
assert response.status_code == 404
|
||
|
||
|
||
@pytest.mark.parametrize('number, total, threshold, result', [
|
||
(0, 0, 0, False),
|
||
(1, 1, 0, True),
|
||
(2, 3, 66, True),
|
||
(2, 3, 67, False),
|
||
])
|
||
def test_is_over_threshold(number, total, threshold, result):
|
||
assert is_over_threshold(number, total, threshold) is result
|
||
|
||
|
||
def test_get_tech_failure_status_box_data_removes_percentage_data():
|
||
stats = {
|
||
'failures':
|
||
{'permanent-failure': 0, 'technical-failure': 0, 'temporary-failure': 1, 'virus-scan-failed': 0},
|
||
'test-key': 0,
|
||
'total': 5589
|
||
}
|
||
tech_failure_data = get_tech_failure_status_box_data(stats)
|
||
|
||
assert 'percentage' not in tech_failure_data
|
||
|
||
|
||
def test_platform_admin_splash_doesnt_talk_to_api(
|
||
client_request,
|
||
platform_admin_user,
|
||
):
|
||
|
||
client_request.login(platform_admin_user)
|
||
|
||
page = client_request.get('main.platform_admin_splash_page')
|
||
|
||
assert page.select_one('main .govuk-body a')['href'] == url_for(
|
||
'main.platform_admin',
|
||
)
|
||
|
||
|
||
def test_platform_admin_with_start_and_end_dates_provided(mocker, platform_admin_client):
|
||
start_date = '2018-01-01'
|
||
end_date = '2018-06-01'
|
||
api_args = {'start_date': datetime.date(2018, 1, 1), 'end_date': datetime.date(2018, 6, 1)}
|
||
|
||
mocker.patch('app.main.views.platform_admin.make_columns')
|
||
aggregate_stats_mock = mocker.patch(
|
||
'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats')
|
||
complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count')
|
||
|
||
platform_admin_client.get(
|
||
url_for('main.platform_admin', start_date=start_date, end_date=end_date)
|
||
)
|
||
|
||
aggregate_stats_mock.assert_called_with(api_args)
|
||
complaint_count_mock.assert_called_with(api_args)
|
||
|
||
|
||
@freeze_time('2018-6-11')
|
||
def test_platform_admin_with_only_a_start_date_provided(mocker, platform_admin_client):
|
||
start_date = '2018-01-01'
|
||
api_args = {'start_date': datetime.date(2018, 1, 1), 'end_date': datetime.datetime.utcnow().date()}
|
||
|
||
mocker.patch('app.main.views.platform_admin.make_columns')
|
||
aggregate_stats_mock = mocker.patch(
|
||
'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats')
|
||
complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count')
|
||
|
||
platform_admin_client.get(url_for('main.platform_admin', start_date=start_date))
|
||
|
||
aggregate_stats_mock.assert_called_with(api_args)
|
||
complaint_count_mock.assert_called_with(api_args)
|
||
|
||
|
||
def test_platform_admin_without_dates_provided(mocker, platform_admin_client):
|
||
api_args = {}
|
||
|
||
mocker.patch('app.main.views.platform_admin.make_columns')
|
||
aggregate_stats_mock = mocker.patch(
|
||
'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats')
|
||
complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count')
|
||
|
||
platform_admin_client.get(url_for('main.platform_admin'))
|
||
|
||
aggregate_stats_mock.assert_called_with(api_args)
|
||
complaint_count_mock.assert_called_with(api_args)
|
||
|
||
|
||
def test_platform_admin_displays_stats_in_right_boxes_and_with_correct_styling(
|
||
mocker,
|
||
platform_admin_client,
|
||
):
|
||
platform_stats = {
|
||
'email': {'failures':
|
||
{'permanent-failure': 3, 'technical-failure': 0, 'temporary-failure': 0, 'virus-scan-failed': 0},
|
||
'test-key': 0,
|
||
'total': 145},
|
||
'sms': {'failures':
|
||
{'permanent-failure': 0, 'technical-failure': 1, 'temporary-failure': 0, 'virus-scan-failed': 0},
|
||
'test-key': 5,
|
||
'total': 168},
|
||
'letter': {'failures':
|
||
{'permanent-failure': 0, 'technical-failure': 0, 'temporary-failure': 1, 'virus-scan-failed': 1},
|
||
'test-key': 0,
|
||
'total': 500}
|
||
}
|
||
mocker.patch('app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats',
|
||
return_value=platform_stats)
|
||
mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count', return_value=15)
|
||
|
||
response = platform_admin_client.get(url_for('main.platform_admin'))
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
|
||
# Email permanent failure status box - number is correct
|
||
assert '3 permanent failures' in page.find_all(
|
||
'div', class_='govuk-grid-column-one-third'
|
||
)[0].find(string=re.compile('permanent'))
|
||
# Email complaints status box - link exists and number is correct
|
||
assert page.find('a', string='15 complaints')
|
||
# SMS total box - number is correct
|
||
assert page.find_all('div', class_='big-number-number')[1].text.strip() == '168'
|
||
# Test SMS box - number is correct
|
||
assert '5' in page.find_all('div', class_='govuk-grid-column-one-third')[4].text
|
||
# SMS technical failure status box - number is correct and failure class is used
|
||
assert '1 technical failures' in page.find_all('div', class_='govuk-grid-column-one-third')[1].find(
|
||
'div', class_='big-number-status-failing').text
|
||
# Letter virus scan failure status box - number is correct and failure class is used
|
||
assert '1 virus scan failures' in page.find_all('div', class_='govuk-grid-column-one-third')[2].find(
|
||
'div', class_='big-number-status-failing').text
|
||
|
||
|
||
def test_platform_admin_submit_returned_letters(mocker, platform_admin_client):
|
||
|
||
redis = mocker.patch('app.main.views.platform_admin.redis_client')
|
||
mock_client = mocker.patch('app.letter_jobs_client.submit_returned_letters')
|
||
|
||
response = platform_admin_client.post(
|
||
url_for('main.platform_admin_returned_letters'),
|
||
data={'references': ' NOTIFY000REF1 \n NOTIFY002REF2 '}
|
||
)
|
||
|
||
mock_client.assert_called_once_with(['REF1', 'REF2'])
|
||
assert redis.delete_cache_keys_by_pattern.call_args_list == [
|
||
call('service-????????-????-????-????-????????????-returned-letters-statistics'),
|
||
call('service-????????-????-????-????-????????????-returned-letters-summary'),
|
||
]
|
||
assert response.status_code == 302
|
||
assert response.location == url_for('main.platform_admin_returned_letters', _external=True)
|
||
|
||
|
||
def test_platform_admin_submit_empty_returned_letters(mocker, platform_admin_client):
|
||
|
||
mock_client = mocker.patch('app.letter_jobs_client.submit_returned_letters')
|
||
|
||
response = platform_admin_client.post(
|
||
url_for('main.platform_admin_returned_letters'),
|
||
data={'references': ' \n '}
|
||
)
|
||
|
||
assert not mock_client.called
|
||
|
||
assert response.status_code == 200
|
||
assert "Cannot be empty" in response.get_data(as_text=True)
|
||
|
||
|
||
def test_clear_cache_shows_form(client_request, platform_admin_user, mocker):
|
||
redis = mocker.patch('app.main.views.platform_admin.redis_client')
|
||
client_request.login(platform_admin_user)
|
||
|
||
page = client_request.get('main.clear_cache')
|
||
|
||
assert page.select('input[type=radio]')[0]['value'] == 'user'
|
||
assert page.select('input[type=radio]')[1]['value'] == 'service'
|
||
assert page.select('input[type=radio]')[2]['value'] == 'template'
|
||
assert page.select('input[type=radio]')[3]['value'] == 'email_branding'
|
||
assert page.select('input[type=radio]')[4]['value'] == 'letter_branding'
|
||
assert page.select('input[type=radio]')[5]['value'] == 'organisation'
|
||
assert not redis.delete_cache_keys_by_pattern.called
|
||
|
||
|
||
@pytest.mark.parametrize('model_type, expected_calls, expected_confirmation', (
|
||
('template', [
|
||
# Returns 101
|
||
call('service-????????-????-????-????-????????????-templates'),
|
||
# Returns 102
|
||
call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*'),
|
||
# Returns 103
|
||
call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions'),
|
||
# 103 shown here because it’s the `max` of the 3 counts
|
||
], 'Removed 103 template objects from redis'),
|
||
('service', [
|
||
call('has_jobs-????????-????-????-????-????????????'),
|
||
call('service-????????-????-????-????-????????????'),
|
||
call('service-????????-????-????-????-????????????-templates'),
|
||
call('service-????????-????-????-????-????????????-data-retention'),
|
||
call('service-????????-????-????-????-????????????-template-folders'),
|
||
call('service-????????-????-????-????-????????????-returned-letters-statistics'),
|
||
call('service-????????-????-????-????-????????????-returned-letters-summary'),
|
||
], 'Removed 107 service objects from redis'),
|
||
('organisation', [
|
||
call('organisations'),
|
||
call('domains'),
|
||
call('live-service-and-organisation-counts'),
|
||
call('organisation-????????-????-????-????-????????????-name'),
|
||
], 'Removed 104 organisation objects from redis'),
|
||
('broadcast', [
|
||
call('service-????????-????-????-????-????????????-broadcast-message-????????-????-????-????-????????????'),
|
||
], 'Removed 101 broadcast objects from redis'),
|
||
))
|
||
def test_clear_cache_submits_and_tells_you_how_many_things_were_deleted(
|
||
client_request,
|
||
platform_admin_user,
|
||
mocker,
|
||
model_type,
|
||
expected_calls,
|
||
expected_confirmation,
|
||
):
|
||
redis = mocker.patch('app.main.views.platform_admin.redis_client')
|
||
# The way this is set up means the first time `delete_cache_keys_by_pattern`
|
||
# is called it will return `101`, the second time it will return `102`, etc
|
||
redis.delete_cache_keys_by_pattern.side_effect = [101, 102, 103, 104, 105, 106, 107, 108, 109]
|
||
client_request.login(platform_admin_user)
|
||
|
||
page = client_request.post('main.clear_cache', _data={'model_type': model_type}, _expected_status=200)
|
||
|
||
assert redis.delete_cache_keys_by_pattern.call_args_list == expected_calls
|
||
|
||
flash_banner = page.find('div', class_='banner-default')
|
||
assert flash_banner.text.strip() == expected_confirmation
|
||
|
||
|
||
def test_clear_cache_requires_option(client_request, platform_admin_user, mocker):
|
||
redis = mocker.patch('app.main.views.platform_admin.redis_client')
|
||
client_request.login(platform_admin_user)
|
||
|
||
page = client_request.post('main.clear_cache', _data={}, _expected_status=200)
|
||
|
||
assert normalize_spaces(page.find('span', class_='error-message').text) == 'Select an option'
|
||
assert not redis.delete_cache_keys_by_pattern.called
|
||
|
||
|
||
def test_reports_page(
|
||
platform_admin_client
|
||
):
|
||
|
||
response = platform_admin_client.get(url_for('main.platform_admin_reports'))
|
||
|
||
assert response.status_code == 200
|
||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||
assert page.find(
|
||
'a', text="Download live services csv report"
|
||
).attrs['href'] == '/platform-admin/reports/live-services.csv'
|
||
|
||
assert page.find(
|
||
'a', text="Download performance platform report (.xlsx)"
|
||
).attrs['href'] == '/platform-admin/reports/performance-platform.xlsx'
|
||
|
||
assert page.find(
|
||
'a', text="Monthly notification statuses for live services"
|
||
).attrs['href'] == url_for('main.notifications_sent_by_service')
|
||
|
||
|
||
def test_get_live_services_report(platform_admin_client, mocker):
|
||
|
||
mocker.patch(
|
||
'app.service_api_client.get_live_services_data',
|
||
return_value={'data': [
|
||
{'service_id': 1, 'service_name': 'jessie the oak tree', 'organisation_name': 'Forest',
|
||
'consent_to_research': True, 'contact_name': 'Forest fairy', 'organisation_type': 'Ecosystem',
|
||
'contact_email': 'forest.fairy@digital.cabinet-office.gov.uk', 'contact_mobile': '+447700900986',
|
||
'live_date': 'Sat, 29 Mar 2014 00:00:00 GMT', 'sms_volume_intent': 100, 'email_volume_intent': 50,
|
||
'letter_volume_intent': 20, 'sms_totals': 300, 'email_totals': 1200, 'letter_totals': 0,
|
||
'free_sms_fragment_limit': 100},
|
||
{'service_id': 2, 'service_name': 'james the pine tree', 'organisation_name': 'Forest',
|
||
'consent_to_research': None, 'contact_name': None, 'organisation_type': 'Ecosystem',
|
||
'contact_email': None, 'contact_mobile': None,
|
||
'live_date': None, 'sms_volume_intent': None, 'email_volume_intent': 60,
|
||
'letter_volume_intent': 0, 'sms_totals': 0, 'email_totals': 0, 'letter_totals': 0,
|
||
'free_sms_fragment_limit': 200},
|
||
]}
|
||
)
|
||
response = platform_admin_client.get(url_for('main.live_services_csv'))
|
||
assert response.status_code == 200
|
||
report = response.get_data(as_text=True)
|
||
assert report.strip() == (
|
||
'Service ID,Organisation,Organisation type,Service name,Consent to research,Main contact,Contact email,'
|
||
+ 'Contact mobile,Live date,SMS volume intent,Email volume intent,Letter volume intent,SMS sent this year,'
|
||
+ 'Emails sent this year,Letters sent this year,Free sms allowance\r\n'
|
||
|
||
+ '1,Forest,Ecosystem,jessie the oak tree,True,Forest fairy,forest.fairy@digital.cabinet-office.gov.uk,'
|
||
+ '+447700900986,29-03-2014,100,50,20,300,1200,0,100\r\n'
|
||
|
||
+ '2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,0,0,200'
|
||
)
|
||
|
||
|
||
def test_get_performance_platform_report(platform_admin_client, mocker):
|
||
|
||
mocker.patch(
|
||
'app.service_api_client.get_live_services_data',
|
||
return_value={'data': [
|
||
{'service_id': 'abc123', 'service_name': 'jessie the oak tree', 'organisation_name': 'Forest',
|
||
'consent_to_research': True, 'contact_name': 'Forest fairy', 'organisation_type': 'Ecosystem',
|
||
'contact_email': 'forest.fairy@digital.cabinet-office.gov.uk', 'contact_mobile': '+447700900986',
|
||
'live_date': 'Sat, 29 Mar 2014 00:00:00 GMT', 'sms_volume_intent': 100, 'email_volume_intent': 50,
|
||
'letter_volume_intent': 20, 'sms_totals': 300, 'email_totals': 1200, 'letter_totals': 0},
|
||
{'service_id': 'def456', 'service_name': 'james the pine tree', 'organisation_name': 'Forest',
|
||
'consent_to_research': None, 'contact_name': None, 'organisation_type': 'Ecosystem',
|
||
'contact_email': None, 'contact_mobile': None,
|
||
'live_date': None, 'sms_volume_intent': None, 'email_volume_intent': 60,
|
||
'letter_volume_intent': 0, 'sms_totals': 0, 'email_totals': 0, 'letter_totals': 0},
|
||
]}
|
||
)
|
||
response = platform_admin_client.get(url_for('main.performance_platform_xlsx'))
|
||
assert response.status_code == 200
|
||
assert pyexcel.get_array(
|
||
file_type='xlsx',
|
||
file_stream=response.get_data(),
|
||
) == [
|
||
['service_id', 'agency', 'service_name', '_timestamp', 'service', 'count'],
|
||
['abc123', 'Forest', 'jessie the oak tree', '2014-03-29T00:00:00Z', 'govuk-notify', 1],
|
||
['def456', 'Forest', 'james the pine tree', '', 'govuk-notify', 1],
|
||
]
|
||
|
||
|
||
def test_get_notifications_sent_by_service_shows_date_form(client_request, platform_admin_user):
|
||
client_request.login(platform_admin_user)
|
||
page = client_request.get('main.notifications_sent_by_service')
|
||
|
||
assert [
|
||
(input['type'], input['name'], input.get('value'))
|
||
for input in page.select('input')
|
||
] == [
|
||
('text', 'start_date', None),
|
||
('text', 'end_date', None),
|
||
('hidden', 'csrf_token', ANY)
|
||
]
|
||
|
||
|
||
def test_get_notifications_sent_by_service_validates_form(mocker, client_request, platform_admin_user):
|
||
mock_get_stats_from_api = mocker.patch('app.main.views.platform_admin.notification_api_client')
|
||
|
||
client_request.login(platform_admin_user)
|
||
|
||
page = client_request.post(
|
||
'main.notifications_sent_by_service',
|
||
_expected_status=200,
|
||
_data={'start_date': '', 'end_date': '20190101'}
|
||
)
|
||
|
||
errors = page.select('.govuk-error-message')
|
||
assert len(errors) == 2
|
||
|
||
for error in errors:
|
||
assert 'Not a valid date value' in error.text
|
||
|
||
assert mock_get_stats_from_api.called is False
|
||
|
||
|
||
def test_usage_for_all_services_when_no_results_for_date(client_request, platform_admin_user, mocker):
|
||
client_request.login(platform_admin_user)
|
||
|
||
mocker.patch("app.main.views.platform_admin.billing_api_client.get_usage_for_all_services",
|
||
return_value=[])
|
||
|
||
page = client_request.post('main.usage_for_all_services',
|
||
_expected_status=200,
|
||
_data={'start_date': '2019-01-01', 'end_date': '2019-03-31'})
|
||
|
||
error = page.select_one('.banner-dangerous')
|
||
assert normalize_spaces(error.text) == 'No results for dates'
|
||
|
||
|
||
def test_usage_for_all_services_when_calls_api_and_download_data(platform_admin_client, mocker):
|
||
mocker.patch("app.main.views.platform_admin.billing_api_client.get_usage_for_all_services",
|
||
return_value=[{'letter_breakdown': '6 second class letters at 45p\n2 first class letters at 35p\n',
|
||
'letter_cost': 3.4,
|
||
'organisation_id': '7832a1be-a1f0-4f2a-982f-05adfd3d6354',
|
||
'organisation_name': 'Org for a - with sms and letter',
|
||
'service_id': '48e82ac0-c8c4-4e46-8712-c83c35a94006',
|
||
'service_name': 'a - with sms and letter',
|
||
'sms_cost': 0, 'sms_fragments': 0
|
||
}])
|
||
|
||
response = platform_admin_client.post(url_for('main.usage_for_all_services'),
|
||
data={'start_date': '2019-01-01', 'end_date': '2019-03-31'})
|
||
|
||
assert response.status_code == 200
|
||
assert response.content_type == 'text/csv; charset=utf-8'
|
||
assert response.headers['Content-Disposition'] == (
|
||
'attachment; filename="Usage for all services from {} to {}.csv"'.format('2019-01-01', '2019-03-31')
|
||
)
|
||
|
||
assert response.get_data(as_text=True) == (
|
||
'organisation_id,organisation_name,service_id,service_name,' +
|
||
'sms_cost,sms_fragments,letter_cost,letter_breakdown' +
|
||
|
||
'\r\n' +
|
||
|
||
'7832a1be-a1f0-4f2a-982f-05adfd3d6354,' +
|
||
'Org for a - with sms and letter,' +
|
||
'48e82ac0-c8c4-4e46-8712-c83c35a94006,' +
|
||
'a - with sms and letter,' +
|
||
'0,' +
|
||
'0,' +
|
||
'3.4,' +
|
||
'"6 second class letters at 45p' +
|
||
'\n' +
|
||
'2 first class letters at 35p"' +
|
||
|
||
'\r\n'
|
||
)
|
||
|
||
|
||
def test_get_notifications_sent_by_service_calls_api_and_downloads_data(
|
||
mocker,
|
||
platform_admin_client,
|
||
service_one,
|
||
service_two,
|
||
):
|
||
api_data = [
|
||
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_ONE_ID, service_one['name'], 'email', 191, 0, 0, 14, 0, 0],
|
||
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_ONE_ID, service_one['name'], 'sms', 42, 0, 0, 8, 0, 0],
|
||
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_TWO_ID, service_two['name'], 'email', 3, 1, 0, 2, 0, 0],
|
||
]
|
||
mocker.patch('app.main.views.platform_admin.notification_api_client.get_notification_status_by_service',
|
||
return_value=api_data)
|
||
start_date = datetime.date(2019, 1, 1)
|
||
end_date = datetime.date(2019, 1, 31)
|
||
|
||
response = platform_admin_client.post(
|
||
url_for('main.notifications_sent_by_service'),
|
||
data={'start_date': start_date, 'end_date': end_date}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.content_type == 'text/csv; charset=utf-8'
|
||
assert response.headers['Content-Disposition'] == (
|
||
'attachment; filename="{} to {} notification status per service report.csv"'.format(start_date, end_date)
|
||
)
|
||
assert response.get_data(as_text=True) == (
|
||
'date_created,service_id,service_name,notification_type,count_sending,count_delivered,count_technical_failure,'
|
||
'count_temporary_failure,count_permanent_failure,count_sent\r\n'
|
||
'2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,email,191,0,0,14,0,0\r\n'
|
||
'2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,sms,42,0,0,8,0,0\r\n'
|
||
'2019-01-01,147ad62a-2951-4fa1-9ca0-093cd1a52c52,service two,email,3,1,0,2,0,0\r\n'
|
||
)
|