Skip to content

Commit bfbc604

Browse files
authored
NPA-5087: GET Consent by ID - sandbox environment and Postman collection updates (#218)
* npa-5087 - Added get consent by ID to sandbox * npa-5087 - Added invalid and missing ID routes * npa-5087 - Added sandbox tests for get consent by id * npa-5087 - Updated sandbox * npa-5087 - Update postman collection * npa-5087 - Updated OAS and postman collection * npa-5087 - formatting updates * npa-5087 - further formatting updates * npa-5087 - Updated import positioning
1 parent d9f4a28 commit bfbc604

11 files changed

+3428
-1369
lines changed

postman/Validate Relationship Service Sandbox.postman_collection.json

Lines changed: 2883 additions & 1366 deletions
Large diffs are not rendered by default.

sandbox/api/app.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from flask import Flask
55

66
from .get_consent import get_consent_response
7+
from .get_consent_by_id import get_consent_by_id_response
78
from .get_questionnaire_response import get_questionnaire_response_response
89
from .get_related_person import get_related_person_response
910
from .patch_consent import patch_consent_response
@@ -68,6 +69,16 @@ def get_consent() -> Union[dict, tuple]:
6869
return get_consent_response()
6970

7071

72+
@app.route(f"/{COMMON_PATH}/Consent/<identifier>", methods=["GET"])
73+
def get_consent_by_id(identifier: str) -> Union[dict, tuple]:
74+
"""Sandbox API for GET /Consent/{id}
75+
76+
Returns:
77+
Union[dict, tuple]: Response for GET /Consent/{id}
78+
"""
79+
return get_consent_by_id_response(identifier)
80+
81+
7182
@app.route(f"/{COMMON_PATH}/Consent", methods=["POST"])
7283
def post_consent() -> Union[dict, tuple]:
7384
"""Sandbox API for POST /Consent

sandbox/api/constants.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,22 @@
4141
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH = (
4242
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-performer-patient.yaml"
4343
)
44+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT = (
45+
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-patient.yaml"
46+
)
47+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER = (
48+
f"{GET_CONSENT__DIRECTORY}single-consenting-adult-relationship-include-performer.yaml"
49+
)
4450
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP = f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship.yaml"
4551
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH = (
4652
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-performer-patient.yaml"
4753
)
54+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT = (
55+
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-patient.yaml"
56+
)
57+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER = (
58+
f"{GET_CONSENT__DIRECTORY}single-mother-child-relationship-include-performer.yaml"
59+
)
4860
GET_CONSENT__STATUS_PARAM_INVALID = f"{GET_CONSENT__DIRECTORY}errors/invalid-status-parameter.yaml"
4961
GET_CONSENT__MULTIPLE_RELATIONSHIPS_SINGLE_PATIENT = (
5062
f"{GET_CONSENT__DIRECTORY}multiple-relationships-single-patient.yaml"
@@ -58,6 +70,11 @@
5870
GET_CONSENT__MULTIPLE_RELATIONSHIPS_SINGLE_PATIENT_INCLUDE_BOTH = (
5971
f"{GET_CONSENT__DIRECTORY}multiple-relationships-single-patient-include-performer-patient.yaml"
6072
)
73+
74+
# GET Consent by ID
75+
GET_CONSENT_BY_ID__INVALID_ID_ERROR = f"{GET_CONSENT__DIRECTORY}ID/errors/invalid-id.yaml"
76+
GET_CONSENT_BY_ID__MISSING_ID_ERROR = f"{GET_CONSENT__DIRECTORY}ID/errors/missing-id.yaml"
77+
6178
# POST Consent
6279
POST_CONSENT__DIRECTORY = "./api/examples/POST_Consent/"
6380
POST_CONSENT__SUCCESS = f"{POST_CONSENT__DIRECTORY}success.yaml"

sandbox/api/get_consent_by_id.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from logging import getLogger
2+
from typing import Union
3+
4+
from flask import request
5+
6+
from .constants import (
7+
INTERNAL_SERVER_ERROR_EXAMPLE,
8+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP,
9+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH,
10+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT,
11+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER,
12+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP,
13+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH,
14+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT,
15+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER,
16+
GET_CONSENT_BY_ID__INVALID_ID_ERROR,
17+
GET_CONSENT_BY_ID__MISSING_ID_ERROR,
18+
BAD_REQUEST_INCLUDE_PARAM_INVALID,
19+
INVALIDATED_RESOURCE,
20+
)
21+
from .utils import generate_response_from_example, check_for_consent_include_params
22+
23+
logger = getLogger(__name__)
24+
25+
26+
def get_consent_by_id_response(identifier: str) -> Union[dict, tuple]:
27+
"""Sandbox API for GET /Consent/{id}
28+
29+
Returns:
30+
Union[dict, tuple]: Response for GET /Consent/{id}
31+
"""
32+
try:
33+
params = request.args.to_dict()
34+
if "_include" not in params and len(params) > 0:
35+
return generate_response_from_example(BAD_REQUEST_INCLUDE_PARAM_INVALID, 422)
36+
else:
37+
_include = request.args.getlist("_include")
38+
39+
if identifier == "74eed847-ca25-4e76-8cf2-f2c2d7842a7a":
40+
return check_for_consent_include_params(
41+
_include,
42+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP,
43+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_BOTH,
44+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PATIENT,
45+
GET_CONSENT__SINGLE_CONSENTING_ADULT_RELATIONSHIP_INCLUDE_PERFORMER,
46+
)
47+
elif identifier == "39df03a2-1b14-4d19-b1dc-d5d8cbf96948":
48+
return check_for_consent_include_params(
49+
_include,
50+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP,
51+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_BOTH,
52+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PATIENT,
53+
GET_CONSENT__SINGLE_MOTHER_CHILD_RELATIONSHIP_INCLUDE_PERFORMER,
54+
)
55+
elif identifier == "a0922245-1072-40c3-8f4e-a7490c10d365":
56+
return generate_response_from_example(INVALIDATED_RESOURCE, 404)
57+
elif identifier == " " or identifier is None:
58+
return generate_response_from_example(GET_CONSENT_BY_ID__MISSING_ID_ERROR, 400)
59+
else:
60+
return generate_response_from_example(GET_CONSENT_BY_ID__INVALID_ID_ERROR, 400)
61+
62+
except Exception:
63+
logger.exception("An error occurred while processing the request")
64+
return generate_response_from_example(INTERNAL_SERVER_ERROR_EXAMPLE, 500)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from json import dumps, loads
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
5+
from flask import Response
6+
7+
CONSENT_API_ENDPOINT = "/FHIR/R4/Consent"
8+
GET_CONSENT_BY_ID_FILE_PATH = "sandbox.api.get_consent_by_id"
9+
10+
11+
@pytest.mark.parametrize(
12+
("consent_id", "include_params", "response_file_name", "status_code"),
13+
[
14+
(
15+
"a0922245-1072-40c3-8f4e-a7490c10d365", # Invalid parameters
16+
"_invalid=test",
17+
"./api/examples/errors/invalid-include-parameter.yaml",
18+
422,
19+
),
20+
(
21+
"a0922245-1072-40c3-8f4e-a7490c10d365", # No proxy-role record found error
22+
"",
23+
"./api/examples/errors/invalidated-resource.yaml",
24+
404,
25+
),
26+
(
27+
" ", # Missing consent ID
28+
"",
29+
"./api/examples/GET_Consent/ID/errors/missing-id.yaml",
30+
400,
31+
),
32+
(
33+
"test", # Invalid consent ID
34+
"",
35+
"./api/examples/GET_Consent/ID/errors/invalid-id.yaml",
36+
400,
37+
),
38+
],
39+
)
40+
@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.generate_response_from_example")
41+
def test_get_consent_by_id_returns_expected_responses__mocked_get_consent_by_id(
42+
mock_generate_response_from_example: MagicMock,
43+
consent_id: str,
44+
include_params: str,
45+
response_file_name: str,
46+
status_code: int,
47+
client: object,
48+
) -> None:
49+
"""Test GET /Consent/{ID} endpoint."""
50+
mock_generate_response_from_example.return_value = mocked_response = Response(
51+
dumps({"data": "mocked"}),
52+
status=status_code,
53+
content_type="application/json",
54+
)
55+
# Act
56+
response = client.get(f"{CONSENT_API_ENDPOINT}/{consent_id}?{include_params}")
57+
# import pdb; pdb.set_trace()
58+
# Assert
59+
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
60+
assert response.status_code == status_code
61+
assert response.json == loads(mocked_response.get_data(as_text=True))
62+
63+
64+
@pytest.mark.parametrize(
65+
("consent_id", "include_params", "response_file_name", "status_code"),
66+
[
67+
(
68+
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship no include
69+
"",
70+
"./api/examples/GET_Consent/single-consenting-adult-relationship.yaml",
71+
200,
72+
),
73+
(
74+
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include performer
75+
"_include=Consent:performer",
76+
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-performer.yaml",
77+
200,
78+
),
79+
(
80+
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include patient
81+
"_include=Consent:patient",
82+
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-patient.yaml",
83+
200,
84+
),
85+
(
86+
"74eed847-ca25-4e76-8cf2-f2c2d7842a7a", # Single consenting adult relationship with include both
87+
"_include=Consent:performer&_include=Consent:patient",
88+
"./api/examples/GET_Consent/single-consenting-adult-relationship-include-performer-patient.yaml",
89+
200,
90+
),
91+
(
92+
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship no include
93+
"",
94+
"./api/examples/GET_Consent/single-mother-child-relationship.yaml",
95+
200,
96+
),
97+
(
98+
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include performer
99+
"_include=Consent:performer",
100+
"./api/examples/GET_Consent/single-mother-child-relationship-include-performer.yaml",
101+
200,
102+
),
103+
(
104+
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include patient
105+
"_include=Consent:patient",
106+
"./api/examples/GET_Consent/single-mother-child-relationship-include-patient.yaml",
107+
200,
108+
),
109+
(
110+
"39df03a2-1b14-4d19-b1dc-d5d8cbf96948", # Single adult-child relationship with include both
111+
"_include=Consent:performer&_include=Consent:patient",
112+
"./api/examples/GET_Consent/single-mother-child-relationship-include-performer-patient.yaml",
113+
200,
114+
),
115+
],
116+
)
117+
@patch("sandbox.api.utils.generate_response_from_example")
118+
def test_get_consent_by_id_returns_expected_responses__mocked_utils(
119+
mock_generate_response_from_example: MagicMock,
120+
consent_id: str,
121+
include_params: str,
122+
response_file_name: str,
123+
status_code: int,
124+
client: object,
125+
) -> None:
126+
"""Test GET /Consent/{ID} endpoint."""
127+
mock_generate_response_from_example.return_value = mocked_response = Response(
128+
dumps({"data": "mocked"}),
129+
status=status_code,
130+
content_type="application/json",
131+
)
132+
# Act
133+
response = client.get(f"{CONSENT_API_ENDPOINT}/{consent_id}?{include_params}")
134+
# import pdb; pdb.set_trace()
135+
# Assert
136+
mock_generate_response_from_example.assert_called_once_with(response_file_name, status_code)
137+
assert response.status_code == status_code
138+
assert response.json == loads(mocked_response.get_data(as_text=True))
139+
140+
141+
@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.check_for_consent_include_params")
142+
@patch(f"{GET_CONSENT_BY_ID_FILE_PATH}.generate_response_from_example")
143+
def test_get_consent_by_id__500_internal_server_error(
144+
mock_generate_response_from_example: MagicMock,
145+
mock_check_for_consent_include_params: MagicMock,
146+
client: object,
147+
) -> None:
148+
"""Test GET /Consent/{ID} endpoint."""
149+
mock_check_for_consent_include_params.side_effect = Exception("Test exception")
150+
# Act
151+
client.get(f"{CONSENT_API_ENDPOINT}/74eed847-ca25-4e76-8cf2-f2c2d7842a7a")
152+
# Assert
153+
mock_generate_response_from_example.assert_called_once_with("./api/examples/errors/internal-server-error.yaml", 500)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
ConsentSingleConsentingAdultRelationshipIncludePatientBundle:
2+
summary: Single consenting adult proxy relationship with patient details
3+
description:
4+
A Bundle containing a single proxy relationship between consenting adults including the patient details.
5+
value:
6+
resourceType: Bundle
7+
timestamp: '2020-08-26T14:00:00+00:00'
8+
total: 2
9+
type: searchset
10+
entry:
11+
- fullUrl: https://api.service.nhs.uk/validated-relationships/FHIR/R4/Patient/DFCC67F5
12+
resource:
13+
resourceType: Patient
14+
id: DFCC67F5
15+
identifier:
16+
- system: 'https://fhir.nhs.uk/Id/nhs-number'
17+
value: '9000000005'
18+
- system: 'https://placeholder.fhir.nhs.uk/Id/local-gp-patient-identifier'
19+
value: ABC1234567
20+
name:
21+
- id: '123456'
22+
use: usual
23+
period:
24+
start: '2020-01-01'
25+
end: '2021-12-31'
26+
given:
27+
- Sally
28+
family: Evans
29+
prefix:
30+
- Mrs
31+
birthDate: '1995-10-22'
32+
generalPractitioner:
33+
- type: Organization
34+
identifier:
35+
value: ODS12345
36+
system: 'https://fhir.nhs.uk/Id/ods-organization-code'
37+
search:
38+
mode: include
39+
- fullUrl: https://api.service.nhs.uk/validated-relationships/FHIR/R4/Consent/WWCC67T1
40+
resource:
41+
resourceType: Consent
42+
id: WWCC67T1
43+
status: active
44+
scope:
45+
coding:
46+
- system: 'http://terminology.hl7.org/CodeSystem/consentscope'
47+
code: patient-privacy
48+
display: Privacy Consent
49+
text: Patient Privacy Consent
50+
category:
51+
- coding:
52+
- system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode'
53+
code: INFA
54+
display: Information Access
55+
text: Information Access Consent
56+
patient:
57+
identifier:
58+
system: 'https://fhir.nhs.uk/Id/nhs-number'
59+
value: '9000000005'
60+
dateTime: '2024-07-21T17:32:28Z'
61+
performer:
62+
- identifier:
63+
system: 'https://fhir.nhs.uk/Id/nhs-number'
64+
value: '9000000010'
65+
verification:
66+
- verified: true
67+
verifiedWith:
68+
identifier:
69+
system: 'https://fhir.nhs.uk/Id/nhs-number'
70+
value: '9000000005'
71+
verificationDate: '2024-07-21T17:32:28Z'
72+
search:
73+
mode: match

0 commit comments

Comments
 (0)