2018-03-08 12:47:24 +00:00
|
|
|
|
from unittest.mock import ANY, Mock
|
2016-03-01 17:23:23 +00:00
|
|
|
|
|
2018-07-05 11:45:09 +01:00
|
|
|
|
import pytest
|
2018-02-20 11:22:17 +00:00
|
|
|
|
from bs4 import BeautifulSoup
|
|
|
|
|
|
from flask import url_for
|
2018-03-08 12:47:24 +00:00
|
|
|
|
from notifications_python_client.errors import HTTPError
|
2018-02-20 11:22:17 +00:00
|
|
|
|
|
2016-03-04 14:42:52 +00:00
|
|
|
|
import app
|
2018-07-05 11:45:09 +01:00
|
|
|
|
from tests.conftest import (
|
|
|
|
|
|
SERVICE_ONE_ID,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
USER_ONE_ID,
|
2019-12-19 16:59:07 +00:00
|
|
|
|
create_active_caseworking_user,
|
|
|
|
|
|
create_active_user_with_permissions,
|
2020-05-19 13:49:17 +01:00
|
|
|
|
create_api_user_active,
|
2019-12-19 11:19:28 +00:00
|
|
|
|
normalize_spaces,
|
2018-07-05 11:45:09 +01:00
|
|
|
|
)
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
2016-02-29 17:35:21 +00:00
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
service_one,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_check_invite_token,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_add_user_to_service,
|
2017-11-15 17:19:32 +00:00
|
|
|
|
mock_get_service,
|
2017-11-01 16:02:05 +00:00
|
|
|
|
mocker,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2016-02-29 17:35:21 +00:00
|
|
|
|
expected_service = service_one['id']
|
2018-06-12 14:29:47 +01:00
|
|
|
|
expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'}
|
2016-02-29 17:35:21 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
2016-02-29 17:35:21 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
mock_check_invite_token.assert_called_with('thisisnotarealtoken')
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email.assert_called_with('invited_user@test.gov.uk')
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert mock_accept_invite.call_count == 1
|
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
|
|
|
|
mock_add_user_to_service.assert_called_with(
|
|
|
|
|
|
expected_service,
|
|
|
|
|
|
USER_ONE_ID,
|
|
|
|
|
|
expected_permissions,
|
|
|
|
|
|
[],
|
|
|
|
|
|
)
|
2016-02-29 17:35:21 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert response.status_code == 302
|
2017-11-15 14:59:03 +00:00
|
|
|
|
assert response.location == url_for('main.service_dashboard', service_id=expected_service, _external=True)
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
2019-03-15 14:57:39 +00:00
|
|
|
|
def test_existing_user_with_no_permissions_or_folder_permissions_accept_invite(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_check_invite_token,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_add_user_to_service,
|
2017-11-15 17:19:32 +00:00
|
|
|
|
mock_get_service,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2016-03-11 12:50:25 +00:00
|
|
|
|
expected_service = service_one['id']
|
|
|
|
|
|
sample_invite['permissions'] = ''
|
2018-03-01 16:59:01 +00:00
|
|
|
|
expected_permissions = set()
|
2019-03-15 14:57:39 +00:00
|
|
|
|
expected_folder_permissions = []
|
2016-03-11 12:50:25 +00:00
|
|
|
|
mocker.patch('app.invite_api_client.accept_invite', return_value=sample_invite)
|
|
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
2019-03-15 14:57:39 +00:00
|
|
|
|
mock_add_user_to_service.assert_called_with(expected_service,
|
|
|
|
|
|
USER_ONE_ID,
|
|
|
|
|
|
expected_permissions,
|
|
|
|
|
|
expected_folder_permissions)
|
2016-03-11 12:50:25 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert response.status_code == 302
|
2016-03-11 12:50:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_if_existing_user_accepts_twice_they_redirect_to_sign_in(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
):
|
2016-03-10 11:57:40 +00:00
|
|
|
|
sample_invite['status'] = 'accepted'
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2016-03-10 11:57:40 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
2017-02-16 13:33:32 +00:00
|
|
|
|
assert (
|
|
|
|
|
|
page.h1.string,
|
|
|
|
|
|
page.select('main p')[0].text.strip(),
|
|
|
|
|
|
) == (
|
|
|
|
|
|
'You need to sign in again',
|
2019-09-13 12:59:14 +01:00
|
|
|
|
'We signed you out because you have not used Notify for a while.',
|
2017-02-16 13:33:32 +00:00
|
|
|
|
)
|
2016-03-10 11:57:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
2018-07-05 11:45:09 +01:00
|
|
|
|
def test_invite_goes_in_session(
|
|
|
|
|
|
client_request,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_get_user_by_email,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = 'test@user.gov.uk'
|
|
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2018-07-05 11:45:09 +01:00
|
|
|
|
|
|
|
|
|
|
client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for(
|
|
|
|
|
|
'main.service_dashboard',
|
|
|
|
|
|
service_id=SERVICE_ONE_ID,
|
|
|
|
|
|
_external=True,
|
|
|
|
|
|
),
|
|
|
|
|
|
_follow_redirects=False,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
with client_request.session_transaction() as session:
|
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
|
|
|
|
assert session['invited_user']['email_address'] == 'test@user.gov.uk'
|
2018-07-05 11:45:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('user, landing_page_title', [
|
2019-12-19 16:59:07 +00:00
|
|
|
|
(create_active_user_with_permissions(), 'Dashboard'),
|
|
|
|
|
|
(create_active_caseworking_user(), 'Templates'),
|
2018-07-05 11:45:09 +01:00
|
|
|
|
])
|
|
|
|
|
|
def test_accepting_invite_removes_invite_from_session(
|
|
|
|
|
|
client_request,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_get_user_by_email,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_service_templates,
|
|
|
|
|
|
mock_get_template_statistics,
|
|
|
|
|
|
mock_get_jobs,
|
|
|
|
|
|
mock_get_service_statistics,
|
2018-11-01 17:11:58 +00:00
|
|
|
|
mock_get_template_folders,
|
2018-07-05 11:45:09 +01:00
|
|
|
|
mock_get_usage,
|
2019-10-25 13:27:46 +01:00
|
|
|
|
mock_get_billable_units,
|
|
|
|
|
|
mock_get_free_sms_fragment_limit,
|
2018-07-05 11:45:09 +01:00
|
|
|
|
mock_get_inbound_sms_summary,
|
2020-03-03 17:40:50 +00:00
|
|
|
|
mock_get_returned_letter_statistics_with_no_returned_letters,
|
2018-07-05 11:45:09 +01:00
|
|
|
|
fake_uuid,
|
|
|
|
|
|
user,
|
|
|
|
|
|
landing_page_title,
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = user['email_address']
|
2018-07-05 11:45:09 +01:00
|
|
|
|
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2018-07-05 11:45:09 +01:00
|
|
|
|
client_request.login(user)
|
|
|
|
|
|
|
|
|
|
|
|
page = client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_follow_redirects=True,
|
|
|
|
|
|
)
|
2019-04-30 16:32:20 +01:00
|
|
|
|
assert normalize_spaces(page.select_one('h1').text) == landing_page_title
|
2018-07-05 11:45:09 +01:00
|
|
|
|
|
|
|
|
|
|
with client_request.session_transaction() as session:
|
|
|
|
|
|
assert 'invited_user' not in session
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_existing_user_of_service_get_redirected_to_signin(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
mock_get_user_by_email,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
|
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2020-01-16 15:57:51 +00:00
|
|
|
|
mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active])
|
2016-03-15 15:32:30 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
2017-02-16 13:33:32 +00:00
|
|
|
|
assert (
|
|
|
|
|
|
page.h1.string,
|
|
|
|
|
|
page.select('main p')[0].text.strip(),
|
|
|
|
|
|
) == (
|
|
|
|
|
|
'You need to sign in again',
|
2019-09-13 12:59:14 +01:00
|
|
|
|
'We signed you out because you have not used Notify for a while.',
|
2017-02-16 13:33:32 +00:00
|
|
|
|
)
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert mock_accept_invite.call_count == 1
|
2016-03-15 15:32:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-05-19 13:49:17 +01:00
|
|
|
|
def test_accept_invite_redirects_if_api_raises_an_error_that_they_are_already_part_of_the_service(
|
|
|
|
|
|
client,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
mock_get_users_by_service
|
|
|
|
|
|
):
|
|
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
|
|
|
|
|
|
|
|
|
|
|
# This mock needs to return a user with a different ID to the invited user so that
|
|
|
|
|
|
# `existing_user in Users(invited_user.service)` returns False and the right code path is tested
|
|
|
|
|
|
mocker.patch('app.user_api_client.get_user_by_email', return_value=create_api_user_active(with_unique_id=True))
|
|
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
|
|
|
|
|
|
|
|
|
|
|
mocker.patch('app.user_api_client.add_user_to_service', side_effect=HTTPError(
|
|
|
|
|
|
response=Mock(
|
|
|
|
|
|
status_code=400,
|
|
|
|
|
|
json={
|
|
|
|
|
|
"result": "error",
|
|
|
|
|
|
"message": {f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}"}
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
message=f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}"
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=False)
|
|
|
|
|
|
assert response.location == url_for('main.service_dashboard', service_id=SERVICE_ONE_ID, _external=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_existing_signed_out_user_accept_invite_redirects_to_sign_in(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
service_one,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_check_invite_token,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_service,
|
2017-11-01 16:02:05 +00:00
|
|
|
|
mocker,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2016-03-02 15:25:04 +00:00
|
|
|
|
expected_service = service_one['id']
|
2018-06-12 14:29:47 +01:00
|
|
|
|
expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'}
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
mock_check_invite_token.assert_called_with('thisisnotarealtoken')
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email.assert_called_with('invited_user@test.gov.uk')
|
2019-03-15 14:57:39 +00:00
|
|
|
|
mock_add_user_to_service.assert_called_with(expected_service,
|
|
|
|
|
|
USER_ONE_ID,
|
|
|
|
|
|
expected_permissions,
|
|
|
|
|
|
sample_invite['folder_permissions'])
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert mock_accept_invite.call_count == 1
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
2017-02-16 13:33:32 +00:00
|
|
|
|
assert (
|
|
|
|
|
|
page.h1.string,
|
|
|
|
|
|
page.select('main p')[0].text.strip(),
|
|
|
|
|
|
) == (
|
|
|
|
|
|
'You need to sign in again',
|
2019-09-13 12:59:14 +01:00
|
|
|
|
'We signed you out because you have not used Notify for a while.',
|
2017-02-16 13:33:32 +00:00
|
|
|
|
)
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_new_user_accept_invite_calls_api_and_redirects_to_registration(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
service_one,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_dont_get_user_by_email,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_get_service,
|
2017-11-01 16:02:05 +00:00
|
|
|
|
mocker,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2016-03-02 15:25:04 +00:00
|
|
|
|
expected_redirect_location = 'http://localhost/register-from-invite'
|
|
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
mock_check_invite_token.assert_called_with('thisisnotarealtoken')
|
|
|
|
|
|
mock_dont_get_user_by_email.assert_called_with('invited_user@test.gov.uk')
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert response.status_code == 302
|
|
|
|
|
|
assert response.location == expected_redirect_location
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_new_user_accept_invite_calls_api_and_views_registration_page(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
service_one,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_dont_get_user_by_email,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_get_service,
|
2017-11-01 16:02:05 +00:00
|
|
|
|
mocker,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
2016-03-07 11:55:18 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
mock_check_invite_token.assert_called_with('thisisnotarealtoken')
|
|
|
|
|
|
mock_dont_get_user_by_email.assert_called_with('invited_user@test.gov.uk')
|
2016-03-07 11:55:18 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
assert page.h1.string.strip() == 'Create an account'
|
2016-03-07 11:55:18 +00:00
|
|
|
|
|
2018-07-02 09:16:48 +01:00
|
|
|
|
assert normalize_spaces(page.select_one('main p').text) == (
|
|
|
|
|
|
'Your account will be created with this email address: '
|
|
|
|
|
|
'invited_user@test.gov.uk'
|
|
|
|
|
|
)
|
2016-03-09 11:21:53 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
form = page.find('form')
|
|
|
|
|
|
name = form.find('input', id='name')
|
|
|
|
|
|
password = form.find('input', id='password')
|
|
|
|
|
|
service = form.find('input', type='hidden', id='service')
|
|
|
|
|
|
email = form.find('input', type='hidden', id='email_address')
|
2016-03-07 11:55:18 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert email
|
|
|
|
|
|
assert email.attrs['value'] == 'invited_user@test.gov.uk'
|
|
|
|
|
|
assert name
|
|
|
|
|
|
assert password
|
|
|
|
|
|
assert service
|
|
|
|
|
|
assert service.attrs['value'] == service_one['id']
|
2016-03-07 11:55:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mock_get_user,
|
|
|
|
|
|
mock_get_service,
|
2019-12-19 11:19:28 +00:00
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_check_invite_token,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2019-12-19 11:19:28 +00:00
|
|
|
|
sample_invite['status'] = 'cancelled'
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
2016-03-04 14:42:52 +00:00
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
app.invite_api_client.check_token.assert_called_with('thisisnotarealtoken')
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
assert page.h1.string.strip() == 'The invitation you were sent has been cancelled'
|
2016-03-04 14:42:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
2020-03-06 11:53:55 +00:00
|
|
|
|
@pytest.mark.parametrize('admin_endpoint, api_endpoint', [
|
|
|
|
|
|
('main.accept_invite', 'app.invite_api_client.check_token'),
|
|
|
|
|
|
('main.accept_org_invite', 'app.org_invite_api_client.check_token'),
|
|
|
|
|
|
])
|
2018-03-08 12:47:24 +00:00
|
|
|
|
def test_new_user_accept_invite_with_malformed_token(
|
2020-03-06 11:53:55 +00:00
|
|
|
|
admin_endpoint,
|
|
|
|
|
|
api_endpoint,
|
2018-03-08 12:47:24 +00:00
|
|
|
|
client,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
mocker,
|
|
|
|
|
|
):
|
2020-03-06 11:53:55 +00:00
|
|
|
|
mocker.patch(api_endpoint, side_effect=HTTPError(
|
2018-03-08 12:47:24 +00:00
|
|
|
|
response=Mock(
|
|
|
|
|
|
status_code=400,
|
|
|
|
|
|
json={
|
|
|
|
|
|
'result': 'error',
|
|
|
|
|
|
'message': {
|
|
|
|
|
|
'invitation': {
|
|
|
|
|
|
'Something’s wrong with this link. Make sure you’ve copied the whole thing.'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
),
|
|
|
|
|
|
message={'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'}
|
|
|
|
|
|
))
|
|
|
|
|
|
|
2020-03-06 11:53:55 +00:00
|
|
|
|
response = client.get(url_for(admin_endpoint, token='thisisnotarealtoken'), follow_redirects=True)
|
2018-03-08 12:47:24 +00:00
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
|
|
|
|
|
|
|
|
|
|
|
assert normalize_spaces(
|
|
|
|
|
|
page.select_one('.banner-dangerous').text
|
|
|
|
|
|
) == 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_new_user_accept_invite_completes_new_registration_redirects_to_verify(
|
2017-02-03 12:07:21 +00:00
|
|
|
|
client,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_dont_get_user_by_email,
|
2018-02-19 16:53:29 +00:00
|
|
|
|
mock_email_is_not_already_in_use,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mock_register_user,
|
|
|
|
|
|
mock_send_verify_code,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_get_service,
|
2017-11-01 16:02:05 +00:00
|
|
|
|
mocker,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
):
|
2016-03-02 15:25:04 +00:00
|
|
|
|
expected_service = service_one['id']
|
|
|
|
|
|
expected_email = sample_invite['email_address']
|
|
|
|
|
|
expected_from_user = service_one['users'][0]
|
|
|
|
|
|
expected_redirect_location = 'http://localhost/register-from-invite'
|
|
|
|
|
|
|
2017-02-03 12:07:21 +00:00
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
|
|
|
|
|
with client.session_transaction() as session:
|
|
|
|
|
|
assert response.status_code == 302
|
|
|
|
|
|
assert response.location == expected_redirect_location
|
|
|
|
|
|
invited_user = session.get('invited_user')
|
|
|
|
|
|
assert invited_user
|
|
|
|
|
|
assert expected_service == invited_user['service']
|
|
|
|
|
|
assert expected_email == invited_user['email_address']
|
|
|
|
|
|
assert expected_from_user == invited_user['from_user']
|
|
|
|
|
|
|
|
|
|
|
|
data = {'service': invited_user['service'],
|
|
|
|
|
|
'email_address': invited_user['email_address'],
|
|
|
|
|
|
'from_user': invited_user['from_user'],
|
|
|
|
|
|
'password': 'longpassword',
|
|
|
|
|
|
'mobile_number': '+447890123456',
|
2017-11-13 13:39:31 +00:00
|
|
|
|
'name': 'Invited User',
|
|
|
|
|
|
'auth_type': 'email_auth'
|
2017-02-03 12:07:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expected_redirect_location = 'http://localhost/verify'
|
|
|
|
|
|
response = client.post(url_for('main.register_from_invite'), data=data)
|
|
|
|
|
|
assert response.status_code == 302
|
|
|
|
|
|
assert response.location == expected_redirect_location
|
|
|
|
|
|
|
|
|
|
|
|
mock_send_verify_code.assert_called_once_with(ANY, 'sms', data['mobile_number'])
|
|
|
|
|
|
|
|
|
|
|
|
mock_register_user.assert_called_with(data['name'],
|
|
|
|
|
|
data['email_address'],
|
|
|
|
|
|
data['mobile_number'],
|
2017-11-13 13:39:31 +00:00
|
|
|
|
data['password'],
|
|
|
|
|
|
data['auth_type'])
|
2017-02-03 12:07:21 +00:00
|
|
|
|
|
|
|
|
|
|
assert mock_accept_invite.call_count == 1
|
2016-06-08 11:52:26 +01:00
|
|
|
|
|
2016-03-02 15:25:04 +00:00
|
|
|
|
|
2017-02-03 10:42:01 +00:00
|
|
|
|
def test_signed_in_existing_user_cannot_use_anothers_invite(
|
2019-03-26 12:35:32 +00:00
|
|
|
|
client_request,
|
2017-02-03 10:42:01 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_user,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
):
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2016-03-30 16:16:34 +01:00
|
|
|
|
mocker.patch('app.user_api_client.get_users_for_service', return_value=[api_user_active])
|
|
|
|
|
|
|
2019-03-26 12:35:32 +00:00
|
|
|
|
page = client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_follow_redirects=True,
|
|
|
|
|
|
_expected_status=403,
|
|
|
|
|
|
)
|
2019-12-04 12:03:07 +00:00
|
|
|
|
assert page.h1.string.strip() == 'You’re not allowed to see this page'
|
2017-02-03 12:07:21 +00:00
|
|
|
|
flash_banners = page.find_all('div', class_='banner-dangerous')
|
|
|
|
|
|
assert len(flash_banners) == 1
|
2020-05-29 17:25:11 +01:00
|
|
|
|
banner_contents = normalize_spaces(flash_banners[0].text)
|
2017-02-03 12:07:21 +00:00
|
|
|
|
assert "You’re signed in as test@user.gov.uk." in banner_contents
|
|
|
|
|
|
assert "This invite is for another email address." in banner_contents
|
|
|
|
|
|
assert "Sign out and click the link again to accept this invite." in banner_contents
|
|
|
|
|
|
assert mock_accept_invite.call_count == 0
|
2016-03-30 16:16:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
2017-12-21 16:42:16 +00:00
|
|
|
|
def test_accept_invite_does_not_treat_email_addresses_as_case_sensitive(
|
2019-03-26 12:35:32 +00:00
|
|
|
|
client_request,
|
2017-12-21 16:42:16 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_user_by_email
|
|
|
|
|
|
):
|
|
|
|
|
|
# the email address of api_user_active is 'test@user.gov.uk'
|
|
|
|
|
|
sample_invite['email_address'] = 'TEST@user.gov.uk'
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2020-01-16 15:57:51 +00:00
|
|
|
|
mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active])
|
2017-12-21 16:42:16 +00:00
|
|
|
|
|
2019-03-26 12:35:32 +00:00
|
|
|
|
client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for(
|
|
|
|
|
|
'main.service_dashboard',
|
|
|
|
|
|
service_id=SERVICE_ONE_ID,
|
|
|
|
|
|
_external=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2017-12-21 16:42:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
2017-11-14 13:30:31 +00:00
|
|
|
|
def test_new_invited_user_verifies_and_added_to_service(
|
|
|
|
|
|
client,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
mock_check_invite_token,
|
|
|
|
|
|
mock_dont_get_user_by_email,
|
2018-02-19 16:53:29 +00:00
|
|
|
|
mock_email_is_not_already_in_use,
|
2017-11-14 13:30:31 +00:00
|
|
|
|
mock_register_user,
|
|
|
|
|
|
mock_send_verify_code,
|
|
|
|
|
|
mock_check_verify_code,
|
|
|
|
|
|
mock_get_user,
|
|
|
|
|
|
mock_update_user_attribute,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_get_service,
|
|
|
|
|
|
mock_get_service_templates,
|
|
|
|
|
|
mock_get_template_statistics,
|
|
|
|
|
|
mock_get_jobs,
|
|
|
|
|
|
mock_has_permissions,
|
|
|
|
|
|
mock_get_users_by_service,
|
2018-05-09 13:53:02 +01:00
|
|
|
|
mock_get_service_statistics,
|
2017-11-14 13:30:31 +00:00
|
|
|
|
mock_get_usage,
|
2019-10-25 13:27:46 +01:00
|
|
|
|
mock_get_free_sms_fragment_limit,
|
2020-03-03 17:40:50 +00:00
|
|
|
|
mock_get_returned_letter_statistics_with_no_returned_letters,
|
2018-05-02 10:27:01 +01:00
|
|
|
|
mock_create_event,
|
2017-11-14 13:30:31 +00:00
|
|
|
|
mocker,
|
|
|
|
|
|
):
|
|
|
|
|
|
# visit accept token page
|
|
|
|
|
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
|
|
|
|
|
assert response.status_code == 302
|
|
|
|
|
|
assert response.location == url_for('main.register_from_invite', _external=True)
|
|
|
|
|
|
|
|
|
|
|
|
# get redirected to register from invite
|
|
|
|
|
|
data = {
|
|
|
|
|
|
'service': sample_invite['service'],
|
|
|
|
|
|
'email_address': sample_invite['email_address'],
|
|
|
|
|
|
'from_user': sample_invite['from_user'],
|
|
|
|
|
|
'password': 'longpassword',
|
|
|
|
|
|
'mobile_number': '+447890123456',
|
|
|
|
|
|
'name': 'Invited User',
|
2017-11-14 15:53:38 +00:00
|
|
|
|
'auth_type': 'sms_auth'
|
2017-11-14 13:30:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
response = client.post(url_for('main.register_from_invite'), data=data)
|
|
|
|
|
|
assert response.status_code == 302
|
|
|
|
|
|
assert response.location == url_for('main.verify', _external=True)
|
|
|
|
|
|
|
|
|
|
|
|
# that sends user on to verify
|
|
|
|
|
|
response = client.post(url_for('main.verify'), data={'sms_code': '12345'}, follow_redirects=True)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
# when they post codes back to admin user should be added to
|
|
|
|
|
|
# service and sent on to dash board
|
2018-06-12 14:29:47 +01:00
|
|
|
|
expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'}
|
2017-11-14 13:30:31 +00:00
|
|
|
|
|
|
|
|
|
|
with client.session_transaction() as session:
|
|
|
|
|
|
new_user_id = session['user_id']
|
2019-03-15 14:57:39 +00:00
|
|
|
|
mock_add_user_to_service.assert_called_with(data['service'], new_user_id, expected_permissions, [])
|
2017-11-14 13:30:31 +00:00
|
|
|
|
mock_accept_invite.assert_called_with(data['service'], sample_invite['id'])
|
|
|
|
|
|
mock_check_verify_code.assert_called_once_with(new_user_id, '12345', 'sms')
|
|
|
|
|
|
assert service_one['id'] == session['service_id']
|
|
|
|
|
|
|
|
|
|
|
|
raw_html = response.data.decode('utf-8')
|
|
|
|
|
|
page = BeautifulSoup(raw_html, 'html.parser')
|
|
|
|
|
|
assert page.find('h1').text == 'Dashboard'
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-11-15 17:19:32 +00:00
|
|
|
|
def test_existing_user_accepts_and_sets_email_auth(
|
2017-11-15 14:59:03 +00:00
|
|
|
|
client_request,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email,
|
2017-11-15 14:59:03 +00:00
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_update_user_attribute,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mocker
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
service_one['permissions'].append('email_auth')
|
2017-11-15 14:59:03 +00:00
|
|
|
|
sample_invite['auth_type'] = 'email_auth'
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2017-11-15 14:59:03 +00:00
|
|
|
|
|
2017-11-15 17:19:32 +00:00
|
|
|
|
client_request.get(
|
2017-11-15 14:59:03 +00:00
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for('main.service_dashboard', service_id=service_one['id'], _external=True),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email.assert_called_once_with('test@user.gov.uk')
|
|
|
|
|
|
mock_update_user_attribute.assert_called_once_with(USER_ONE_ID, auth_type='email_auth')
|
2019-03-15 14:57:39 +00:00
|
|
|
|
mock_add_user_to_service.assert_called_once_with(ANY, USER_ONE_ID, ANY, ANY)
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_existing_user_doesnt_get_auth_changed_by_service_without_permission(
|
|
|
|
|
|
client_request,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_user_by_email,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_update_user_attribute,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mocker
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
assert 'email_auth' not in service_one['permissions']
|
|
|
|
|
|
|
|
|
|
|
|
sample_invite['auth_type'] = 'email_auth'
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for('main.service_dashboard', service_id=service_one['id'], _external=True),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert not mock_update_user_attribute.called
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_existing_email_auth_user_without_phone_cannot_set_sms_auth(
|
|
|
|
|
|
client_request,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_users_by_service,
|
|
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_update_user_attribute,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mocker
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
service_one['permissions'].append('email_auth')
|
|
|
|
|
|
|
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
|
|
|
|
api_user_active['auth_type'] = 'email_auth'
|
|
|
|
|
|
api_user_active['mobile_number'] = None
|
2017-11-15 17:19:32 +00:00
|
|
|
|
sample_invite['auth_type'] = 'sms_auth'
|
|
|
|
|
|
|
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
|
|
|
|
mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_active)
|
|
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for('main.service_dashboard', service_id=service_one['id'], _external=True),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert not mock_update_user_attribute.called
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_existing_email_auth_user_with_phone_can_set_sms_auth(
|
|
|
|
|
|
client_request,
|
|
|
|
|
|
api_user_active,
|
|
|
|
|
|
service_one,
|
|
|
|
|
|
sample_invite,
|
|
|
|
|
|
mock_get_users_by_service,
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email,
|
2017-11-15 17:19:32 +00:00
|
|
|
|
mock_accept_invite,
|
|
|
|
|
|
mock_update_user_attribute,
|
|
|
|
|
|
mock_add_user_to_service,
|
|
|
|
|
|
mocker
|
|
|
|
|
|
):
|
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
|
|
|
|
sample_invite['email_address'] = api_user_active['email_address']
|
2017-11-15 17:19:32 +00:00
|
|
|
|
service_one['permissions'].append('email_auth')
|
|
|
|
|
|
sample_invite['auth_type'] = 'sms_auth'
|
|
|
|
|
|
|
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
|
|
|
|
mocker.patch('app.invite_api_client.check_token', return_value=sample_invite)
|
2017-11-15 17:19:32 +00:00
|
|
|
|
|
|
|
|
|
|
client_request.get(
|
|
|
|
|
|
'main.accept_invite',
|
|
|
|
|
|
token='thisisnotarealtoken',
|
|
|
|
|
|
_expected_status=302,
|
|
|
|
|
|
_expected_redirect=url_for('main.service_dashboard', service_id=service_one['id'], _external=True),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2019-02-25 16:51:37 +00:00
|
|
|
|
mock_get_unknown_user_by_email.assert_called_once_with(sample_invite['email_address'])
|
|
|
|
|
|
mock_update_user_attribute.assert_called_once_with(USER_ONE_ID, auth_type='sms_auth')
|