Merge pull request #1695 from alphagov/org-user-endpoints

Organisation user endpoints
This commit is contained in:
Rebecca Law
2018-02-26 16:27:01 +00:00
committed by GitHub
23 changed files with 747 additions and 70 deletions

View File

@@ -1,6 +1,7 @@
import uuid
from flask import json
import pytest
from flask import json, current_app
from freezegun import freeze_time
from notifications_utils.url_safe_token import generate_token
from tests import create_authorization_header
@@ -56,3 +57,59 @@ def test_accept_invite_returns_400_when_invited_user_does_not_exist(notify_api):
json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['result'] == 'error'
assert json_resp['message'] == 'No result found'
@pytest.mark.parametrize('invitation_type', ['service', 'organisation'])
def test_validate_invitation_token_for_expired_token_returns_400(client, invitation_type):
with freeze_time('2016-01-01T12:00:00'):
token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'])
url = '/invite/{}/{}'.format(invitation_type, token)
auth_header = create_authorization_header()
response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 400
json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['result'] == 'error'
assert json_resp['message'] == {'invitation': [
'Your invitation to GOV.UK Notify has expired. '
'Please ask the person that invited you to send you another one']}
@pytest.mark.parametrize('invitation_type', ['service', 'organisation'])
def test_validate_invitation_token_returns_200_when_token_valid(
client, invitation_type, sample_invited_user, sample_invited_org_user
):
invited_user = sample_invited_user if invitation_type == 'service' else sample_invited_org_user
token = generate_token(str(invited_user.id), current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'])
url = '/invite/{}/{}'.format(invitation_type, token)
auth_header = create_authorization_header()
response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 200
json_resp = json.loads(response.get_data(as_text=True))
if invitation_type == 'service':
assert json_resp['data']['id'] == str(sample_invited_user.id)
assert json_resp['data']['email_address'] == sample_invited_user.email_address
assert json_resp['data']['from_user'] == str(sample_invited_user.user_id)
assert json_resp['data']['service'] == str(sample_invited_user.service_id)
assert json_resp['data']['status'] == sample_invited_user.status
assert json_resp['data']['permissions'] == sample_invited_user.permissions
if invitation_type == 'organisation':
assert json_resp['data'] == sample_invited_org_user.serialize()
@pytest.mark.parametrize('invitation_type', ['service', 'organisation'])
def test_validate_invitation_token_returns_400_when_invited_user_does_not_exist(client, invitation_type):
token = generate_token(str(uuid.uuid4()), current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'])
url = '/invite/{}/{}'.format(invitation_type, token)
auth_header = create_authorization_header()
response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 404
json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['result'] == 'error'
assert json_resp['message'] == 'No result found'

View File

@@ -59,7 +59,8 @@ from tests.app.db import (
create_service,
create_api_key,
create_inbound_number,
create_letter_contact
create_letter_contact,
create_invited_org_user,
)
@@ -747,6 +748,16 @@ def sample_invited_user(notify_db,
return invited_user
@pytest.fixture(scope='function')
def sample_invited_org_user(
notify_db,
notify_db_session,
sample_user,
sample_organisation
):
return create_invited_org_user(sample_organisation, sample_user)
@pytest.fixture(scope='function')
def sample_permission(notify_db,
notify_db_session,
@@ -920,6 +931,20 @@ def invitation_email_template(notify_db,
)
@pytest.fixture(scope='function')
def org_invite_email_template(notify_db, notify_db_session):
service, user = notify_service(notify_db, notify_db_session)
return create_custom_template(
service=service,
user=user,
template_config_name='ORGANISATION_INVITATION_EMAIL_TEMPLATE_ID',
content='((user_name)) ((organisation_name)) ((url))',
subject='Invitation to ((organisation_name))',
template_type='email'
)
@pytest.fixture(scope='function')
def password_reset_email_template(notify_db,
notify_db_session):

View File

@@ -1,5 +1,7 @@
import uuid
import pytest
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from app.dao.organisation_dao import (
dao_get_organisations,
@@ -8,15 +10,18 @@ from app.dao.organisation_dao import (
dao_get_organisation_services,
dao_update_organisation,
dao_add_service_to_organisation,
dao_get_invited_organisation_user,
dao_get_users_for_organisation,
dao_add_user_to_organisation
)
from app.models import Organisation
from tests.app.db import create_organisation, create_service
from tests.app.db import create_organisation, create_service, create_user
def test_get_organisations_gets_all_organisations_alphabetically_with_active_organisations_first(
notify_db,
notify_db_session
notify_db,
notify_db_session
):
m_active_org = create_organisation(name='m_active_organisation')
z_inactive_org = create_organisation(name='z_inactive_organisation', active=False)
@@ -104,3 +109,65 @@ def test_get_organisation_by_service_id(notify_db, notify_db_session, sample_ser
assert organisation_1 == sample_organisation
assert organisation_2 == another_org
def test_dao_get_invited_organisation_user(sample_invited_org_user):
invited_org_user = dao_get_invited_organisation_user(sample_invited_org_user.id)
assert invited_org_user == sample_invited_org_user
def test_dao_get_invited_organisation_user_returns_none(notify_db):
with pytest.raises(expected_exception=SQLAlchemyError):
dao_get_invited_organisation_user(uuid.uuid4())
def test_dao_get_users_for_organisation(sample_organisation):
first = create_user(email='first@invited.com')
second = create_user(email='another@invited.com')
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=first.id)
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=second.id)
results = dao_get_users_for_organisation(organisation_id=sample_organisation.id)
assert len(results) == 2
assert results[0] == first
assert results[1] == second
def test_dao_get_users_for_organisation_returns_empty_list(sample_organisation):
results = dao_get_users_for_organisation(organisation_id=sample_organisation.id)
assert len(results) == 0
def test_dao_get_users_for_organisation_only_returns_active_users(sample_organisation):
first = create_user(email='first@invited.com')
second = create_user(email='another@invited.com')
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=first.id)
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=second.id)
second.state = 'inactive'
results = dao_get_users_for_organisation(organisation_id=sample_organisation.id)
assert len(results) == 1
assert results[0] == first
def test_add_user_to_organisation_returns_user(sample_organisation):
org_user = create_user()
assert not org_user.organisations
added_user = dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=org_user.id)
assert len(added_user.organisations) == 1
assert added_user.organisations[0] == sample_organisation
def test_add_user_to_organisation_when_user_does_not_exist(sample_organisation):
with pytest.raises(expected_exception=SQLAlchemyError):
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=uuid.uuid4())
def test_add_user_to_organisation_when_organisation_does_not_exist(sample_user):
with pytest.raises(expected_exception=SQLAlchemyError):
dao_add_user_to_organisation(organisation_id=uuid.uuid4(), user_id=sample_user.id)

View File

@@ -6,6 +6,7 @@ from app.dao.jobs_dao import dao_create_job
from app.dao.service_inbound_api_dao import save_service_inbound_api
from app.dao.service_callback_api_dao import save_service_callback_api
from app.dao.service_sms_sender_dao import update_existing_sms_sender_with_inbound_number, dao_update_service_sms_sender
from app.dao.invited_org_user_dao import save_invited_org_user
from app.models import (
ApiKey,
InboundSms,
@@ -30,7 +31,8 @@ from app.models import (
SMS_TYPE,
KEY_TYPE_NORMAL,
AnnualBilling,
LetterRate
LetterRate,
InvitedOrganisationUser,
)
from app.dao.users_dao import save_model_user
from app.dao.notifications_dao import (
@@ -492,3 +494,13 @@ def create_organisation(name='test_org_1', active=True):
dao_create_organisation(organisation)
return organisation
def create_invited_org_user(organisation, invited_by, email_address='invite@example.com'):
invited_org_user = InvitedOrganisationUser(
email_address=email_address,
invited_by=invited_by,
organisation=organisation,
)
save_invited_org_user(invited_org_user)
return invited_org_user

View File

@@ -1,6 +1,5 @@
import json
import pytest
import uuid
from app.models import Notification, SMS_AUTH_TYPE, EMAIL_AUTH_TYPE
from tests import create_authorization_header
@@ -161,40 +160,6 @@ def test_get_invited_users_by_service_with_no_invites(client, notify_db, notify_
assert len(json_resp['data']) == 0
def test_get_invited_user_by_service_and_id(client, sample_service, sample_invited_user):
url = '/service/{}/invite/{}'.format(sample_service.id, sample_invited_user.id)
auth_header = create_authorization_header()
response = client.get(
url,
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 200
json_resp = json.loads(response.get_data(as_text=True))
invite_email_address = sample_invited_user.email_address
invite_from = sample_service.users[0]
assert json_resp['data']['service'] == str(sample_service.id)
assert json_resp['data']['email_address'] == invite_email_address
assert json_resp['data']['from_user'] == str(invite_from.id)
assert json_resp['data']['id']
def test_get_invited_user_by_service_but_unknown_invite_id_returns_404(client, sample_service):
unknown_id = uuid.uuid4()
url = '/service/{}/invite/{}'.format(sample_service.id, unknown_id)
auth_header = create_authorization_header()
response = client.get(
url,
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 404
def test_update_invited_user_set_status_to_cancelled(client, sample_invited_user):
data = {'status': 'cancelled'}
url = '/service/{0}/invite/{1}'.format(sample_invited_user.service_id, sample_invited_user.id)

View File

@@ -0,0 +1,17 @@
import json
from flask import current_app
from notifications_utils.url_safe_token import generate_token
from tests import create_authorization_header
def test_accept_organisation_invitation(client, sample_invited_org_user):
token = generate_token(str(sample_invited_org_user.id), current_app.config['SECRET_KEY'],
current_app.config['DANGEROUS_SALT'])
url = '/organisation-invitation/{}'.format(token)
auth_header = create_authorization_header()
response = client.get(url, headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 200
json_resp = json.loads(response.get_data(as_text=True))
assert json_resp['data'] == sample_invited_org_user.serialize()

View File

@@ -0,0 +1,148 @@
import pytest
from app.models import Notification, INVITE_PENDING
from tests.app.db import create_invited_org_user
@pytest.mark.parametrize('extra_args, expected_start_of_invite_url', [
(
{},
'http://localhost:6012/organisation-invitation/'
),
(
{'invite_link_host': 'https://www.example.com'},
'https://www.example.com/organisation-invitation/'
),
])
def test_create_invited_org_user(
admin_request,
sample_organisation,
sample_user,
mocker,
org_invite_email_template,
extra_args,
expected_start_of_invite_url,
):
mocked = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
email_address = 'invited_user@example.com'
data = dict(
organisation=str(sample_organisation.id),
email_address=email_address,
invited_by=str(sample_user.id),
**extra_args
)
json_resp = admin_request.post(
'organisation_invite.invite_user_to_org',
organisation_id=sample_organisation.id,
_data=data,
_expected_status=201
)
assert json_resp['data']['organisation'] == str(sample_organisation.id)
assert json_resp['data']['email_address'] == email_address
assert json_resp['data']['invited_by'] == str(sample_user.id)
assert json_resp['data']['status'] == INVITE_PENDING
assert json_resp['data']['id']
notification = Notification.query.first()
assert notification.reply_to_text == sample_user.email_address
assert len(notification.personalisation.keys()) == 3
assert notification.personalisation['organisation_name'] == 'sample organisation'
assert notification.personalisation['user_name'] == 'Test User'
assert notification.personalisation['url'].startswith(expected_start_of_invite_url)
assert len(notification.personalisation['url']) > len(expected_start_of_invite_url)
mocked.assert_called_once_with([(str(notification.id))], queue="notify-internal-tasks")
def test_create_invited_user_invalid_email(admin_request, sample_organisation, sample_user, mocker):
mocked = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
email_address = 'notanemail'
data = {
'service': str(sample_organisation.id),
'email_address': email_address,
'invited_by': str(sample_user.id),
}
json_resp = admin_request.post(
'organisation_invite.invite_user_to_org',
organisation_id=sample_organisation.id,
_data=data,
_expected_status=400
)
assert json_resp['errors'][0]['message'] == 'email_address Not a valid email address'
assert mocked.call_count == 0
def test_get_all_invited_users_by_service(admin_request, sample_organisation, sample_user):
for i in range(5):
create_invited_org_user(
sample_organisation,
sample_user,
email_address='invited_user_{}@service.gov.uk'.format(i)
)
json_resp = admin_request.get(
'organisation_invite.get_invited_org_users_by_organisation',
organisation_id=sample_organisation.id
)
assert len(json_resp['data']) == 5
for invite in json_resp['data']:
assert invite['organisation'] == str(sample_organisation.id)
assert invite['invited_by'] == str(sample_user.id)
assert invite['id']
def test_get_invited_users_by_service_with_no_invites(admin_request, sample_organisation):
json_resp = admin_request.get(
'organisation_invite.get_invited_org_users_by_organisation',
organisation_id=sample_organisation.id
)
assert len(json_resp['data']) == 0
def test_update_org_invited_user_set_status_to_cancelled(admin_request, sample_invited_org_user):
data = {'status': 'cancelled'}
json_resp = admin_request.post(
'organisation_invite.update_org_invite_status',
organisation_id=sample_invited_org_user.organisation_id,
invited_org_user_id=sample_invited_org_user.id,
_data=data
)
assert json_resp['data']['status'] == 'cancelled'
def test_update_org_invited_user_for_wrong_service_returns_404(admin_request, sample_invited_org_user, fake_uuid):
data = {'status': 'cancelled'}
json_resp = admin_request.post(
'organisation_invite.update_org_invite_status',
organisation_id=fake_uuid,
invited_org_user_id=sample_invited_org_user.id,
_data=data,
_expected_status=404
)
assert json_resp['message'] == 'No result found'
def test_update_org_invited_user_for_invalid_data_returns_400(admin_request, sample_invited_org_user):
data = {'status': 'garbage'}
json_resp = admin_request.post(
'organisation_invite.update_org_invite_status',
organisation_id=sample_invited_org_user.organisation_id,
invited_org_user_id=sample_invited_org_user.id,
_data=data,
_expected_status=400
)
assert len(json_resp['errors']) == 1
assert json_resp['errors'][0]['message'] == 'status garbage is not one of [pending, accepted, cancelled]'

View File

@@ -1,6 +1,8 @@
import uuid
from app.models import Organisation
from app.dao.organisation_dao import dao_add_service_to_organisation
from tests.app.db import create_organisation, create_service
from app.dao.organisation_dao import dao_add_service_to_organisation, dao_add_user_to_organisation
from tests.app.db import create_organisation, create_service, create_user
def test_get_all_organisations(admin_request, notify_db_session):
@@ -270,3 +272,41 @@ def test_rest_get_organisation_services_inactive_services_at_end(
assert response[0]['name'] == service.name
assert response[1]['name'] == inactive_service.name
assert response[2]['name'] == inactive_service_1.name
def test_add_user_to_organisation_returns_added_user(admin_request, sample_organisation, sample_user):
response = admin_request.post(
'organisation.add_user_to_organisation',
organisation_id=str(sample_organisation.id),
user_id=str(sample_user.id),
_expected_status=200
)
assert response['data']['id'] == str(sample_user.id)
assert len(response['data']['organisations']) == 1
assert response['data']['organisations'][0] == str(sample_organisation.id)
def test_add_user_to_organisation_returns_404_if_user_does_not_exist(admin_request, sample_organisation):
admin_request.post(
'organisation.add_user_to_organisation',
organisation_id=str(sample_organisation.id),
user_id=str(uuid.uuid4()),
_expected_status=404
)
def test_get_organisation_users_returns_users_for_organisation(admin_request, sample_organisation):
first = create_user(email='first@invited.com')
second = create_user(email='another@invited.com')
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=first.id)
dao_add_user_to_organisation(organisation_id=sample_organisation.id, user_id=second.id)
response = admin_request.get(
'organisation.get_organisation_users',
organisation_id=sample_organisation.id,
_expected_status=200
)
assert len(response['data']) == 2
assert response['data'][0]['id'] == str(first.id)