mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-04-18 00:01:59 -04:00
305 lines
9.3 KiB
Python
305 lines
9.3 KiB
Python
import ast
|
|
import inspect
|
|
import re
|
|
|
|
import pytest
|
|
from flask import current_app
|
|
|
|
from tests import service_json
|
|
from tests.conftest import (
|
|
ORGANISATION_ID,
|
|
ORGANISATION_TWO_ID,
|
|
SERVICE_ONE_ID,
|
|
SERVICE_TWO_ID,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'user_services, user_organisations, expected_status, organisation_checked',
|
|
(
|
|
([SERVICE_ONE_ID], [], 200, False),
|
|
([SERVICE_ONE_ID, SERVICE_TWO_ID], [], 200, False),
|
|
([], [ORGANISATION_ID], 200, True),
|
|
([SERVICE_ONE_ID], [ORGANISATION_ID], 200, False),
|
|
([], [], 403, True),
|
|
([SERVICE_TWO_ID], [], 403, True),
|
|
([SERVICE_TWO_ID], [ORGANISATION_ID], 200, True),
|
|
([SERVICE_ONE_ID, SERVICE_TWO_ID], [ORGANISATION_ID], 200, False),
|
|
([], [ORGANISATION_TWO_ID], 403, True),
|
|
([], [ORGANISATION_ID, ORGANISATION_TWO_ID], 200, True),
|
|
)
|
|
)
|
|
def test_services_pages_that_org_users_are_allowed_to_see(
|
|
client_request,
|
|
mocker,
|
|
api_user_active,
|
|
mock_get_annual_usage_for_service,
|
|
mock_get_monthly_usage_for_service,
|
|
mock_get_free_sms_fragment_limit,
|
|
mock_get_monthly_notification_stats,
|
|
mock_get_service,
|
|
mock_get_invites_for_service,
|
|
mock_get_users_by_service,
|
|
mock_get_template_folders,
|
|
mock_get_organisation,
|
|
mock_has_jobs,
|
|
user_services,
|
|
user_organisations,
|
|
expected_status,
|
|
organisation_checked,
|
|
):
|
|
api_user_active['services'] = user_services
|
|
api_user_active['organisations'] = user_organisations
|
|
api_user_active['permissions'] = {
|
|
service_id: ['manage_users', 'manage_settings']
|
|
for service_id in user_services
|
|
}
|
|
service = service_json(
|
|
name='SERVICE WITH ORG',
|
|
id_=SERVICE_ONE_ID,
|
|
users=[api_user_active['id']],
|
|
organisation_id=ORGANISATION_ID,
|
|
)
|
|
|
|
mock_get_service = mocker.patch(
|
|
'app.notify_client.service_api_client.service_api_client.get_service',
|
|
return_value={'data': service}
|
|
)
|
|
client_request.login(
|
|
api_user_active,
|
|
service=service if SERVICE_ONE_ID in user_services else None,
|
|
)
|
|
|
|
endpoints = (
|
|
'main.usage',
|
|
'main.manage_users',
|
|
)
|
|
|
|
for endpoint in endpoints:
|
|
client_request.get(
|
|
endpoint,
|
|
service_id=SERVICE_ONE_ID,
|
|
_expected_status=expected_status,
|
|
)
|
|
|
|
assert mock_get_service.called is organisation_checked
|
|
|
|
|
|
def test_service_navigation_for_org_user(
|
|
client_request,
|
|
mocker,
|
|
api_user_active,
|
|
mock_get_annual_usage_for_service,
|
|
mock_get_monthly_usage_for_service,
|
|
mock_get_free_sms_fragment_limit,
|
|
mock_get_monthly_notification_stats,
|
|
mock_get_service,
|
|
mock_get_invites_for_service,
|
|
mock_get_users_by_service,
|
|
mock_get_organisation,
|
|
):
|
|
api_user_active['services'] = []
|
|
api_user_active['organisations'] = [ORGANISATION_ID]
|
|
service = service_json(
|
|
id_=SERVICE_ONE_ID,
|
|
organisation_id=ORGANISATION_ID,
|
|
)
|
|
mocker.patch(
|
|
'app.service_api_client.get_service',
|
|
return_value={'data': service}
|
|
)
|
|
client_request.login(api_user_active, service=service)
|
|
|
|
page = client_request.get(
|
|
'main.usage',
|
|
service_id=SERVICE_ONE_ID,
|
|
)
|
|
assert [
|
|
item.text.strip() for item in page.select('nav.navigation a')
|
|
] == [
|
|
'Usage',
|
|
'Team members',
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize('user_organisations, expected_menu_items, expected_status', [
|
|
(
|
|
[],
|
|
(
|
|
'Send messages',
|
|
'Sent messages',
|
|
'Team members',
|
|
),
|
|
403,
|
|
),
|
|
(
|
|
[ORGANISATION_ID],
|
|
(
|
|
'Send messages',
|
|
'Sent messages',
|
|
'Team members',
|
|
'Usage',
|
|
),
|
|
200,
|
|
),
|
|
])
|
|
def test_service_user_without_manage_service_permission_can_see_usage_page_when_org_user(
|
|
client_request,
|
|
mocker,
|
|
active_caseworking_user,
|
|
mock_has_no_jobs,
|
|
mock_get_annual_usage_for_service,
|
|
mock_get_monthly_usage_for_service,
|
|
mock_get_free_sms_fragment_limit,
|
|
mock_get_monthly_notification_stats,
|
|
mock_get_service,
|
|
mock_get_invites_for_service,
|
|
mock_get_users_by_service,
|
|
mock_get_organisation,
|
|
mock_get_service_templates,
|
|
mock_get_template_folders,
|
|
mock_get_api_keys,
|
|
user_organisations,
|
|
expected_status,
|
|
expected_menu_items,
|
|
):
|
|
active_caseworking_user['services'] = [SERVICE_ONE_ID]
|
|
active_caseworking_user['organisations'] = user_organisations
|
|
service = service_json(
|
|
id_=SERVICE_ONE_ID,
|
|
organisation_id=ORGANISATION_ID,
|
|
)
|
|
mocker.patch(
|
|
'app.service_api_client.get_service',
|
|
return_value={'data': service}
|
|
)
|
|
client_request.login(active_caseworking_user, service=service)
|
|
page = client_request.get(
|
|
'main.choose_template',
|
|
service_id=SERVICE_ONE_ID,
|
|
)
|
|
assert tuple(
|
|
item.text.strip() for item in page.select('nav.navigation a')
|
|
) == expected_menu_items
|
|
|
|
client_request.get(
|
|
'main.usage',
|
|
service_id=SERVICE_ONE_ID,
|
|
_expected_status=expected_status,
|
|
)
|
|
|
|
|
|
def get_name_of_decorator_from_ast_node(node):
|
|
if isinstance(node, ast.Name):
|
|
return str(node.id)
|
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
return get_name_of_decorator_from_ast_node(node.func)
|
|
if isinstance(node, ast.Attribute):
|
|
return node.value.id
|
|
return '{}.{}'.format(node.func.value.id, node.func.attr)
|
|
|
|
|
|
def get_decorators_for_function(function):
|
|
for node in ast.walk(ast.parse(inspect.getsource(function))):
|
|
if isinstance(node, ast.FunctionDef):
|
|
for decorator in node.decorator_list:
|
|
yield get_name_of_decorator_from_ast_node(decorator)
|
|
|
|
|
|
SERVICE_ID_ARGUMENT = 'service_id'
|
|
ORGANISATION_ID_ARGUMENT = 'org_id'
|
|
|
|
|
|
def get_routes_and_decorators(argument_name=None):
|
|
import app.main.views as views
|
|
for module_name, module in inspect.getmembers(views):
|
|
for function_name, function in inspect.getmembers(module):
|
|
if inspect.isfunction(function):
|
|
decorators = list(get_decorators_for_function(function))
|
|
if 'main.route' in decorators and (
|
|
not argument_name or
|
|
argument_name in inspect.signature(function).parameters.keys()
|
|
):
|
|
yield '{}.{}'.format(module_name, function_name), decorators
|
|
|
|
|
|
def format_decorators(decorators, indent=8):
|
|
return '\n'.join(
|
|
'{}@{}'.format(' ' * indent, decorator)
|
|
for decorator in decorators
|
|
)
|
|
|
|
|
|
def test_code_to_extract_decorators_works_with_known_examples():
|
|
assert (
|
|
'templates.choose_template',
|
|
['main.route', 'main.route', 'main.route', 'main.route', 'main.route', 'main.route', 'user_has_permissions'],
|
|
) in list(
|
|
get_routes_and_decorators(SERVICE_ID_ARGUMENT)
|
|
)
|
|
assert (
|
|
'organisations.organisation_dashboard',
|
|
['main.route', 'user_has_permissions'],
|
|
) in list(
|
|
get_routes_and_decorators(ORGANISATION_ID_ARGUMENT)
|
|
)
|
|
assert (
|
|
'platform_admin.platform_admin',
|
|
['main.route', 'user_is_platform_admin'],
|
|
) in list(
|
|
get_routes_and_decorators()
|
|
)
|
|
|
|
|
|
def test_routes_have_permissions_decorators():
|
|
|
|
for endpoint, decorators in (
|
|
list(get_routes_and_decorators(SERVICE_ID_ARGUMENT)) +
|
|
list(get_routes_and_decorators(ORGANISATION_ID_ARGUMENT))
|
|
):
|
|
file, function = endpoint.split('.')
|
|
|
|
assert 'user_is_logged_in' not in decorators, (
|
|
'@user_is_logged_in used on service or organisation specific endpoint\n'
|
|
'Use @user_has_permissions() or @user_is_platform_admin only\n'
|
|
'app/main/views/{}.py::{}\n'
|
|
).format(file, function)
|
|
|
|
if 'user_is_platform_admin' in decorators:
|
|
continue
|
|
|
|
assert 'user_has_permissions' in decorators, (
|
|
'Missing @user_has_permissions decorator\n'
|
|
'Use @user_has_permissions() or @user_is_platform_admin instead\n'
|
|
'app/main/views/{}.py::{}\n'
|
|
).format(file, function)
|
|
|
|
for _endpoint, decorators in get_routes_and_decorators():
|
|
|
|
assert 'login_required' not in decorators, (
|
|
'@login_required found\n'
|
|
'For consistency, use @user_is_logged_in() instead (from app.utils)\n'
|
|
'app/main/views/{}.py::{}\n'
|
|
).format(file, function)
|
|
|
|
if 'user_is_platform_admin' in decorators:
|
|
assert 'user_has_permissions' not in decorators, (
|
|
'@user_has_permissions and @user_is_platform_admin decorating same function\n'
|
|
'You can only use one of these at a time\n'
|
|
'app/main/views/{}.py::{}\n'
|
|
).format(file, function)
|
|
assert 'user_is_logged_in' not in decorators, (
|
|
'@user_is_logged_in used with @user_is_platform_admin\n'
|
|
'Use @user_is_platform_admin only\n'
|
|
'app/main/views/{}.py::{}\n'
|
|
).format(file, function)
|
|
|
|
|
|
def test_routes_require_uuids(client_request):
|
|
for rule in current_app.url_map.iter_rules():
|
|
for param in re.findall('<([^>]*)>', rule.rule):
|
|
if '_id' in param and not param.startswith('uuid:'):
|
|
pytest.fail((
|
|
'Should be <uuid:{}> in {}'
|
|
).format(param, rule.rule))
|