Files
notifications-admin/tests/app/main/views/test_providers.py
Chris Hill-Scott 581296b95e Fix off-by-one dates on provider history
We chunk history entries by their YYYY-MM-DD date representation to
display them day-by-day.

However trying to convert a YYYY-MM-DD string into a timestamp is
unpredictable, and was causing our dates to be one day out (probably
because of midnight/daylight savings/general datetime horrors).

This commit changes the code to do the same thing as the history page,
which is look at the `updated_at` field on the first entry to get a
datetime object and from that the formatted date we show in the headings
on the page.
2019-12-13 13:20:56 +00:00

552 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import copy
import re
from datetime import datetime
from unittest.mock import call
import pytest
from bs4 import BeautifulSoup
from flask import url_for
from freezegun import freeze_time
import app
from app.main.views.providers import add_monthly_traffic
from tests.conftest import normalize_spaces
stub_providers = {
'provider_details': [
{
'id': '6005e192-4738-4962-beec-ebd982d0b03f',
'active': True,
'priority': 1,
'display_name': 'Domestic SMS Provider',
'identifier': 'first_sms_domestic',
'notification_type': 'sms',
'updated_at': datetime(2017, 1, 16, 15, 20, 40).isoformat(),
'version': 1,
'created_by_name': 'Test User',
'supports_international': False,
'current_month_billable_sms': 5020,
},
{
'id': '0bd529cd-a0fd-43e5-80ee-b95ef6b0d51f',
'active': True,
'priority': 2,
'display_name': 'Second Domestic SMS Provider',
'identifier': 'second_sms_domestic',
'notification_type': 'sms',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': False,
'current_month_billable_sms': 6891,
},
{
'id': '6005e192-4738-4962-beec-ebd982d0b03a',
'active': True,
'priority': 1,
'display_name': 'first_email_provider',
'identifier': 'first_email',
'notification_type': 'email',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': False,
'current_month_billable_sms': 0,
},
{
'active': True,
'priority': 2,
'display_name': 'second_email_provider',
'identifier': 'second_email',
'id': '0bd529cd-a0fd-43e5-80ee-b95ef6b0d51b',
'notification_type': 'email',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': False,
'current_month_billable_sms': 0,
},
{
'id': '67c770f5-918e-4afa-a5ff-880b9beb161d',
'active': False,
'priority': 10,
'display_name': 'First International SMS Provider',
'identifier': 'first_sms_international',
'notification_type': 'sms',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': True,
'current_month_billable_sms': 0,
},
{
'id': '67c770f5-918e-4afa-a5ff-880b9beb161d',
'active': False,
'priority': 10,
'display_name': 'Second International SMS Provider',
'identifier': 'second_sms_international',
'notification_type': 'sms',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': True,
'current_month_billable_sms': 0,
}
]
}
stub_provider = {
'provider_details':
{
'id': '6005e192-4738-4962-beec-ebd982d0b03f',
'active': True,
'priority': 1,
'display_name': 'Domestic SMS Provider',
'identifier': 'first_sms_domestic',
'notification_type': 'sms',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': False
}
}
stub_provider_history = {
'data': [
{
'id': 'f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb',
'active': True,
'priority': 20,
'display_name': 'Foo',
'identifier': 'foo',
'notification_type': 'sms',
'updated_at': None,
'version': 2,
'created_by': {
'email_address': 'test@foo.bar',
'name': 'Test User',
'id': '7cc1dddb-bcbc-4739-8fc1-61bedde3332a'
},
'supports_international': False
},
{
'id': 'f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb',
'active': True,
'priority': 10,
'display_name': 'Bar',
'identifier': 'bar',
'notification_type': 'sms',
'updated_at': None,
'version': 1,
'created_by': None,
'supports_international': False
}
]
}
def test_should_show_all_providers(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_all_providers', return_value=copy.deepcopy(stub_providers))
client.login(platform_admin_user, mocker)
response = client.get(url_for('main.view_providers'))
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
h1 = [header.text.strip() for header in page.find_all('h1')]
assert 'Providers' in h1
h2 = [header.text.strip() for header in page.find_all('h2')]
assert 'Email' in h2
assert 'SMS' in h2
tables = page.find_all('table')
assert len(tables) == 3
domestic_sms_table = tables[0]
domestic_email_table = tables[1]
international_sms_table = tables[2]
domestic_sms_first_row = domestic_sms_table.tbody.find_all('tr')[0]
table_data = domestic_sms_first_row.find_all('td')
assert not table_data[0].find_all("a")
assert table_data[0].text.strip() == "Domestic SMS Provider"
assert table_data[1].text.strip() == "1"
assert table_data[2].text.strip() == "42"
assert table_data[3].text.strip() == "True"
assert table_data[4].text.strip() == "16 January at 3:20pm"
assert table_data[5].text.strip() == "Test User"
domestic_sms_second_row = domestic_sms_table.tbody.find_all('tr')[1]
table_data = domestic_sms_second_row.find_all('td')
assert not table_data[0].find_all("a")
assert table_data[0].text.strip() == "Second Domestic SMS Provider"
assert table_data[1].text.strip() == "2"
assert table_data[2].text.strip() == "58"
assert table_data[3].text.strip() == "True"
assert table_data[4].text.strip() == "None"
assert table_data[5].text.strip() == "None"
domestic_email_first_row = domestic_email_table.tbody.find_all('tr')[0]
domestic_email_table_data = domestic_email_first_row.find_all('td')
assert domestic_email_table_data[0].find_all("a")[0]['href'] == '/provider/6005e192-4738-4962-beec-ebd982d0b03a'
assert domestic_email_table_data[0].text.strip() == "first_email_provider"
assert domestic_email_table_data[1].text.strip() == "1"
assert domestic_email_table_data[2].text.strip() == "True"
assert domestic_email_table_data[3].text.strip() == "None"
assert domestic_email_table_data[4].text.strip() == "None"
assert domestic_email_table_data[5].find_all("a")[0]['href'] \
== '/provider/6005e192-4738-4962-beec-ebd982d0b03a/edit'
domestic_email_second_row = domestic_email_table.tbody.find_all('tr')[1]
domestic_email_table_data = domestic_email_second_row.find_all('td')
assert domestic_email_table_data[0].find_all("a")[0]['href'] == '/provider/0bd529cd-a0fd-43e5-80ee-b95ef6b0d51b'
assert domestic_email_table_data[0].text.strip() == "second_email_provider"
assert domestic_email_table_data[1].text.strip() == "2"
assert domestic_email_table_data[2].text.strip() == "True"
assert domestic_email_table_data[3].text.strip() == "None"
assert domestic_email_table_data[4].text.strip() == "None"
assert domestic_email_table_data[5].find_all("a")[0]['href'] \
== '/provider/0bd529cd-a0fd-43e5-80ee-b95ef6b0d51b/edit'
international_sms_first_row = international_sms_table.tbody.find_all('tr')[0]
table_data = international_sms_first_row.find_all('td')
assert table_data[0].find_all("a")[0]['href'] == '/provider/67c770f5-918e-4afa-a5ff-880b9beb161d'
assert table_data[0].text.strip() == "First International SMS Provider"
assert table_data[1].text.strip() == "10"
assert table_data[2].text.strip() == "False"
assert table_data[3].text.strip() == "None"
assert table_data[4].text.strip() == "None"
def test_add_monthly_traffic():
domestic_sms_providers = [{
'identifier': 'mmg',
'current_month_billable_sms': 27,
}, {
'identifier': 'firetext',
'current_month_billable_sms': 5,
}, {
'identifier': 'loadtesting',
'current_month_billable_sms': 0,
}]
add_monthly_traffic(domestic_sms_providers)
assert domestic_sms_providers == [{
'identifier': 'mmg',
'current_month_billable_sms': 27,
'monthly_traffic': 84
}, {
'identifier': 'firetext',
'current_month_billable_sms': 5,
'monthly_traffic': 16
}, {
'identifier': 'loadtesting',
'current_month_billable_sms': 0,
'monthly_traffic': 0
}]
def test_should_show_edit_provider_form(
client,
platform_admin_user,
mocker,
fake_uuid,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.get(url_for('main.edit_provider', provider_id=fake_uuid))
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
h1 = [header.text.strip() for header in page.find_all('h1')]
assert 'Domestic SMS Provider' in h1
form = [form for form in page.find_all('form')]
form_elements = [element for element in form[0].find_all('input')]
assert form_elements[0]['value'] == '1'
assert form_elements[0]['name'] == 'priority'
def test_should_show_error_on_bad_provider_priority(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.post(
url_for('main.edit_provider', provider_id=stub_provider['provider_details']['id']),
data={'priority': "not valid"})
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert response.status_code == 200
assert "Not a valid integer value" in str(page.find_all("span", {"class": re.compile(r"error-message")})[0])
def test_should_show_error_on_negative_provider_priority(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.post(
url_for('main.edit_provider', provider_id=stub_provider['provider_details']['id']),
data={'priority': -1})
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert response.status_code == 200
assert "Must be between 1 and 100" in str(page.find_all("span", {"class": re.compile(r"error-message")})[0])
def test_should_show_error_on_too_big_provider_priority(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.post(
url_for('main.edit_provider', provider_id=stub_provider['provider_details']['id']),
data={'priority': 101})
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert response.status_code == 200
assert "Must be between 1 and 100" in str(page.find_all("span", {"class": re.compile(r"error-message")})[0])
def test_should_show_error_on_too_little_provider_priority(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.post(
url_for('main.edit_provider', provider_id=stub_provider['provider_details']['id']),
data={'priority': 0})
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert response.status_code == 200
assert "Must be between 1 and 100" in str(page.find_all("span", {"class": re.compile(r"error-message")})[0])
def test_should_update_provider_priority(
client,
platform_admin_user,
mocker,
):
mocker.patch('app.provider_client.get_provider_by_id', return_value=copy.deepcopy(stub_provider))
mocker.patch('app.provider_client.update_provider', return_value=copy.deepcopy(stub_provider))
client.login(platform_admin_user, mocker)
response = client.post(
url_for('main.edit_provider', provider_id=stub_provider['provider_details']['id']),
data={'priority': 2})
app.provider_client.update_provider.assert_called_with(stub_provider['provider_details']['id'], 2)
assert response.status_code == 302
assert response.location == 'http://localhost/providers'
def test_should_show_provider_version_history(
client,
platform_admin_user,
mocker
):
mocker.patch('app.provider_client.get_provider_versions', return_value=copy.deepcopy(stub_provider_history))
client.login(platform_admin_user, mocker)
response = client.get(
url_for('main.view_provider', provider_id=stub_provider_history['data'][0]['id']))
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
table = page.find('table')
table_rows = table.find_all('tr')
table_headings = table_rows[0].find_all('th')
first_row = table_rows[1].find_all('td')
second_row = table_rows[2].find_all('td')
assert response.status_code == 200
assert page.find_all('h1')[0].text.strip() == stub_provider_history['data'][0]["display_name"]
assert len(table_rows) == 3
assert table_headings[0].text.strip() == "Version"
assert table_headings[1].text.strip() == "Last Updated"
assert table_headings[2].text.strip() == "Updated By"
assert table_headings[3].text.strip() == "Priority"
assert table_headings[4].text.strip() == "Active"
assert first_row[0].text.strip() == "2"
assert first_row[1].text.strip() == "None"
assert first_row[2].text.strip() == "Test User"
assert first_row[3].text.strip() == "20"
assert first_row[4].text.strip() == "True"
assert second_row[0].text.strip() == "1"
assert second_row[1].text.strip() == "None"
assert second_row[2].text.strip() == "None"
assert second_row[3].text.strip() == "10"
assert second_row[4].text.strip() == "True"
@freeze_time('2022-2-22 15:00')
def test_should_show_version_history_for_first_two_sms_providers(
client_request,
platform_admin_user,
mocker
):
mocker.patch(
'app.provider_client.get_all_providers',
return_value=copy.deepcopy(stub_providers)
)
# second_sms_international will be the primary provider because its
# the first in the list when reverse sorting the SMS providers
second_sms_international = stub_providers['provider_details'][5]
mocker.patch(
'app.provider_client.get_provider_versions',
return_value={'data': [
{
'id': id,
'priority': priority,
'display_name': second_sms_international['display_name'],
'identifier': second_sms_international['identifier'],
'updated_at': updated_at,
'created_by': {
'email_address': 'test@foo.bar',
'name': 'Test User',
'id': '7cc1dddb-bcbc-4739-8fc1-61bedde3332a'
},
'supports_international': False,
}
for updated_at, priority in [
(datetime(2022, 2, 22, 14).isoformat(), 100),
(datetime(2020, 1, 1, 5).isoformat(), 80),
(datetime(2020, 1, 1, 3).isoformat(), 10),
# Anything older than 11am on 29 November 2019
# should be ignored because the priority numbers
# didnt mean the same thing before then
(datetime(2019, 11, 29, 10, 59).isoformat(), 123),
(datetime(2000, 1, 1, 0).isoformat(), 1999),
(None, 30),
]
]}
)
client_request.login(platform_admin_user)
page = client_request.get('main.edit_sms_provider_ratio')
assert [
radio['value']
for radio in page.select('input[name=ratio]')
] == [
'100', '90', '80', '70', '60', '50', '40', '30', '20', '10', '0',
]
assert [
radio['value']
for radio in page.select('input[checked]')
] == [
str(second_sms_international['priority'])
]
assert [
normalize_spaces(heading.text)
for heading in page.select('main h2')
] == [
'Now',
'Today',
'1 January 2020',
]
assert [
normalize_spaces(version.text)
for version in page.select('li.history-list-item')
] == [
(
'Test User 2:00pm '
'Second International SMS Provider 100% '
'Second Domestic SMS Provider 0%'
),
(
'Test User 5:00am '
'Second International SMS Provider 80% '
'Second Domestic SMS Provider 20%'
),
(
'Test User 3:00am '
'Second International SMS Provider 10% '
'Second Domestic SMS Provider 90%'
),
]
@pytest.mark.parametrize('posted_number, expected_calls', [
(
'10',
[
call(stub_providers['provider_details'][5]['id'], 10),
call(stub_providers['provider_details'][1]['id'], 90),
],
),
(
'80',
[
call(stub_providers['provider_details'][5]['id'], 80),
call(stub_providers['provider_details'][1]['id'], 20),
],
),
])
def test_should_update_priority_of_first_two_sms_providers(
client_request,
platform_admin_user,
mocker,
posted_number,
expected_calls,
):
mocker.patch(
'app.provider_client.get_all_providers',
return_value=copy.deepcopy(stub_providers)
)
mocker.patch(
'app.provider_client.get_provider_versions',
return_value={'data': []}
)
mock_update_provider = mocker.patch(
'app.provider_client.update_provider'
)
client_request.login(platform_admin_user)
client_request.post(
'.edit_sms_provider_ratio',
_data={
'ratio': posted_number,
},
_expected_redirect=url_for(
'.edit_sms_provider_ratio',
_external=True,
),
)
assert mock_update_provider.call_args_list == expected_calls