[WIP] Start of user accepting invite.

This commit only deals with acceptance by
users who are already in system.

Changed invite client to return invited user objects
instead of dictionaries.

Added commented out test. fixed up fixtures to return invited user
object for invites
This commit is contained in:
Adam Shimali
2016-02-29 17:35:21 +00:00
parent e3740f314f
commit 5f3c72729e
11 changed files with 220 additions and 79 deletions

View File

@@ -3,7 +3,24 @@ from flask import Blueprint
main = Blueprint('main', __name__)
from app.main.views import (
index, sign_in, sign_out, register, two_factor, verify, send, add_service,
code_not_received, jobs, dashboard, templates, service_settings, forgot_password,
new_password, styleguide, user_profile, choose_service, api_keys, manage_users
index,
sign_in,
sign_out,
register,
two_factor,
verify,
send,
add_service,
code_not_received,
jobs, dashboard,
templates,
service_settings,
forgot_password,
new_password,
styleguide,
user_profile,
choose_service,
api_keys,
manage_users,
invites
)

34
app/main/views/invites.py Normal file
View File

@@ -0,0 +1,34 @@
from flask import (
redirect,
url_for,
abort
)
from notifications_python_client.errors import HTTPError
from app.main import main
from app import (
invite_api_client,
user_api_client
)
@main.route("/invitation/<token>")
def accept_invite(token):
try:
invited_user = invite_api_client.accept_invite(token)
existing_user = user_api_client.get_user_by_email(invited_user['email_address'])
if existing_user:
user_api_client.add_user_to_service(invited_user['service'],
existing_user.id,
invited_user)
return redirect(url_for('main.service_dashboard', service_id=invited_user['service']))
else:
# TODO implement registration flow for new users
abort(404)
except HTTPError as e:
if e.status_code == 404:
abort(404)
else:
raise e

View File

@@ -58,8 +58,8 @@ def invite_user(service_id):
email_address = form.email_address.data
permissions = _get_permissions(request.form)
try:
resp = invite_api_client.create_invite(current_user.id, service_id, email_address, permissions)
flash('Invite sent to {}'.format(resp['email_address']), 'default_with_tick')
invited_user = invite_api_client.create_invite(current_user.id, service_id, email_address, permissions)
flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick')
return redirect(url_for('.manage_users', service_id=service_id))
except HTTPError as e:

View File

@@ -1,6 +1,5 @@
from notifications_python_client.base import BaseAPIClient
from app.notify_client.models import User
from app.notify_client.models import InvitedUser
class InviteApiClient(BaseAPIClient):
@@ -22,9 +21,23 @@ class InviteApiClient(BaseAPIClient):
'permissions': permissions
}
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)
return resp['data']
return InvitedUser(**resp['data'])
def get_invites_for_service(self, service_id):
endpoint = '/service/{}/invite'.format(service_id)
resp = self.get(endpoint)
return [User(data) for data in resp['data']]
invites = resp['data']
invited_users = _get_invited_users(invites)
return invited_users
def accept_invite(self, token):
resp = self.get(url='/invite/{}'.format(token))
return InvitedUser(**resp['data'])
def _get_invited_users(invites):
invited_users = []
for invite in invites:
invited_user = InvitedUser(**invite)
invited_users.append(invited_user)
return invited_users

View File

@@ -110,3 +110,18 @@ class User(UserMixin):
def set_password(self, pwd):
self._password = pwd
class InvitedUser(object):
def __init__(self, id, service, from_user, email_address, permissions, status, created_at):
self.id = id
self.service = str(service)
self.from_user = from_user
self.email_address = email_address
self.permissions = permissions.split(',')
self.status = status
self.created_at = created_at
def has_permissions(self, permission):
return permission in self.permissions

View File

@@ -85,3 +85,8 @@ class UserApiClient(BaseAPIClient):
endpoint = '/service/{}/users'.format(service_id)
resp = self.get(endpoint)
return [User(data) for data in resp['data']]
def add_user_to_service(self, service_id, user_id, invited_user):
endpoint = '/service/{}/users/{}'.format(service_id, user_id)
resp = self.post(endpoint, data=invited_user)
return User(resp['data'], max_failed_login_count=self.max_failed_login_count)

View File

@@ -43,9 +43,9 @@ Manage users GOV.UK Notify
{% call field() %}
{{ item.email_address }}
{% endcall %}
{{ boolean_field(item.has_permissions(service_id, 'send_messages')) }}
{{ boolean_field(item.has_permissions(service_id, 'manage_service')) }}
{{ boolean_field(item.has_permissions(service_id, 'api_keys')) }}
{{ boolean_field(item.has_permissions('send_messages')) }}
{{ boolean_field(item.has_permissions('manage_service')) }}
{{ boolean_field(item.has_permissions('api_keys')) }}
{% call field(align='right') %}
<a href="{{ url_for('.edit_user', service_id=service_id, user_id=item.id)}}">Change</a>
{% endcall %}

View File

@@ -48,12 +48,15 @@ def api_key_json(id_, name, expiry_date=None):
}
def invite_json(id, from_user, service_id, email_address):
def invite_json(id, from_user, service_id, email_address, permissions, created_at):
return {'id': id,
'from_user': from_user,
'service': service_id,
'email_address': email_address,
'status': 'pending'}
'status': 'pending',
'permissions': permissions,
'created_at': created_at
}
TEST_USER_EMAIL = 'test@user.gov.uk'

View File

@@ -0,0 +1,25 @@
from flask import url_for
def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard(app_,
service_one,
sample_invite,
mock_accept_invite,
mock_get_user_by_email,
mock_add_user_to_service):
expected_service = service_one['id']
expected_from_user = service_one['users'][0]
expected_redirect_location = 'http://localhost/services/{}/dashboard'.format(expected_service)
with app_.test_request_context():
with app_.test_client() as client:
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
mock_accept_invite.assert_called_with('thisisnotarealtoken')
mock_get_user_by_email.assert_called_with('invited_user@test.gov.uk')
mock_add_user_to_service.assert_called_with(expected_service, expected_from_user, sample_invite)
assert response.status_code == 302
assert response.location == expected_redirect_location

View File

@@ -3,22 +3,22 @@ from flask import url_for
from bs4 import BeautifulSoup
# def test_should_show_overview_page(
# app_,
# api_user_active,
# mock_login,
# mock_get_service,
# mock_get_users_by_service,
# mock_get_invites_for_service
# ):
# with app_.test_request_context():
# with app_.test_client() as client:
# client.login(api_user_active)
# response = client.get(url_for('main.manage_users', service_id=55555))
#
# assert 'Manage team' in response.get_data(as_text=True)
# assert response.status_code == 200
# mock_get_users_by_service.assert_called_once_with(service_id='55555')
def test_should_show_overview_page(
app_,
api_user_active,
mock_login,
mock_get_service,
mock_get_users_by_service,
mock_get_invites_for_service
):
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active)
response = client.get(url_for('main.manage_users', service_id=55555))
assert 'Manage team' in response.get_data(as_text=True)
assert response.status_code == 200
mock_get_users_by_service.assert_called_once_with(service_id='55555')
def test_should_show_page_for_one_user(
@@ -70,37 +70,37 @@ def test_should_show_page_for_inviting_user(
assert 'Add a new team member' in response.get_data(as_text=True)
assert response.status_code == 200
#
# def test_invite_user(
# app_,
# service_one,
# api_user_active,
# mock_login,
# mock_get_users_by_service,
# mock_create_invite,
# mock_get_invites_for_service
# ):
# from_user = api_user_active.id
# service_id = service_one['id']
# email_address = 'test@example.gov.uk'
# permissions = 'send_messages,manage_service,manage_api_keys'
#
# with app_.test_request_context():
# with app_.test_client() as client:
# client.login(api_user_active)
# response = client.post(
# url_for('main.invite_user', service_id=service_id),
# data={'email_address': email_address,
# 'send_messages': 'yes',
# 'manage_service': 'yes',
# 'manage_api_keys': 'yes'},
# follow_redirects=True
# )
#
# assert response.status_code == 200
# mock_create_invite.assert_called_with(from_user, service_id, email_address, permissions)
# mock_get_invites_for_service.assert_called_with(service_id=service_id)
# page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
# assert page.h1.string.strip() == 'Manage team'
# flash_banner = page.find('div', class_='banner-default-with-tick').string.strip()
# assert flash_banner == 'Invite sent to test@example.gov.uk'
def test_invite_user(
app_,
service_one,
api_user_active,
mock_login,
mock_get_users_by_service,
mock_create_invite,
mock_get_invites_for_service
):
from_user = api_user_active.id
service_id = service_one['id']
email_address = 'test@example.gov.uk'
permissions = 'send_messages,manage_service,manage_api_keys'
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active)
response = client.post(
url_for('main.invite_user', service_id=service_id),
data={'email_address': email_address,
'send_messages': 'yes',
'manage_service': 'yes',
'manage_api_keys': 'yes'},
follow_redirects=True
)
assert response.status_code == 200
mock_create_invite.assert_called_with(from_user, service_id, email_address, permissions)
mock_get_invites_for_service.assert_called_with(service_id=service_id)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.string.strip() == 'Manage team'
flash_banner = page.find('div', class_='banner-default-with-tick').string.strip()
assert flash_banner == 'Invite sent to test@example.gov.uk'

View File

@@ -12,7 +12,10 @@ from . import (
job_json,
invite_json
)
from app.notify_client.models import User
from app.notify_client.models import (
User,
InvitedUser
)
@pytest.fixture(scope='session')
@@ -551,27 +554,53 @@ def mock_s3_upload(mocker):
@pytest.fixture(scope='function')
def mock_create_invite(mocker):
def sample_invite(mocker, service_one):
import datetime
id = str(uuid.uuid4())
from_user = service_one['users'][0]
email_address = 'invited_user@test.gov.uk'
service_id = service_one['id']
permissions = 'send_messages,manage_service,manage_api_keys'
created_at = datetime.datetime.now()
return invite_json(id, from_user, service_id, email_address, permissions, created_at)
@pytest.fixture(scope='function')
def mock_create_invite(mocker, sample_invite):
def _create_invite(from_user, service_id, email_address, permissions):
data = {'id': uuid.uuid4(),
'from_user': from_user,
'service': service_id,
'email_address': email_address,
'status': 'pending',
'permissions': permissions}
return data
sample_invite['from_user'] = from_user
sample_invite['service'] = service_id
sample_invite['email_address'] = email_address
sample_invite['status'] = 'pending'
sample_invite['permissions'] = permissions
return InvitedUser(**sample_invite)
return mocker.patch('app.invite_api_client.create_invite', side_effect=_create_invite)
@pytest.fixture(scope='function')
def mock_get_invites_for_service(mocker, service_one):
def mock_get_invites_for_service(mocker, service_one, sample_invite):
import copy
def _get_invites(service_id):
data = []
from_user = service_one['users'][0]
service_id = service_one['id']
for i in range(0, 5):
email_address = 'user_{}@testnotify.gov.uk'.format(i)
invite = invite_json(uuid.uuid4(), from_user, service_id, email_address)
data.append(invite)
invite = copy.copy(sample_invite)
invite['email_address'] = 'user_{}@testnotify.gov.uk'.format(i)
data.append(InvitedUser(**invite))
return data
return mocker.patch('app.invite_api_client.get_invites_for_service', side_effect=_get_invites)
@pytest.fixture(scope='function')
def mock_accept_invite(mocker, sample_invite):
def _accept_token(token):
return sample_invite
return mocker.patch('app.invite_api_client.accept_invite', side_effect=_accept_token)
@pytest.fixture(scope='function')
def mock_add_user_to_service(mocker, service_one):
def _add_user(service_id, user_id, invitation):
return api_user_active
return mocker.patch('app.user_api_client.add_user_to_service', side_effect=_add_user)