Skip to content

Commit fff7efe

Browse files
authored
Merge pull request #632 from NHSDigital/feature/eema1-NRL-703-ignoreSupersedeDeleteSyncPerm
NRL-703 add perm check to upsert
2 parents 299a773 + 224aaf2 commit fff7efe

File tree

4 files changed

+116
-27
lines changed

4 files changed

+116
-27
lines changed

api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
_set_upsert_time_fields,
99
handler,
1010
)
11+
from nrlf.core.constants import PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL
1112
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
1213
from nrlf.producer.fhir.r4.model import (
1314
DocumentReferenceRelatesTo,
@@ -603,6 +604,63 @@ def test_create_document_reference_invalid_relatesto_not_exists(repository):
603604
}
604605

605606

607+
@mock_aws
608+
@mock_repository
609+
def test_create_document_reference_invalid_relatesto_not_exists_still_creates_with_ignore_perm(
610+
repository,
611+
):
612+
doc_ref = load_document_reference("Y05868-736253002-Valid")
613+
doc_ref.relatesTo = [
614+
DocumentReferenceRelatesTo(
615+
code="transforms",
616+
target=Reference(
617+
reference=None,
618+
identifier=Identifier(value="Y05868-99999-99999-999999"),
619+
),
620+
)
621+
]
622+
623+
event = create_test_api_gateway_event(
624+
headers=create_headers(
625+
nrl_permissions=[PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL]
626+
),
627+
body=doc_ref.json(exclude_none=True),
628+
)
629+
630+
result = handler(event, create_mock_context())
631+
body = result.pop("body")
632+
633+
assert result == {
634+
"statusCode": "201",
635+
"headers": {
636+
"Location": "/nrl-producer-api/FHIR/R4/DocumentReference/Y05868-99999-99999-999999"
637+
},
638+
"isBase64Encoded": False,
639+
}
640+
641+
parsed_body = json.loads(body)
642+
643+
assert parsed_body == {
644+
"resourceType": "OperationOutcome",
645+
"issue": [
646+
{
647+
"severity": "information",
648+
"code": "informational",
649+
"details": {
650+
"coding": [
651+
{
652+
"code": "RESOURCE_CREATED",
653+
"display": "Resource created",
654+
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
655+
}
656+
]
657+
},
658+
"diagnostics": "The document has been created",
659+
}
660+
],
661+
}
662+
663+
606664
@mock_aws
607665
@mock_repository
608666
def test_create_document_reference_invalid_relatesto_nhs_number(

api/producer/upsertDocumentReference/upsert_document_reference.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from nrlf.core.constants import PERMISSION_AUDIT_DATES_FROM_PAYLOAD
1+
from nrlf.core.constants import (
2+
PERMISSION_AUDIT_DATES_FROM_PAYLOAD,
3+
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL,
4+
)
25
from nrlf.core.decorators import request_handler
36
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
47
from nrlf.core.logger import LogReference, logger
@@ -105,6 +108,9 @@ def handler(
105108

106109
if result.resource.relatesTo:
107110
logger.log(LogReference.PROUPSERT006, relatesTo=result.resource.relatesTo)
111+
can_ignore_delete_fail = (
112+
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL in metadata.nrl_permissions
113+
)
108114

109115
for idx, relates_to in enumerate(result.resource.relatesTo):
110116
if not (identifier := getattr(relates_to.target.identifier, "value", None)):
@@ -125,27 +131,33 @@ def handler(
125131
diagnostics="The relatesTo target identifier value does not include the expected ODS code for this organisation",
126132
expression=f"relatesTo[{idx}].target.identifier.value",
127133
)
128-
129-
if not (existing_pointer := repository.get_by_id(identifier)):
130-
logger.log(LogReference.PROUPSERT007c, related_identifier=identifier)
131-
return SpineErrorResponse.BAD_REQUEST(
132-
diagnostics="The relatesTo target document does not exist",
133-
expression=f"relatesTo[{idx}].target.identifier.value",
134-
)
135-
136-
if existing_pointer.nhs_number != core_model.nhs_number:
137-
logger.log(LogReference.PROUPSERT007d, related_identifier=identifier)
138-
return SpineErrorResponse.BAD_REQUEST(
139-
diagnostics="The relatesTo target document NHS number does not match the NHS number in the request",
140-
expression=f"relatesTo[{idx}].target.identifier.value",
141-
)
142-
143-
if existing_pointer.type != core_model.type:
144-
logger.log(LogReference.PROUPSERT007e, related_identifier=identifier)
145-
return SpineErrorResponse.BAD_REQUEST(
146-
diagnostics="The relatesTo target document type does not match the type in the request",
147-
expression=f"relatesTo[{idx}].target.identifier.value",
148-
)
134+
if not can_ignore_delete_fail:
135+
if not (existing_pointer := repository.get_by_id(identifier)):
136+
logger.log(
137+
LogReference.PROCREATE007c, related_identifier=identifier
138+
)
139+
return SpineErrorResponse.BAD_REQUEST(
140+
diagnostics="The relatesTo target document does not exist",
141+
expression=f"relatesTo[{idx}].target.identifier.value",
142+
)
143+
144+
if existing_pointer.nhs_number != core_model.nhs_number:
145+
logger.log(
146+
LogReference.PROUPSERT007d, related_identifier=identifier
147+
)
148+
return SpineErrorResponse.BAD_REQUEST(
149+
diagnostics="The relatesTo target document NHS number does not match the NHS number in the request",
150+
expression=f"relatesTo[{idx}].target.identifier.value",
151+
)
152+
153+
if existing_pointer.type != core_model.type:
154+
logger.log(
155+
LogReference.PROUPSERT007e, related_identifier=identifier
156+
)
157+
return SpineErrorResponse.BAD_REQUEST(
158+
diagnostics="The relatesTo target document type does not match the type in the request",
159+
expression=f"relatesTo[{idx}].target.identifier.value",
160+
)
149161

150162
if relates_to.code == "replaces":
151163
logger.log(
@@ -161,7 +173,9 @@ def handler(
161173
pointer_id=result.resource.id,
162174
ids_to_delete=ids_to_delete,
163175
)
164-
saved_model = repository.supersede(core_model, ids_to_delete)
176+
saved_model = repository.supersede(
177+
core_model, ids_to_delete, can_ignore_delete_fail
178+
)
165179
logger.log(LogReference.PROUPSERT999)
166180
return NRLResponse.RESOURCE_SUPERSEDED(resource_id=saved_model.id)
167181

layer/nrlf/core/dynamodb/repository.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,16 @@ def save(self, item: DocumentPointer) -> DocumentPointer:
374374
return self.update(item)
375375

376376
def supersede(
377-
self, item: DocumentPointer, ids_to_delete: List[str]
377+
self,
378+
item: DocumentPointer,
379+
ids_to_delete: List[str],
380+
can_ignore_delete_fail: bool = False,
378381
) -> DocumentPointer:
379382
""" """
380383
saved_item = self.create(item)
384+
381385
for id_ in ids_to_delete:
382-
self.delete_by_id(id_)
386+
self.delete_by_id(id_, can_ignore_delete_fail)
383387

384388
return saved_item
385389

@@ -411,12 +415,22 @@ def delete(self, item: DocumentPointer) -> None:
411415
details=SpineErrorConcept.from_code("INTERNAL_SERVER_ERROR"),
412416
) from exc
413417

414-
def delete_by_id(self, id_: str):
418+
def delete_by_id(self, id_: str, can_ignore_delete_fail: bool = False):
415419
""" """
416420
producer_id, document_id = id_.split("-", 1)
417421
ods_code_parts = producer_id.split(".")
418422
partition_key = "D#" + "#".join([*ods_code_parts, document_id])
419-
self.table.delete_item(Key={"pk": partition_key, "sk": partition_key})
423+
try:
424+
self.table.delete_item(Key={"pk": partition_key, "sk": partition_key})
425+
except ClientError as exc:
426+
if can_ignore_delete_fail:
427+
logger.log(
428+
LogReference.REPOSITORY026a,
429+
exc_info=sys.exc_info(),
430+
stacklevel=5,
431+
error=str(exc),
432+
)
433+
return
420434

421435
def _query(self, **kwargs) -> Iterator[DocumentPointer]:
422436
"""

layer/nrlf/core/log_references.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ class LogReference(Enum):
101101

102102
REPOSITORY025 = _Reference("DEBUG", "Deleting resource from DynamoDB")
103103
REPOSITORY026 = _Reference("EXCEPTION", "Failed to delete resource from DynamoDB")
104+
REPOSITORY026a = _Reference(
105+
"EXCEPTION", "Ignoring failure to delete resource from DynamoDB"
106+
)
104107
REPOSITORY027 = _Reference("INFO", "Successfully deleted item from DynamoDB")
105108
REPOSITORY028 = _Reference("INFO", "Received page of search results")
106109
REPOSITORY028a = _Reference("DEBUG", "Received page of search results with result")

0 commit comments

Comments
 (0)