Skip to content
Merged
4,249 changes: 2,883 additions & 1,366 deletions postman/Validate Relationship Service Sandbox.postman_collection.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions sandbox/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from flask import Flask

from .get_consent import get_consent_response
from .get_consent_by_id import get_consent_by_id_response
from .get_questionnaire_response import get_questionnaire_response_response
from .get_related_person import get_related_person_response
from .patch_consent import patch_consent_response
Expand Down Expand Up @@ -68,6 +69,16 @@ def get_consent() -> Union[dict, tuple]:
return get_consent_response()


@app.route(f"/{COMMON_PATH}/Consent/<identifier>", methods=["GET"])
def get_consent_by_id(identifier: str) -> Union[dict, tuple]:
"""Sandbox API for GET /Consent/{id}

Returns:
Union[dict, tuple]: Response for GET /Consent/{id}
"""
return get_consent_by_id_response(identifier)


@app.route(f"/{COMMON_PATH}/Consent", methods=["POST"])
def post_consent() -> Union[dict, tuple]:
"""Sandbox API for POST /Consent
Expand Down
17 changes: 17 additions & 0 deletions sandbox/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,22 @@
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH = (
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-performer-patient.yaml"
)
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT = (
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-patient.yaml"
)
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER = (
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-performer.yaml"
)
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP = f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship.yaml"
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH = (
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-performer-patient.yaml"
)
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT = (
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-patient.yaml"
)
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER = (
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-performer.yaml"
)
GET_CONSENT__STATUS_PARAM_INVALID = f"{GET_CONSENT__DIRECTORY}errors/invalid-status-parameter.yaml"
GET_CONSENT__MULTIPLE_RELATIONSHIPS_SINGLE_PATIENT = (
f"{GET_CONSENT__DIRECTORY}multiple-relationships-single-patient.yaml"
Expand All @@ -58,6 +70,11 @@
GET_CONSENT__MULTIPLE_RELATIONSHIPS_SINGLE_PATIENT_INCLUDE_BOTH = (
f"{GET_CONSENT__DIRECTORY}multiple-relationships-single-patient-include-performer-patient.yaml"
)

# GET Consent by ID
GET_CONSENT_BY_ID__INVALID_ID_ERROR = f"{GET_CONSENT__DIRECTORY}ID/errors/invalid-id.yaml"
GET_CONSENT_BY_ID__MISSING_ID_ERROR = f"{GET_CONSENT__DIRECTORY}ID/errors/missing-id.yaml"

# POST Consent
POST_CONSENT__DIRECTORY = "./api/examples/POST_Consent/"
POST_CONSENT__SUCCESS = f"{POST_CONSENT__DIRECTORY}success.yaml"
Expand Down
64 changes: 64 additions & 0 deletions sandbox/api/get_consent_by_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from logging import getLogger
from typing import Union

from flask import request

from .constants import (
INTERNAL_SERVER_ERROR_EXAMPLE,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER,
GET_CONSENT_BY_ID__INVALID_ID_ERROR,
GET_CONSENT_BY_ID__MISSING_ID_ERROR,
BAD_REQUEST_INCLUDE_PARAM_INVALID,
INVALIDATED_RESOURCE,
)
from .utils import generate_response_from_example, check_for_consent_include_params

logger = getLogger(__name__)


def get_consent_by_id_response(identifier: str) -> Union[dict, tuple]:
"""Sandbox API for GET /Consent/{id}

Returns:
Union[dict, tuple]: Response for GET /Consent/{id}
"""
try:
params = request.args.to_dict()
if "_include" not in params and len(params) > 0:
return generate_response_from_example(BAD_REQUEST_INCLUDE_PARAM_INVALID, 422)
else:
_include = request.args.getlist("_include")

if identifier == "74eed847-ca25-4e76-8cf2-f2c2d7842a7a":
return check_for_consent_include_params(
_include,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT,
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER,
)
elif identifier == "39df03a2-1b14-4d19-b1dc-d5d8cbf96948":
return check_for_consent_include_params(
_include,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT,
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER,
)
elif identifier == "a0922245-1072-40c3-8f4e-a7490c10d365":
return generate_response_from_example(INVALIDATED_RESOURCE, 404)
elif identifier == " " or identifier is None:
return generate_response_from_example(GET_CONSENT_BY_ID__MISSING_ID_ERROR, 400)
else:
return generate_response_from_example(GET_CONSENT_BY_ID__INVALID_ID_ERROR, 400)

except Exception:
logger.exception("An error occurred while processing the request")
return generate_response_from_example(INTERNAL_SERVER_ERROR_EXAMPLE, 500)
153 changes: 153 additions & 0 deletions sandbox/api/tests/test_get_consent_by_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from json import dumps, loads
from unittest.mock import MagicMock, patch

import pytest
from flask import Response

CONSENT_API_ENDPOINT = "/FHIR/R4/Consent"
GET_CONSENT_BY_ID_FILE_PATH = "sandbox.api.get_consent_by_id"


@pytest.mark.parametrize(
("consent_id", "include_params", "response_file_name", "status_code"),
[
(
"a0922245-1072-40c3-8f4e-a7490c10d365", # Invalid parameters
"_invalid=test",
"./api/examples/errors/invalid-include-parameter.yaml",
422,
),
(
"a0922245-1072-40c3-8f4e-a7490c10d365", # No proxy-role record found error
"",
"./api/examples/errors/invalidated-resource.yaml",
404,
),
(
" ", # Missing consent ID
"",
"./api/examples/GET_Consent/ID/errors/missing-id.yaml",
400,
),
(
"test", # Invalid consent ID
"",
"./api/examples/GET_Consent/ID/errors/invalid-id.yaml",
400,
),
],
)
@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.generate_response_from_example")
def test_get_consent_by_id_returns_expected_responses__mocked_get_consent_by_id(
mock_generate_response_from_example: MagicMock,
consent_id: str,
include_params: str,
response_file_name: str,
status_code: int,
client: object,
) -> None:
"""Test GET /Consent/{ID} endpoint."""
mock_generate_response_from_example.return_value = mocked_response = Response(
dumps({"data": "mocked"}),
status=status_code,
content_type="application/json",
)
# Act
response = client.get(f"{CONSENT_API_ENDPOINT}/{consent_id}?{include_params}")
# import pdb; pdb.set_trace()
# Assert
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
assert response.status_code == status_code
assert response.json == loads(mocked_response.get_data(as_text=True))


@pytest.mark.parametrize(
("consent_id", "include_params", "response_file_name", "status_code"),
[
(
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship no include
"",
"./api/examples/GET_Consent/single-consenting-adult-relationship.yaml",
200,
),
(
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include performer
"_include=Consent:performer",
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-performer.yaml",
200,
),
(
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include patient
"_include=Consent:patient",
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-patient.yaml",
200,
),
(
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include both
"_include=Consent:performer&_include=Consent:patient",
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-performer-patient.yaml",
200,
),
(
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship no include
"",
"./api/examples/GET_Consent/single-mother-child-relationship.yaml",
200,
),
(
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include performer
"_include=Consent:performer",
"./api/examples/GET_Consent/single-mother-child-relationship-include-performer.yaml",
200,
),
(
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include patient
"_include=Consent:patient",
"./api/examples/GET_Consent/single-mother-child-relationship-include-patient.yaml",
200,
),
(
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include both
"_include=Consent:performer&_include=Consent:patient",
"./api/examples/GET_Consent/single-mother-child-relationship-include-performer-patient.yaml",
200,
),
],
)
@patch("sandbox.api.utils.generate_response_from_example")
def test_get_consent_by_id_returns_expected_responses__mocked_utils(
mock_generate_response_from_example: MagicMock,
consent_id: str,
include_params: str,
response_file_name: str,
status_code: int,
client: object,
) -> None:
"""Test GET /Consent/{ID} endpoint."""
mock_generate_response_from_example.return_value = mocked_response = Response(
dumps({"data": "mocked"}),
status=status_code,
content_type="application/json",
)
# Act
response = client.get(f"{CONSENT_API_ENDPOINT}/{consent_id}?{include_params}")
# import pdb; pdb.set_trace()
# Assert
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
assert response.status_code == status_code
assert response.json == loads(mocked_response.get_data(as_text=True))


@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.check_for_consent_include_params")
@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.generate_response_from_example")
def test_get_consent_by_id__500_internal_server_error(
mock_generate_response_from_example: MagicMock,
mock_check_for_consent_include_params: MagicMock,
client: object,
) -> None:
"""Test GET /Consent/{ID} endpoint."""
mock_check_for_consent_include_params.side_effect = Exception("Test exception")
# Act
client.get(f"{CONSENT_API_ENDPOINT}/74eed847-ca25-4e76-8cf2-f2c2d7842a7a")
# Assert
mock_generate_response_from_example.assert_called_once_with("./api/examples/errors/internal-server-error.yaml", 500)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
ConsentSingleConsentingAdultRelationshipIncludePatientBundle:
summary: Single consenting adult proxy relationship with patient details
description:
A Bundle containing a single proxy relationship between consenting adults including the patient details.
value:
resourceType: Bundle
timestamp: '2020-08-26T14:00:00+00:00'
total: 2
type: searchset
entry:
- fullUrl: https://api.service.nhs.uk/validated-relationships/FHIR/R4/Patient/DFCC67F5
resource:
resourceType: Patient
id: DFCC67F5
identifier:
- system: 'https://fhir.nhs.uk/Id/nhs-number'
value: '9000000005'
- system: 'https://placeholder.fhir.nhs.uk/Id/local-gp-patient-identifier'
value: ABC1234567
name:
- id: '123456'
use: usual
period:
start: '2020-01-01'
end: '2021-12-31'
given:
- Sally
family: Evans
prefix:
- Mrs
birthDate: '1995-10-22'
generalPractitioner:
- type: Organization
identifier:
value: ODS12345
system: 'https://fhir.nhs.uk/Id/ods-organization-code'
search:
mode: include
- fullUrl: https://api.service.nhs.uk/validated-relationships/FHIR/R4/Consent/WWCC67T1
resource:
resourceType: Consent
id: WWCC67T1
status: active
scope:
coding:
- system: 'http://terminology.hl7.org/CodeSystem/consentscope'
code: patient-privacy
display: Privacy Consent
text: Patient Privacy Consent
category:
- coding:
- system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode'
code: INFA
display: Information Access
text: Information Access Consent
patient:
identifier:
system: 'https://fhir.nhs.uk/Id/nhs-number'
value: '9000000005'
dateTime: '2024-07-21T17:32:28Z'
performer:
- identifier:
system: 'https://fhir.nhs.uk/Id/nhs-number'
value: '9000000010'
verification:
- verified: true
verifiedWith:
identifier:
system: 'https://fhir.nhs.uk/Id/nhs-number'
value: '9000000005'
verificationDate: '2024-07-21T17:32:28Z'
search:
mode: match
Loading