Files
notifications-admin/tests/app/main/views/test_platform_admin.py

1158 lines
38 KiB
Python
Raw Normal View History

import datetime
import re
import uuid
from functools import partial
2019-02-15 11:13:53 +00:00
from unittest.mock import ANY, call
import pytest
2019-11-29 13:03:23 +00:00
from flask import url_for
from freezegun import freeze_time
2016-05-24 15:52:44 +01:00
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
2017-09-22 23:09:44 +01:00
2016-05-24 15:52:44 +01:00
@pytest.mark.parametrize('endpoint', [
'main.platform_admin',
'main.live_services',
'main.trial_services',
])
def test_should_redirect_if_not_logged_in(
client_request,
endpoint
):
client_request.logout()
client_request.get(
endpoint,
_expected_redirect=url_for('main.sign_in', next=url_for(endpoint)),
)
2016-05-24 15:52:44 +01:00
@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)
2016-05-24 15:52:44 +01:00
@pytest.mark.parametrize('endpoint, expected_services_shown', [
('main.live_services', 1),
('main.trial_services', 1),
])
def test_should_render_platform_admin_page(
client_request,
platform_admin_user,
mock_get_detailed_services,
endpoint,
expected_services_shown
):
client_request.login(platform_admin_user)
page = client_request.get(endpoint)
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',
]
2017-09-22 23:09:44 +01:00
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,
client_request,
platform_admin_user,
mock_get_detailed_services,
endpoint,
2017-09-22 23:09:44 +01:00
inc
):
client_request.login(platform_admin_user)
client_request.get_url(partial_url_for(endpoint))
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(
client_request,
platform_admin_user,
mock_get_detailed_services,
2017-09-22 23:09:44 +01:00
endpoint
):
client_request.login(platform_admin_user)
page = client_request.get(
endpoint,
start_date='2016-12-20',
end_date='2016-12-28',
)
assert 'Platform admin' in page.text
mock_get_detailed_services.assert_called_once_with({
'include_from_test_key': False,
'end_date': datetime.date(2016, 12, 28),
2017-09-22 23:09:44 +01:00
'start_date': datetime.date(2016, 12, 20),
'detailed': True,
2017-09-22 23:09:44 +01:00
'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(
client_request,
platform_admin_user,
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}
client_request.login(platform_admin_user)
page = client_request.get(endpoint)
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(
Add flake8 linting to project The GDS Way™[1] recommends using Flake8 to lint Python projects. This commit takes the Flake8 config from Digital Marketplace API[2] and removes the bits we don’t need. It changes the `max_complexity` setting to 14, which is the most complex code we have in this repo currently (we shouldn’t be writing code _more_ complex than what we already have). This commit also fixes the errors found by Flake8, which includes 6(!) tests which were never getting run because they had the same names as existing tests. Here is a full list of the errors that were found and fixed: ``` ./app/__init__.py:2:1: F401 're' imported but unused ./app/__init__.py:4:1: F401 'json' imported but unused ./app/__init__.py:8:1: F401 'dateutil' imported but unused ./app/__init__.py:11:1: F401 'flask.escape' imported but unused ./app/__init__.py:41:1: F401 'app.proxy_fix' imported but unused ./app/__init__.py:129:5: F821 undefined name 'proxy_fix' ./app/__init__.py:221:19: F821 undefined name 'highlight' ./app/__init__.py:221:35: F821 undefined name 'JavascriptLexer' ./app/__init__.py:221:54: F821 undefined name 'HtmlFormatter' ./app/config.py:2:1: F401 'datetime.timedelta' imported but unused ./app/event_handlers.py:2:1: F401 'flask_login.current_user' imported but unused ./app/utils.py:11:1: F401 'dateutil.parser' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.two_factor' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.notifications' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.add_service' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.forgot_password' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.inbound_number' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.styleguide' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.organisations' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.letter_jobs' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.verify' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.conversation' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.api_keys' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.send' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.dashboard' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.jobs' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.manage_users' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.sign_in' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.sign_out' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.code_not_received' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.invites' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.platform_admin' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.providers' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.service_settings' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.index' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.new_password' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.user_profile' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.feedback' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.choose_service' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.templates' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.register' imported but unused ./app/main/forms.py:12:1: F401 'wtforms.SelectField' imported but unused ./app/main/views/api_keys.py:37:29: E241 multiple spaces after ':' ./app/main/views/feedback.py:3:1: F401 'flask.flash' imported but unused ./app/main/views/feedback.py:122:17: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/inbound_number.py:1:1: F401 'flask.url_for' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.session' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.redirect' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.request' imported but unused ./app/main/views/inbound_number.py:13:1: F401 'flask.jsonify' imported but unused ./app/main/views/jobs.py:31:1: F401 'app.utils.get_template' imported but unused ./app/main/views/letter_jobs.py:1:1: F401 'datetime' imported but unused ./app/main/views/letter_jobs.py:6:1: F401 'app.format_datetime_24h' imported but unused ./app/main/views/manage_users.py:111:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/notifications.py:121:5: F841 local variable 'status_args' is assigned to but never used ./app/main/views/organisations.py:1:1: F401 'flask.request' imported but unused ./app/main/views/service_settings.py:77:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/service_settings.py:82:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/service_settings.py:420:13: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/sign_in.py:12:1: F401 'flask_login.confirm_login' imported but unused ./app/main/views/sign_in.py:17:1: F401 'app.service_api_client' imported but unused ./app/main/views/sign_in.py:62:13: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/templates.py:4:1: F401 'flask.json' imported but unused ./app/main/views/templates.py:17:1: F401 'notifications_utils.formatters.escape_html' imported but unused ./app/main/views/templates.py:23:1: F401 'app.utils.get_help_argument' imported but unused ./app/main/views/templates.py:64:13: E123 closing bracket does not match indentation of opening bracket's line ./app/notify_client/service_api_client.py:6:1: F401 '.notification_api_client' imported but unused ./app/notify_client/user_api_client.py:1:1: F401 'uuid' imported but unused ./app/notify_client/user_api_client.py:3:1: F401 'flask.session' imported but unused ./tests/__init__.py:1:1: F401 'csv' imported but unused ./tests/app/main/test_asset_fingerprinter.py:2:1: F401 'os' imported but unused ./tests/app/main/test_asset_fingerprinter.py:4:1: F401 'unittest.mock' imported but unused ./tests/app/main/test_asset_fingerprinter.py:98:9: F841 local variable 'string_with_unicode_character' is assigned to but never used ./tests/app/main/test_errorhandlers.py:2:1: F401 'flask.url_for' imported but unused ./tests/app/main/test_permissions.py:26:13: F841 local variable 'response' is assigned to but never used ./tests/app/main/test_placeholder_form.py:3:1: F401 'wtforms.Label' imported but unused ./tests/app/main/test_placeholder_form.py:11:10: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:10:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:23:65: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:37:48: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:51:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:65:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/views/test_accept_invite.py:356:5: F841 local variable 'element' is assigned to but never used ./tests/app/main/views/test_activity.py:11:1: F811 redefinition of unused 'mock_get_notifications' from line 11 ./tests/app/main/views/test_activity.py:18:1: F401 'datetime.datetime' imported but unused ./tests/app/main/views/test_activity.py:102:5: F841 local variable 'content' is assigned to but never used ./tests/app/main/views/test_activity.py:104:5: F841 local variable 'notification' is assigned to but never used ./tests/app/main/views/test_activity.py:337:5: F841 local variable '_notifications_mock' is assigned to but never used ./tests/app/main/views/test_activity.py:373:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_activity.py:378:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_activity.py:404:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_activity.py:407:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_api_keys.py:354:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_conversation.py:5:1: F401 'bs4.BeautifulSoup' imported but unused ./tests/app/main/views/test_conversation.py:198:5: F841 local variable 'mock_get_inbound_sms' is assigned to but never used ./tests/app/main/views/test_dashboard.py:53:5: F841 local variable 'mock_template_stats' is assigned to but never used ./tests/app/main/views/test_dashboard.py:72:5: F841 local variable 'mock_template_stats' is assigned to but never used ./tests/app/main/views/test_jobs.py:2:1: F401 'uuid' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.urlparse' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.quote' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.parse_qs' imported but unused ./tests/app/main/views/test_jobs.py:9:1: F401 'app.main.views.jobs.get_status_filters' imported but unused ./tests/app/main/views/test_jobs.py:10:1: F401 'tests.notification_json' imported but unused ./tests/app/main/views/test_letters.py:6:1: F401 'tests.service_json' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.REQUESTED_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.DELIVERED_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.SENDING_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.FAILURE_STATUSES' imported but unused ./tests/app/main/views/test_platform_admin.py:242:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_platform_admin.py:247:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:3:1: F401 'unittest.mock.Mock' imported but unused ./tests/app/main/views/test_send.py:18:1: F811 redefinition of unused 'mock_get_service' from line 18 ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_letter_contact_blocks' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_sms_senders' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_sms_senders' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_letter_contact_blocks' imported but unused ./tests/app/main/views/test_send.py:102:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_send.py:870:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_send.py:1367:5: F841 local variable 'service_id' is assigned to but never used ./tests/app/main/views/test_send.py:1451:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:1620:80: E226 missing whitespace around arithmetic operator ./tests/app/main/views/test_send.py:1909:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:1912:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_service_settings.py:13:1: F811 redefinition of unused 'no_reply_to_email_addresses' from line 13 ./tests/app/main/views/test_service_settings.py:13:1: F401 'tests.conftest.single_reply_to_email_address' imported but unused ./tests/app/main/views/test_service_settings.py:28:5: E123 closing bracket does not match indentation of opening bracket's line ./tests/app/main/views/test_service_settings.py:104:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:166:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:186:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:217:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:237:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:257:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:307:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:340:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:466:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:555:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:615:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:719:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:874:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:902:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:954:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:986:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1101:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1121:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1271:1: F811 redefinition of unused 'test_set_letter_contact_block_saves' from line 1189 ./tests/app/main/views/test_service_settings.py:1433:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1495:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1540:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1570:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1589:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1621:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1641:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1658:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1676:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1697:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1759:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1775:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_templates.py:3:1: F401 'uuid' imported but unused ./tests/app/main/views/test_templates.py:11:1: F401 'tests.conftest.mock_get_user' imported but unused ./tests/app/main/views/test_templates.py:514:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:672:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:795:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:835:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_two_factor.py:67:13: E126 continuation line over-indented for hanging indent ./tests/app/notify_client/test_notification_client.py:79:5: F841 local variable 'mock_post' is assigned to but never used ``` 1. https://gds-way.cloudapps.digital/manuals/programming-languages/python/linting.html#how-to-use-flake8 2. https://github.com/alphagov/digitalmarketplace-api/blob/d5ab8afef4a0472f9d266d94ab4ffe1333a9aaad/.flake8
2017-10-18 14:51:26 +01:00
emails_requested=1,
emails_delivered=1,
emails_failed=0,
)
services[1]['statistics'] = create_stats(
Add flake8 linting to project The GDS Way™[1] recommends using Flake8 to lint Python projects. This commit takes the Flake8 config from Digital Marketplace API[2] and removes the bits we don’t need. It changes the `max_complexity` setting to 14, which is the most complex code we have in this repo currently (we shouldn’t be writing code _more_ complex than what we already have). This commit also fixes the errors found by Flake8, which includes 6(!) tests which were never getting run because they had the same names as existing tests. Here is a full list of the errors that were found and fixed: ``` ./app/__init__.py:2:1: F401 're' imported but unused ./app/__init__.py:4:1: F401 'json' imported but unused ./app/__init__.py:8:1: F401 'dateutil' imported but unused ./app/__init__.py:11:1: F401 'flask.escape' imported but unused ./app/__init__.py:41:1: F401 'app.proxy_fix' imported but unused ./app/__init__.py:129:5: F821 undefined name 'proxy_fix' ./app/__init__.py:221:19: F821 undefined name 'highlight' ./app/__init__.py:221:35: F821 undefined name 'JavascriptLexer' ./app/__init__.py:221:54: F821 undefined name 'HtmlFormatter' ./app/config.py:2:1: F401 'datetime.timedelta' imported but unused ./app/event_handlers.py:2:1: F401 'flask_login.current_user' imported but unused ./app/utils.py:11:1: F401 'dateutil.parser' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.two_factor' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.notifications' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.add_service' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.forgot_password' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.inbound_number' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.styleguide' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.organisations' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.letter_jobs' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.verify' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.conversation' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.api_keys' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.send' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.dashboard' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.jobs' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.manage_users' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.sign_in' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.sign_out' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.code_not_received' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.invites' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.platform_admin' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.providers' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.service_settings' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.index' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.new_password' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.user_profile' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.feedback' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.choose_service' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.templates' imported but unused ./app/main/__init__.py:5:1: F401 'app.main.views.register' imported but unused ./app/main/forms.py:12:1: F401 'wtforms.SelectField' imported but unused ./app/main/views/api_keys.py:37:29: E241 multiple spaces after ':' ./app/main/views/feedback.py:3:1: F401 'flask.flash' imported but unused ./app/main/views/feedback.py:122:17: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/inbound_number.py:1:1: F401 'flask.url_for' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.session' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.redirect' imported but unused ./app/main/views/inbound_number.py:1:1: F401 'flask.request' imported but unused ./app/main/views/inbound_number.py:13:1: F401 'flask.jsonify' imported but unused ./app/main/views/jobs.py:31:1: F401 'app.utils.get_template' imported but unused ./app/main/views/letter_jobs.py:1:1: F401 'datetime' imported but unused ./app/main/views/letter_jobs.py:6:1: F401 'app.format_datetime_24h' imported but unused ./app/main/views/manage_users.py:111:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/notifications.py:121:5: F841 local variable 'status_args' is assigned to but never used ./app/main/views/organisations.py:1:1: F401 'flask.request' imported but unused ./app/main/views/service_settings.py:77:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/service_settings.py:82:9: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/service_settings.py:420:13: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/sign_in.py:12:1: F401 'flask_login.confirm_login' imported but unused ./app/main/views/sign_in.py:17:1: F401 'app.service_api_client' imported but unused ./app/main/views/sign_in.py:62:13: E123 closing bracket does not match indentation of opening bracket's line ./app/main/views/templates.py:4:1: F401 'flask.json' imported but unused ./app/main/views/templates.py:17:1: F401 'notifications_utils.formatters.escape_html' imported but unused ./app/main/views/templates.py:23:1: F401 'app.utils.get_help_argument' imported but unused ./app/main/views/templates.py:64:13: E123 closing bracket does not match indentation of opening bracket's line ./app/notify_client/service_api_client.py:6:1: F401 '.notification_api_client' imported but unused ./app/notify_client/user_api_client.py:1:1: F401 'uuid' imported but unused ./app/notify_client/user_api_client.py:3:1: F401 'flask.session' imported but unused ./tests/__init__.py:1:1: F401 'csv' imported but unused ./tests/app/main/test_asset_fingerprinter.py:2:1: F401 'os' imported but unused ./tests/app/main/test_asset_fingerprinter.py:4:1: F401 'unittest.mock' imported but unused ./tests/app/main/test_asset_fingerprinter.py:98:9: F841 local variable 'string_with_unicode_character' is assigned to but never used ./tests/app/main/test_errorhandlers.py:2:1: F401 'flask.url_for' imported but unused ./tests/app/main/test_permissions.py:26:13: F841 local variable 'response' is assigned to but never used ./tests/app/main/test_placeholder_form.py:3:1: F401 'wtforms.Label' imported but unused ./tests/app/main/test_placeholder_form.py:11:10: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:10:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:23:65: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:37:48: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:51:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/test_two_factor_form.py:65:67: F841 local variable 'req' is assigned to but never used ./tests/app/main/views/test_accept_invite.py:356:5: F841 local variable 'element' is assigned to but never used ./tests/app/main/views/test_activity.py:11:1: F811 redefinition of unused 'mock_get_notifications' from line 11 ./tests/app/main/views/test_activity.py:18:1: F401 'datetime.datetime' imported but unused ./tests/app/main/views/test_activity.py:102:5: F841 local variable 'content' is assigned to but never used ./tests/app/main/views/test_activity.py:104:5: F841 local variable 'notification' is assigned to but never used ./tests/app/main/views/test_activity.py:337:5: F841 local variable '_notifications_mock' is assigned to but never used ./tests/app/main/views/test_activity.py:373:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_activity.py:378:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_activity.py:404:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_activity.py:407:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_api_keys.py:354:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_conversation.py:5:1: F401 'bs4.BeautifulSoup' imported but unused ./tests/app/main/views/test_conversation.py:198:5: F841 local variable 'mock_get_inbound_sms' is assigned to but never used ./tests/app/main/views/test_dashboard.py:53:5: F841 local variable 'mock_template_stats' is assigned to but never used ./tests/app/main/views/test_dashboard.py:72:5: F841 local variable 'mock_template_stats' is assigned to but never used ./tests/app/main/views/test_jobs.py:2:1: F401 'uuid' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.urlparse' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.quote' imported but unused ./tests/app/main/views/test_jobs.py:3:1: F401 'urllib.parse.parse_qs' imported but unused ./tests/app/main/views/test_jobs.py:9:1: F401 'app.main.views.jobs.get_status_filters' imported but unused ./tests/app/main/views/test_jobs.py:10:1: F401 'tests.notification_json' imported but unused ./tests/app/main/views/test_letters.py:6:1: F401 'tests.service_json' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.REQUESTED_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.DELIVERED_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.SENDING_STATUSES' imported but unused ./tests/app/main/views/test_notifications.py:5:1: F401 'app.utils.FAILURE_STATUSES' imported but unused ./tests/app/main/views/test_platform_admin.py:242:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_platform_admin.py:247:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:3:1: F401 'unittest.mock.Mock' imported but unused ./tests/app/main/views/test_send.py:18:1: F811 redefinition of unused 'mock_get_service' from line 18 ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_letter_contact_blocks' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_sms_senders' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.multiple_sms_senders' imported but unused ./tests/app/main/views/test_send.py:18:1: F401 'tests.conftest.no_letter_contact_blocks' imported but unused ./tests/app/main/views/test_send.py:102:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_send.py:870:5: F841 local variable 'response' is assigned to but never used ./tests/app/main/views/test_send.py:1367:5: F841 local variable 'service_id' is assigned to but never used ./tests/app/main/views/test_send.py:1451:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:1620:80: E226 missing whitespace around arithmetic operator ./tests/app/main/views/test_send.py:1909:13: E126 continuation line over-indented for hanging indent ./tests/app/main/views/test_send.py:1912:9: E121 continuation line under-indented for hanging indent ./tests/app/main/views/test_service_settings.py:13:1: F811 redefinition of unused 'no_reply_to_email_addresses' from line 13 ./tests/app/main/views/test_service_settings.py:13:1: F401 'tests.conftest.single_reply_to_email_address' imported but unused ./tests/app/main/views/test_service_settings.py:28:5: E123 closing bracket does not match indentation of opening bracket's line ./tests/app/main/views/test_service_settings.py:104:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:166:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:186:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:217:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:237:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:257:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:307:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:340:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:466:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:555:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:615:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:719:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:874:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:902:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:954:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:986:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1101:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1121:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1271:1: F811 redefinition of unused 'test_set_letter_contact_block_saves' from line 1189 ./tests/app/main/views/test_service_settings.py:1433:5: F841 local variable 'page' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1495:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1540:5: F841 local variable 'mocked_get_fn' is assigned to but never used ./tests/app/main/views/test_service_settings.py:1570:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1589:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1621:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1641:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1658:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1676:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1697:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1759:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_service_settings.py:1775:1: F811 redefinition of unused 'single_reply_to_email_address' from line 13 ./tests/app/main/views/test_templates.py:3:1: F401 'uuid' imported but unused ./tests/app/main/views/test_templates.py:11:1: F401 'tests.conftest.mock_get_user' imported but unused ./tests/app/main/views/test_templates.py:514:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:672:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:795:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_templates.py:835:1: F811 redefinition of unused 'mock_get_user' from line 11 ./tests/app/main/views/test_two_factor.py:67:13: E126 continuation line over-indented for hanging indent ./tests/app/notify_client/test_notification_client.py:79:5: F841 local variable 'mock_post' is assigned to but never used ``` 1. https://gds-way.cloudapps.digital/manuals/programming-languages/python/linting.html#how-to-use-flake8 2. https://github.com/alphagov/digitalmarketplace-api/blob/d5ab8afef4a0472f9d266d94ab4ffe1333a9aaad/.flake8
2017-10-18 14:51:26 +01:00
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,
client_request,
platform_admin_user,
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}
client_request.login(platform_admin_user)
page = client_request.get(endpoint)
2017-09-22 23:09:44 +01:00
mock_get_detailed_services.assert_called_once_with({'detailed': True,
'include_from_test_key': True,
'only_active': ANY})
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,
client_request,
platform_admin_user,
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}
client_request.login(platform_admin_user)
page = client_request.get(endpoint)
2017-09-22 23:09:44 +01:00
mock_get_detailed_services.assert_called_once_with({'detailed': True,
'include_from_test_key': True,
'only_active': ANY})
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,
client_request,
platform_admin_user,
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}
client_request.login(platform_admin_user)
page = client_request.get(endpoint)
2017-09-22 23:09:44 +01:00
mock_get_detailed_services.assert_called_once_with({'detailed': True,
'include_from_test_key': True,
'only_active': ANY})
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(
client_request,
platform_admin_user,
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': {}})
client_request.login(platform_admin_user)
page = client_request.get(
'main.platform_admin_list_complaints'
)
assert 'Email complaints' in page.text
assert mock.called
def test_should_show_complaints_with_next_previous(
client_request,
platform_admin_user,
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)
client_request.login(platform_admin_user)
page = client_request.get(
'main.platform_admin_list_complaints',
page=2,
)
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(
client_request,
platform_admin_user,
mocker,
):
mocker.patch('app.complaint_api_client.get_all_complaints', return_value={'complaints': [], 'links': {}})
client_request.login(platform_admin_user)
client_request.get(
'main.platform_admin_list_complaints',
page='invalid',
_expected_status=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,
client_request,
platform_admin_user,
):
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')
client_request.login(platform_admin_user)
client_request.get(
'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,
client_request,
platform_admin_user,
):
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')
client_request.login(platform_admin_user)
client_request.get(
'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,
client_request,
platform_admin_user,
):
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')
client_request.login(platform_admin_user)
client_request.get('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,
client_request,
platform_admin_user,
):
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)
client_request.login(platform_admin_user)
page = client_request.get('main.platform_admin')
# 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('span', 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,
client_request,
platform_admin_user,
):
redis = mocker.patch('app.main.views.platform_admin.redis_client')
mock_client = mocker.patch('app.letter_jobs_client.submit_returned_letters')
client_request.login(platform_admin_user)
client_request.post(
'main.platform_admin_returned_letters',
_data={'references': ' NOTIFY000REF1 \n NOTIFY002REF2 '},
_expected_redirect=url_for(
'main.platform_admin_returned_letters',
)
)
mock_client.assert_called_once_with(['REF1', 'REF2'])
assert redis.delete_by_pattern.call_args_list == [
call('service-????????-????-????-????-????????????-returned-letters-statistics'),
call('service-????????-????-????-????-????????????-returned-letters-summary'),
]
def test_platform_admin_submit_empty_returned_letters(
mocker,
client_request,
platform_admin_user,
):
mock_client = mocker.patch('app.letter_jobs_client.submit_returned_letters')
client_request.login(platform_admin_user)
page = client_request.post(
'main.platform_admin_returned_letters',
_data={'references': ' \n '},
_expected_status=200,
)
assert not mock_client.called
assert "Cannot be empty" in page.text
def test_clear_cache_shows_form(
client_request,
platform_admin_user,
mocker,
):
2019-02-15 11:13:53 +00:00
redis = mocker.patch('app.main.views.platform_admin.redis_client')
client_request.login(platform_admin_user)
page = client_request.get('main.clear_cache')
assert not redis.delete_by_pattern.called
radios = {el['value'] for el in page.select('input[type=checkbox]')}
assert radios == {
'user',
'service',
'template',
'email_branding',
'letter_branding',
'organisation',
'broadcast'
}
2019-02-15 11:13:53 +00:00
@pytest.mark.parametrize('model_type, expected_calls, expected_confirmation', (
('template', [
call('service-????????-????-????-????-????????????-templates'),
call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*'),
call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions'),
], 'Removed 6 objects across 3 key formats for template'),
(['service', 'organisation'], [
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'),
call('organisations'),
call('domains'),
call('live-service-and-organisation-counts'),
call('organisation-????????-????-????-????-????????????-name'),
], 'Removed 22 objects across 11 key formats for service, organisation'),
('broadcast', [
call('service-????????-????-????-????-????????????-broadcast-message-????????-????-????-????-????????????'),
], 'Removed 2 objects across 1 key formats for broadcast'),
))
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,
):
2019-02-15 11:13:53 +00:00
redis = mocker.patch('app.main.views.platform_admin.redis_client')
redis.delete_by_pattern.return_value = 2
2019-02-15 11:13:53 +00:00
client_request.login(platform_admin_user)
page = client_request.post(
'main.clear_cache',
_data={'model_type': model_type},
_expected_status=200
)
2019-02-15 11:13:53 +00:00
assert redis.delete_by_pattern.call_args_list == expected_calls
2019-02-15 11:13:53 +00:00
flash_banner = page.find('div', class_='banner-default')
assert flash_banner.text.strip() == expected_confirmation
2019-02-15 11:13:53 +00:00
def test_clear_cache_requires_option(
client_request,
platform_admin_user,
mocker,
):
2019-02-15 11:13:53 +00:00
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_='govuk-error-message').text) == 'Error: Select at least one option'
assert not redis.delete_by_pattern.called
def test_reports_page(
client_request,
platform_admin_user,
):
client_request.login(platform_admin_user)
page = client_request.get('main.platform_admin_reports')
2019-04-24 16:23:12 +01:00
assert page.find(
'a', text="Download live services csv report"
).attrs['href'] == '/platform-admin/reports/live-services.csv'
assert page.find(
'a', text="Monthly notification statuses for live services"
).attrs['href'] == url_for('main.notifications_sent_by_service')
2019-04-24 16:23:12 +01:00
def test_get_live_services_report(
client_request,
platform_admin_user,
mocker,
):
2019-04-24 16:23:12 +01:00
mocker.patch(
'app.service_api_client.get_live_services_data',
2019-04-24 16:23:12 +01:00
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},
2019-04-24 16:23:12 +01:00
]}
)
client_request.login(platform_admin_user)
response = client_request.get_response(
'main.live_services_csv',
)
2019-04-24 16:23:12 +01:00
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' +
2019-05-31 12:22:54 +01:00
'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' +
2019-05-31 12:22:54 +01:00
'2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,0,0,200'
)
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_get_billing_report_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_data_for_billing_report",
return_value=[])
page = client_request.post('main.get_billing_report',
_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_get_billing_report_calls_api_and_download_data(
client_request,
platform_admin_user,
mocker
):
mocker.patch(
"app.main.views.platform_admin.billing_api_client.get_data_for_billing_report",
return_value=[{
'letter_breakdown': '6 second class letters at 45p\n2 first class letters at 35p\n',
'total_letters': 8,
'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_chargeable_units': 0,
'purchase_order_number': 'PO1234',
'contact_names': 'Anne, Marie, Josh',
'contact_email_addresses': 'billing@example.com, accounts@example.com',
'billing_reference': 'Notify2020'
}]
)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.get_billing_report',
_data={'start_date': '2019-01-01', 'end_date': '2019-03-31'},
_expected_status=200,
)
assert response.content_type == 'text/csv; charset=utf-8'
assert response.headers['Content-Disposition'] == (
'attachment; filename="Billing Report 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_chargeable_units,' +
'total_letters,letter_cost,letter_breakdown,purchase_order_number,contact_names,contact_email_addresses,' +
'billing_reference\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,' +
'8,' +
'3.4,' +
'"6 second class letters at 45p' +
'\n' +
'2 first class letters at 35p",' +
'PO1234,"Anne, Marie, Josh","billing@example.com, accounts@example.com",Notify2020' +
'\r\n'
)
def test_get_notifications_sent_by_service_calls_api_and_downloads_data(
mocker,
client_request,
platform_admin_user,
service_one,
service_two,
):
api_data = [
['2019-01-01', SERVICE_ONE_ID, service_one['name'], 'email', 191, 0, 0, 14, 0, 0],
['2019-01-01', SERVICE_ONE_ID, service_one['name'], 'sms', 42, 0, 0, 8, 0, 0],
['2019-01-01', 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)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.notifications_sent_by_service',
_data={'start_date': start_date, 'end_date': end_date},
_expected_status=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'
)
2022-03-09 15:02:52 +00:00
def test_get_volumes_by_service_report_calls_api_and_download_data(
2022-03-09 15:02:52 +00:00
client_request,
platform_admin_user,
mocker
):
mocker.patch(
"app.main.views.platform_admin.billing_api_client.get_data_for_volumes_by_service_report",
return_value=[{
"organisation_id": "7832a1be-a1f0-4f2a-982f-05adfd3d6354",
"organisation_name": "Org name",
"service_id": "48e82ac0-c8c4-4e46-8712-c83c35a94006",
"service_name": "service name",
"free_allowance": 10000,
"sms_notifications": 10,
"sms_chargeable_units": 20,
"email_totals": 8,
"letter_totals": 10,
"letter_cost": 4.5,
"letter_sheet_totals": 10
}]
)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.get_volumes_by_service',
_data={'start_date': '2019-01-01', 'end_date': '2019-03-31'},
_expected_status=200,
)
assert response.content_type == 'text/csv; charset=utf-8'
assert response.headers['Content-Disposition'] == (
'attachment; filename="Volumes by service report 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,free allowance,sms notifications," +
"sms chargeable units,email totals,letter totals,letter cost,letter sheet totals\r\n" +
'7832a1be-a1f0-4f2a-982f-05adfd3d6354,' +
'Org name,' +
'48e82ac0-c8c4-4e46-8712-c83c35a94006,' +
'service name,' +
'10000,' +
'10,' +
'20,' +
'8,' +
'10,' +
'4.5,' +
'10' +
2022-03-09 15:02:52 +00:00
'\r\n'
)
def test_get_daily_volumes_report_calls_api_and_download_data(
2022-03-09 15:02:52 +00:00
client_request,
platform_admin_user,
mocker
):
mocker.patch(
"app.main.views.platform_admin.billing_api_client.get_data_for_daily_volumes_report",
return_value=[{
"day": '2019-01-01',
"sms_totals": 20,
"sms_fragment_totals": 40,
"sms_chargeable_units": 60,
"email_totals": 100,
"letter_totals": 10,
"letter_sheet_totals": 20
}]
)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.get_daily_volumes',
_data={'start_date': '2019-01-01', 'end_date': '2019-03-31'},
_expected_status=200,
)
assert response.content_type == 'text/csv; charset=utf-8'
assert response.headers['Content-Disposition'] == (
'attachment; filename="Daily volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31')
)
assert response.get_data(as_text=True) == (
"day,sms totals,sms fragment totals,sms chargeable units,email totals,letter totals,letter sheet totals\r\n" +
'2019-01-01,' +
'20,' +
'40,' +
'60,' +
'100,' +
'10,' +
'20' +
2022-03-09 15:02:52 +00:00
'\r\n'
)
def test_get_daily_sms_provider_volumes_report_calls_api_and_download_data(
client_request,
platform_admin_user,
mocker
):
mocker.patch(
"app.main.views.platform_admin.billing_api_client.get_data_for_daily_sms_provider_volumes_report",
return_value=[{
"day": '2019-01-01',
"provider": 'foo',
"sms_totals": 20,
"sms_fragment_totals": 40,
"sms_chargeable_units": 60,
"sms_cost": 80,
}]
)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.get_daily_sms_provider_volumes',
_data={'start_date': '2019-01-01', 'end_date': '2019-03-31'},
_expected_status=200,
)
assert response.content_type == 'text/csv; charset=utf-8'
assert response.headers['Content-Disposition'] == (
'attachment; filename="Daily SMS provider volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31')
)
assert response.get_data(as_text=True) == (
"day,provider,sms totals,sms fragment totals,sms chargeable units,sms cost\r\n" +
'2019-01-01,' +
'foo,' +
'20,' +
'40,' +
'60,' +
'80' +
'\r\n'
)