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.
This commit is contained in:
Chris Hill-Scott
2019-05-23 15:27:35 +01:00
parent d66cabf300
commit 628e344b36
56 changed files with 961 additions and 872 deletions

View File

@@ -5,7 +5,6 @@ import pytest
from flask import url_for
import app
from app.models.user import InvitedUser
from app.utils import is_gov_user
from tests.conftest import (
SERVICE_ONE_ID,
@@ -135,12 +134,12 @@ def test_should_show_overview_page(
):
current_user = user(fake_uuid)
other_user = copy.deepcopy(active_user_view_permissions)
other_user.email_address = 'zzzzzzz@example.gov.uk'
other_user.name = 'ZZZZZZZZ'
other_user.id = 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz'
other_user['email_address'] = 'zzzzzzz@example.gov.uk'
other_user['name'] = 'ZZZZZZZZ'
other_user['id'] = 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz'
mocker.patch('app.user_api_client.get_user', return_value=current_user)
mocker.patch('app.user_api_client.get_users_for_service', return_value=[
mock_get_users = mocker.patch('app.models.user.Users.client', return_value=[
current_user,
other_user,
])
@@ -151,7 +150,7 @@ def test_should_show_overview_page(
assert normalize_spaces(page.select('.user-list-item')[0].text) == expected_self_text
# [1:5] are invited users
assert normalize_spaces(page.select('.user-list-item')[6].text) == expected_coworker_text
app.user_api_client.get_users_for_service.assert_called_once_with(service_id=SERVICE_ONE_ID)
mock_get_users.assert_called_once_with(SERVICE_ONE_ID)
def test_should_show_caseworker_on_overview_page(
@@ -165,10 +164,10 @@ def test_should_show_caseworker_on_overview_page(
service_one['permissions'].append('caseworking')
current_user = active_user_view_permissions(active_user_view_permissions)
other_user = active_caseworking_user(fake_uuid)
other_user.email_address = 'zzzzzzz@example.gov.uk'
other_user['email_address'] = 'zzzzzzz@example.gov.uk'
mocker.patch('app.user_api_client.get_user', return_value=current_user)
mocker.patch('app.user_api_client.get_users_for_service', return_value=[
mocker.patch('app.models.user.Users.client', return_value=[
current_user,
other_user,
])
@@ -602,9 +601,9 @@ def test_edit_user_permissions_including_authentication_with_email_auth_service(
client_request.post(
'main.edit_user_permissions',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={
'email_address': active_user_with_permissions.email_address,
'email_address': active_user_with_permissions['email_address'],
'send_messages': 'y',
'manage_templates': 'y',
'manage_service': 'y',
@@ -620,7 +619,7 @@ def test_edit_user_permissions_including_authentication_with_email_auth_service(
)
mock_set_user_permissions.assert_called_with(
str(active_user_with_permissions.id),
str(active_user_with_permissions['id']),
SERVICE_ONE_ID,
permissions={
'send_messages',
@@ -631,7 +630,7 @@ def test_edit_user_permissions_including_authentication_with_email_auth_service(
folder_permissions=[]
)
mock_update_user_attribute.assert_called_with(
str(active_user_with_permissions.id),
str(active_user_with_permissions['id']),
auth_type=auth_type
)
@@ -687,11 +686,10 @@ def test_invite_user(
):
sample_invite['email_address'] = 'test@example.gov.uk'
data = [InvitedUser(**sample_invite)]
assert is_gov_user(email_address) == gov_user
mocker.patch('app.invite_api_client.get_invites_for_service', return_value=data)
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
mocker.patch('app.invite_api_client.create_invite', return_value=InvitedUser(**sample_invite))
mocker.patch('app.models.user.InvitedUsers.client', return_value=[sample_invite])
mocker.patch('app.models.user.Users.client', return_value=[active_user_with_permissions])
mocker.patch('app.invite_api_client.create_invite', return_value=sample_invite)
page = client_request.post(
'main.invite_user',
service_id=SERVICE_ONE_ID,
@@ -742,11 +740,11 @@ def test_invite_user_with_email_auth_service(
service_one['permissions'].append('email_auth')
sample_invite['email_address'] = 'test@example.gov.uk'
data = [InvitedUser(**sample_invite)]
assert is_gov_user(email_address) is gov_user
mocker.patch('app.invite_api_client.get_invites_for_service', return_value=data)
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
mocker.patch('app.invite_api_client.create_invite', return_value=InvitedUser(**sample_invite))
mocker.patch('app.models.user.InvitedUsers.client', return_value=[sample_invite])
mocker.patch('app.models.user.Users.client', return_value=[active_user_with_permissions])
mocker.patch('app.invite_api_client.create_invite', return_value=sample_invite)
page = client_request.post(
'main.invite_user',
service_id=SERVICE_ONE_ID,
@@ -780,6 +778,7 @@ def test_invite_user_with_email_auth_service(
def test_cancel_invited_user_cancels_user_invitations(
client_request,
mock_get_invites_for_service,
sample_invite,
active_user_with_permissions,
mocker,
):
@@ -787,7 +786,7 @@ def test_cancel_invited_user_cancels_user_invitations(
client_request.get(
'main.cancel_invited_user',
service_id=SERVICE_ONE_ID,
invited_user_id=sample_uuid(),
invited_user_id=sample_invite['id'],
_expected_status=302,
_expected_redirect=url_for(
'main.manage_users', service_id=SERVICE_ONE_ID, _external=True
@@ -795,7 +794,7 @@ def test_cancel_invited_user_cancels_user_invitations(
)
mock_cancel.assert_called_once_with(
service_id=SERVICE_ONE_ID,
invited_user_id=sample_uuid(),
invited_user_id=sample_invite['id'],
)
@@ -808,7 +807,7 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service(
client_request.get(
'main.cancel_invited_user',
service_id=SERVICE_ONE_ID,
invited_user_id=USER_ONE_ID,
invited_user_id=sample_uuid(),
_expected_status=404,
)
assert mock_cancel.called is False
@@ -844,9 +843,8 @@ def test_manage_users_shows_invited_user(
expected_text,
):
sample_invite['status'] = invite_status
data = [InvitedUser(**sample_invite)]
mocker.patch('app.invite_api_client.get_invites_for_service', return_value=data)
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
mocker.patch('app.models.user.InvitedUsers.client', return_value=[sample_invite])
mocker.patch('app.models.user.Users.client', return_value=[active_user_with_permissions])
page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID)
assert page.h1.string.strip() == 'Team members'
@@ -863,8 +861,8 @@ def test_manage_users_does_not_show_accepted_invite(
invited_user_id = uuid.uuid4()
sample_invite['id'] = invited_user_id
sample_invite['status'] = 'accepted'
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
mocker.patch('app.invite_api_client._get_invites_for_service', return_value=[sample_invite])
mocker.patch('app.models.user.InvitedUsers.client', return_value=[sample_invite])
mocker.patch('app.models.user.Users.client', return_value=[active_user_with_permissions])
page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID)
@@ -885,7 +883,7 @@ def test_user_cant_invite_themselves(
'main.invite_user',
service_id=SERVICE_ONE_ID,
_data={
'email_address': active_user_with_permissions.email_address,
'email_address': active_user_with_permissions['email_address'],
'send_messages': 'y',
'manage_service': 'y',
'manage_api_keys': 'y',
@@ -937,7 +935,7 @@ def test_manage_user_page_shows_how_many_folders_user_can_view(
{'id': 'folder-id-3', 'name': 'f3', 'parent_id': None, 'users_with_permission': []},
]
for i in range(folders_user_can_see):
mock_get_template_folders.return_value[i]['users_with_permission'].append(api_user_active.id)
mock_get_template_folders.return_value[i]['users_with_permission'].append(api_user_active['id'])
page = client_request.get('main.manage_users', service_id=service_one['id'])
@@ -972,7 +970,7 @@ def test_manage_user_page_doesnt_show_folder_hint_if_service_cant_edit_folder_pe
):
service_one['permissions'] = []
mock_get_template_folders.return_value = [
{'id': 'folder-id-1', 'name': 'f1', 'parent_id': None, 'users_with_permission': [api_user_active.id]},
{'id': 'folder-id-1', 'name': 'f1', 'parent_id': None, 'users_with_permission': [api_user_active['id']]},
]
page = client_request.get('main.manage_users', service_id=service_one['id'])
@@ -990,12 +988,12 @@ def test_remove_user_from_service(
client_request.post(
'main.remove_user_from_service',
service_id=service_one['id'],
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_expected_redirect=url_for('main.manage_users', service_id=service_one['id'], _external=True)
)
mock_remove_user_from_service.assert_called_once_with(
service_one['id'],
str(active_user_with_permissions.id)
str(active_user_with_permissions['id'])
)
@@ -1008,7 +1006,7 @@ def test_can_invite_user_as_platform_admin(
mock_get_template_folders,
mocker,
):
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
mocker.patch('app.models.user.Users.client', return_value=[active_user_with_permissions])
page = client_request.get(
'main.manage_users',
@@ -1034,8 +1032,8 @@ def test_edit_user_email_page(
)
assert page.find('h1').text == "Change team members email address"
assert page.select('p[id=user_name]')[0].text == "This will change the email address for {}.".format(user.name)
assert page.select('input[type=email]')[0].attrs["value"] == user.email_address
assert page.select('p[id=user_name]')[0].text == "This will change the email address for {}.".format(user['name'])
assert page.select('input[type=email]')[0].attrs["value"] == user['email_address']
assert page.select('button[type=submit]')[0].text == "Save"
@@ -1055,16 +1053,17 @@ def test_edit_user_email_redirects_to_confirmation(
client_request,
active_user_with_permissions,
mock_get_users_by_service,
mock_get_user_by_email_not_found,
):
client_request.post(
'main.edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_expected_status=302,
_expected_redirect=url_for(
'main.confirm_edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_external=True,
),
)
@@ -1073,16 +1072,16 @@ def test_edit_user_email_redirects_to_confirmation(
def test_edit_user_email_without_changing_goes_back_to_team_members(
client_request,
active_user_with_permissions,
mock_get_user,
mock_get_user_by_email,
mock_get_users_by_service,
mock_update_user_attribute,
):
client_request.post(
'main.edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={
'email_address': active_user_with_permissions.email_address
'email_address': active_user_with_permissions['email_address']
},
_expected_status=302,
_expected_redirect=url_for(
@@ -1098,18 +1097,18 @@ def test_edit_user_email_without_changing_goes_back_to_team_members(
def test_edit_user_email_can_change_any_email_address_to_a_gov_email_address(
client_request,
active_user_with_permissions,
mock_get_user,
mock_get_user_by_email_not_found,
mock_get_users_by_service,
mock_update_user_attribute,
mock_get_organisations,
original_email_address,
):
active_user_with_permissions.email_address = original_email_address
active_user_with_permissions['email_address'] = original_email_address
client_request.post(
'main.edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={
'email_address': 'new-email-address@gov.uk'
},
@@ -1117,7 +1116,7 @@ def test_edit_user_email_can_change_any_email_address_to_a_gov_email_address(
_expected_redirect=url_for(
'main.confirm_edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_external=True
),
)
@@ -1126,17 +1125,17 @@ def test_edit_user_email_can_change_any_email_address_to_a_gov_email_address(
def test_edit_user_email_can_change_a_non_gov_email_address_to_another_non_gov_email_address(
client_request,
active_user_with_permissions,
mock_get_user,
mock_get_user_by_email_not_found,
mock_get_users_by_service,
mock_update_user_attribute,
mock_get_organisations,
):
active_user_with_permissions.email_address = 'old@example.com'
active_user_with_permissions['email_address'] = 'old@example.com'
client_request.post(
'main.edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={
'email_address': 'new@example.com'
},
@@ -1144,7 +1143,7 @@ def test_edit_user_email_can_change_a_non_gov_email_address_to_another_non_gov_e
_expected_redirect=url_for(
'main.confirm_edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_external=True
),
)
@@ -1153,7 +1152,7 @@ def test_edit_user_email_can_change_a_non_gov_email_address_to_another_non_gov_e
def test_edit_user_email_cannot_change_a_gov_email_address_to_a_non_gov_email_address(
client_request,
active_user_with_permissions,
mock_get_user,
mock_get_user_by_email_not_found,
mock_get_users_by_service,
mock_update_user_attribute,
mock_get_organisations,
@@ -1161,7 +1160,7 @@ def test_edit_user_email_cannot_change_a_gov_email_address_to_a_non_gov_email_ad
page = client_request.post(
'main.edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={
'email_address': 'new_email@example.com'
},
@@ -1183,14 +1182,14 @@ def test_confirm_edit_user_email_page(
page = client_request.get(
'main.confirm_edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
)
assert 'Confirm change of email address' in page.text
for text in [
'New email address:',
new_email,
'We will send {} an email to tell them about the change.'.format(active_user_with_permissions.name)
'We will send {} an email to tell them about the change.'.format(active_user_with_permissions['name'])
]:
assert text in page.text
assert 'Confirm' in page.text
@@ -1204,7 +1203,7 @@ def test_confirm_edit_user_email_page_redirects_if_session_empty(
page = client_request.get(
'main.confirm_edit_user_email',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_follow_redirects=True,
)
assert 'Confirm change of email address' not in page.text
@@ -1232,12 +1231,11 @@ def test_confirm_edit_user_email_changes_user_email(
):
# We want active_user_with_permissions (the current user) to update the email address for api_user_active
# By default both users would have the same id, so we change the id of api_user_active
api_user_active.id = str(uuid.uuid4())
mocker.patch('app.user_api_client.get_users_for_service',
return_value=[api_user_active, active_user_with_permissions])
api_user_active['id'] = str(uuid.uuid4())
mocker.patch('app.models.user.Users.client', return_value=[api_user_active, active_user_with_permissions])
# get_user gets called twice - first to check if current user can see the page, then to see if the team member
# whose email address we're changing belongs to the service
mocker.patch('app.models.service.user_api_client.get_user',
mocker.patch('app.user_api_client.get_user',
side_effect=[active_user_with_permissions, api_user_active])
mock_event_handler = mocker.patch('app.main.views.manage_users.create_email_change_event')
@@ -1248,7 +1246,7 @@ def test_confirm_edit_user_email_changes_user_email(
client_request.post(
'main.confirm_edit_user_email',
service_id=service_one['id'],
user_id=api_user_active.id,
user_id=api_user_active['id'],
_expected_status=302,
_expected_redirect=url_for(
'main.manage_users',
@@ -1258,14 +1256,14 @@ def test_confirm_edit_user_email_changes_user_email(
)
mock_update_user_attribute.assert_called_once_with(
api_user_active.id,
api_user_active['id'],
email_address=new_email,
updated_by=active_user_with_permissions.id
updated_by=active_user_with_permissions['id']
)
mock_event_handler.assert_called_once_with(
api_user_active.id,
active_user_with_permissions.id,
api_user_active.email_address,
api_user_active['id'],
active_user_with_permissions['id'],
api_user_active['email_address'],
new_email)
@@ -1291,21 +1289,18 @@ def test_edit_user_permissions_page_displays_redacted_mobile_number_and_change_l
service_one,
mocker
):
user = active_user_with_permissions
mocker.patch('app.user_api_client.get_user', return_value=user)
page = client_request.get(
'main.edit_user_permissions',
service_id=service_one['id'],
user_id=user.id
user_id=active_user_with_permissions['id'],
)
assert user.name in page.find('h1').text
assert active_user_with_permissions['name'] in page.find('h1').text
mobile_number_paragraph = page.select('p[id=user_mobile_number]')[0]
assert '0770 • • • • 762' in mobile_number_paragraph.text
change_link = mobile_number_paragraph.findChild()
assert change_link.attrs['href'] == '/services/{}/users/{}/edit-mobile-number'.format(
service_one['id'], user.id
service_one['id'], active_user_with_permissions['id']
)
@@ -1319,7 +1314,7 @@ def test_edit_user_permissions_with_delete_query_shows_banner(
page = client_request.get(
'main.edit_user_permissions',
service_id=service_one['id'],
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
delete=1
)
@@ -1328,7 +1323,7 @@ def test_edit_user_permissions_with_delete_query_shows_banner(
assert banner.form.attrs['action'] == url_for(
'main.remove_user_from_service',
service_id=service_one['id'],
user_id=active_user_with_permissions.id
user_id=active_user_with_permissions['id']
)
@@ -1339,17 +1334,16 @@ def test_edit_user_mobile_number_page(
service_one,
mocker
):
user = active_user_with_permissions
mocker.patch('app.user_api_client.get_user', return_value=user)
page = client_request.get(
'main.edit_user_mobile_number',
service_id=service_one['id'],
user_id=user.id
user_id=active_user_with_permissions['id'],
)
assert page.find('h1').text == "Change team members mobile number"
assert page.select('p[id=user_name]')[0].text == "This will change the mobile number for {}.".format(user.name)
assert page.select('p[id=user_name]')[0].text == (
"This will change the mobile number for {}."
).format(active_user_with_permissions['name'])
assert page.select('input[name=mobile_number]')[0].attrs["value"] == "0770••••762"
assert page.select('button[type=submit]')[0].text == "Save"
@@ -1362,13 +1356,13 @@ def test_edit_user_mobile_number_redirects_to_confirmation(
client_request.post(
'main.edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={'mobile_number': '07554080636'},
_expected_status=302,
_expected_redirect=url_for(
'main.confirm_edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_external=True,
),
)
@@ -1385,7 +1379,7 @@ def test_edit_user_mobile_number_redirects_to_manage_users_if_number_not_changed
client_request.post(
'main.edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_data={'mobile_number': '0770••••762'},
_expected_status=302,
_expected_redirect=url_for(
@@ -1410,14 +1404,14 @@ def test_confirm_edit_user_mobile_number_page(
page = client_request.get(
'main.confirm_edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
)
assert 'Confirm change of mobile number' in page.text
for text in [
'New mobile number:',
new_number,
'We will send {} a text message to tell them about the change.'.format(active_user_with_permissions.name)
'We will send {} a text message to tell them about the change.'.format(active_user_with_permissions['name'])
]:
assert text in page.text
assert 'Confirm' in page.text
@@ -1434,7 +1428,7 @@ def test_confirm_edit_user_mobile_number_page_redirects_if_session_empty(
page = client_request.get(
'main.confirm_edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=active_user_with_permissions.id,
user_id=active_user_with_permissions['id'],
_expected_status=302,
)
assert 'Confirm change of mobile number' not in page.text
@@ -1450,13 +1444,12 @@ def test_confirm_edit_user_mobile_number_changes_user_mobile_number(
):
# We want active_user_with_permissions (the current user) to update the mobile number for api_user_active
# By default both users would have the same id, so we change the id of api_user_active
api_user_active.id = str(uuid.uuid4())
api_user_active['id'] = str(uuid.uuid4())
mocker.patch('app.user_api_client.get_users_for_service',
return_value=[api_user_active, active_user_with_permissions])
mocker.patch('app.models.user.Users.client', return_value=[api_user_active, active_user_with_permissions])
# get_user gets called twice - first to check if current user can see the page, then to see if the team member
# whose mobile number we're changing belongs to the service
mocker.patch('app.models.service.user_api_client.get_user',
mocker.patch('app.user_api_client.get_user',
side_effect=[active_user_with_permissions, api_user_active])
mock_event_handler = mocker.patch('app.main.views.manage_users.create_mobile_number_change_event')
@@ -1467,7 +1460,7 @@ def test_confirm_edit_user_mobile_number_changes_user_mobile_number(
client_request.post(
'main.confirm_edit_user_mobile_number',
service_id=SERVICE_ONE_ID,
user_id=api_user_active.id,
user_id=api_user_active['id'],
_expected_status=302,
_expected_redirect=url_for(
'main.manage_users',
@@ -1476,14 +1469,14 @@ def test_confirm_edit_user_mobile_number_changes_user_mobile_number(
),
)
mock_update_user_attribute.assert_called_once_with(
api_user_active.id,
api_user_active['id'],
mobile_number=new_number,
updated_by=active_user_with_permissions.id
updated_by=active_user_with_permissions['id']
)
mock_event_handler.assert_called_once_with(
api_user_active.id,
active_user_with_permissions.id,
api_user_active.mobile_number,
api_user_active['id'],
active_user_with_permissions['id'],
api_user_active['mobile_number'],
new_number)