2019-11-04 11:07:55 +00:00
|
|
|
|
import uuid
|
|
|
|
|
|
|
2019-05-22 14:05:19 +01:00
|
|
|
|
import pytest
|
2019-05-22 11:38:47 +01:00
|
|
|
|
from bs4 import BeautifulSoup
|
2018-07-06 11:10:14 +01:00
|
|
|
|
from flask import url_for
|
|
|
|
|
|
from lxml import html
|
2020-04-21 17:40:47 +01:00
|
|
|
|
from notifications_python_client.errors import HTTPError
|
2018-07-06 11:10:14 +01:00
|
|
|
|
|
2018-07-06 16:16:21 +01:00
|
|
|
|
from tests import user_json
|
2019-06-05 11:25:27 +01:00
|
|
|
|
from tests.conftest import normalize_spaces
|
2018-07-06 11:10:14 +01:00
|
|
|
|
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
2018-07-16 15:04:03 +01:00
|
|
|
|
def test_find_users_by_email_page_loads_correctly(client_request, platform_admin_user):
|
|
|
|
|
|
client_request.login(platform_admin_user)
|
|
|
|
|
|
document = client_request.get('main.find_users_by_email')
|
2018-07-06 11:10:14 +01:00
|
|
|
|
|
2018-07-16 15:04:03 +01:00
|
|
|
|
assert document.h1.text.strip() == 'Find users by email'
|
|
|
|
|
|
assert len(document.find_all('input', {'type': 'search'})) > 0
|
2018-07-06 16:16:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_find_users_by_email_displays_users_found(
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
platform_admin_user,
|
|
|
|
|
|
mocker
|
2018-07-06 16:16:21 +01:00
|
|
|
|
):
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request.login(platform_admin_user)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
mocker.patch(
|
|
|
|
|
|
'app.user_api_client.find_users_by_full_or_partial_email',
|
|
|
|
|
|
return_value={"data": [user_json()]},
|
|
|
|
|
|
autospec=True,
|
|
|
|
|
|
)
|
2018-07-16 15:04:03 +01:00
|
|
|
|
document = client_request.post(
|
|
|
|
|
|
'main.find_users_by_email',
|
|
|
|
|
|
_data={"search": "twilight.sparkle"},
|
|
|
|
|
|
_expected_status=200
|
|
|
|
|
|
)
|
2018-07-06 16:16:21 +01:00
|
|
|
|
|
2018-07-16 15:04:03 +01:00
|
|
|
|
assert any(element.text.strip() == 'test@gov.uk' for element in document.find_all(
|
|
|
|
|
|
'a', {'class': 'browse-list-link'}, href=True)
|
|
|
|
|
|
)
|
|
|
|
|
|
assert any(element.text.strip() == 'Test User' for element in document.find_all('p', {'class': 'browse-list-hint'}))
|
|
|
|
|
|
|
|
|
|
|
|
assert document.find('a', {'class': 'browse-list-link'}).text.strip() == 'test@gov.uk'
|
|
|
|
|
|
assert document.find('p', {'class': 'browse-list-hint'}).text.strip() == 'Test User'
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_find_users_by_email_displays_multiple_users(
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
platform_admin_user,
|
|
|
|
|
|
mocker
|
|
|
|
|
|
):
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request.login(platform_admin_user)
|
|
|
|
|
|
mocker.patch(
|
|
|
|
|
|
'app.user_api_client.find_users_by_full_or_partial_email',
|
|
|
|
|
|
return_value={"data": [user_json(name="Apple Jack"), user_json(name="Apple Bloom")]},
|
|
|
|
|
|
autospec=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
document = client_request.post('main.find_users_by_email', _data={"search": "apple"}, _expected_status=200)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
2018-07-16 15:04:03 +01:00
|
|
|
|
assert any(
|
|
|
|
|
|
element.text.strip() == 'Apple Jack' for element in document.find_all('p', {'class': 'browse-list-hint'})
|
|
|
|
|
|
)
|
|
|
|
|
|
assert any(
|
|
|
|
|
|
element.text.strip() == 'Apple Bloom' for element in document.find_all('p', {'class': 'browse-list-hint'})
|
|
|
|
|
|
)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
2018-07-06 16:16:21 +01:00
|
|
|
|
|
|
|
|
|
|
def test_find_users_by_email_displays_message_if_no_users_found(
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
platform_admin_user,
|
|
|
|
|
|
mocker
|
2018-07-06 16:16:21 +01:00
|
|
|
|
):
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request.login(platform_admin_user)
|
2018-07-06 16:16:21 +01:00
|
|
|
|
mocker.patch('app.user_api_client.find_users_by_full_or_partial_email', return_value={"data": []}, autospec=True)
|
2018-07-16 15:04:03 +01:00
|
|
|
|
document = client_request.post(
|
|
|
|
|
|
'main.find_users_by_email', _data={"search": "twilight.sparkle"}, _expected_status=200
|
|
|
|
|
|
)
|
2018-07-06 16:16:21 +01:00
|
|
|
|
|
2018-07-16 15:04:03 +01:00
|
|
|
|
assert document.find('p', {'class': 'browse-list-hint'}).text.strip() == 'No users found.'
|
2018-07-10 16:33:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_find_users_by_email_validates_against_empty_search_submission(
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
platform_admin_user,
|
|
|
|
|
|
mocker
|
2018-07-10 16:33:13 +01:00
|
|
|
|
):
|
2018-07-16 15:04:03 +01:00
|
|
|
|
client_request.login(platform_admin_user)
|
2019-08-16 11:20:36 +01:00
|
|
|
|
document = client_request.post('main.find_users_by_email', _data={"search": ""}, _expected_status=200)
|
2018-07-10 16:33:13 +01:00
|
|
|
|
|
2018-07-10 17:24:20 +01:00
|
|
|
|
expected_message = "You need to enter full or partial email address to search by."
|
2018-07-16 15:04:03 +01:00
|
|
|
|
assert document.find('span', {'class': 'error-message'}).text.strip() == expected_message
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_information_page_shows_information_about_user(
|
|
|
|
|
|
client,
|
|
|
|
|
|
platform_admin_user,
|
2019-11-04 11:07:55 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
fake_uuid,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
):
|
2019-11-04 11:07:55 +00:00
|
|
|
|
user_service_one = uuid.uuid4()
|
|
|
|
|
|
user_service_two = uuid.uuid4()
|
2018-07-10 17:24:20 +01:00
|
|
|
|
mocker.patch('app.user_api_client.get_user', side_effect=[
|
|
|
|
|
|
platform_admin_user,
|
2019-11-04 11:07:55 +00:00
|
|
|
|
user_json(name="Apple Bloom", services=[user_service_one, user_service_two])
|
2018-07-16 16:34:10 +01:00
|
|
|
|
], autospec=True)
|
|
|
|
|
|
|
|
|
|
|
|
mocker.patch(
|
|
|
|
|
|
'app.user_api_client.get_organisations_and_services_for_user',
|
2019-06-12 12:09:26 +01:00
|
|
|
|
return_value={'organisations': [], 'services': [
|
2019-11-04 11:07:55 +00:00
|
|
|
|
{"id": user_service_one, "name": "Fresh Orchard Juice", "restricted": True},
|
|
|
|
|
|
{"id": user_service_two, "name": "Nature Therapy", "restricted": False},
|
2018-07-16 16:34:10 +01:00
|
|
|
|
]},
|
|
|
|
|
|
autospec=True
|
|
|
|
|
|
)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
client.login(platform_admin_user)
|
2019-11-04 11:07:55 +00:00
|
|
|
|
response = client.get(url_for('main.user_information', user_id=fake_uuid))
|
2018-07-10 17:24:20 +01:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
document = html.fromstring(response.get_data(as_text=True))
|
|
|
|
|
|
|
|
|
|
|
|
assert document.xpath("//h1/text()[normalize-space()='Apple Bloom']")
|
|
|
|
|
|
assert document.xpath("//p/text()[normalize-space()='test@gov.uk']")
|
|
|
|
|
|
assert document.xpath("//p/text()[normalize-space()='+447700900986']")
|
|
|
|
|
|
|
2019-06-12 09:50:11 +01:00
|
|
|
|
assert document.xpath("//h2/text()[normalize-space()='Live services']")
|
2018-07-16 17:36:34 +01:00
|
|
|
|
assert document.xpath("//a/text()[normalize-space()='Nature Therapy']")
|
2018-07-10 17:24:20 +01:00
|
|
|
|
|
2019-06-12 09:50:11 +01:00
|
|
|
|
assert document.xpath("//h2/text()[normalize-space()='Trial mode services']")
|
|
|
|
|
|
assert document.xpath("//a/text()[normalize-space()='Fresh Orchard Juice']")
|
|
|
|
|
|
|
2018-07-10 17:24:20 +01:00
|
|
|
|
assert document.xpath("//h2/text()[normalize-space()='Last login']")
|
|
|
|
|
|
assert not document.xpath("//p/text()[normalize-space()='0 failed login attempts']")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_information_page_displays_if_there_are_failed_login_attempts(
|
|
|
|
|
|
client,
|
|
|
|
|
|
platform_admin_user,
|
2019-11-04 11:07:55 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
fake_uuid,
|
2018-07-10 17:24:20 +01:00
|
|
|
|
):
|
|
|
|
|
|
mocker.patch('app.user_api_client.get_user', side_effect=[
|
|
|
|
|
|
platform_admin_user,
|
Make user API client return JSON, not a model
The data flow of other bits of our application looks like this:
```
API (returns JSON)
⬇
API client (returns a built in type, usually `dict`)
⬇
Model (returns an instance, eg of type `Service`)
⬇
View (returns HTML)
```
The user API client was architected weirdly, in that it returned a model
directly, like this:
```
API (returns JSON)
⬇
API client (returns a model, of type `User`, `InvitedUser`, etc)
⬇
View (returns HTML)
```
This mixing of different layers of the application is bad because it
makes it hard to write model code that doesn’t have circular
dependencies. As our application gets more complicated we will be
relying more on models to manage this complexity, so we should make it
easy, not hard to write them.
It also means that most of our mocking was of the User model, not just
the underlying JSON. So it would have been easy to introduce subtle bugs
to the user model, because it wasn’t being comprehensively tested. A lot
of the changed lines of code in this commit mean changing the tests to
mock only the JSON, which means that the model layer gets implicitly
tested.
For those reasons this commit changes the user API client to return
JSON, not an instance of `User` or other models.
2019-05-23 15:27:35 +01:00
|
|
|
|
user_json(name="Apple Bloom", failed_login_count=2)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
], autospec=True)
|
2018-07-16 16:34:10 +01:00
|
|
|
|
|
|
|
|
|
|
mocker.patch(
|
|
|
|
|
|
'app.user_api_client.get_organisations_and_services_for_user',
|
2019-11-04 11:07:55 +00:00
|
|
|
|
return_value={'organisations': [], 'services': []},
|
2018-07-16 16:34:10 +01:00
|
|
|
|
autospec=True
|
|
|
|
|
|
)
|
2018-07-10 17:24:20 +01:00
|
|
|
|
client.login(platform_admin_user)
|
2019-11-04 11:07:55 +00:00
|
|
|
|
response = client.get(url_for('main.user_information', user_id=fake_uuid))
|
2018-07-10 17:24:20 +01:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
document = html.fromstring(response.get_data(as_text=True))
|
|
|
|
|
|
assert document.xpath("//p/text()[normalize-space()='2 failed login attempts']")
|
2019-05-22 11:38:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-06-05 11:25:27 +01:00
|
|
|
|
def test_user_information_page_shows_archive_link_for_active_users(
|
2019-08-27 16:01:28 +01:00
|
|
|
|
platform_admin_client,
|
2019-06-05 11:25:27 +01:00
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_get_organisations_and_services_for_user,
|
|
|
|
|
|
):
|
2019-08-27 16:01:28 +01:00
|
|
|
|
response = platform_admin_client.get(
|
2019-06-05 11:25:27 +01:00
|
|
|
|
url_for('main.user_information', user_id=api_user_active['id'])
|
|
|
|
|
|
)
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
archive_url = url_for('main.archive_user', user_id=api_user_active['id'])
|
|
|
|
|
|
|
|
|
|
|
|
link = page.find('a', {'href': archive_url})
|
|
|
|
|
|
assert normalize_spaces(link.text) == 'Archive user'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_information_page_does_not_show_archive_link_for_inactive_users(
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
client,
|
|
|
|
|
|
platform_admin_user,
|
|
|
|
|
|
mock_get_organisations_and_services_for_user,
|
|
|
|
|
|
):
|
|
|
|
|
|
inactive_user = user_json(state='inactive')
|
|
|
|
|
|
mocker.patch('app.user_api_client.get_user', side_effect=[platform_admin_user, inactive_user], autospec=True)
|
|
|
|
|
|
client.login(platform_admin_user)
|
|
|
|
|
|
response = client.get(
|
|
|
|
|
|
url_for('main.user_information', user_id=inactive_user['id'])
|
|
|
|
|
|
)
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
|
|
|
|
|
|
archive_url = url_for('main.archive_user', user_id=inactive_user['id'])
|
|
|
|
|
|
assert not page.find('a', {'href': archive_url})
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-05-22 11:38:47 +01:00
|
|
|
|
def test_archive_user_prompts_for_confirmation(
|
2019-08-27 16:01:28 +01:00
|
|
|
|
platform_admin_client,
|
2019-05-22 11:38:47 +01:00
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_get_organisations_and_services_for_user,
|
|
|
|
|
|
):
|
2019-08-27 16:01:28 +01:00
|
|
|
|
response = platform_admin_client.get(
|
2019-06-05 11:25:27 +01:00
|
|
|
|
url_for('main.archive_user', user_id=api_user_active['id'])
|
2019-05-22 11:38:47 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
assert 'Are you sure you want to archive this user?' in page.find('div', class_='banner-dangerous').text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_archive_user_posts_to_user_client(
|
2019-08-27 16:01:28 +01:00
|
|
|
|
platform_admin_client,
|
2019-05-22 11:38:47 +01:00
|
|
|
|
api_user_active,
|
|
|
|
|
|
mocker,
|
2019-05-22 14:05:19 +01:00
|
|
|
|
mock_events,
|
2019-05-22 11:38:47 +01:00
|
|
|
|
):
|
|
|
|
|
|
mock_user_client = mocker.patch('app.user_api_client.post')
|
|
|
|
|
|
|
2019-08-27 16:01:28 +01:00
|
|
|
|
response = platform_admin_client.post(
|
2019-06-05 11:25:27 +01:00
|
|
|
|
url_for('main.archive_user', user_id=api_user_active['id'])
|
2019-05-22 11:38:47 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 302
|
2019-06-05 11:25:27 +01:00
|
|
|
|
assert response.location == url_for('main.user_information', user_id=api_user_active['id'], _external=True)
|
|
|
|
|
|
mock_user_client.assert_called_once_with('/user/{}/archive'.format(api_user_active['id']), data=None)
|
2019-05-22 14:05:19 +01:00
|
|
|
|
|
|
|
|
|
|
assert mock_events.called
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-21 17:40:47 +01:00
|
|
|
|
def test_archive_user_shows_error_message_if_user_cannot_be_archived(
|
|
|
|
|
|
platform_admin_client,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
mock_get_non_empty_organisations_and_services_for_user,
|
|
|
|
|
|
):
|
|
|
|
|
|
mocker.patch(
|
|
|
|
|
|
'app.user_api_client.post',
|
|
|
|
|
|
side_effect=HTTPError(
|
|
|
|
|
|
response=mocker.Mock(
|
|
|
|
|
|
status_code=400,
|
|
|
|
|
|
json={'result': 'error',
|
|
|
|
|
|
'message': 'User can’t be removed from a service - check all services have another '
|
|
|
|
|
|
'team member with manage_settings'}
|
|
|
|
|
|
),
|
|
|
|
|
|
message='User can’t be removed from a service - check all services have another team member '
|
|
|
|
|
|
'with manage_settings'
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
response = platform_admin_client.post(
|
|
|
|
|
|
url_for('main.archive_user', user_id=api_user_active['id']),
|
|
|
|
|
|
follow_redirects=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
|
|
|
|
|
|
assert normalize_spaces(page.find('h1').text) == 'Platform admin user'
|
|
|
|
|
|
assert normalize_spaces(
|
|
|
|
|
|
page.select_one('.banner-dangerous').text
|
|
|
|
|
|
) == 'User can’t be removed from a service - check all services have another team member with manage_settings'
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-04-22 16:31:59 +01:00
|
|
|
|
def test_archive_user_does_not_create_event_if_user_client_raises_unexpected_exception(
|
2019-08-27 16:01:28 +01:00
|
|
|
|
platform_admin_client,
|
2019-05-22 14:05:19 +01:00
|
|
|
|
api_user_active,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
mock_events,
|
|
|
|
|
|
):
|
|
|
|
|
|
with pytest.raises(Exception):
|
2020-04-22 16:31:59 +01:00
|
|
|
|
platform_admin_client.post(
|
2019-05-22 14:05:19 +01:00
|
|
|
|
url_for('main.archive_user', user_id=api_user_active.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2020-04-22 16:31:59 +01:00
|
|
|
|
assert not mock_events.called
|