From ceee721d55a456f29471c219d5e409cb9a626b7c Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 13 Aug 2024 12:58:38 +0100 Subject: [PATCH 01/43] [ERSSUP-78354]-[]-[Initial inclusion of apim nhsd_apim_auth_headers pytest fixture]-[JW] --- tests/integration/test_headers.py | 8 +++--- tests/utils.py | 45 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/utils.py diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index b68985327..86b3887b3 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -3,6 +3,7 @@ from requests import Response from data import RenamedHeader from asserts import assert_ok_response +from utils import user_restricated_access _HEADER_AUTHORIZATION = "Authorization" _HEADER_ECHO = "echo" # enable echo target @@ -32,14 +33,13 @@ @pytest.mark.integration_test class TestHeaders: @pytest.mark.asyncio + @user_restricated_access async def test_headers_on_echo_target( - self, authenticate_user, service_url, referring_clinician, asid + self, nhsd_apim_auth_headers, service_url, referring_clinician, asid ): - access_code = await authenticate_user(referring_clinician) - client_request_headers = { + client_request_headers = nhsd_apim_auth_headers + { _HEADER_ECHO: "", # enable echo target - _HEADER_AUTHORIZATION: "Bearer " + access_code, _HEADER_REQUEST_ID: "DUMMY-VALUE", RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID, RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID, diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 000000000..38f00af89 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,45 @@ +import pytest + +from functools import wraps + +from data import Actor +from conftest import get_env + + +def _calculate_default_user() -> Actor: + return ( + Actor.RC_DEV + if get_env("ENVIRONMENT") == "internal-dev" + and "ft" in get_env("SERVICE_BASE_PATH") + else Actor.RC + ) + + +_DEFAULT_USER: Actor = _calculate_default_user() + + +def user_restricated_access(user: Actor): + """ + Decorator indicating that a given function should be authenticated with User Restricted access with a supplied user. + This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the supplied user. + + :param User: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. + """ + + _user: Actor = user if user else _DEFAULT_USER + + def decorator(func): + @pytest.mark.nhsd_apim_authorization( + { + "access": "healthcare_worker", + "level": "3", + "login_form": {"username": _user.user_id}, + } + ) + @wraps(func) + def wrapper(*args, **kwargs): + return func(args, kwargs) + + return wrapper + + return decorator From ea3f73965cee306dc22b151205dd568451c41473 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 13 Aug 2024 14:28:18 +0100 Subject: [PATCH 02/43] [ERSSUP-78354]-[]-[Allowed for non-parameterised usage of user_restricted_access decorator]-[JW] --- tests/{utils.py => decorators.py} | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename tests/{utils.py => decorators.py} (77%) diff --git a/tests/utils.py b/tests/decorators.py similarity index 77% rename from tests/utils.py rename to tests/decorators.py index 38f00af89..bc57343b4 100644 --- a/tests/utils.py +++ b/tests/decorators.py @@ -18,7 +18,7 @@ def _calculate_default_user() -> Actor: _DEFAULT_USER: Actor = _calculate_default_user() -def user_restricated_access(user: Actor): +def user_restricated_access(user: Actor = None): """ Decorator indicating that a given function should be authenticated with User Restricted access with a supplied user. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the supplied user. @@ -26,7 +26,9 @@ def user_restricated_access(user: Actor): :param User: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. """ - _user: Actor = user if user else _DEFAULT_USER + # Need to check that the provided user both exists and is of type Actor as non-parameterised decorators in python are pass with the wrapped + # function as their first argument. + _user: Actor = user if type(user) == Actor else _DEFAULT_USER def decorator(func): @pytest.mark.nhsd_apim_authorization( @@ -38,7 +40,7 @@ def decorator(func): ) @wraps(func) def wrapper(*args, **kwargs): - return func(args, kwargs) + return func(*args, **kwargs) return wrapper From 52882515ca101c46af217324a395cc3a143cc257 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 13 Aug 2024 15:47:46 +0100 Subject: [PATCH 03/43] [ERSSUP-78354]-[]-[Fixed import within test_headers.py]-[JW] --- tests/integration/test_headers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index 86b3887b3..5f70cb17e 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -3,7 +3,7 @@ from requests import Response from data import RenamedHeader from asserts import assert_ok_response -from utils import user_restricated_access +from decorators import user_restricated_access _HEADER_AUTHORIZATION = "Authorization" _HEADER_ECHO = "echo" # enable echo target From 0cd0ec468622c38ce20a3272372180cd66a3662d Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 13 Aug 2024 16:48:41 +0100 Subject: [PATCH 04/43] [ERSSUP-78354]-[]-[Added async support to decorator]-[JW] --- tests/decorators.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/tests/decorators.py b/tests/decorators.py index bc57343b4..cb99bc47a 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -1,6 +1,7 @@ import pytest from functools import wraps +from asyncio import iscoroutinefunction from data import Actor from conftest import get_env @@ -31,17 +32,32 @@ def user_restricated_access(user: Actor = None): _user: Actor = user if type(user) == Actor else _DEFAULT_USER def decorator(func): - @pytest.mark.nhsd_apim_authorization( - { - "access": "healthcare_worker", - "level": "3", - "login_form": {"username": _user.user_id}, - } - ) - @wraps(func) - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - return wrapper + # if the decorated function is async, return an async function. + if iscoroutinefunction(func): + + @pytest.mark.nhsd_apim_authorization( + { + "access": "healthcare_worker", + "level": "3", + "login_form": {"username": _user.user_id}, + } + ) + async def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + else: + + @pytest.mark.nhsd_apim_authorization( + { + "access": "healthcare_worker", + "level": "3", + "login_form": {"username": _user.user_id}, + } + ) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + # use functools.wraps to maintain the function details of the decorated function on the result. + return wraps(func)(wrapper) return decorator From b6308a2ff5db011163e83aaded1da11bae59858b Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 13 Aug 2024 17:47:51 +0100 Subject: [PATCH 05/43] [ERSSUP-78354]-[]-[Added await to async user_restricted_access decorator]-[JW] --- tests/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/decorators.py b/tests/decorators.py index cb99bc47a..ee8ed4988 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -43,7 +43,7 @@ def decorator(func): } ) async def wrapper(*args, **kwargs): - return func(*args, **kwargs) + return await func(*args, **kwargs) else: From 0dfe2fcd0853ecd602ecf20eee4ac4b47bd6ebf4 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Wed, 14 Aug 2024 09:41:04 +0100 Subject: [PATCH 06/43] [ERSSUP-78354]-[]-[Removed asyncio annotation from test_headers test. Also added failing assertion for testing.]-[JW] --- tests/decorators.py | 2 +- tests/integration/test_headers.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/decorators.py b/tests/decorators.py index ee8ed4988..a76d74a85 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -27,7 +27,7 @@ def user_restricated_access(user: Actor = None): :param User: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. """ - # Need to check that the provided user both exists and is of type Actor as non-parameterised decorators in python are pass with the wrapped + # Need to check that the provided user both exists and is of type Actor as non-parameterised decorators in python are passed the wrapped # function as their first argument. _user: Actor = user if type(user) == Actor else _DEFAULT_USER diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index 5f70cb17e..47ecf4184 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -32,7 +32,7 @@ @pytest.mark.integration_test class TestHeaders: - @pytest.mark.asyncio + @user_restricated_access async def test_headers_on_echo_target( self, nhsd_apim_auth_headers, service_url, referring_clinician, asid @@ -56,6 +56,8 @@ async def test_headers_on_echo_target( response, service_url, referring_clinician, asid, _EXPECTED_FILENAME ) + assert 1 == 2 + @pytest.mark.asyncio @pytest.mark.parametrize( "endpoint_url,is_r4", From 241c34377e0cd0b29832fbc14426e245f8e99966 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Wed, 21 Aug 2024 14:47:18 +0100 Subject: [PATCH 07/43] [ERSSUP-78354]-[]-[Re-added asyncio mark to test]-[JW] --- tests/integration/test_headers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index 47ecf4184..2913e569d 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -34,6 +34,7 @@ class TestHeaders: @user_restricated_access + @pytest.mark.asyncio async def test_headers_on_echo_target( self, nhsd_apim_auth_headers, service_url, referring_clinician, asid ): From c676a7cad32d55426a773091ba13baf9fd123010 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Wed, 21 Aug 2024 17:04:22 +0100 Subject: [PATCH 08/43] [ERSSUP-78354]-[]-[Reordered asyncio mark]-[JW] --- tests/integration/test_headers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index 2913e569d..cfde1b0e5 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -33,8 +33,8 @@ @pytest.mark.integration_test class TestHeaders: - @user_restricated_access @pytest.mark.asyncio + @user_restricated_access async def test_headers_on_echo_target( self, nhsd_apim_auth_headers, service_url, referring_clinician, asid ): From bef637cbae8c05097cda2b2ab0f35212eeea1aa6 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 22 Aug 2024 13:42:13 +0100 Subject: [PATCH 09/43] [ERSSUP-78354]-[]-[Updated decorator to fully support no-parameter usage]-[JW] --- tests/decorators.py | 58 +++++++++++++------------------ tests/integration/test_headers.py | 2 +- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/tests/decorators.py b/tests/decorators.py index a76d74a85..85ce65c23 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -1,6 +1,7 @@ import pytest from functools import wraps +from typing import Callable from asyncio import iscoroutinefunction from data import Actor @@ -19,7 +20,7 @@ def _calculate_default_user() -> Actor: _DEFAULT_USER: Actor = _calculate_default_user() -def user_restricated_access(user: Actor = None): +def user_restricated_access(function: Callable = None, user: Actor = _DEFAULT_USER): """ Decorator indicating that a given function should be authenticated with User Restricted access with a supplied user. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the supplied user. @@ -27,37 +28,26 @@ def user_restricated_access(user: Actor = None): :param User: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. """ - # Need to check that the provided user both exists and is of type Actor as non-parameterised decorators in python are passed the wrapped - # function as their first argument. - _user: Actor = user if type(user) == Actor else _DEFAULT_USER - def decorator(func): - # if the decorated function is async, return an async function. - if iscoroutinefunction(func): - - @pytest.mark.nhsd_apim_authorization( - { - "access": "healthcare_worker", - "level": "3", - "login_form": {"username": _user.user_id}, - } - ) - async def wrapper(*args, **kwargs): - return await func(*args, **kwargs) - - else: - - @pytest.mark.nhsd_apim_authorization( - { - "access": "healthcare_worker", - "level": "3", - "login_form": {"username": _user.user_id}, - } - ) - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - # use functools.wraps to maintain the function details of the decorated function on the result. - return wraps(func)(wrapper) - - return decorator + kwargs = { + "access": "healthcare_worker", + "level": "3", + "login_form": {"username": user.user_id}, + } + + @pytest.mark.nhsd_apim_authorization(kwargs) + @wraps(func) + async def async_wrapper(*args, **kwargs): + return await func(*args, **kwargs) + + @pytest.mark.nhsd_apim_authorization(kwargs) + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + # if the decorated function is async, return an async function, else return a synchronous version. + return async_wrapper if iscoroutinefunction(func) else wrapper + + # If a function is provided the decorator is being called without any parameters and we need to call our decorator supplying this as the function. + # Otherwise the decorator is being called with arguments and can be returned directly. + return decorator(function) if function else decorator diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index cfde1b0e5..bf44ad150 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -1,7 +1,7 @@ import pytest import requests from requests import Response -from data import RenamedHeader +from data import RenamedHeader, Actor from asserts import assert_ok_response from decorators import user_restricated_access From 412932f7a443bc86fd2976b652e9f3148690e6df Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 22 Aug 2024 15:20:39 +0100 Subject: [PATCH 10/43] [ERSSUP-78354]-[]-[Added API_NAME environment variable to azure pipeline runs]-[JW] --- azure/templates/ers-integration-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index a2b2c34fa..8b3fb67cf 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -7,6 +7,7 @@ steps: export OAUTH_PROXY="oauth2-mock" export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" + export API_NAME="$(SERVICE_NAME)" poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml displayName: Run eRS integration tests From 72aa2721202fa00d0d48ad7b809c95fdfc87e1be Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 22 Aug 2024 16:11:44 +0100 Subject: [PATCH 11/43] [ERSSUP-78354]-[]-[Swapped api name environment variable to pytest parameter]-[JW] --- azure/templates/ers-integration-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index 8b3fb67cf..5cf19855c 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -7,9 +7,8 @@ steps: export OAUTH_PROXY="oauth2-mock" export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - export API_NAME="$(SERVICE_NAME)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 From 2fc02477698f4fd471326bc286c75d2a1df7b4cd Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 22 Aug 2024 16:39:54 +0100 Subject: [PATCH 12/43] [ERSSUP-78354]-[]-[Updated aal parameter to 'aal3' from '3']-[JW] --- tests/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/decorators.py b/tests/decorators.py index 85ce65c23..0d38855a4 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -31,7 +31,7 @@ def user_restricated_access(function: Callable = None, user: Actor = _DEFAULT_US def decorator(func): kwargs = { "access": "healthcare_worker", - "level": "3", + "level": "aal3", "login_form": {"username": user.user_id}, } From b5e65471e6808249cef71df79b9d24c02b5f1ae1 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 10:48:13 +0100 Subject: [PATCH 13/43] [ERSSUP-78354]-[]-[Added proxy name to integration test template]-[JW] --- azure/templates/ers-integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index 5cf19855c..198cbc704 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -8,7 +8,7 @@ steps: export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) --proxy-name=$(SERVICE_BASE_PATH) displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 From 90f8839239e0b7512afe6593f71ccdb4c0a1d220 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 12:06:07 +0100 Subject: [PATCH 14/43] [ERSSUP-78354]-[]-[Swapped proxy-name parameter to use FULLY_QUALIFIED_SERVICE_NAME]-[JW] --- azure/templates/ers-integration-tests.yml | 2 +- tests/decorators.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index 198cbc704..78dd549d9 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -8,7 +8,7 @@ steps: export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) --proxy-name=$(SERVICE_BASE_PATH) + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME) displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 diff --git a/tests/decorators.py b/tests/decorators.py index 0d38855a4..d1a5b2a7b 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -29,18 +29,18 @@ def user_restricated_access(function: Callable = None, user: Actor = _DEFAULT_US """ def decorator(func): - kwargs = { + auth_args = { "access": "healthcare_worker", "level": "aal3", "login_form": {"username": user.user_id}, } - @pytest.mark.nhsd_apim_authorization(kwargs) + @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) async def async_wrapper(*args, **kwargs): return await func(*args, **kwargs) - @pytest.mark.nhsd_apim_authorization(kwargs) + @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) From b6785fa8d1fe5d25ae22ba3c86994b1fd2bb1c0b Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 14:55:29 +0100 Subject: [PATCH 15/43] [ERSSUP-78354]-[]-[Further test parameter tweaks]-[JW] --- azure/templates/ers-integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index 78dd549d9..5fcfa32dd 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -8,7 +8,7 @@ steps: export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=$(SERVICE_NAME) --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME) + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=e-referrals-service-api --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME) displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 From 3cce169821a26982e9883ecbf3f8496a11f81ef1 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 15:38:42 +0100 Subject: [PATCH 16/43] [ERSSUP-78354]-[]-[Added healthcare worker suffix to proxy name parameter]-[JW] --- azure/templates/ers-integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index 5fcfa32dd..bdb6d2a0f 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -8,7 +8,7 @@ steps: export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=e-referrals-service-api --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME) + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=e-referrals-service-api --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME)-healthcare-worker displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 From ec9000bf54e76e6b74c5282d207ce1a79923b6e7 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 16:40:15 +0100 Subject: [PATCH 17/43] [ERSSUP-78354]-[]-[More test parameter changes]-[JW] --- azure/templates/ers-integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/templates/ers-integration-tests.yml b/azure/templates/ers-integration-tests.yml index bdb6d2a0f..5fcfa32dd 100644 --- a/azure/templates/ers-integration-tests.yml +++ b/azure/templates/ers-integration-tests.yml @@ -8,7 +8,7 @@ steps: export OAUTH_BASE_URI="https://$(ENVIRONMENT).api.service.nhs.uk" export JWT_PRIVATE_KEY_ABSOLUTE_PATH="$(Pipeline.Workspace)/secrets/$(JWT_TESTING_PRIVATE_KEY)" - poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=e-referrals-service-api --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME)-healthcare-worker + poetry run pytest -v tests/integration --junitxml=tests/ers-test-integration-report.xml --api-name=e-referrals-service-api --proxy-name=$(FULLY_QUALIFIED_SERVICE_NAME) displayName: Run eRS integration tests workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)" - task: PublishTestResults@2 From 565f3d135df976698022ac6b8a8bdc4645541dbd Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 23 Aug 2024 17:26:26 +0100 Subject: [PATCH 18/43] [ERSSUP-78354]-[]-[Added new cis2 aal3 scope to user restricted proxies-[JW] --- manifest_template.yml | 2 +- proxies/live/apiproxy/policies/OAuthV2.VerifyAccessToken.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest_template.yml b/manifest_template.yml index fa233f0bb..d911b007a 100644 --- a/manifest_template.yml +++ b/manifest_template.yml @@ -79,7 +79,7 @@ ACCESS_MODES: - name: healthcare-worker nameSuffix: -healthcare-worker displayName: Healthcare Worker - scopes: ['urn:nhsd:apim:user-nhs-id:aal3:e-referrals-service-api'] + scopes: ['urn:nhsd:apim:user-nhs-id:aal3:e-referrals-service-api', 'urn:nhsd:apim:user-nhs-cis2:aal3:e-referrals-service-api'] requireCallbackUrl: true description: User restricted diff --git a/proxies/live/apiproxy/policies/OAuthV2.VerifyAccessToken.xml b/proxies/live/apiproxy/policies/OAuthV2.VerifyAccessToken.xml index bfe7f192c..c2db2a7f4 100644 --- a/proxies/live/apiproxy/policies/OAuthV2.VerifyAccessToken.xml +++ b/proxies/live/apiproxy/policies/OAuthV2.VerifyAccessToken.xml @@ -1,4 +1,4 @@ - VerifyAccessToken - urn:nhsd:apim:app:level3:e-referrals-service-api urn:nhsd:apim:user-nhs-id:aal3:e-referrals-service-api + VerifyAccessToken + urn:nhsd:apim:app:level3:e-referrals-service-api urn:nhsd:apim:user-nhs-id:aal3:e-referrals-service-api urn:nhsd:apim:user-nhs-cis2:aal3:e-referrals-service-api From 3e2eb42fd5ab06de2c471d7aafcf2404a565375f Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 10:08:29 +0100 Subject: [PATCH 19/43] [ERSSUP-78354]-[]-[Fixed dictionary concatination]-[JW] --- tests/integration/test_headers.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index bf44ad150..dcca2e164 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -39,17 +39,20 @@ async def test_headers_on_echo_target( self, nhsd_apim_auth_headers, service_url, referring_clinician, asid ): - client_request_headers = nhsd_apim_auth_headers + { - _HEADER_ECHO: "", # enable echo target - _HEADER_REQUEST_ID: "DUMMY-VALUE", - RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID, - RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID, - RenamedHeader.BUSINESS_FUNCTION.original: referring_clinician.business_function, - RenamedHeader.ODS_CODE.original: referring_clinician.org_code, - RenamedHeader.FILENAME.original: _EXPECTED_FILENAME, - RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG, - RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID, - } + client_request_headers = dict( + nhsd_apim_auth_headers, + **{ + _HEADER_ECHO: "", # enable echo target + _HEADER_REQUEST_ID: "DUMMY-VALUE", + RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID, + RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID, + RenamedHeader.BUSINESS_FUNCTION.original: referring_clinician.business_function, + RenamedHeader.ODS_CODE.original: referring_clinician.org_code, + RenamedHeader.FILENAME.original: _EXPECTED_FILENAME, + RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG, + RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID, + }, + ) # Make the API call response = requests.get(service_url, headers=client_request_headers) @@ -57,8 +60,6 @@ async def test_headers_on_echo_target( response, service_url, referring_clinician, asid, _EXPECTED_FILENAME ) - assert 1 == 2 - @pytest.mark.asyncio @pytest.mark.parametrize( "endpoint_url,is_r4", From 2515b6fd5c88fa096c26b4092328e167a36494ed Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 12:28:45 +0100 Subject: [PATCH 20/43] [ERSSUP-78354]-[]-[Override for _create_test_app to append ASID to attribute list]-[JW] --- tests/conftest.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c1900ba51..f2ca818ac 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from uuid import uuid4 +import pytest_nhsd_apim.apigee_edge from pytest_nhsd_apim.identity_service import ( AuthorizationCodeConfig, AuthorizationCodeAuthenticator, @@ -304,3 +305,45 @@ def app_restricted_access_code( token_response = authenticator.get_token() assert "access_token" in token_response return token_response["access_token"] + + +@pytest.fixture(scope="session") +def _create_test_app( + _apigee_app_base_url, + _apigee_app_base_url_no_dev, + _apigee_edge_session, + jwt_public_key_url, + nhsd_apim_pre_create_app, + _test_app_id, + client, + asid, +): + """ + This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to update any created app with the ASID currently being utilised. + This is required as the private fixture does not publicise the attributes it associates with any created application, so instead we need to modify the created application to + append the ASID required. + """ + + created_app = pytest_nhsd_apim.apigee_edge._create_test_app( + _apigee_app_base_url, + _apigee_app_base_url_no_dev, + _apigee_edge_session, + jwt_public_key_url, + nhsd_apim_pre_create_app, + _test_app_id, + ) + + api = DeveloperAppsAPI(client=client) + + # Update the attributes of the created application to add in the ASID attribute. + modified_attributes = dict(created_app["attributes"][0], **{"asid": asid}) + + created_app["attributes"] = [modified_attributes] + + api.put_app_by_name( + email="apm-testing-internal-dev@nhs.net", + app_name=created_app["name"], + body=created_app, + ) + + return created_app From bb0f8705e1139b7f9e3cc8ef4689975c59e55bd9 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 14:15:57 +0100 Subject: [PATCH 21/43] [ERSSUP-78354]-[]-[Altered create_test_app to no longer rely on client fixture]-[JW] --- tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f2ca818ac..d153d32ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -315,7 +315,6 @@ def _create_test_app( jwt_public_key_url, nhsd_apim_pre_create_app, _test_app_id, - client, asid, ): """ @@ -333,7 +332,7 @@ def _create_test_app( _test_app_id, ) - api = DeveloperAppsAPI(client=client) + api = DeveloperAppsAPI(client=client()) # Update the attributes of the created application to add in the ASID attribute. modified_attributes = dict(created_app["attributes"][0], **{"asid": asid}) From d2ab1a97527a74e90209ae007fc4001b160c590a Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 15:07:28 +0100 Subject: [PATCH 22/43] [ERSSUP-78354]-[]-[Updated _create_test_app to rely on fixture rather than calling method directly]-[JW] --- tests/conftest.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d153d32ee..fdc2d2d66 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,6 @@ from uuid import uuid4 -import pytest_nhsd_apim.apigee_edge from pytest_nhsd_apim.identity_service import ( AuthorizationCodeConfig, AuthorizationCodeAuthenticator, @@ -22,6 +21,11 @@ from data import Actor +def _create_apigee_client(): + config = ApigeeNonProdCredentials() + return ApigeeClient(config=config) + + def get_env(variable_name: str) -> str: """Returns an environment variable""" try: @@ -112,8 +116,7 @@ def app_restricted_business_function(request): @pytest.fixture() def client(): - config = ApigeeNonProdCredentials() - return ApigeeClient(config=config) + return _create_apigee_client() @pytest_asyncio.fixture @@ -309,12 +312,7 @@ def app_restricted_access_code( @pytest.fixture(scope="session") def _create_test_app( - _apigee_app_base_url, - _apigee_app_base_url_no_dev, - _apigee_edge_session, - jwt_public_key_url, - nhsd_apim_pre_create_app, - _test_app_id, + _create_test_app, asid, ): """ @@ -323,16 +321,9 @@ def _create_test_app( append the ASID required. """ - created_app = pytest_nhsd_apim.apigee_edge._create_test_app( - _apigee_app_base_url, - _apigee_app_base_url_no_dev, - _apigee_edge_session, - jwt_public_key_url, - nhsd_apim_pre_create_app, - _test_app_id, - ) + created_app = _create_test_app - api = DeveloperAppsAPI(client=client()) + api = DeveloperAppsAPI(client=_create_apigee_client()) # Update the attributes of the created application to add in the ASID attribute. modified_attributes = dict(created_app["attributes"][0], **{"asid": asid}) From 67b9ead9090a6269c40babbe509531b7f2e38aee Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 17:24:14 +0100 Subject: [PATCH 23/43] [ERSSUP-78354]-[]-[Added debugging around create test add fixture]-[JW] --- tests/conftest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fdc2d2d66..e42d57011 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import os import pytest import pytest_asyncio +import warnings from uuid import uuid4 @@ -321,7 +322,10 @@ def _create_test_app( append the ASID required. """ + warnings.warn("invoking custom create test app.") + created_app = _create_test_app + warnings.warn(f"created app={created_app}") api = DeveloperAppsAPI(client=_create_apigee_client()) @@ -330,10 +334,10 @@ def _create_test_app( created_app["attributes"] = [modified_attributes] - api.put_app_by_name( + warnings.warn(f"updated app={created_app}") + + return api.put_app_by_name( email="apm-testing-internal-dev@nhs.net", app_name=created_app["name"], body=created_app, ) - - return created_app From 5626d8bc459b0e312f3c9292d06d1fb5c05959be Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 18:14:33 +0100 Subject: [PATCH 24/43] [ERSSUP-78354]-[]-[Updated create test app to supply a list of dicts]-[JW] --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e42d57011..42d286d4e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -330,9 +330,9 @@ def _create_test_app( api = DeveloperAppsAPI(client=_create_apigee_client()) # Update the attributes of the created application to add in the ASID attribute. - modified_attributes = dict(created_app["attributes"][0], **{"asid": asid}) + modified_attributes = created_app["attributes"] + [{"asid": asid}] - created_app["attributes"] = [modified_attributes] + created_app["attributes"] = modified_attributes warnings.warn(f"updated app={created_app}") From 75a1187d9bf3865a1c606470060f56a9c7dc1e52 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 27 Aug 2024 18:46:15 +0100 Subject: [PATCH 25/43] [ERSSUP-78354]-[]-[Fixed attributes for create_test_app fixture]-[JW] --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 42d286d4e..452c7ec5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -330,7 +330,7 @@ def _create_test_app( api = DeveloperAppsAPI(client=_create_apigee_client()) # Update the attributes of the created application to add in the ASID attribute. - modified_attributes = created_app["attributes"] + [{"asid": asid}] + modified_attributes = created_app["attributes"] + [{"key": "asid", "value": asid}] created_app["attributes"] = modified_attributes From fdaaf9c079ccfb598d7b84ae12b80104ba7d7d44 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Wed, 28 Aug 2024 11:08:12 +0100 Subject: [PATCH 26/43] [ERSSUP-78354]-[]-[Fixed ASID attribute name]-[JW] --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 452c7ec5a..c199f71b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -330,7 +330,7 @@ def _create_test_app( api = DeveloperAppsAPI(client=_create_apigee_client()) # Update the attributes of the created application to add in the ASID attribute. - modified_attributes = created_app["attributes"] + [{"key": "asid", "value": asid}] + modified_attributes = created_app["attributes"] + [{"name": "asid", "value": asid}] created_app["attributes"] = modified_attributes From 85a9fd4be775b6c7262b921c3518ab6d35f6457c Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Wed, 28 Aug 2024 18:35:16 +0100 Subject: [PATCH 27/43] [ERSSUP-78354]-[]-[Added initial app-restricted authentication support]-[JW] --- tests/conftest.py | 8 +- tests/decorators.py | 49 +++++++- tests/integration/test_app_restricted.py | 19 ++-- tests/integration/test_headers.py | 2 +- tests/state.py | 139 +++++++++++++++++++++++ 5 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 tests/state.py diff --git a/tests/conftest.py b/tests/conftest.py index c199f71b5..c6af7b2a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,7 @@ from data import Actor +from state import current_authentication_context def _create_apigee_client(): @@ -330,7 +331,12 @@ def _create_test_app( api = DeveloperAppsAPI(client=_create_apigee_client()) # Update the attributes of the created application to add in the ASID attribute. - modified_attributes = created_app["attributes"] + [{"name": "asid", "value": asid}] + additional_attribtues = [ + {"name": key, "value": value} + for key, value in current_authentication_context().app_attributes + ] + + modified_attributes = created_app["attributes"] + additional_attribtues created_app["attributes"] = modified_attributes diff --git a/tests/decorators.py b/tests/decorators.py index d1a5b2a7b..0aae20d50 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -1,5 +1,7 @@ import pytest +import state + from functools import wraps from typing import Callable from asyncio import iscoroutinefunction @@ -25,7 +27,7 @@ def user_restricated_access(function: Callable = None, user: Actor = _DEFAULT_US Decorator indicating that a given function should be authenticated with User Restricted access with a supplied user. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the supplied user. - :param User: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. + :param user: An Actor indicating the user to authenticate as. If no user is provided _DEFAULT_USER will be used instead. """ def decorator(func): @@ -38,12 +40,18 @@ def decorator(func): @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) async def async_wrapper(*args, **kwargs): - return await func(*args, **kwargs) + with state.authentication_context( + state.AuthenticationConfig.user_restricted_config() + ): + return await func(*args, **kwargs) @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) def wrapper(*args, **kwargs): - return func(*args, **kwargs) + with state.authentication_context( + state.AuthenticationConfig.user_restricted_config() + ): + return func(*args, **kwargs) # if the decorated function is async, return an async function, else return a synchronous version. return async_wrapper if iscoroutinefunction(func) else wrapper @@ -51,3 +59,38 @@ def wrapper(*args, **kwargs): # If a function is provided the decorator is being called without any parameters and we need to call our decorator supplying this as the function. # Otherwise the decorator is being called with arguments and can be returned directly. return decorator(function) if function else decorator + + +def app_restricted_access(types: list[state.ApplicationRestrictedType]): + """ + Decorator indicating that the given function should be authenticated with Application Restricted access with a list of set types. + This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the default application. + + :param types: A list of ApplicationRestrictedType values indicating the types of application restricted access the decorated function should be authenticated with. + """ + + def decorator(func): + auth_args = {"access": "application", "level": "level3"} + + @pytest.mark.nhsd_apim_authorization(auth_args) + @wraps(func) + async def async_wrapper(*args, **kwargs): + for type in types: + with state.authentication_context( + state.AuthenticationConfig.application_restricted_config(type=type) + ): + return await func(*args, **kwargs) + + @pytest.mark.nhsd_apim_authorization(auth_args) + @wraps(func) + def wrapper(*args, **kwargs): + for type in types: + with state.authentication_context( + state.AuthenticationConfig.application_restricted_config(type=type) + ): + return func(*args, **kwargs) + + # if the decorated function is async, return an async function as a decorator, otherwise use a synchronous decorator. + return async_wrapper if iscoroutinefunction(func) else wrapper + + return decorator diff --git a/tests/integration/test_app_restricted.py b/tests/integration/test_app_restricted.py index 620aaecdb..e410a0241 100644 --- a/tests/integration/test_app_restricted.py +++ b/tests/integration/test_app_restricted.py @@ -3,6 +3,8 @@ from requests import Response from data import RenamedHeader from asserts import assert_ok_response, assert_error_response +from decorators import app_restricted_access +from state import ApplicationRestrictedType _HEADER_AUTHORIZATION = "Authorization" _HEADER_ECHO = "echo" # enable echo target @@ -86,21 +88,24 @@ def test_authorised_application_header_rejected( assert_error_response(response, _EXPECTED_CORRELATION_ID, 403) + @app_restricted_access(types=list(ApplicationRestrictedType)) def test_headers_on_echo_target( self, - app_restricted_access_code, + nhsd_apim_auth_headers, service_url, asid, app_restricted_ods_code, app_restricted_user_id, app_restricted_business_function, ): - client_request_headers = { - _HEADER_ECHO: "", # enable echo target - _HEADER_AUTHORIZATION: "Bearer " + app_restricted_access_code, - RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID, - _HEADER_REQUEST_ID: "DUMMY", # this must be less than 10 characters - } + client_request_headers = dict( + nhsd_apim_auth_headers, + **{ + _HEADER_ECHO: "", # enable echo target + RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID, + _HEADER_REQUEST_ID: "DUMMY", # this must be less than 10 characters + }, + ) # Make the API call response = requests.get(service_url, headers=client_request_headers) diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index dcca2e164..8103454cb 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -1,7 +1,7 @@ import pytest import requests from requests import Response -from data import RenamedHeader, Actor +from data import RenamedHeader from asserts import assert_ok_response from decorators import user_restricated_access diff --git a/tests/state.py b/tests/state.py new file mode 100644 index 000000000..5b002b80b --- /dev/null +++ b/tests/state.py @@ -0,0 +1,139 @@ +from typing import Dict +from enum import Enum +from contextlib import contextmanager + +from conftest import get_env + + +def _is_mocked_environment() -> bool: + return get_env("ENVIRONMENT") == "internal-dev" and "ft" in get_env( + "SERVICE_BASE_PATH" + ) + + +def _calculate_default_asid() -> str: + return ( + get_env("ERS_MOCK_ASID") + if _is_mocked_environment() + else get_env("ERS_TEST_ASID") + ) + + +def _calculate_default_app_restricted_ods_code() -> str: + return "R68" if _is_mocked_environment() else "RCD" + + +def _calculate_default_app_restricted_user_id() -> str: + return "000000000101" if _is_mocked_environment() else "555032000100" + + +_DEFAULT_ASID: str = _calculate_default_asid() +_DEFAULT_APP_RESTRICTED_ODS_CODE: str = _calculate_default_app_restricted_ods_code() +_DEFAULT_APP_RESTRICTED_USER_ID: str = _calculate_default_app_restricted_user_id() + + +class ApplicationRestrictedType(Enum): + REFERRER = "REFERRER_APPLICATION_RESTRICTED" + PROVIDER = "PROVIDER_APPLICATION_RESTRICTED" + + +class AuthenticationConfig: + """ + Defines some configuration for use as part of authentication. + """ + + def __init__(self, app_attributes: Dict[str, str]): + self._app_attributes = app_attributes + + @property + def app_attributes(self) -> dict[str, str]: + """ + Any attributes that should be included against the application used for authentication within Apigee. + """ + + # return a new dict to hide the internal parameters so they cannot be modified via the public interface. + return dict(self._app_attributes) + + @staticmethod + def user_restricted_config(asid: str = _DEFAULT_ASID) -> "AuthenticationConfig": + """ + Create a AuthenticationConfig object detaililng that authentication should be completed using User Restricted based authentication. + + :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to _DEFAULT_ASID. + :returns: a new AuthenticationConfig object configured for User Restricted access. + """ + + return AuthenticationConfig(app_attributes={"asid": asid}) + + @staticmethod + def application_restricted_config( + type: ApplicationRestrictedType, + user_id: str = _DEFAULT_APP_RESTRICTED_USER_ID, + ods_code: str = _DEFAULT_APP_RESTRICTED_ODS_CODE, + asid: str = _DEFAULT_ASID, + ) -> "AuthenticationConfig": + """ + Create a AuthenticationConfig object detailing that authentication should be completed using Application Restricted based authentication. + + :param type: a ApplicationRestrictedType detailing the type of Application Restricted access to be utilised. + :param user_id: the user ID to be associated with the app used for authentication. Defaults to _DEFAULT_APP_RESTRICTED_USER_ID. + :param ods_code: the ODS code to be associated with the app used for authentication. Defaults to _DEFAULT_APP_RESTRICTED_ODS_CODE. + :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to _DEFAULT_ASID. + + :returns: a new AuthenticationConfig object configured for Application Restricted access. + """ + + if type not in ApplicationRestrictedType: + raise ValueError( + f"Provided type not supported. Supported_business_functions={list(ApplicationRestrictedType)} provided_type={type}" + ) + + return AuthenticationConfig( + app_attributes={ + "asid": asid, + "app-restricted-business-function": type.value, + "app-restricted-ods-code": ods_code, + "app-restricted-user-id": user_id, + } + ) + + +_AUTHENTICATION_CONFIG_INSTANCE = None + + +@contextmanager +def authentication_context(config: AuthenticationConfig) -> AuthenticationConfig: + """ + Stores the supplied AuthenticationConfig so it can be used by other fixtures/test utilities. + Returns the provided AuthenticationConfig within a contextmanager which will handle the clear down of clearing the provided configuration. + + Standard usage: + + config = AuthenticationConfig.user_restricted_config() + + with authentication_context(config): + # test logic.... + + :param config: An AuthenticationConfig object detailing the configuration that should be stored for the current test context. + :returns: the provided config wrapped as a contextmanager. + """ + _AUTHENTICATION_CONFIG_INSTANCE = config + try: + yield _AUTHENTICATION_CONFIG_INSTANCE + finally: + _AUTHENTICATION_CONFIG_INSTANCE = None + + +def current_authentication_context() -> AuthenticationConfig: + """ + Retrieve the AuthenticationConfig instance being used within the current test context. + + :returns: the current AuthenticationConfig + :throws ValueError: if no AuthenticationConfig has been configured for the current test context. + """ + if not _AUTHENTICATION_CONFIG_INSTANCE: + raise ValueError( + "No authentication configuration is currently instantiated. Has with_authentication_context been called?" + ) + + return _AUTHENTICATION_CONFIG_INSTANCE From 21b42311cfa9c82928f19d25e83342747aa3366f Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 09:51:31 +0100 Subject: [PATCH 28/43] [ERSSUP-78354]-[]-[Moved get_env to new utils.py to avoid cyclic dependency]-[JW] --- tests/conftest.py | 13 +------------ tests/state.py | 2 +- tests/utils.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 tests/utils.py diff --git a/tests/conftest.py b/tests/conftest.py index c6af7b2a4..533bf5b0e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,9 @@ -import os import pytest import pytest_asyncio import warnings from uuid import uuid4 +from utils import get_env from pytest_nhsd_apim.identity_service import ( AuthorizationCodeConfig, @@ -28,17 +28,6 @@ def _create_apigee_client(): return ApigeeClient(config=config) -def get_env(variable_name: str) -> str: - """Returns an environment variable""" - try: - var = os.environ[variable_name] - if not var: - raise RuntimeError(f"Variable is null, Check {variable_name}.") - return var - except KeyError: - raise RuntimeError(f"Variable is not set, Check {variable_name}.") - - @pytest.fixture(scope="session") def environment(): return get_env("ENVIRONMENT") diff --git a/tests/state.py b/tests/state.py index 5b002b80b..9a2756750 100644 --- a/tests/state.py +++ b/tests/state.py @@ -2,7 +2,7 @@ from enum import Enum from contextlib import contextmanager -from conftest import get_env +from utils import get_env def _is_mocked_environment() -> bool: diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 000000000..41c388a10 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,12 @@ +import os + + +def get_env(variable_name: str) -> str: + """Returns an environment variable""" + try: + var = os.environ[variable_name] + if not var: + raise RuntimeError(f"Variable is null, Check {variable_name}.") + return var + except KeyError: + raise RuntimeError(f"Variable is not set, Check {variable_name}.") From f504ce22cac60012dcb780ac8419aee426381a21 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 10:56:01 +0100 Subject: [PATCH 29/43] [ERSSUP-78354]-[]-[Altered state default values to not be calculated on import]-[JW] --- tests/state.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/state.py b/tests/state.py index 9a2756750..1bb148c52 100644 --- a/tests/state.py +++ b/tests/state.py @@ -27,11 +27,6 @@ def _calculate_default_app_restricted_user_id() -> str: return "000000000101" if _is_mocked_environment() else "555032000100" -_DEFAULT_ASID: str = _calculate_default_asid() -_DEFAULT_APP_RESTRICTED_ODS_CODE: str = _calculate_default_app_restricted_ods_code() -_DEFAULT_APP_RESTRICTED_USER_ID: str = _calculate_default_app_restricted_user_id() - - class ApplicationRestrictedType(Enum): REFERRER = "REFERRER_APPLICATION_RESTRICTED" PROVIDER = "PROVIDER_APPLICATION_RESTRICTED" @@ -55,11 +50,13 @@ def app_attributes(self) -> dict[str, str]: return dict(self._app_attributes) @staticmethod - def user_restricted_config(asid: str = _DEFAULT_ASID) -> "AuthenticationConfig": + def user_restricted_config( + asid: str = _calculate_default_asid(), + ) -> "AuthenticationConfig": """ Create a AuthenticationConfig object detaililng that authentication should be completed using User Restricted based authentication. - :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to _DEFAULT_ASID. + :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to the result of _calculate_default_asid(). :returns: a new AuthenticationConfig object configured for User Restricted access. """ @@ -68,17 +65,17 @@ def user_restricted_config(asid: str = _DEFAULT_ASID) -> "AuthenticationConfig": @staticmethod def application_restricted_config( type: ApplicationRestrictedType, - user_id: str = _DEFAULT_APP_RESTRICTED_USER_ID, - ods_code: str = _DEFAULT_APP_RESTRICTED_ODS_CODE, - asid: str = _DEFAULT_ASID, + user_id: str = _calculate_default_app_restricted_user_id(), + ods_code: str = _calculate_default_app_restricted_ods_code(), + asid: str = _calculate_default_asid(), ) -> "AuthenticationConfig": """ Create a AuthenticationConfig object detailing that authentication should be completed using Application Restricted based authentication. :param type: a ApplicationRestrictedType detailing the type of Application Restricted access to be utilised. - :param user_id: the user ID to be associated with the app used for authentication. Defaults to _DEFAULT_APP_RESTRICTED_USER_ID. - :param ods_code: the ODS code to be associated with the app used for authentication. Defaults to _DEFAULT_APP_RESTRICTED_ODS_CODE. - :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to _DEFAULT_ASID. + :param user_id: the user ID to be associated with the app used for authentication. Defaults to the result of _calculate_default_app_restricted_user_id(). + :param ods_code: the ODS code to be associated with the app used for authentication. Defaults to the result of _calculate_default_app_restricted_ods_code(). + :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to the result of _calculate_default_asid(). :returns: a new AuthenticationConfig object configured for Application Restricted access. """ From e4776213881e13f4fb372252de154c15d61a9ceb Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 11:45:19 +0100 Subject: [PATCH 30/43] [ERSSUP-78354]-[]-[Fixed type hint within state.app_attributes]-[JW] --- tests/state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/state.py b/tests/state.py index 1bb148c52..96f48aa32 100644 --- a/tests/state.py +++ b/tests/state.py @@ -41,7 +41,7 @@ def __init__(self, app_attributes: Dict[str, str]): self._app_attributes = app_attributes @property - def app_attributes(self) -> dict[str, str]: + def app_attributes(self) -> Dict[str, str]: """ Any attributes that should be included against the application used for authentication within Apigee. """ From 27be9018bb75bcf482ffd4a7983d033530f4c6b1 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 13:25:28 +0100 Subject: [PATCH 31/43] [ERSSUP-78354]-[]-[Swapped state functions away from using default parameters]-[JW] --- tests/state.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/state.py b/tests/state.py index 96f48aa32..d7bfaa30a 100644 --- a/tests/state.py +++ b/tests/state.py @@ -51,7 +51,7 @@ def app_attributes(self) -> Dict[str, str]: @staticmethod def user_restricted_config( - asid: str = _calculate_default_asid(), + asid: str, ) -> "AuthenticationConfig": """ Create a AuthenticationConfig object detaililng that authentication should be completed using User Restricted based authentication. @@ -60,14 +60,16 @@ def user_restricted_config( :returns: a new AuthenticationConfig object configured for User Restricted access. """ - return AuthenticationConfig(app_attributes={"asid": asid}) + # Not using a default value here to prevent this function from being exeucted when this method is not required (when running the standard smoke tests for example). + _asid = asid if asid else _calculate_default_asid() + return AuthenticationConfig(app_attributes={"asid": _asid}) @staticmethod def application_restricted_config( type: ApplicationRestrictedType, - user_id: str = _calculate_default_app_restricted_user_id(), - ods_code: str = _calculate_default_app_restricted_ods_code(), - asid: str = _calculate_default_asid(), + user_id: str, + ods_code: str, + asid: str, ) -> "AuthenticationConfig": """ Create a AuthenticationConfig object detailing that authentication should be completed using Application Restricted based authentication. @@ -80,6 +82,13 @@ def application_restricted_config( :returns: a new AuthenticationConfig object configured for Application Restricted access. """ + # Not using default values here to prevent these functions from being exeucted when this method is not required (when running the standard smoke tests for example). + _user_id = user_id if user_id else _calculate_default_app_restricted_user_id() + _ods_code = ( + ods_code if ods_code else _calculate_default_app_restricted_ods_code() + ) + _asid = asid if asid else _calculate_default_asid() + if type not in ApplicationRestrictedType: raise ValueError( f"Provided type not supported. Supported_business_functions={list(ApplicationRestrictedType)} provided_type={type}" @@ -87,10 +96,10 @@ def application_restricted_config( return AuthenticationConfig( app_attributes={ - "asid": asid, + "asid": _asid, "app-restricted-business-function": type.value, - "app-restricted-ods-code": ods_code, - "app-restricted-user-id": user_id, + "app-restricted-ods-code": _ods_code, + "app-restricted-user-id": _user_id, } ) From 357c7051c5f45e297d1616b0d28641685e60cf83 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 14:33:12 +0100 Subject: [PATCH 32/43] [ERSSUP-78354]-[]-[More minor type hint fixes]-[JW] --- tests/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/decorators.py b/tests/decorators.py index 0aae20d50..d2bf15af4 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -3,7 +3,7 @@ import state from functools import wraps -from typing import Callable +from typing import Callable, List from asyncio import iscoroutinefunction from data import Actor @@ -61,7 +61,7 @@ def wrapper(*args, **kwargs): return decorator(function) if function else decorator -def app_restricted_access(types: list[state.ApplicationRestrictedType]): +def app_restricted_access(types: List[state.ApplicationRestrictedType]): """ Decorator indicating that the given function should be authenticated with Application Restricted access with a list of set types. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the default application. From f0c4bf9aaccdc5852a8b1f994b4c48fdfb455105 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Thu, 29 Aug 2024 16:45:12 +0100 Subject: [PATCH 33/43] [ERSSUP-78354]-[]-[Swapped _create_test_app fixture to function scope]-[JW] --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 533bf5b0e..24ec29824 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -301,7 +301,7 @@ def app_restricted_access_code( return token_response["access_token"] -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def _create_test_app( _create_test_app, asid, From 3e8ae8d054041058c3c310348a0e004b02a899bc Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 30 Aug 2024 11:42:36 +0100 Subject: [PATCH 34/43] [ERSSUP-78354]-[]-[Swapped auth-type to use mark rather than custom state]-[JW] --- pyproject.toml | 2 +- tests/conftest.py | 36 +++++++++++++++++++++++++++++------- tests/decorators.py | 26 +++++++++----------------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 46d2cb1e2..fd8a16cbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,4 +45,4 @@ pytest-nhsd-apim = "^3.4.2" [tool.poetry.scripts] [tool.pytest.ini_options] -markers = ["smoke_test", "integration_test", "sandbox"] +markers = ["smoke_test", "integration_test", "sandbox", "authentication_type"] diff --git a/tests/conftest.py b/tests/conftest.py index 24ec29824..0015b9ef6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,7 +20,6 @@ from data import Actor -from state import current_authentication_context def _create_apigee_client(): @@ -301,10 +300,14 @@ def app_restricted_access_code( return token_response["access_token"] -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def _create_test_app( + request, _create_test_app, asid, + app_restricted_ods_code, + app_restricted_user_id, + app_restricted_business_function, ): """ This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to update any created app with the ASID currently being utilised. @@ -319,14 +322,33 @@ def _create_test_app( api = DeveloperAppsAPI(client=_create_apigee_client()) + marker = request.node.get_closest_marker("authentication_type") + if not marker: + raise ValueError( + "No pytest.mark.authentication_type included with request. Have you used the user_restricted_access or app_restricted_access decorators?" + ) + # Update the attributes of the created application to add in the ASID attribute. - additional_attribtues = [ - {"name": key, "value": value} - for key, value in current_authentication_context().app_attributes - ] + additional_attribtues = [{"name": "asid", "value": asid}] - modified_attributes = created_app["attributes"] + additional_attribtues + if marker.args and marker.args[0]: + type = marker.args[0] + elif marker.kwargs and marker.kwargs["type"]: + type = marker.kwargs["type"] + else: + raise ValueError("No type provided with pytest.mark.authentication_type marker") + + if type == "app-restricted": + additional_attribtues = additional_attribtues + [ + {"name": "app-restricted-ods-code", "value": app_restricted_ods_code}, + {"name": "app-restricted-user-id", "value": app_restricted_user_id}, + { + "name": "app-restricted-business-function", + "value": app_restricted_business_function, + }, + ] + modified_attributes = created_app["attributes"] + additional_attribtues created_app["attributes"] = modified_attributes warnings.warn(f"updated app={created_app}") diff --git a/tests/decorators.py b/tests/decorators.py index d2bf15af4..35f02e189 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -7,7 +7,7 @@ from asyncio import iscoroutinefunction from data import Actor -from conftest import get_env +from utils import get_env def _calculate_default_user() -> Actor: @@ -37,21 +37,17 @@ def decorator(func): "login_form": {"username": user.user_id}, } + @pytest.mark.authentication_type("user-restricted") @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) async def async_wrapper(*args, **kwargs): - with state.authentication_context( - state.AuthenticationConfig.user_restricted_config() - ): - return await func(*args, **kwargs) + return await func(*args, **kwargs) + @pytest.mark.authentication_type("user-restricted") @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) def wrapper(*args, **kwargs): - with state.authentication_context( - state.AuthenticationConfig.user_restricted_config() - ): - return func(*args, **kwargs) + return func(*args, **kwargs) # if the decorated function is async, return an async function, else return a synchronous version. return async_wrapper if iscoroutinefunction(func) else wrapper @@ -72,23 +68,19 @@ def app_restricted_access(types: List[state.ApplicationRestrictedType]): def decorator(func): auth_args = {"access": "application", "level": "level3"} + @pytest.mark.authentication_type("app-restricted") @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) async def async_wrapper(*args, **kwargs): for type in types: - with state.authentication_context( - state.AuthenticationConfig.application_restricted_config(type=type) - ): - return await func(*args, **kwargs) + return await func(*args, **kwargs) + @pytest.mark.authentication_type("app-restricted") @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) def wrapper(*args, **kwargs): for type in types: - with state.authentication_context( - state.AuthenticationConfig.application_restricted_config(type=type) - ): - return func(*args, **kwargs) + return func(*args, **kwargs) # if the decorated function is async, return an async function as a decorator, otherwise use a synchronous decorator. return async_wrapper if iscoroutinefunction(func) else wrapper From 51ea62e75e73570c15891256338b276b590e6280 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 30 Aug 2024 16:25:09 +0100 Subject: [PATCH 35/43] [ERSSUP-78354]-[]-[Swapped custom attributes to new _pre_authentication fixture]-[JW] --- tests/conftest.py | 54 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0015b9ef6..555d4ede8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,8 @@ from data import Actor +_TEST_APP = None + def _create_apigee_client(): config = ApigeeNonProdCredentials() @@ -300,24 +302,24 @@ def app_restricted_access_code( return token_response["access_token"] -@pytest.fixture(scope="session") -def _create_test_app( +@pytest.fixture +def _pre_authentication( request, - _create_test_app, asid, app_restricted_ods_code, app_restricted_user_id, app_restricted_business_function, ): """ - This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to update any created app with the ASID currently being utilised. - This is required as the private fixture does not publicise the attributes it associates with any created application, so instead we need to modify the created application to - append the ASID required. + Fixture adding custom attributes to the application created by the pytest_nhsd_apim module's fixtures. This is required as custom attributes are not publically exposed by the module itself. """ warnings.warn("invoking custom create test app.") - created_app = _create_test_app + created_app = _TEST_APP + if not created_app: + raise ValueError("No app has been initialised.") + warnings.warn(f"created app={created_app}") api = DeveloperAppsAPI(client=_create_apigee_client()) @@ -329,7 +331,7 @@ def _create_test_app( ) # Update the attributes of the created application to add in the ASID attribute. - additional_attribtues = [{"name": "asid", "value": asid}] + additional_attributes = [{"name": "asid", "value": asid}] if marker.args and marker.args[0]: type = marker.args[0] @@ -339,7 +341,7 @@ def _create_test_app( raise ValueError("No type provided with pytest.mark.authentication_type marker") if type == "app-restricted": - additional_attribtues = additional_attribtues + [ + additional_attributes = additional_attributes + [ {"name": "app-restricted-ods-code", "value": app_restricted_ods_code}, {"name": "app-restricted-user-id", "value": app_restricted_user_id}, { @@ -348,7 +350,7 @@ def _create_test_app( }, ] - modified_attributes = created_app["attributes"] + additional_attribtues + modified_attributes = created_app["attributes"] + additional_attributes created_app["attributes"] = modified_attributes warnings.warn(f"updated app={created_app}") @@ -358,3 +360,35 @@ def _create_test_app( app_name=created_app["name"], body=created_app, ) + + +@pytest.fixture(scope="session") +def _create_test_app(_create_test_app): + """ + This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to capture the created app so that it can be later updated. + """ + + _TEST_APP = _create_test_app + return _TEST_APP + + +@pytest.fixture +def get_access_token_via_user_restricted_flow_separate_auth( + _pre_authentication, get_access_token_via_user_restricted_flow_separate_auth +): + """ + Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. + """ + + return get_access_token_via_user_restricted_flow_separate_auth + + +@pytest.fixture +def get_access_token_via_signed_jwt_flow( + _pre_authentication, get_access_token_via_signed_jwt_flow +): + """ + Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. + """ + + return get_access_token_via_signed_jwt_flow From 610506eee7d7f47eade718af3f2929e3f0742644 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Fri, 30 Aug 2024 17:00:46 +0100 Subject: [PATCH 36/43] [ERSSUP-78354]-[]-[Ensured pre authentication fixture is explicitly called]-[JW] --- tests/conftest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 555d4ede8..1ede74dee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -314,7 +314,7 @@ def _pre_authentication( Fixture adding custom attributes to the application created by the pytest_nhsd_apim module's fixtures. This is required as custom attributes are not publically exposed by the module itself. """ - warnings.warn("invoking custom create test app.") + warnings.warn("Pre authentication fixture:") created_app = _TEST_APP if not created_app: @@ -368,6 +368,8 @@ def _create_test_app(_create_test_app): This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to capture the created app so that it can be later updated. """ + warnings.warn("Invoked custom create test app.") + _TEST_APP = _create_test_app return _TEST_APP @@ -380,6 +382,7 @@ def get_access_token_via_user_restricted_flow_separate_auth( Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. """ + _pre_authentication return get_access_token_via_user_restricted_flow_separate_auth @@ -391,4 +394,5 @@ def get_access_token_via_signed_jwt_flow( Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. """ + _pre_authentication return get_access_token_via_signed_jwt_flow From 2d1920e35561fd659375ba27337ae47a5efa5cfd Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Mon, 2 Sep 2024 13:56:50 +0100 Subject: [PATCH 37/43] [ERSSUP-78354]-[]-[Swapped _pre_authentication fixture to be invoked by _nhsd_apim_auth_token_data]-[JW] --- tests/conftest.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1ede74dee..5d2a63dff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -375,24 +375,5 @@ def _create_test_app(_create_test_app): @pytest.fixture -def get_access_token_via_user_restricted_flow_separate_auth( - _pre_authentication, get_access_token_via_user_restricted_flow_separate_auth -): - """ - Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. - """ - - _pre_authentication - return get_access_token_via_user_restricted_flow_separate_auth - - -@pytest.fixture -def get_access_token_via_signed_jwt_flow( - _pre_authentication, get_access_token_via_signed_jwt_flow -): - """ - Fixure overridding pytest_nhsd_apim module fixture with the same name to ensure that the _pre_authentication fixture is invoked before this fixture is executed. - """ - - _pre_authentication - return get_access_token_via_signed_jwt_flow +def _nhsd_apim_auth_token_data(_pre_authentication, _nhsd_apim_auth_token_data): + return _nhsd_apim_auth_token_data From 13162875eccffce120df387169456f52a72a3807 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Mon, 2 Sep 2024 15:40:39 +0100 Subject: [PATCH 38/43] [ERSSUP-78354]-[]-[Swapped _pre_authentication fixture to use _create_test_app directly]-[JW] --- tests/conftest.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5d2a63dff..00cb65bde 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,8 +21,6 @@ from data import Actor -_TEST_APP = None - def _create_apigee_client(): config = ApigeeNonProdCredentials() @@ -304,6 +302,7 @@ def app_restricted_access_code( @pytest.fixture def _pre_authentication( + _create_test_app, request, asid, app_restricted_ods_code, @@ -316,7 +315,7 @@ def _pre_authentication( warnings.warn("Pre authentication fixture:") - created_app = _TEST_APP + created_app = _create_test_app if not created_app: raise ValueError("No app has been initialised.") @@ -362,18 +361,6 @@ def _pre_authentication( ) -@pytest.fixture(scope="session") -def _create_test_app(_create_test_app): - """ - This fixture is overriding a private fixture housed within the pytest_nhsd_apim module to capture the created app so that it can be later updated. - """ - - warnings.warn("Invoked custom create test app.") - - _TEST_APP = _create_test_app - return _TEST_APP - - @pytest.fixture def _nhsd_apim_auth_token_data(_pre_authentication, _nhsd_apim_auth_token_data): return _nhsd_apim_auth_token_data From 62ead556018a51a4f1963c0bc091cde36c12611b Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Mon, 2 Sep 2024 17:14:28 +0100 Subject: [PATCH 39/43] [ERSSUP-78354]-[]-[Altered _pre_authentication fixture to reset the modified app once the test has been executed]-[JW] --- tests/conftest.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 00cb65bde..7acf97ef9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -349,12 +349,21 @@ def _pre_authentication( }, ] - modified_attributes = created_app["attributes"] + additional_attributes + original_attributes = created_app["attributes"] + modified_attributes = original_attributes + additional_attributes created_app["attributes"] = modified_attributes warnings.warn(f"updated app={created_app}") - return api.put_app_by_name( + yield api.put_app_by_name( + email="apm-testing-internal-dev@nhs.net", + app_name=created_app["name"], + body=created_app, + ) + + # Reset the app back to its original attributes so it can be updated by a subsequent test + created_app["attributes"] = original_attributes + api.put_app_by_name( email="apm-testing-internal-dev@nhs.net", app_name=created_app["name"], body=created_app, From a22575fbae9e1ae57412b90986eab799d87b043f Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 3 Sep 2024 13:37:20 +0100 Subject: [PATCH 40/43] [ERSSUP-78354]-[]-[Moved testing utils to use proper modules]-[JW] --- tests/__init__.py | 0 tests/asserts.py | 2 +- tests/conftest.py | 4 +- tests/decorators.py | 18 +-- tests/integration/test_app_restricted.py | 9 +- tests/integration/test_headers.py | 6 +- tests/sandbox/SandboxTest.py | 6 +- tests/sandbox/__init__.py | 0 tests/sandbox/conftest.py | 6 +- ...st_a030_retrieve_eRS_business_functions.py | 11 +- .../test_a033_retrieve_healthcare_service.py | 11 +- ...est_a035_search_for_healthcare_services.py | 11 +- ...037_retrieve_healthcare_service_version.py | 11 +- .../r4/test_a040_retrieve_practioner_info.py | 22 +-- .../test_a041_search_for_service_requests.py | 11 +- .../sandbox/stu3/test_a004_get_codesystem.py | 11 +- .../stu3/test_a005_get_referral_request.py | 11 +- ...est_a005_get_referral_request_versioned.py | 11 +- .../sandbox/stu3/test_a006_get_attachment.py | 13 +- ...test_a007_retrieve_clinical_information.py | 13 +- .../stu3/test_a008_get_referral_worklist.py | 11 +- .../stu3/test_a010_patient_service_search.py | 8 +- .../sandbox/stu3/test_a011_create_referral.py | 8 +- .../test_a012_maintain_referral_letter.py | 11 +- .../sandbox/stu3/test_a013_accept_referral.py | 19 ++- .../sandbox/stu3/test_a014_reject_referral.py | 20 ++- .../stu3/test_a015_get_appointment_slots.py | 11 +- .../test_a016_book_or_defer_appointment.py | 11 +- .../stu3/test_a019_generate_patient_letter.py | 13 +- .../stu3/test_a020_upload_attachment.py | 11 +- ...st_a021_create_referral_send_for_triage.py | 8 +- ...st_a022_cancel_appointment_action_later.py | 16 +- .../stu3/test_a023_get_advice_worklist.py | 11 +- ...st_a024_get_advice_and_guidance_request.py | 11 +- ...t_advice_and_guidance_request_versioned.py | 11 +- ...25_get_advice_and_guidance_conversation.py | 11 +- .../test_a026_send_advice_and_guidance.py | 11 +- .../test_a027_convert_advice_and_guidance.py | 11 +- .../stu3/test_a028_record_triage_outcome.py | 16 +- .../test_a029_available_actions_for_user.py | 11 +- .../stu3/test_a031_change_shortlist.py | 11 +- ...t_a032_change_shortlist_send_for_triage.py | 11 +- .../stu3/test_a034_update_appointment.py | 11 +- .../sandbox/stu3/test_a036_cancel_referral.py | 11 +- .../sandbox/stu3/test_a038_get_appointment.py | 11 +- .../test_a038_get_appointment_versioned.py | 11 +- ...3_retrieve_advice_and_guidance_overview.py | 13 +- ...a044_create_advice_and_guidance_request.py | 8 +- tests/state.py | 145 ------------------ 49 files changed, 241 insertions(+), 418 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/sandbox/__init__.py delete mode 100644 tests/state.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/asserts.py b/tests/asserts.py index 48bc53b68..3609e055d 100644 --- a/tests/asserts.py +++ b/tests/asserts.py @@ -2,7 +2,7 @@ from typing import Dict, Iterable from pytest_check import check from requests import Response -from data import RenamedHeader +from .data import RenamedHeader # Headers which are expected for all requests _generic_headers = { diff --git a/tests/conftest.py b/tests/conftest.py index 7acf97ef9..1406f023a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import warnings from uuid import uuid4 -from utils import get_env +from .utils import get_env from pytest_nhsd_apim.identity_service import ( AuthorizationCodeConfig, @@ -19,7 +19,7 @@ ) -from data import Actor +from .data import Actor def _create_apigee_client(): diff --git a/tests/decorators.py b/tests/decorators.py index 35f02e189..7157c9b93 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -1,13 +1,11 @@ import pytest -import state - from functools import wraps -from typing import Callable, List +from typing import Callable from asyncio import iscoroutinefunction -from data import Actor -from utils import get_env +from .data import Actor +from .utils import get_env def _calculate_default_user() -> Actor: @@ -57,12 +55,10 @@ def wrapper(*args, **kwargs): return decorator(function) if function else decorator -def app_restricted_access(types: List[state.ApplicationRestrictedType]): +def app_restricted_access(): """ Decorator indicating that the given function should be authenticated with Application Restricted access with a list of set types. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the default application. - - :param types: A list of ApplicationRestrictedType values indicating the types of application restricted access the decorated function should be authenticated with. """ def decorator(func): @@ -72,15 +68,13 @@ def decorator(func): @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) async def async_wrapper(*args, **kwargs): - for type in types: - return await func(*args, **kwargs) + return await func(*args, **kwargs) @pytest.mark.authentication_type("app-restricted") @pytest.mark.nhsd_apim_authorization(auth_args) @wraps(func) def wrapper(*args, **kwargs): - for type in types: - return func(*args, **kwargs) + return func(*args, **kwargs) # if the decorated function is async, return an async function as a decorator, otherwise use a synchronous decorator. return async_wrapper if iscoroutinefunction(func) else wrapper diff --git a/tests/integration/test_app_restricted.py b/tests/integration/test_app_restricted.py index e410a0241..7913dafad 100644 --- a/tests/integration/test_app_restricted.py +++ b/tests/integration/test_app_restricted.py @@ -1,10 +1,9 @@ import pytest import requests from requests import Response -from data import RenamedHeader -from asserts import assert_ok_response, assert_error_response -from decorators import app_restricted_access -from state import ApplicationRestrictedType +from tests.data import RenamedHeader +from tests.asserts import assert_ok_response, assert_error_response +from tests.decorators import app_restricted_access _HEADER_AUTHORIZATION = "Authorization" _HEADER_ECHO = "echo" # enable echo target @@ -88,7 +87,7 @@ def test_authorised_application_header_rejected( assert_error_response(response, _EXPECTED_CORRELATION_ID, 403) - @app_restricted_access(types=list(ApplicationRestrictedType)) + @app_restricted_access def test_headers_on_echo_target( self, nhsd_apim_auth_headers, diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py index 8103454cb..c468786e0 100644 --- a/tests/integration/test_headers.py +++ b/tests/integration/test_headers.py @@ -1,9 +1,9 @@ import pytest import requests from requests import Response -from data import RenamedHeader -from asserts import assert_ok_response -from decorators import user_restricated_access +from tests.data import RenamedHeader +from tests.asserts import assert_ok_response +from tests.decorators import user_restricated_access _HEADER_AUTHORIZATION = "Authorization" _HEADER_ECHO = "echo" # enable echo target diff --git a/tests/sandbox/SandboxTest.py b/tests/sandbox/SandboxTest.py index 861082945..846a39cb3 100644 --- a/tests/sandbox/SandboxTest.py +++ b/tests/sandbox/SandboxTest.py @@ -5,10 +5,10 @@ from pytest_check import check import pytest -import asserts +from tests import asserts -from data import Actor, RenamedHeader -from utils import HttpMethod +from tests.data import Actor, RenamedHeader +from .utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/__init__.py b/tests/sandbox/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/sandbox/conftest.py b/tests/sandbox/conftest.py index 48dc42854..cae6aedb6 100644 --- a/tests/sandbox/conftest.py +++ b/tests/sandbox/conftest.py @@ -1,13 +1,11 @@ -import os - import pytest import json import requests from typing import Dict, Callable -from data import Actor, RenamedHeader -from utils import HttpMethod +from tests.data import Actor, RenamedHeader +from .utils import HttpMethod @pytest.fixture(scope="session") diff --git a/tests/sandbox/r4/test_a030_retrieve_eRS_business_functions.py b/tests/sandbox/r4/test_a030_retrieve_eRS_business_functions.py index 2e5aadaa9..7f1fb812a 100644 --- a/tests/sandbox/r4/test_a030_retrieve_eRS_business_functions.py +++ b/tests/sandbox/r4/test_a030_retrieve_eRS_business_functions.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/r4/test_a033_retrieve_healthcare_service.py b/tests/sandbox/r4/test_a033_retrieve_healthcare_service.py index 21309f771..b0071d736 100644 --- a/tests/sandbox/r4/test_a033_retrieve_healthcare_service.py +++ b/tests/sandbox/r4/test_a033_retrieve_healthcare_service.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.sandbox.utils import HttpMethod +from tests.data import Actor @pytest.mark.sandbox diff --git a/tests/sandbox/r4/test_a035_search_for_healthcare_services.py b/tests/sandbox/r4/test_a035_search_for_healthcare_services.py index 8f69518cc..f28d3cd5f 100644 --- a/tests/sandbox/r4/test_a035_search_for_healthcare_services.py +++ b/tests/sandbox/r4/test_a035_search_for_healthcare_services.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/r4/test_a037_retrieve_healthcare_service_version.py b/tests/sandbox/r4/test_a037_retrieve_healthcare_service_version.py index d1898ea51..cd48f0c43 100644 --- a/tests/sandbox/r4/test_a037_retrieve_healthcare_service_version.py +++ b/tests/sandbox/r4/test_a037_retrieve_healthcare_service_version.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/r4/test_a040_retrieve_practioner_info.py b/tests/sandbox/r4/test_a040_retrieve_practioner_info.py index bea1f8cbf..fa6efdbd4 100644 --- a/tests/sandbox/r4/test_a040_retrieve_practioner_info.py +++ b/tests/sandbox/r4/test_a040_retrieve_practioner_info.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox @@ -36,10 +35,13 @@ def allowed_business_functions(self) -> Iterable[str]: @pytest.fixture def call_endpoint( - self, call_endpoint_url_with_query: Callable[[Actor, Dict[str, str]], Response], + self, + call_endpoint_url_with_query: Callable[[Actor, Dict[str, str]], Response], ) -> Callable[[Actor], Response]: return lambda actor, headers={}: call_endpoint_url_with_query( - actor, {"_query": "onBehalfOf"}, headers, + actor, + {"_query": "onBehalfOf"}, + headers, ) @pytest.mark.parametrize("actor", authorised_actor_data) @@ -57,4 +59,6 @@ def test_success( asserts.assert_status_code(200, actual_response.status_code) asserts.assert_response(expected_response, actual_response) - asserts.assert_json_response_headers(actual_response,) + asserts.assert_json_response_headers( + actual_response, + ) diff --git a/tests/sandbox/r4/test_a041_search_for_service_requests.py b/tests/sandbox/r4/test_a041_search_for_service_requests.py index c94a82c93..f4d9f4e7b 100644 --- a/tests/sandbox/r4/test_a041_search_for_service_requests.py +++ b/tests/sandbox/r4/test_a041_search_for_service_requests.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a004_get_codesystem.py b/tests/sandbox/stu3/test_a004_get_codesystem.py index 0fefedc09..4adeed00e 100644 --- a/tests/sandbox/stu3/test_a004_get_codesystem.py +++ b/tests/sandbox/stu3/test_a004_get_codesystem.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a005_get_referral_request.py b/tests/sandbox/stu3/test_a005_get_referral_request.py index deb42b311..992d6c452 100644 --- a/tests/sandbox/stu3/test_a005_get_referral_request.py +++ b/tests/sandbox/stu3/test_a005_get_referral_request.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a005_get_referral_request_versioned.py b/tests/sandbox/stu3/test_a005_get_referral_request_versioned.py index b4dc7dbd7..8f08566d3 100644 --- a/tests/sandbox/stu3/test_a005_get_referral_request_versioned.py +++ b/tests/sandbox/stu3/test_a005_get_referral_request_versioned.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a006_get_attachment.py b/tests/sandbox/stu3/test_a006_get_attachment.py index 7d765804b..21157482c 100644 --- a/tests/sandbox/stu3/test_a006_get_attachment.py +++ b/tests/sandbox/stu3/test_a006_get_attachment.py @@ -1,15 +1,12 @@ -import sys -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from pytest_check import check -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a007_retrieve_clinical_information.py b/tests/sandbox/stu3/test_a007_retrieve_clinical_information.py index 870a9362e..bb4ceae6b 100644 --- a/tests/sandbox/stu3/test_a007_retrieve_clinical_information.py +++ b/tests/sandbox/stu3/test_a007_retrieve_clinical_information.py @@ -1,15 +1,12 @@ -import sys -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from pytest_check import check -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a008_get_referral_worklist.py b/tests/sandbox/stu3/test_a008_get_referral_worklist.py index 36507c493..88c550cdf 100644 --- a/tests/sandbox/stu3/test_a008_get_referral_worklist.py +++ b/tests/sandbox/stu3/test_a008_get_referral_worklist.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a010_patient_service_search.py b/tests/sandbox/stu3/test_a010_patient_service_search.py index c9cfb861b..548b432cd 100644 --- a/tests/sandbox/stu3/test_a010_patient_service_search.py +++ b/tests/sandbox/stu3/test_a010_patient_service_search.py @@ -1,11 +1,11 @@ from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod from requests import Response diff --git a/tests/sandbox/stu3/test_a011_create_referral.py b/tests/sandbox/stu3/test_a011_create_referral.py index 3012ded45..8ce988b59 100644 --- a/tests/sandbox/stu3/test_a011_create_referral.py +++ b/tests/sandbox/stu3/test_a011_create_referral.py @@ -1,11 +1,11 @@ from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod from requests import Response diff --git a/tests/sandbox/stu3/test_a012_maintain_referral_letter.py b/tests/sandbox/stu3/test_a012_maintain_referral_letter.py index 29df4a32e..9779483db 100644 --- a/tests/sandbox/stu3/test_a012_maintain_referral_letter.py +++ b/tests/sandbox/stu3/test_a012_maintain_referral_letter.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a013_accept_referral.py b/tests/sandbox/stu3/test_a013_accept_referral.py index 040090568..ded2fb539 100644 --- a/tests/sandbox/stu3/test_a013_accept_referral.py +++ b/tests/sandbox/stu3/test_a013_accept_referral.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox @@ -29,7 +28,8 @@ def allowed_business_functions(self) -> Iterable[str]: @pytest.fixture def call_endpoint( - self, send_rest_request: Callable[[HttpMethod, str, Actor], Response], + self, + send_rest_request: Callable[[HttpMethod, str, Actor], Response], ) -> Callable[[Actor], Response]: return lambda actor, headers={}: send_rest_request( HttpMethod.POST, @@ -55,5 +55,8 @@ def test_success( ) asserts.assert_json_response_headers( - actual_response, additional={"etag": 'W/"9"',}, + actual_response, + additional={ + "etag": 'W/"9"', + }, ) diff --git a/tests/sandbox/stu3/test_a014_reject_referral.py b/tests/sandbox/stu3/test_a014_reject_referral.py index 37223009b..3ac67280b 100644 --- a/tests/sandbox/stu3/test_a014_reject_referral.py +++ b/tests/sandbox/stu3/test_a014_reject_referral.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox @@ -53,7 +52,9 @@ def call_endpoint( ], ) -> Callable[[Actor], Response]: return lambda actor, headers={}: call_endpoint_url_with_request( - actor, "stu3/rejectReferral/requests/BasicExampleIbs.json", headers, + actor, + "stu3/rejectReferral/requests/BasicExampleIbs.json", + headers, ) @pytest.mark.parametrize("actor", authorised_actor_data) @@ -75,5 +76,8 @@ def test_success( asserts.assert_response(expected_response, actual_response) asserts.assert_json_response_headers( - actual_response, additional={"etag": 'W/"10"',}, + actual_response, + additional={ + "etag": 'W/"10"', + }, ) diff --git a/tests/sandbox/stu3/test_a015_get_appointment_slots.py b/tests/sandbox/stu3/test_a015_get_appointment_slots.py index cc318c095..2fbb24d19 100644 --- a/tests/sandbox/stu3/test_a015_get_appointment_slots.py +++ b/tests/sandbox/stu3/test_a015_get_appointment_slots.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a016_book_or_defer_appointment.py b/tests/sandbox/stu3/test_a016_book_or_defer_appointment.py index d9148de72..b1837484a 100644 --- a/tests/sandbox/stu3/test_a016_book_or_defer_appointment.py +++ b/tests/sandbox/stu3/test_a016_book_or_defer_appointment.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a019_generate_patient_letter.py b/tests/sandbox/stu3/test_a019_generate_patient_letter.py index 1e779beaf..47fe81aaa 100644 --- a/tests/sandbox/stu3/test_a019_generate_patient_letter.py +++ b/tests/sandbox/stu3/test_a019_generate_patient_letter.py @@ -1,15 +1,12 @@ -import sys -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from pytest_check import check -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a020_upload_attachment.py b/tests/sandbox/stu3/test_a020_upload_attachment.py index c8247cecc..a62769731 100644 --- a/tests/sandbox/stu3/test_a020_upload_attachment.py +++ b/tests/sandbox/stu3/test_a020_upload_attachment.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor, RenamedHeader -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor, RenamedHeader +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a021_create_referral_send_for_triage.py b/tests/sandbox/stu3/test_a021_create_referral_send_for_triage.py index abfda5eb1..276c985cd 100644 --- a/tests/sandbox/stu3/test_a021_create_referral_send_for_triage.py +++ b/tests/sandbox/stu3/test_a021_create_referral_send_for_triage.py @@ -1,11 +1,11 @@ from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod from requests import Response diff --git a/tests/sandbox/stu3/test_a022_cancel_appointment_action_later.py b/tests/sandbox/stu3/test_a022_cancel_appointment_action_later.py index e45158934..45f65da8a 100644 --- a/tests/sandbox/stu3/test_a022_cancel_appointment_action_later.py +++ b/tests/sandbox/stu3/test_a022_cancel_appointment_action_later.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox @@ -84,5 +83,8 @@ def test_success( asserts.assert_response(expected_response, actual_response) asserts.assert_json_response_headers( - actual_response, additional={"etag": 'W/"11"',}, + actual_response, + additional={ + "etag": 'W/"11"', + }, ) diff --git a/tests/sandbox/stu3/test_a023_get_advice_worklist.py b/tests/sandbox/stu3/test_a023_get_advice_worklist.py index 42b74974d..4a6d81595 100644 --- a/tests/sandbox/stu3/test_a023_get_advice_worklist.py +++ b/tests/sandbox/stu3/test_a023_get_advice_worklist.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request.py b/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request.py index e729a498d..d0b06e06e 100644 --- a/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request.py +++ b/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request_versioned.py b/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request_versioned.py index 6c3287fbe..5b1f07f5e 100644 --- a/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request_versioned.py +++ b/tests/sandbox/stu3/test_a024_get_advice_and_guidance_request_versioned.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a025_get_advice_and_guidance_conversation.py b/tests/sandbox/stu3/test_a025_get_advice_and_guidance_conversation.py index 0d45b6971..625e22b37 100644 --- a/tests/sandbox/stu3/test_a025_get_advice_and_guidance_conversation.py +++ b/tests/sandbox/stu3/test_a025_get_advice_and_guidance_conversation.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a026_send_advice_and_guidance.py b/tests/sandbox/stu3/test_a026_send_advice_and_guidance.py index 6cade57b1..4e0acc5d4 100644 --- a/tests/sandbox/stu3/test_a026_send_advice_and_guidance.py +++ b/tests/sandbox/stu3/test_a026_send_advice_and_guidance.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a027_convert_advice_and_guidance.py b/tests/sandbox/stu3/test_a027_convert_advice_and_guidance.py index d05c96881..f106715d1 100644 --- a/tests/sandbox/stu3/test_a027_convert_advice_and_guidance.py +++ b/tests/sandbox/stu3/test_a027_convert_advice_and_guidance.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a028_record_triage_outcome.py b/tests/sandbox/stu3/test_a028_record_triage_outcome.py index e4aa2c93c..006e861cf 100644 --- a/tests/sandbox/stu3/test_a028_record_triage_outcome.py +++ b/tests/sandbox/stu3/test_a028_record_triage_outcome.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox @@ -81,5 +80,8 @@ def test_success( asserts.assert_response(expected_response, actual_response) asserts.assert_json_response_headers( - actual_response, additional={"etag": 'W/"10"',}, + actual_response, + additional={ + "etag": 'W/"10"', + }, ) diff --git a/tests/sandbox/stu3/test_a029_available_actions_for_user.py b/tests/sandbox/stu3/test_a029_available_actions_for_user.py index 021e9d485..877c7b43c 100644 --- a/tests/sandbox/stu3/test_a029_available_actions_for_user.py +++ b/tests/sandbox/stu3/test_a029_available_actions_for_user.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a031_change_shortlist.py b/tests/sandbox/stu3/test_a031_change_shortlist.py index d08734b8f..9f42cb9d9 100644 --- a/tests/sandbox/stu3/test_a031_change_shortlist.py +++ b/tests/sandbox/stu3/test_a031_change_shortlist.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a032_change_shortlist_send_for_triage.py b/tests/sandbox/stu3/test_a032_change_shortlist_send_for_triage.py index f0b5e940d..5d171b099 100644 --- a/tests/sandbox/stu3/test_a032_change_shortlist_send_for_triage.py +++ b/tests/sandbox/stu3/test_a032_change_shortlist_send_for_triage.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a034_update_appointment.py b/tests/sandbox/stu3/test_a034_update_appointment.py index aeb244076..86da5f8a7 100644 --- a/tests/sandbox/stu3/test_a034_update_appointment.py +++ b/tests/sandbox/stu3/test_a034_update_appointment.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a036_cancel_referral.py b/tests/sandbox/stu3/test_a036_cancel_referral.py index 8e06be17d..7e02f7104 100644 --- a/tests/sandbox/stu3/test_a036_cancel_referral.py +++ b/tests/sandbox/stu3/test_a036_cancel_referral.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a038_get_appointment.py b/tests/sandbox/stu3/test_a038_get_appointment.py index f6bb41892..b92797a44 100644 --- a/tests/sandbox/stu3/test_a038_get_appointment.py +++ b/tests/sandbox/stu3/test_a038_get_appointment.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a038_get_appointment_versioned.py b/tests/sandbox/stu3/test_a038_get_appointment_versioned.py index f9db2c21e..197551617 100644 --- a/tests/sandbox/stu3/test_a038_get_appointment_versioned.py +++ b/tests/sandbox/stu3/test_a038_get_appointment_versioned.py @@ -1,13 +1,12 @@ -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a043_retrieve_advice_and_guidance_overview.py b/tests/sandbox/stu3/test_a043_retrieve_advice_and_guidance_overview.py index d3d44b5e1..0d9c08582 100644 --- a/tests/sandbox/stu3/test_a043_retrieve_advice_and_guidance_overview.py +++ b/tests/sandbox/stu3/test_a043_retrieve_advice_and_guidance_overview.py @@ -1,15 +1,12 @@ -import sys -from typing import Callable, Dict, Iterable, List -from urllib import response +from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from pytest_check import check -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod @pytest.mark.sandbox diff --git a/tests/sandbox/stu3/test_a044_create_advice_and_guidance_request.py b/tests/sandbox/stu3/test_a044_create_advice_and_guidance_request.py index 9e7a05b73..3198e5d70 100644 --- a/tests/sandbox/stu3/test_a044_create_advice_and_guidance_request.py +++ b/tests/sandbox/stu3/test_a044_create_advice_and_guidance_request.py @@ -1,13 +1,13 @@ from typing import Callable, Dict, Iterable import pytest -import asserts +from tests import asserts from requests import Response -from SandboxTest import SandboxTest -from data import Actor -from utils import HttpMethod +from tests.sandbox.SandboxTest import SandboxTest +from tests.data import Actor +from tests.sandbox.utils import HttpMethod authorised_actor_data = [Actor.RC, Actor.RC_DEV, Actor.RC_INSUFFICIENT_IAL, Actor.RCA] diff --git a/tests/state.py b/tests/state.py deleted file mode 100644 index d7bfaa30a..000000000 --- a/tests/state.py +++ /dev/null @@ -1,145 +0,0 @@ -from typing import Dict -from enum import Enum -from contextlib import contextmanager - -from utils import get_env - - -def _is_mocked_environment() -> bool: - return get_env("ENVIRONMENT") == "internal-dev" and "ft" in get_env( - "SERVICE_BASE_PATH" - ) - - -def _calculate_default_asid() -> str: - return ( - get_env("ERS_MOCK_ASID") - if _is_mocked_environment() - else get_env("ERS_TEST_ASID") - ) - - -def _calculate_default_app_restricted_ods_code() -> str: - return "R68" if _is_mocked_environment() else "RCD" - - -def _calculate_default_app_restricted_user_id() -> str: - return "000000000101" if _is_mocked_environment() else "555032000100" - - -class ApplicationRestrictedType(Enum): - REFERRER = "REFERRER_APPLICATION_RESTRICTED" - PROVIDER = "PROVIDER_APPLICATION_RESTRICTED" - - -class AuthenticationConfig: - """ - Defines some configuration for use as part of authentication. - """ - - def __init__(self, app_attributes: Dict[str, str]): - self._app_attributes = app_attributes - - @property - def app_attributes(self) -> Dict[str, str]: - """ - Any attributes that should be included against the application used for authentication within Apigee. - """ - - # return a new dict to hide the internal parameters so they cannot be modified via the public interface. - return dict(self._app_attributes) - - @staticmethod - def user_restricted_config( - asid: str, - ) -> "AuthenticationConfig": - """ - Create a AuthenticationConfig object detaililng that authentication should be completed using User Restricted based authentication. - - :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to the result of _calculate_default_asid(). - :returns: a new AuthenticationConfig object configured for User Restricted access. - """ - - # Not using a default value here to prevent this function from being exeucted when this method is not required (when running the standard smoke tests for example). - _asid = asid if asid else _calculate_default_asid() - return AuthenticationConfig(app_attributes={"asid": _asid}) - - @staticmethod - def application_restricted_config( - type: ApplicationRestrictedType, - user_id: str, - ods_code: str, - asid: str, - ) -> "AuthenticationConfig": - """ - Create a AuthenticationConfig object detailing that authentication should be completed using Application Restricted based authentication. - - :param type: a ApplicationRestrictedType detailing the type of Application Restricted access to be utilised. - :param user_id: the user ID to be associated with the app used for authentication. Defaults to the result of _calculate_default_app_restricted_user_id(). - :param ods_code: the ODS code to be associated with the app used for authentication. Defaults to the result of _calculate_default_app_restricted_ods_code(). - :param asid: a string detailing the ASID value to be associated with the app used for authentication. Defaults to the result of _calculate_default_asid(). - - :returns: a new AuthenticationConfig object configured for Application Restricted access. - """ - - # Not using default values here to prevent these functions from being exeucted when this method is not required (when running the standard smoke tests for example). - _user_id = user_id if user_id else _calculate_default_app_restricted_user_id() - _ods_code = ( - ods_code if ods_code else _calculate_default_app_restricted_ods_code() - ) - _asid = asid if asid else _calculate_default_asid() - - if type not in ApplicationRestrictedType: - raise ValueError( - f"Provided type not supported. Supported_business_functions={list(ApplicationRestrictedType)} provided_type={type}" - ) - - return AuthenticationConfig( - app_attributes={ - "asid": _asid, - "app-restricted-business-function": type.value, - "app-restricted-ods-code": _ods_code, - "app-restricted-user-id": _user_id, - } - ) - - -_AUTHENTICATION_CONFIG_INSTANCE = None - - -@contextmanager -def authentication_context(config: AuthenticationConfig) -> AuthenticationConfig: - """ - Stores the supplied AuthenticationConfig so it can be used by other fixtures/test utilities. - Returns the provided AuthenticationConfig within a contextmanager which will handle the clear down of clearing the provided configuration. - - Standard usage: - - config = AuthenticationConfig.user_restricted_config() - - with authentication_context(config): - # test logic.... - - :param config: An AuthenticationConfig object detailing the configuration that should be stored for the current test context. - :returns: the provided config wrapped as a contextmanager. - """ - _AUTHENTICATION_CONFIG_INSTANCE = config - try: - yield _AUTHENTICATION_CONFIG_INSTANCE - finally: - _AUTHENTICATION_CONFIG_INSTANCE = None - - -def current_authentication_context() -> AuthenticationConfig: - """ - Retrieve the AuthenticationConfig instance being used within the current test context. - - :returns: the current AuthenticationConfig - :throws ValueError: if no AuthenticationConfig has been configured for the current test context. - """ - if not _AUTHENTICATION_CONFIG_INSTANCE: - raise ValueError( - "No authentication configuration is currently instantiated. Has with_authentication_context been called?" - ) - - return _AUTHENTICATION_CONFIG_INSTANCE From a12833d5bfb0f773d73b0a0de304d53112ed5266 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 3 Sep 2024 15:06:09 +0100 Subject: [PATCH 41/43] [ERSSUP-78354]-[]-[Fixed app_restricted_access decorator]-[JW] --- tests/decorators.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/decorators.py b/tests/decorators.py index 7157c9b93..d0aed8e39 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -55,28 +55,25 @@ def wrapper(*args, **kwargs): return decorator(function) if function else decorator -def app_restricted_access(): +def app_restricted_access(func): """ Decorator indicating that the given function should be authenticated with Application Restricted access with a list of set types. This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the default application. """ - def decorator(func): - auth_args = {"access": "application", "level": "level3"} + auth_args = {"access": "application", "level": "level3"} - @pytest.mark.authentication_type("app-restricted") - @pytest.mark.nhsd_apim_authorization(auth_args) - @wraps(func) - async def async_wrapper(*args, **kwargs): - return await func(*args, **kwargs) + @pytest.mark.authentication_type("app-restricted") + @pytest.mark.nhsd_apim_authorization(auth_args) + @wraps(func) + async def async_wrapper(*args, **kwargs): + return await func(*args, **kwargs) - @pytest.mark.authentication_type("app-restricted") - @pytest.mark.nhsd_apim_authorization(auth_args) - @wraps(func) - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - # if the decorated function is async, return an async function as a decorator, otherwise use a synchronous decorator. - return async_wrapper if iscoroutinefunction(func) else wrapper + @pytest.mark.authentication_type("app-restricted") + @pytest.mark.nhsd_apim_authorization(auth_args) + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) - return decorator + # if the decorated function is async, return an async function as a decorator, otherwise use a synchronous decorator. + return async_wrapper if iscoroutinefunction(func) else wrapper From 242ad24cd6d69af5758777b18f83d3f09ef4f17a Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 3 Sep 2024 16:08:04 +0100 Subject: [PATCH 42/43] [ERSSUP-78354]-[]-[Altered app_restricted_business_function to no longer be session scoped]-[JW] --- tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1406f023a..c35f12de8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -97,7 +97,6 @@ def app_restricted_user_id(is_mocked_environment): @pytest.fixture( - scope="session", params=["PROVIDER_AUTHORISED_APPLICATION", "REFERRER_AUTHORISED_APPLICATION"], ) def app_restricted_business_function(request): @@ -372,4 +371,8 @@ def _pre_authentication( @pytest.fixture def _nhsd_apim_auth_token_data(_pre_authentication, _nhsd_apim_auth_token_data): + """ + Override of the pytest_nhsd_apim _nhsd_apim_auth_token_data fixture to invoke _pre_authenication before being executed. + """ + return _nhsd_apim_auth_token_data From 16342454656ea484dda8295f81fc4c141cfc2193 Mon Sep 17 00:00:00 2001 From: Jack Wainwright Date: Tue, 3 Sep 2024 17:44:35 +0100 Subject: [PATCH 43/43] [ERSSUP-78354]-[]-[Added force_new_token parameter]-[JW] --- tests/decorators.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/decorators.py b/tests/decorators.py index d0aed8e39..a138f2b05 100644 --- a/tests/decorators.py +++ b/tests/decorators.py @@ -33,6 +33,8 @@ def decorator(func): "access": "healthcare_worker", "level": "aal3", "login_form": {"username": user.user_id}, + # Force a token to always be created to ensure no app details are cached + "force_new_token": True, } @pytest.mark.authentication_type("user-restricted") @@ -61,7 +63,12 @@ def app_restricted_access(func): This will lead to a fixture named 'nhsd_apim_auth_headers' being provided to the function as a dictionary, including the headers required to authenticate as the default application. """ - auth_args = {"access": "application", "level": "level3"} + auth_args = { + "access": "application", + "level": "level3", + # Force a token to always be created to ensure no app details are cached + "force_new_token": True, + } @pytest.mark.authentication_type("app-restricted") @pytest.mark.nhsd_apim_authorization(auth_args)