Skip to content

Commit bd61b69

Browse files
committed
Refactored delete journey
1 parent b603f12 commit bd61b69

File tree

6 files changed

+56
-121
lines changed

6 files changed

+56
-121
lines changed

backend/src/fhir_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def delete_immunization(self, aws_event):
374374
return response
375375

376376
try:
377-
self.fhir_service.delete_immunization(imms_id, imms_vax_type_perms, supplier_system)
377+
self.fhir_service.delete_immunization(imms_id, supplier_system)
378378
return self.create_response(204)
379379

380380
except ResourceNotFoundError as not_found:

backend/src/fhir_repository.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -334,21 +334,10 @@ def _perform_dynamo_update(
334334
response=error.response,
335335
)
336336

337-
def delete_immunization(
338-
self, imms_id: str, imms_vax_type_perms: str, supplier_system: str) -> dict:
337+
def delete_immunization(self, imms_id: str, supplier_system: str) -> dict:
339338
now_timestamp = int(time.time())
340339

341340
try:
342-
item = self.table.get_item(Key={"PK": _make_immunization_pk(imms_id)}).get("Item")
343-
if not item:
344-
raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id)
345-
346-
if not item.get("DeletedAt") or item.get("DeletedAt") == "reinstated":
347-
vaccine_type = self._vaccine_type(item["PatientSK"])
348-
if not validate_permissions(imms_vax_type_perms, ApiOperationCode.DELETE, [vaccine_type]):
349-
raise UnauthorizedVaxError()
350-
351-
# Proceed with delete update
352341
response = self.table.update_item(
353342
Key={"PK": _make_immunization_pk(imms_id)},
354343
UpdateExpression=(

backend/src/fhir_service.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from authorisation.api_operation_code import ApiOperationCode
1919
from authorisation.authoriser import Authoriser
2020
from fhir_repository import ImmunizationRepository
21-
from models.errors import InvalidPatientId, CustomValidationError, UnauthorizedVaxError
21+
from models.errors import InvalidPatientId, CustomValidationError, UnauthorizedVaxError, ResourceNotFoundError
2222
from models.fhir_immunization import ImmunizationValidator
2323
from models.utils.generic_utils import nhs_number_mod11_check, get_occurrence_datetime, create_diagnostics, form_json, get_contained_patient
2424
from models.errors import MandatoryError
@@ -217,14 +217,24 @@ def update_reinstated_immunization(
217217

218218
return UpdateOutcome.UPDATE, Immunization.parse_obj(imms), updated_version
219219

220-
def delete_immunization(self, imms_id: str, imms_vax_type_perms, supplier_system: str) -> Immunization:
220+
def delete_immunization(self, imms_id: str, supplier_system: str) -> Immunization:
221221
"""
222222
Delete an Immunization if it exits and return the ID back if successful.
223-
Exception will be raised if resource didn't exit. Multiple calls to this method won't change
223+
Exception will be raised if resource does not exist. Multiple calls to this method won't change
224224
the record in the database.
225225
"""
226+
existing_immunisation = self.immunization_repo.get_immunization_by_id(imms_id)
227+
228+
if not existing_immunisation:
229+
raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id)
230+
231+
vaccination_type = get_vaccine_type(existing_immunisation.get("Resource", {}))
232+
233+
if not self.authoriser.authorise(supplier_system, ApiOperationCode.DELETE, {vaccination_type}):
234+
raise UnauthorizedVaxError()
235+
226236
imms = self.immunization_repo.delete_immunization(
227-
imms_id, imms_vax_type_perms, supplier_system
237+
imms_id, supplier_system
228238
)
229239
return Immunization.parse_obj(imms)
230240

backend/tests/test_fhir_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1584,7 +1584,7 @@ def test_delete_immunization(self, mock_get_supplier_permissions):
15841584
response = self.controller.delete_immunization(lambda_event)
15851585

15861586
# Then
1587-
self.service.delete_immunization.assert_called_once_with(imms_id, ["COVID19.CRUDS"], "Test")
1587+
self.service.delete_immunization.assert_called_once_with(imms_id, "Test")
15881588

15891589
self.assertEqual(response["statusCode"], 204)
15901590
self.assertTrue("body" not in response)

backend/tests/test_fhir_repository.py

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ def test_delete_immunization(self):
482482
with patch("time.time") as mock_time:
483483
mock_time.return_value = now_epoch
484484
# When
485-
_id = self.repository.delete_immunization(imms_id, "COVID:delete", "Test")
485+
_id = self.repository.delete_immunization(imms_id, "Test")
486486

487487
# Then
488488
self.table.update_item.assert_called_once_with(
@@ -493,89 +493,17 @@ def test_delete_immunization(self):
493493
ConditionExpression=ANY,
494494
)
495495

496-
def test_delete_returns_old_resource(self):
497-
"""it should return existing Immunization when delete is successful"""
498-
499-
imms_id = "an-id"
500-
resource = {"foo": "bar"}
501-
dynamo_response = {
502-
"ResponseMetadata": {"HTTPStatusCode": 200},
503-
"Attributes": {"Resource": json.dumps(resource)},
504-
}
505-
self.table.update_item = MagicMock(return_value=dynamo_response)
506-
self.table.get_item = MagicMock(
507-
return_value={
508-
"Item": {
509-
"Resource": json.dumps({"foo": "bar"}),
510-
"Version": 1,
511-
"PatientSK": "COVID19#2516525251",
512-
}
513-
}
514-
)
515-
516-
now_epoch = 123456
517-
with patch("time.time") as mock_time:
518-
mock_time.return_value = now_epoch
519-
# When
520-
521-
act_resource = self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
522-
523-
# Then
524-
self.table.update_item.assert_called_once_with(
525-
Key=ANY,
526-
UpdateExpression=ANY,
527-
ExpressionAttributeValues=ANY,
528-
ConditionExpression=ANY,
529-
ReturnValues="ALL_NEW",
530-
)
531-
self.assertDictEqual(act_resource, resource)
532-
533-
def test_unauthorised_vax_delete(self):
534-
"""when delete is called for a resource without proper vax permission"""
535-
imms_id = "an-id"
536-
self.table.get_item = MagicMock(
537-
return_value={
538-
"Item": {
539-
"Resource": json.dumps({"foo": "bar"}),
540-
"Version": 1,
541-
"PatientSK": "FLU#2516525251",
542-
"DeletedAt": "reinstated"
543-
}
544-
}
545-
)
546-
547-
self.repository.table.update_item.return_value = {
548-
"ResponseMetadata": {
549-
"HTTPStatusCode": 200
550-
},
551-
"Attributes": {
552-
"Resource": json.dumps({"id": "valid-id", "status": "deleted"})
553-
}
554-
}
555-
556-
with self.assertRaises(UnauthorizedVaxError) as e:
557-
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
558-
559496
def test_multiple_delete_should_not_update_timestamp(self):
560497
"""when delete is called multiple times, or when it doesn't exist, it should not update DeletedAt,
561498
and it should return Error"""
562499
imms_id = "an-id"
563500
error_res = {"Error": {"Code": "ConditionalCheckFailedException"}}
564-
self.table.get_item = MagicMock(
565-
return_value={
566-
"Item": {
567-
"Resource": json.dumps({"foo": "bar"}),
568-
"Version": 1,
569-
"PatientSK": "COVID19#2516525251",
570-
}
571-
}
572-
)
573501
self.table.update_item.side_effect = botocore.exceptions.ClientError(
574502
error_response=error_res, operation_name="an-op"
575503
)
576504

577505
with self.assertRaises(ResourceNotFoundError) as e:
578-
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
506+
self.repository.delete_immunization(imms_id, "Test")
579507

580508
# Then
581509
self.table.update_item.assert_called_once_with(
@@ -596,20 +524,11 @@ def test_delete_throws_error_when_response_can_not_be_handled(self):
596524
imms_id = "an-id"
597525
bad_request = 400
598526
response = {"ResponseMetadata": {"HTTPStatusCode": bad_request}}
599-
self.table.get_item = MagicMock(
600-
return_value={
601-
"Item": {
602-
"Resource": json.dumps({"foo": "bar"}),
603-
"Version": 1,
604-
"PatientSK": "COVID19#2516525251",
605-
}
606-
}
607-
)
608527
self.table.update_item = MagicMock(return_value=response)
609528

610529
with self.assertRaises(UnhandledResponseError) as e:
611530
# When
612-
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
531+
self.repository.delete_immunization(imms_id, "Test")
613532

614533
# Then
615534
self.assertDictEqual(e.exception.response, response)
@@ -807,7 +726,7 @@ def test_decimal_on_update(self):
807726
self.run_update_immunization_test(imms_id, imms, resource, updated_dose_quantity)
808727

809728
def test_decimal_on_update_patient(self):
810-
"""it should update record by replacing both Immunization and Patient and dosequantity"""
729+
"""it should update record by replacing both Immunization and Patient and dose quantity"""
811730
imms_id = "an-imms-id"
812731
imms = create_covid_19_immunization_dict(imms_id)
813732
imms["doseQuantity"] = 1.590

backend/tests/test_fhir_service.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from authorisation.authoriser import Authoriser
1616
from fhir_repository import ImmunizationRepository
1717
from fhir_service import FhirService, UpdateOutcome, get_service_url
18-
from models.errors import InvalidPatientId, CustomValidationError, UnauthorizedVaxError
18+
from models.errors import InvalidPatientId, CustomValidationError, UnauthorizedVaxError, ResourceNotFoundError
1919
from models.fhir_immunization import ImmunizationValidator
2020
from models.utils.generic_utils import get_contained_patient
2121
from pydantic import ValidationError
@@ -697,10 +697,12 @@ def test_update_reinstated_immunization_with_diagnostics(self):
697697
self.imms_repo.update_reinstated_immunization.assert_not_called()
698698

699699

700-
class TestDeleteImmunization(unittest.TestCase):
700+
class TestDeleteImmunization(TestFhirServiceBase):
701701
"""Tests for FhirService.delete_immunization"""
702+
TEST_IMMUNISATION_ID = "an-id"
702703

703704
def setUp(self):
705+
super().setUp()
704706
self.authoriser = create_autospec(Authoriser)
705707
self.imms_repo = create_autospec(ImmunizationRepository)
706708
self.validator = create_autospec(ImmunizationValidator)
@@ -710,33 +712,48 @@ def setUp(self):
710712

711713
def test_delete_immunization(self):
712714
"""it should delete Immunization record"""
713-
imms_id = "an-id"
714-
imms = json.loads(create_covid_19_immunization(imms_id).json())
715+
imms = json.loads(create_covid_19_immunization(self.TEST_IMMUNISATION_ID).json())
716+
self.mock_redis_client.hget.return_value = "COVID19"
717+
self.authoriser.authorise.return_value = True
718+
self.imms_repo.get_immunization_by_id.return_value = {"Resource": imms}
715719
self.imms_repo.delete_immunization.return_value = imms
716720

717721
# When
718-
act_imms = self.fhir_service.delete_immunization(imms_id, "COVID.CRUDS", "Test")
722+
act_imms = self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test")
719723

720724
# Then
721-
self.imms_repo.delete_immunization.assert_called_once_with(imms_id, "COVID.CRUDS", "Test")
725+
self.imms_repo.get_immunization_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID)
726+
self.imms_repo.delete_immunization.assert_called_once_with(self.TEST_IMMUNISATION_ID, "Test")
727+
self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.DELETE, {"COVID19"})
722728
self.assertIsInstance(act_imms, Immunization)
723-
self.assertEqual(act_imms.id, imms_id)
729+
self.assertEqual(act_imms.id, self.TEST_IMMUNISATION_ID)
724730

725-
def test_delete_immunization_for_batch(self):
726-
"""it should delete Immunization record"""
727-
imms_id = "an-id"
728-
imms = json.loads(create_covid_19_immunization(imms_id).json())
729-
self.imms_repo.delete_immunization.return_value = imms
731+
def test_delete_immunization_throws_not_found_exception_if_does_not_exist(self):
732+
"""it should raise a ResourceNotFound exception if the immunisation does not exist"""
733+
self.imms_repo.get_immunization_by_id.return_value = None
730734

731735
# When
732-
act_imms = self.fhir_service.delete_immunization(imms_id, None, "Test")
736+
with self.assertRaises(ResourceNotFoundError):
737+
self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test")
733738

734739
# Then
735-
self.imms_repo.delete_immunization.assert_called_once_with(
736-
imms_id, None, "Test"
737-
)
738-
self.assertIsInstance(act_imms, Immunization)
739-
self.assertEqual(act_imms.id, imms_id)
740+
self.imms_repo.get_immunization_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID)
741+
self.imms_repo.delete_immunization.assert_not_called()
742+
743+
def test_delete_immunization_throws_authorisation_exception_if_does_not_have_required_permissions(self):
744+
imms = json.loads(create_covid_19_immunization(self.TEST_IMMUNISATION_ID).json())
745+
self.mock_redis_client.hget.return_value = "FLU"
746+
self.authoriser.authorise.return_value = False
747+
self.imms_repo.get_immunization_by_id.return_value = {"Resource": imms}
748+
749+
# When
750+
with self.assertRaises(UnauthorizedVaxError):
751+
self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test")
752+
753+
# Then
754+
self.imms_repo.get_immunization_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID)
755+
self.imms_repo.delete_immunization.assert_not_called()
756+
self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.DELETE, {"FLU"})
740757

741758

742759
class TestSearchImmunizations(unittest.TestCase):

0 commit comments

Comments
 (0)