Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from nrlf.core.constants import (
PERMISSION_AUDIT_DATES_FROM_PAYLOAD,
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL,
TYPES_WITH_MULTIPLES,
)
from nrlf.core.decorators import request_handler
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
Expand Down Expand Up @@ -255,6 +256,24 @@ def handler(
logger.log(LogReference.PROCREATE999)
return NRLResponse.RESOURCE_SUPERSEDED(resource_id=result.resource.id)

pointer_type = core_model.type
if pointer_type not in TYPES_WITH_MULTIPLES:
nhs_number = core_model.nhs_number
existing_pointers_count = repository.count_by_nhs_number(
nhs_number, [pointer_type]
)

if existing_pointers_count > 0:
logger.log(
LogReference.PROCREATE012,
new_pointer_id=core_model.id,
new_pointer_master_id=core_model.master_identifier,
pointer_type=pointer_type,
nhs_number=nhs_number,
custodian=core_model.custodian,
existing_pointers_count=existing_pointers_count,
)

logger.log(LogReference.PROCREATE009, pointer_id=result.resource.id)
repository.create(core_model)
logger.log(LogReference.PROCREATE999)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from unittest.mock import patch
from unittest.mock import Mock, patch

from freeze_uuid import freeze_uuid
from freezegun import freeze_time
Expand Down Expand Up @@ -1734,3 +1734,144 @@ def test__set_create_time_fields_when_no_date_but_perms():
},
"date": test_time,
}


@mock_aws
@mock_repository
@freeze_uuid("00000000-0000-0000-0000-000000000001")
@patch("api.producer.createDocumentReference.create_document_reference.logger")
def test_create_logs_for_unexpected_multi_pointer(
mock_logger: Mock,
repository: DocumentPointerRepository,
):
doc_ref = load_document_reference("Y05868-736253002-Valid-with-master-id")
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)

event = create_test_api_gateway_event(
headers=create_headers(),
body=doc_ref.model_dump_json(exclude_none=True),
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "201",
"headers": {
"Location": "/DocumentReference/Y05868-00000000-0000-0000-0000-000000000001",
**default_response_headers(),
},
"isBase64Encoded": False,
}

parsed_body = json.loads(body)

assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "RESOURCE_CREATED",
"display": "Resource created",
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
}
]
},
"diagnostics": "The document has been created",
}
],
}

assert any(
call[0][0].name == "PROCREATE012" for call in mock_logger.log.call_args_list
)

assert {
"existing_pointers_count": 1,
"nhs_number": (
doc_ref.subject.identifier.value
if doc_ref.subject and doc_ref.subject.identifier
else None
),
"pointer_type": (
f"{doc_ref.type.coding[0].system}|{doc_ref.type.coding[0].code}"
if doc_ref.type and doc_ref.type.coding
else None
),
"custodian": (
doc_ref.custodian.identifier.value
if doc_ref.custodian and doc_ref.custodian.identifier
else None
),
"new_pointer_id": "Y05868-00000000-0000-0000-0000-000000000001",
"new_pointer_master_id": (
doc_ref.masterIdentifier.value if doc_ref.masterIdentifier else None
),
} == [
call[1:][0]
for call in mock_logger.log.call_args_list
if call[0][0].name == "PROCREATE012"
][
0
]


@mock_aws
@mock_repository
@freeze_uuid("00000000-0000-0000-0000-000000000001")
@patch("api.producer.createDocumentReference.create_document_reference.logger")
def test_create_logs_for_expected_multi_pointer(
mock_logger: Mock,
repository: DocumentPointerRepository,
):
doc_ref = load_document_reference("Y05868-Appointment-Valid")
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)

event = create_test_api_gateway_event(
headers=create_headers(),
body=doc_ref.model_dump_json(exclude_none=True),
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "201",
"headers": {
"Location": "/DocumentReference/Y05868-00000000-0000-0000-0000-000000000001",
**default_response_headers(),
},
"isBase64Encoded": False,
}

parsed_body = json.loads(body)

assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "RESOURCE_CREATED",
"display": "Resource created",
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
}
]
},
"diagnostics": "The document has been created",
}
],
}

assert not any(
call[0][0].name == "PROCREATE012" for call in mock_logger.log.call_args_list
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from unittest.mock import patch
from unittest.mock import Mock, patch

from freezegun import freeze_time
from moto import mock_aws
Expand Down Expand Up @@ -1699,3 +1699,146 @@ def test__set_create_time_fields_when_no_date_but_perms():
},
"date": test_time,
}


@mock_aws
@mock_repository
@patch("api.producer.upsertDocumentReference.upsert_document_reference.logger")
def test_upsert_logs_for_unexpected_multi_pointer(
mock_logger: Mock,
repository: DocumentPointerRepository,
):
doc_ref = load_document_reference("Y05868-736253002-Valid-with-master-id")
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)

doc_ref.id = "Y05868-99999-99999-999999-02"

event = create_test_api_gateway_event(
headers=create_headers(),
body=doc_ref.model_dump_json(exclude_none=True),
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "201",
"headers": {
"Location": "/DocumentReference/Y05868-99999-99999-999999-02",
**default_response_headers(),
},
"isBase64Encoded": False,
}

parsed_body = json.loads(body)

assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "RESOURCE_CREATED",
"display": "Resource created",
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
}
]
},
"diagnostics": "The document has been created",
}
],
}

assert any(
call[0][0].name == "PROUPSERT012" for call in mock_logger.log.call_args_list
)

assert {
"existing_pointers_count": 1,
"nhs_number": (
doc_ref.subject.identifier.value
if doc_ref.subject and doc_ref.subject.identifier
else None
),
"pointer_type": (
f"{doc_ref.type.coding[0].system}|{doc_ref.type.coding[0].code}"
if doc_ref.type and doc_ref.type.coding
else None
),
"custodian": (
doc_ref.custodian.identifier.value
if doc_ref.custodian and doc_ref.custodian.identifier
else None
),
"new_pointer_id": doc_ref.id,
"new_pointer_master_id": (
doc_ref.masterIdentifier.value if doc_ref.masterIdentifier else None
),
} == [
call[1:][0]
for call in mock_logger.log.call_args_list
if call[0][0].name == "PROUPSERT012"
][
0
]


@mock_aws
@mock_repository
@patch("api.producer.upsertDocumentReference.upsert_document_reference.logger")
def test_upsert_logs_for_expected_multi_pointer(
mock_logger: Mock,
repository: DocumentPointerRepository,
):
doc_ref = load_document_reference("Y05868-Appointment-Valid")
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)

doc_ref.id = "Y05868-99999-99999-999999-02"

event = create_test_api_gateway_event(
headers=create_headers(),
body=doc_ref.model_dump_json(exclude_none=True),
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "201",
"headers": {
"Location": "/DocumentReference/Y05868-99999-99999-999999-02",
**default_response_headers(),
},
"isBase64Encoded": False,
}

parsed_body = json.loads(body)

assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "RESOURCE_CREATED",
"display": "Resource created",
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
}
]
},
"diagnostics": "The document has been created",
}
],
}

assert not any(
call[0][0].name == "PROUPSERT012" for call in mock_logger.log.call_args_list
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from nrlf.core.constants import (
PERMISSION_AUDIT_DATES_FROM_PAYLOAD,
PERMISSION_SUPERSEDE_IGNORE_DELETE_FAIL,
TYPES_WITH_MULTIPLES,
)
from nrlf.core.decorators import request_handler
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
Expand Down Expand Up @@ -262,6 +263,24 @@ def handler(
logger.log(LogReference.PROUPSERT999)
return NRLResponse.RESOURCE_SUPERSEDED(resource_id=saved_model.id)

pointer_type = core_model.type
if pointer_type not in TYPES_WITH_MULTIPLES:
nhs_number = core_model.nhs_number
existing_pointers_count = repository.count_by_nhs_number(
nhs_number, [pointer_type]
)

if existing_pointers_count > 0:
logger.log(
LogReference.PROUPSERT012,
new_pointer_id=core_model.id,
new_pointer_master_id=core_model.master_identifier,
pointer_type=pointer_type,
nhs_number=nhs_number,
custodian=core_model.custodian,
existing_pointers_count=existing_pointers_count,
)

logger.log(LogReference.PROUPSERT009, pointer_id=result.resource.id)
saved_model = repository.create(core_model)
logger.log(LogReference.PROUPSERT999)
Expand Down
8 changes: 8 additions & 0 deletions layer/nrlf/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ def coding_value(self):
PointerTypes.SHARED_CARE_RECORD.value: Categories.RECORD_HEADINGS.value,
}

#
# Pointer types that can have multiple pointers for a single patient
TYPES_WITH_MULTIPLES = [
PointerTypes.MRA_UPPER_LIMB_ARTERY.value,
PointerTypes.MRI_AXILLA_BOTH.value,
PointerTypes.APPOINTMENT.value,
]

PRACTICE_SETTING_VALUE_SET_URL = (
"https://fhir.nhs.uk/England/ValueSet/England-PracticeSetting"
)
Expand Down
Loading