mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-08 12:23:54 -05:00
295 lines
9.2 KiB
Python
295 lines
9.2 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_organizations", "expected_status", "organization_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_organization,
|
|
mock_has_jobs,
|
|
user_services,
|
|
user_organizations,
|
|
expected_status,
|
|
organization_checked,
|
|
):
|
|
api_user_active["services"] = user_services
|
|
api_user_active["organizations"] = user_organizations
|
|
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"]],
|
|
organization_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 organization_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_organization,
|
|
):
|
|
api_user_active["services"] = []
|
|
api_user_active["organizations"] = [ORGANISATION_ID]
|
|
service = service_json(
|
|
id_=SERVICE_ONE_ID,
|
|
organization_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.nav a")] == [
|
|
"Send messages",
|
|
"Usage",
|
|
"Team members",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("user_organizations", "expected_menu_items", "expected_status"),
|
|
[
|
|
(
|
|
[],
|
|
(
|
|
"Send messages",
|
|
"Sent messages",
|
|
),
|
|
403,
|
|
),
|
|
(
|
|
[ORGANISATION_ID],
|
|
(
|
|
"Send messages",
|
|
"Sent messages",
|
|
),
|
|
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_organization,
|
|
mock_get_service_templates,
|
|
mock_get_template_folders,
|
|
mock_get_api_keys,
|
|
user_organizations,
|
|
expected_status,
|
|
expected_menu_items,
|
|
):
|
|
active_caseworking_user["services"] = [SERVICE_ONE_ID]
|
|
active_caseworking_user["organizations"] = user_organizations
|
|
service = service_json(
|
|
id_=SERVICE_ONE_ID,
|
|
organization_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.nav 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 (
|
|
"organizations.organization_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 organization 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))
|