Skip to content

Commit dac7523

Browse files
authored
VED-1073 Fix unhandled exception when searching record by identifier where pat has no NHS Number (#1229)
1 parent 71a0bad commit dac7523

9 files changed

Lines changed: 117 additions & 24 deletions

File tree

.github/workflows/run-e2e-automation-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ jobs:
197197
echo "::add-mask::$CODE"
198198
199199
echo "Requesting access token from Apigee..."
200-
response=$(curl -s -X POST "https://login.apigee.com/oauth/token" \
200+
response=$(curl -s --retry 3 -X POST "https://login.apigee.com/oauth/token" \
201201
-H "Content-Type: application/x-www-form-urlencoded" \
202202
-H "Accept: application/json;charset=utf-8" \
203203
-H "Authorization: Basic $APIGEE_BASIC_AUTH_TOKEN" \

lambdas/backend/src/filter.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Functions for filtering a FHIR Immunization Resource"""
22

3-
from common.models.constants import Urls
3+
import copy
4+
5+
from common.models.constants import Constants, Urls
46
from common.models.utils.generic_utils import (
57
get_contained_patient,
68
get_contained_practitioner,
@@ -28,23 +30,34 @@ def create_reference_to_patient_resource(patient_full_url: str, patient: dict) -
2830
"""
2931
Returns a reference to the given patient which includes the patient nhs number identifier (system and value fields
3032
only) and a reference to patient full url. "Type" field is set to "Patient".
33+
34+
Due to validation business rules, it is possible (VED-1073) for a patient to be created without a value for NHS
35+
Number or indeed an entry for their identifier.
3136
"""
32-
patient_nhs_number_identifier = [x for x in patient["identifier"] if x.get("system") == Urls.NHS_NUMBER][0]
37+
patient_nhs_number_identifier: dict | None = None
38+
39+
for identifier in patient.get("identifier", []):
40+
if identifier.get("system", "") == Urls.NHS_NUMBER:
41+
patient_nhs_number_identifier = copy.deepcopy(identifier)
42+
break
43+
44+
if patient_nhs_number_identifier is None:
45+
return {
46+
"reference": patient_full_url,
47+
"type": Constants.PATIENT_RESOURCE_TYPE,
48+
}
3349

3450
return {
3551
"reference": patient_full_url,
36-
"type": "Patient",
37-
"identifier": {
38-
"system": patient_nhs_number_identifier["system"],
39-
"value": patient_nhs_number_identifier["value"],
40-
},
52+
"type": Constants.PATIENT_RESOURCE_TYPE,
53+
"identifier": patient_nhs_number_identifier,
4154
}
4255

4356

4457
def replace_address_postal_codes(imms: dict) -> dict:
4558
"""Replace any postal codes found in contained patient address with 'ZZ99 3CZ'"""
4659
for resource in imms.get("contained", [{}]):
47-
if resource.get("resourceType") == "Patient":
60+
if resource.get("resourceType") == Constants.PATIENT_RESOURCE_TYPE:
4861
for address in resource.get("address", [{}]):
4962
if address.get("postalCode") is not None:
5063
address["postalCode"] = "ZZ99 3CZ"

lambdas/backend/tests/service/test_fhir_service.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,38 @@ def test_get_immunization_by_identifier_when_elements_parameter_provided(self):
288288
Immunization.construct(**{"resourceType": "Immunization", "id": "1234-some-id", "meta": {"versionId": 1}}),
289289
)
290290

291+
def test_get_immunization_by_identifier_returns_patient_created_without_nhs_number(self):
292+
"""VED-1073 - the contained patient within an Immunization resource MAY be created without an NHS Number as per
293+
the business rules. The identifier retrieval should still succeed when this is the case."""
294+
mock_resource_no_nhs_number = create_covid_immunization_dict("1234-some-id", omit_nhs_number=True)
295+
296+
self.mock_redis.hget.return_value = "COVID"
297+
self.mock_redis_getter.return_value = self.mock_redis
298+
self.authoriser.authorise.return_value = True
299+
self.imms_repo.get_immunization_by_identifier.return_value = mock_resource_no_nhs_number, self.mock_resource_meta
300+
301+
# When
302+
result = self.fhir_service.get_immunization_by_identifier(self.test_identifier, self.MOCK_SUPPLIER_NAME, None)
303+
304+
# Then
305+
self.imms_repo.get_immunization_by_identifier.assert_called_once_with(self.test_identifier)
306+
self.authoriser.authorise.assert_called_once_with(self.MOCK_SUPPLIER_NAME, ApiOperationCode.SEARCH, {"COVID"})
307+
308+
self.assertEqual(result.type, "searchset")
309+
self.assertEqual(result.total, 1)
310+
self.assertEqual(
311+
result.link[0],
312+
BundleLink.construct(
313+
relation="self",
314+
url="https://internal-dev.api.service.nhs.uk/immunisation-fhir-api/FHIR/R4/Immunization?identifier=some-"
315+
"system|some-value",
316+
),
317+
)
318+
319+
# Search function adds meta to the resource
320+
mock_resource_no_nhs_number["meta"] = {"versionId": "1"}
321+
self.assertEqual(result.entry[0].resource, Immunization.parse_obj(mock_resource_no_nhs_number))
322+
291323

292324
class TestCreateImmunization(TestFhirServiceBase):
293325
"""Tests for FhirService.create_immunization"""

lambdas/backend/tests/test_filter.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@ def test_create_reference_to_patient_resource(self):
8686
expected_output,
8787
)
8888

89+
def test_create_reference_to_patient_resource_when_identifier_missing(self):
90+
"""Test that create_reference_to_patient_resource creates an appropriate reference without identifier info
91+
where the saved patient record does not contain this information"""
92+
test_data = deepcopy(self.bundle_patient_resource)
93+
del test_data["identifier"]
94+
95+
patient_uuid = str(uuid4())
96+
expected_output = {"reference": patient_uuid, "type": "Patient"}
97+
98+
self.assertEqual(
99+
create_reference_to_patient_resource(patient_uuid, test_data),
100+
expected_output,
101+
)
102+
103+
def test_create_reference_to_patient_resource_when_nhs_number_missing(self):
104+
"""Test that create_reference_to_patient_resource creates an appropriate reference with empty
105+
identifier[0].value info where the saved patient record does not contain this information"""
106+
test_data = deepcopy(self.bundle_patient_resource)
107+
del test_data["identifier"][0]["value"]
108+
109+
patient_uuid = str(uuid4())
110+
expected_output = {
111+
"reference": patient_uuid,
112+
"type": "Patient",
113+
"identifier": {"system": "https://fhir.nhs.uk/Id/nhs-number"},
114+
}
115+
116+
self.assertEqual(
117+
create_reference_to_patient_resource(patient_uuid, test_data),
118+
expected_output,
119+
)
120+
89121
def test_add_use_to_identifier(self):
90122
"""Test that a use of "offical" is added to identifier[0] is no use already given"""
91123
input_data = deepcopy(self.covid_immunization_event)

lambdas/shared/src/common/models/constants.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ class Constants:
4545
"address",
4646
},
4747
}
48-
49-
ALLOWED_CONTAINED_RESOURCES = {"Practitioner", "Patient"}
48+
PATIENT_RESOURCE_TYPE = "Patient"
49+
PRACTITIONER_RESOURCE_TYPE = "Practitioner"
50+
ALLOWED_CONTAINED_RESOURCES = {PRACTITIONER_RESOURCE_TYPE, PATIENT_RESOURCE_TYPE}
5051

5152
# As per Personal Demographics Service (PDS) FHIR API, the maximum length of a family name is 35 characters:
5253
# https://digital.nhs.uk/developer/api-catalogue/personal-demographics-service-fhir#post-/Patient

lambdas/shared/tests/test_common/testing_utils/immunization_utils.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from fhir.resources.R4B.immunization import Immunization
44

5+
from common.models.constants import Constants
56
from test_common.testing_utils.generic_utils import load_json_data
67
from test_common.testing_utils.values_for_tests import ValidValues
78

@@ -18,13 +19,17 @@ def create_covid_immunization_dict(
1819
nhs_number: str = VALID_NHS_NUMBER,
1920
occurrence_date_time: str = "2021-02-07T13:28:17+00:00",
2021
status: str = "completed",
22+
omit_nhs_number: bool = False,
2123
):
2224
immunization_json = load_json_data("completed_covid_immunization_event.json")
2325
immunization_json["id"] = imms_id
2426

25-
[x for x in immunization_json["contained"] if x.get("resourceType") == "Patient"][0]["identifier"][0]["value"] = (
26-
nhs_number
27-
)
27+
for contained_resource in immunization_json.get("contained", []):
28+
if contained_resource.get("resourceType") == Constants.PATIENT_RESOURCE_TYPE:
29+
if omit_nhs_number:
30+
del contained_resource["identifier"][0]["value"]
31+
else:
32+
contained_resource["identifier"][0]["value"] = nhs_number
2833

2934
immunization_json["occurrenceDateTime"] = occurrence_date_time
3035
immunization_json["status"] = status

tests/e2e_automation/features/APITests/search.feature

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,22 @@ Feature: Search the immunization of a patient
201201
@Delete_cleanUp @vaccine_type_FLU @patient_id_Random @supplier_name_Postman_Auth
202202
Scenario: Flu event is created and search post request fetch the only one record matched with identifier
203203
Given I have created a valid vaccination record
204-
When Send a search request with Post method using identifier header for Immunization event created
204+
When I send a search request with Post method using identifier parameter for Immunization event created
205+
Then The request will be successful with the status code '200'
206+
And correct immunization event is returned in the response
207+
208+
@smoke
209+
@Delete_cleanUp @vaccine_type_FLU @patient_id_NullNHS @supplier_name_Postman_Auth
210+
Scenario: Search by Identifier (POST) is successful when patient does not have an NHS Number recorded
211+
Given I have created a valid vaccination record
212+
When I send a search request with Post method using identifier parameter for Immunization event created
205213
Then The request will be successful with the status code '200'
206214
And correct immunization event is returned in the response
207215

208216
@Delete_cleanUp @vaccine_type_FLU @patient_id_Random @supplier_name_Postman_Auth
209217
Scenario: Flu event is created and search post request fetch the only one record matched with identifier and _elements
210218
Given I have created a valid vaccination record
211-
When Send a search request with Post method using identifier and _elements header for Immunization event created
219+
When I send a search request with Post method using identifier and _elements parameters for Immunization event created
212220
Then The request will be successful with the status code '200'
213221
And correct immunization event is returned in the response with only specified elements
214222

@@ -217,7 +225,7 @@ Feature: Search the immunization of a patient
217225
Scenario: Flu event is created and search post request fetch the only one record matched with identifier with correct version id
218226
Given I have created a valid vaccination record
219227
And created event is being updated twice
220-
When Send a search request with Post method using identifier header for Immunization event created
228+
When I send a search request with Post method using identifier parameter for Immunization event created
221229
Then The request will be successful with the status code '200'
222230
And correct immunization event is returned in the response
223231

@@ -226,13 +234,13 @@ Feature: Search the immunization of a patient
226234
Scenario: Flu event is created and search post request fetch the only one record matched with identifier and _elements with correct version id
227235
Given I have created a valid vaccination record
228236
And created event is being updated twice
229-
When Send a search request with Post method using identifier and _elements header for Immunization event created
237+
When I send a search request with Post method using identifier and _elements parameters for Immunization event created
230238
Then The request will be successful with the status code '200'
231239
And correct immunization event is returned in the response with only specified elements
232240

233241
@smoke
234242
@vaccine_type_4IN1 @patient_id_Random @supplier_name_Postman_Auth
235243
Scenario: Empty search response will be received when no record is found for the given identifier in search request
236-
When Send a search request with post method using invalid identifier header for Immunization event created
244+
When I send a search request with Post method using an invalid identifier header for Immunization event created
237245
Then The request will be successful with the status code '200'
238246
And Empty immunization event is returned in the response

tests/e2e_automation/features/APITests/steps/test_search_steps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
scenarios("APITests/search.feature")
2121

2222

23-
@when("Send a search request with Post method using identifier header for Immunization event created")
23+
@when("I send a search request with Post method using identifier parameter for Immunization event created")
2424
def send_search_post_request_with_identifier_header(context):
2525
get_search_post_url_header(context)
2626
context.request = {
@@ -30,7 +30,9 @@ def send_search_post_request_with_identifier_header(context):
3030
context.response = http_requests_session.post(context.url, headers=context.headers, data=context.request)
3131

3232

33-
@when("Send a search request with Post method using identifier and _elements header for Immunization event created")
33+
@when(
34+
"I send a search request with Post method using identifier and _elements parameters for Immunization event created"
35+
)
3436
def send_search_post_request_with_identifier_and_elements_header(context):
3537
get_search_post_url_header(context)
3638
context.request = {
@@ -41,7 +43,7 @@ def send_search_post_request_with_identifier_and_elements_header(context):
4143
context.response = http_requests_session.post(context.url, headers=context.headers, data=context.request)
4244

4345

44-
@when("Send a search request with post method using invalid identifier header for Immunization event created")
46+
@when("I send a search request with Post method using an invalid identifier header for Immunization event created")
4547
def send_search_post_request_with_invalid_identifier_header(context):
4648
get_search_post_url_header(context)
4749
context.request = {"identifier": f"https://www.ieds.england.nhs.uk/|{str(uuid.uuid4())}", "_elements": "meta,id"}

tests/e2e_automation/input/testData.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id,nhs_number,family_name,given_name,gender,birth_date,address_text,address_line,city,district,state,postal_code,country,start_date,end_date
22
ValidNHS,9449309981,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
3-
NUllNHS,,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
3+
NullNHS,none,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
44
SFlag,9449310475,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
55
InvalidInPDS,9449310599,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
66
InvalidMOD11Check,1234567890,test1,test2,unknown,1980-11-01,Validate Obf,"1, obf_2",obf_3,obf_4,obf_5,LS01 1AB,obf_7,2000-01-01,2025-01-01
@@ -1006,4 +1006,4 @@ Valid_NHS,9000046696,GLENN,ROWE,male,2005-03-14,Moore 01 Bishop,Essex,City,distr
10061006
Valid_NHS,9734891650,HETTY,DAWKIN,female,2005-03-14,1 GARDEN COURT,EPWORTH,City,district,State,DN9 1GA,UK,2016-06-12,2033-01-01
10071007
Valid_NHS,9734281674,LANDON,EWART,male,2005-03-14,1186 THE MOORS,KIDLINGTON,City,district,State,OX5 2AD,UK,2021-09-29,2033-01-01
10081008
Valid_NHS,9734353055,BETSY,HOPSON,female,2005-03-14,1186 THE MOORS,KIDLINGTON,City,district,State,OX4 4LX,UK,2013-01-28,2033-01-01
1009-
Valid_NHS,9734107720,SARAH,PEILL,female,2005-03-14,1 DELAMERE,WYNYARD,City,district,State,TS22 5GH,UK,2016-11-10,2033-01-01
1009+
Valid_NHS,9734107720,SARAH,PEILL,female,2005-03-14,1 DELAMERE,WYNYARD,City,district,State,TS22 5GH,UK,2016-11-10,2033-01-01

0 commit comments

Comments
 (0)