Skip to content

Commit 360e31c

Browse files
committed
Refactored delete journey
1 parent 8b4de36 commit 360e31c

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
@@ -332,21 +332,10 @@ def _perform_dynamo_update(
332332
response=error.response,
333333
)
334334

335-
def delete_immunization(
336-
self, imms_id: str, imms_vax_type_perms: str, supplier_system: str) -> dict:
335+
def delete_immunization(self, imms_id: str, supplier_system: str) -> dict:
337336
now_timestamp = int(time.time())
338337

339338
try:
340-
item = self.table.get_item(Key={"PK": _make_immunization_pk(imms_id)}).get("Item")
341-
if not item:
342-
raise ResourceNotFoundError(resource_type="Immunization", resource_id=imms_id)
343-
344-
if not item.get("DeletedAt") or item.get("DeletedAt") == "reinstated":
345-
vaccine_type = self._vaccine_type(item["PatientSK"])
346-
if not validate_permissions(imms_vax_type_perms, ApiOperationCode.DELETE, [vaccine_type]):
347-
raise UnauthorizedVaxError()
348-
349-
# Proceed with delete update
350339
response = self.table.update_item(
351340
Key={"PK": _make_immunization_pk(imms_id)},
352341
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
@@ -214,14 +214,24 @@ def update_reinstated_immunization(
214214

215215
return UpdateOutcome.UPDATE, Immunization.parse_obj(imms), updated_version
216216

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

backend/tests/test_fhir_controller.py

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

16161616
# Then
1617-
self.service.delete_immunization.assert_called_once_with(imms_id, ["COVID19.CRUDS"], "Test")
1617+
self.service.delete_immunization.assert_called_once_with(imms_id, "Test")
16181618

16191619
self.assertEqual(response["statusCode"], 204)
16201620
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
@@ -481,7 +481,7 @@ def test_delete_immunization(self):
481481
with patch("time.time") as mock_time:
482482
mock_time.return_value = now_epoch
483483
# When
484-
_id = self.repository.delete_immunization(imms_id, "COVID:delete", "Test")
484+
_id = self.repository.delete_immunization(imms_id, "Test")
485485

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

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

576504
with self.assertRaises(ResourceNotFoundError) as e:
577-
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
505+
self.repository.delete_immunization(imms_id, "Test")
578506

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

609528
with self.assertRaises(UnhandledResponseError) as e:
610529
# When
611-
self.repository.delete_immunization(imms_id, ["COVID19.CRUD"], "Test")
530+
self.repository.delete_immunization(imms_id, "Test")
612531

613532
# Then
614533
self.assertDictEqual(e.exception.response, response)
@@ -806,7 +725,7 @@ def test_decimal_on_update(self):
806725
self.run_update_immunization_test(imms_id, imms, resource, updated_dose_quantity)
807726

808727
def test_decimal_on_update_patient(self):
809-
"""it should update record by replacing both Immunization and Patient and dosequantity"""
728+
"""it should update record by replacing both Immunization and Patient and dose quantity"""
810729
imms_id = "an-imms-id"
811730
imms = create_covid_19_immunization_dict(imms_id)
812731
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
@@ -693,10 +693,12 @@ def test_update_reinstated_immunization_with_diagnostics(self):
693693
self.imms_repo.update_reinstated_immunization.assert_not_called()
694694

695695

696-
class TestDeleteImmunization(unittest.TestCase):
696+
class TestDeleteImmunization(TestFhirServiceBase):
697697
"""Tests for FhirService.delete_immunization"""
698+
TEST_IMMUNISATION_ID = "an-id"
698699

699700
def setUp(self):
701+
super().setUp()
700702
self.authoriser = create_autospec(Authoriser)
701703
self.imms_repo = create_autospec(ImmunizationRepository)
702704
self.validator = create_autospec(ImmunizationValidator)
@@ -706,33 +708,48 @@ def setUp(self):
706708

707709
def test_delete_immunization(self):
708710
"""it should delete Immunization record"""
709-
imms_id = "an-id"
710-
imms = json.loads(create_covid_19_immunization(imms_id).json())
711+
imms = json.loads(create_covid_19_immunization(self.TEST_IMMUNISATION_ID).json())
712+
self.mock_redis_client.hget.return_value = "COVID19"
713+
self.authoriser.authorise.return_value = True
714+
self.imms_repo.get_immunization_by_id.return_value = {"Resource": imms}
711715
self.imms_repo.delete_immunization.return_value = imms
712716

713717
# When
714-
act_imms = self.fhir_service.delete_immunization(imms_id, "COVID:delete", "Test")
718+
act_imms = self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test")
715719

716720
# Then
717-
self.imms_repo.delete_immunization.assert_called_once_with(imms_id, "COVID:delete", "Test")
721+
self.imms_repo.get_immunization_by_id.assert_called_once_with(self.TEST_IMMUNISATION_ID)
722+
self.imms_repo.delete_immunization.assert_called_once_with(self.TEST_IMMUNISATION_ID, "Test")
723+
self.authoriser.authorise.assert_called_once_with("Test", ApiOperationCode.DELETE, {"COVID19"})
718724
self.assertIsInstance(act_imms, Immunization)
719-
self.assertEqual(act_imms.id, imms_id)
725+
self.assertEqual(act_imms.id, self.TEST_IMMUNISATION_ID)
720726

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

727731
# When
728-
act_imms = self.fhir_service.delete_immunization(imms_id, None, "Test")
732+
with self.assertRaises(ResourceNotFoundError):
733+
self.fhir_service.delete_immunization(self.TEST_IMMUNISATION_ID, "Test")
729734

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

737754

738755
class TestSearchImmunizations(unittest.TestCase):

0 commit comments

Comments
 (0)