Skip to content

Commit 857f720

Browse files
committed
Refactor FhirService validation to use deep copy of immunization data
- Updated `_validate_immunization` method to validate a deep copy of the immunization input, ensuring original data remains unchanged. - Enhanced test cases to validate against the modified structure, ensuring all SNOMED codings are persisted during creation and update of immunizations.
1 parent ab0b21f commit 857f720

2 files changed

Lines changed: 36 additions & 20 deletions

File tree

lambdas/backend/src/service/fhir_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ def _normalize_single_snomed_codeable_concepts(cls, immunization: dict) -> None:
9797
field["coding"] = cls._keep_first_snomed_coding(coding)
9898

9999
def _validate_immunization(self, immunization: dict) -> None:
100-
self._normalize_single_snomed_codeable_concepts(immunization)
100+
immunization_to_validate = copy.deepcopy(immunization)
101+
self._normalize_single_snomed_codeable_concepts(immunization_to_validate)
101102

102103
try:
103-
self.validator.validate(immunization)
104+
self.validator.validate(immunization_to_validate)
104105
except (ValueError, MandatoryError) as error:
105106
raise CustomValidationError(message=str(error)) from error
106107

lambdas/backend/tests/service/test_fhir_service.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,19 @@
3636
NHS_NUMBER_USED_IN_SAMPLE_DATA = "9000000009"
3737

3838

39-
def add_snomed_coding(immunization: dict, field_name: str, code: str, display: str) -> dict:
40-
first_coding = deepcopy(immunization[field_name]["coding"][0])
41-
immunization[field_name]["coding"].append({"system": first_coding["system"], "code": code, "display": display})
42-
return first_coding
39+
def add_duplicate_snomed_with_leading_non_snomed_coding(
40+
immunization: dict, field_name: str, code: str, display: str
41+
) -> list:
42+
first_snomed_coding = deepcopy(immunization[field_name]["coding"][0])
43+
leading_non_snomed_coding = deepcopy(first_snomed_coding)
44+
leading_non_snomed_coding["system"] = "http://snomed.info/test"
45+
duplicate_snomed_coding = {**first_snomed_coding, "code": code, "display": display}
46+
immunization[field_name]["coding"] = [
47+
leading_non_snomed_coding,
48+
first_snomed_coding,
49+
duplicate_snomed_coding,
50+
]
51+
return deepcopy(immunization[field_name]["coding"])
4352

4453

4554
class TestFhirServiceBase(unittest.TestCase):
@@ -354,6 +363,7 @@ def test_create_immunization(self):
354363

355364
nhs_number = VALID_NHS_NUMBER
356365
req_imms = create_covid_immunization_dict_no_id(nhs_number)
366+
expected_validated_imms = deepcopy(req_imms)
357367

358368
# When
359369
created_id = self.fhir_service.create_immunization(req_imms, "Test")
@@ -365,28 +375,30 @@ def test_create_immunization(self):
365375
)
366376
self.imms_repo.create_immunization.assert_called_once_with(Immunization.parse_obj(req_imms), "Test")
367377

368-
self.validator.validate.assert_called_once_with(req_imms)
378+
self.validator.validate.assert_called_once_with(expected_validated_imms)
369379
self.assertEqual(self._MOCK_NEW_UUID, created_id)
370380

371-
def test_create_immunization_keeps_first_site_and_route_snomed_coding(self):
372-
"""it should keep the first SNOMED coding for site and route during API create"""
381+
def test_create_immunization_persists_all_site_and_route_codings(self):
382+
"""it should validate against the first SNOMED coding and persist all codings during API create"""
373383
self.mock_redis.hget.return_value = "COVID"
374384
self.mock_redis_getter.return_value = self.mock_redis
375385
self.authoriser.authorise.return_value = True
376386
self.imms_repo.check_immunization_identifier_exists.return_value = False
377387
self.imms_repo.create_immunization.return_value = self._MOCK_NEW_UUID
378388

379389
req_imms = create_covid_immunization_dict_no_id(VALID_NHS_NUMBER)
380-
first_site_coding = add_snomed_coding(req_imms, "site", "999999999", "Replacement site that should be ignored")
381-
first_route_coding = add_snomed_coding(
390+
expected_site_codings = add_duplicate_snomed_with_leading_non_snomed_coding(
391+
req_imms, "site", "999999999", "Replacement site that should be ignored"
392+
)
393+
expected_route_codings = add_duplicate_snomed_with_leading_non_snomed_coding(
382394
req_imms, "route", "888888888", "Replacement route that should be ignored"
383395
)
384396

385397
created_id = self.pre_validate_fhir_service.create_immunization(req_imms, "Test")
386398

387399
self.assertEqual(self._MOCK_NEW_UUID, created_id)
388-
self.assertEqual(req_imms["site"]["coding"], [first_site_coding])
389-
self.assertEqual(req_imms["route"]["coding"], [first_route_coding])
400+
self.assertEqual(req_imms["site"]["coding"], expected_site_codings)
401+
self.assertEqual(req_imms["route"]["coding"], expected_route_codings)
390402
self.imms_repo.create_immunization.assert_called_once_with(Immunization.parse_obj(req_imms), "Test")
391403

392404
def test_create_immunization_with_id_throws_error(self):
@@ -500,6 +512,7 @@ def test_raises_duplicate_error_if_identifier_already_exits(self):
500512

501513
nhs_number = VALID_NHS_NUMBER
502514
req_imms = create_covid_immunization_dict_no_id(nhs_number)
515+
expected_validated_imms = deepcopy(req_imms)
503516

504517
# When
505518
with self.assertRaises(IdentifierDuplicationError) as error:
@@ -511,7 +524,7 @@ def test_raises_duplicate_error_if_identifier_already_exits(self):
511524
"https://supplierABC/identifiers/vacc", "ACME-vacc123456"
512525
)
513526
self.imms_repo.create_immunization.assert_not_called()
514-
self.validator.validate.assert_called_once_with(req_imms)
527+
self.validator.validate.assert_called_once_with(expected_validated_imms)
515528
self.assertEqual(
516529
"The provided identifier: https://supplierABC/identifiers/vacc#ACME-vacc123456 is duplicated",
517530
str(error.exception),
@@ -566,19 +579,19 @@ def test_update_immunization(self):
566579
self.assertEqual(call_args[3], "Test")
567580
self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.UPDATE, {"COVID"})
568581

569-
def test_update_immunization_keeps_first_site_and_route_snomed_coding(self):
570-
"""it should keep the first SNOMED coding for site and route during API update"""
582+
def test_update_immunization_persists_all_site_and_route_codings(self):
583+
"""it should validate against the first SNOMED coding and persist all codings during API update"""
571584
imms_id = "an-id"
572585
original_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER)
573586
identifier = Identifier(
574587
system=original_immunisation["identifier"][0]["system"],
575588
value=original_immunisation["identifier"][0]["value"],
576589
)
577590
updated_immunisation = create_covid_immunization_dict(imms_id, VALID_NHS_NUMBER, "2021-02-07T13:28:00+00:00")
578-
first_site_coding = add_snomed_coding(
591+
expected_site_codings = add_duplicate_snomed_with_leading_non_snomed_coding(
579592
updated_immunisation, "site", "999999999", "Replacement site that should be ignored"
580593
)
581-
first_route_coding = add_snomed_coding(
594+
expected_route_codings = add_duplicate_snomed_with_leading_non_snomed_coding(
582595
updated_immunisation, "route", "888888888", "Replacement route that should be ignored"
583596
)
584597
existing_resource_meta = ImmunizationRecordMetadata(
@@ -595,9 +608,11 @@ def test_update_immunization_keeps_first_site_and_route_snomed_coding(self):
595608
updated_version = self.fhir_service.update_immunization(imms_id, updated_immunisation, "Test", 1)
596609

597610
self.assertEqual(updated_version, 2)
598-
self.assertEqual(updated_immunisation["site"]["coding"], [first_site_coding])
599-
self.assertEqual(updated_immunisation["route"]["coding"], [first_route_coding])
611+
self.assertEqual(updated_immunisation["site"]["coding"], expected_site_codings)
612+
self.assertEqual(updated_immunisation["route"]["coding"], expected_route_codings)
600613
self.imms_repo.update_immunization.assert_called_once()
614+
call_args = self.imms_repo.update_immunization.call_args[0]
615+
self.assertEqual(call_args[1], Immunization.parse_obj(updated_immunisation))
601616

602617
def test_update_immunization_raises_validation_exception_when_nhs_number_invalid(self):
603618
"""it should raise a CustomValidationError when the patient's NHS number in the payload is invalid"""

0 commit comments

Comments
 (0)