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
9 changes: 5 additions & 4 deletions mocks/src/pdm_mock/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class PDMResponse(TypedDict):


class DocumentItem(BaseMockItem):
document: dict[str, Any]
document: str


type RequestHandler = Callable[[], PDMResponse]
Expand Down Expand Up @@ -80,7 +80,8 @@ def _fetch_patient_from_payload(payload: dict[str, Any]) -> str | None:
if (resource := entry.get("resource"))
and resource.get("resourceType") == "Patient"
and "identifier" in resource
and (patient := resource.get("identifier", {}).get("value"))
and len(resource["identifier"]) > 0
and (patient := resource.get("identifier")[0].get("value"))
]

if not patient_values:
Expand Down Expand Up @@ -113,7 +114,7 @@ def handle_post_request(payload: dict[str, Any]) -> PDMResponse:
item: DocumentItem = {
"sessionId": document_id,
"expiresAt": int(time()) + 600,
"document": created_document,
"document": json.dumps(created_document),
"type": "pdm_document",
}

Expand All @@ -125,7 +126,7 @@ def handle_post_request(payload: dict[str, Any]) -> PDMResponse:
def handle_get_request(document_id: str) -> PDMResponse:

table_item = _get_document_from_table(document_id)
document = table_item["document"]
document = json.loads(table_item["document"])

return {"status_code": 200, "response": document}

Expand Down
20 changes: 10 additions & 10 deletions mocks/src/pdm_mock/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
)
from aws_lambda_powertools.utilities.typing import LambdaContext

with patch("boto3.resource"):
from apim_mock.auth_check import AuthenticationError
from common.storage_helper import ItemNotFoundException

os.environ["PDM_TABLE_NAME"] = "test_table"
os.environ["DDB_INDEX_TAG"] = "test_branch"
os.environ["AUTH_URL"] = "auth_url"
os.environ["PUBLIC_KEY_URL"] = "public_key_url"
os.environ["API_KEY"] = "api_key"
os.environ["TOKEN_TABLE_NAME"] = "token_table" # noqa: S105 - Dummy value

with patch("boto3.resource"):
from apim_mock.auth_check import AuthenticationError
from common.storage_helper import ItemNotFoundException


@pytest.fixture
def basic_document_payload() -> dict[str, Any]:
Expand All @@ -38,13 +38,13 @@ def multiple_patient_document_payload(
{
"resource": {
"resourceType": "Patient",
"identifier": {"value": "patient1"},
"identifier": [{"value": "patient1"}],
}
},
{
"resource": {
"resourceType": "Patient",
"identifier": {"value": "patient2"},
"identifier": [{"value": "patient2"}],
}
},
],
Expand All @@ -61,7 +61,7 @@ def validation_error_patient_payload(
{
"resource": {
"resourceType": "Patient",
"identifier": {"value": "PDM_VALIDATION_ERROR"},
"identifier": [{"value": "PDM_VALIDATION_ERROR"}],
}
},
],
Expand All @@ -78,7 +78,7 @@ def server_error_patient_payload(
{
"resource": {
"resourceType": "Patient",
"identifier": {"value": "PDM_SERVER_ERROR"},
"identifier": [{"value": "PDM_SERVER_ERROR"}],
}
},
],
Expand Down Expand Up @@ -271,7 +271,7 @@ def test_handle_get_request(
mock_dynamodb_client.Table.return_value.get_item.return_value = {
"Item": {
"sessionId": "document_id",
"document": basic_saved_document,
"document": json.dumps(basic_saved_document),
"type": "document",
"expiresAt": 1,
"ddb_index": "test_branch",
Expand Down Expand Up @@ -481,7 +481,7 @@ def test_get_document(
check_authenticated_mock.return_value = None
mock_storage_helper.return_value = {
"sessionId": "document_id",
"document": {"test_key": "test_data"},
"document": json.dumps({"test_key": "test_data"}),
}

event = self._create_test_event(
Expand Down
2 changes: 1 addition & 1 deletion pathology-api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def build_valid_test_result() -> Callable[[str, str], Bundle]:
def builder_function(patient: str, ods_code: str) -> Bundle:
return BundleBuilder.with_defaults(
composition_func=lambda service_request_url: Composition.create(
subject=LogicalReference(PatientIdentifier.from_nhs_number(patient)),
subject=LogicalReference(PatientIdentifier.create_with(patient)),
extension=[
# Using HTTP to match profile required by implementation guide.
ReferenceExtension(
Expand Down
16 changes: 2 additions & 14 deletions pathology-api/src/pathology_api/fhir/r4/elements.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
import uuid
from abc import ABC
from dataclasses import dataclass
from typing import Annotated, Any, ClassVar
Expand Down Expand Up @@ -107,25 +106,13 @@ class UnknownIdentifier(Identifier, validate_system=False):
"""Provides a fallback Identifier type for an unknown system."""


class UUIDIdentifier(Identifier, expected_system="https://tools.ietf.org/html/rfc4122"):
"""A UUID identifier utilising the standard RFC 4122 system."""

@classmethod
def create_with_uuid(cls, value: uuid.UUID | None = None) -> "UUIDIdentifier":
"""Create a UUIDIdentifier with the provided UUID value."""
return cls(
value=str(value or uuid.uuid4()),
system=cls.expected_system,
)


class PatientIdentifier(
Identifier, expected_system="https://fhir.nhs.uk/Id/nhs-number"
):
"""A FHIR R4 Patient Identifier utilising the NHS Number system."""

@classmethod
def from_nhs_number(cls, nhs_number: str) -> "PatientIdentifier":
def create_with(cls, nhs_number: str) -> "PatientIdentifier":
"""Create a PatientIdentifier from an NHS number."""
return cls(value=nhs_number, system=cls.expected_system)

Expand All @@ -144,6 +131,7 @@ def from_ods_code(cls, ods_code: str) -> "OrganizationIdentifier":
@dataclass(frozen=True)
class LogicalReference[T: Identifier]:
identifier: T
reference: str | None = None


@dataclass(frozen=True)
Expand Down
2 changes: 0 additions & 2 deletions pathology-api/src/pathology_api/fhir/r4/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
LogicalReference,
Meta,
PatientIdentifier,
UUIDIdentifier,
)


Expand Down Expand Up @@ -128,7 +127,6 @@ class Bundle(Resource, resource_type="Bundle"):
"""A FHIR R4 Bundle resource."""

bundle_type: BundleType = Field(alias="type", frozen=True)
identifier: Annotated[UUIDIdentifier | None, Field(frozen=True)] = None
entries: list["Bundle.Entry"] | None = Field(None, frozen=True, alias="entry")

class Entry(BaseModel):
Expand Down
69 changes: 46 additions & 23 deletions pathology-api/src/pathology_api/fhir/r4/test_elements.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
import uuid

import pydantic
import pytest
Expand All @@ -16,7 +15,6 @@
PatientIdentifier,
ReferenceExtension,
UnknownIdentifier,
UUIDIdentifier,
)


Expand Down Expand Up @@ -66,23 +64,6 @@ def test_with_last_updated_defaults_to_now(self) -> None:
assert meta.last_updated <= after_create


class TestUUIDIdentifier:
def test_create_with_value(self) -> None:
expected_uuid = uuid.UUID("12345678-1234-5678-1234-567812345678")
identifier = UUIDIdentifier.create_with_uuid(expected_uuid)

assert identifier.system == "https://tools.ietf.org/html/rfc4122"
assert identifier.value == str(expected_uuid)

def test_create_without_value(self) -> None:
identifier = UUIDIdentifier.create_with_uuid()

assert identifier.system == "https://tools.ietf.org/html/rfc4122"
# Validates that value is a valid UUID v4
parsed_uuid = uuid.UUID(identifier.value)
assert parsed_uuid.version == 4


class _TestIdentifierContainer(BaseModel):
identifier: "IdentifierStub"

Expand Down Expand Up @@ -159,7 +140,7 @@ class TestPatientIdentifier:
def test_create_from_nhs_number(self) -> None:
"""Test creating a PatientIdentifier from an NHS number."""
nhs_number = "1234567890"
identifier = PatientIdentifier.from_nhs_number(nhs_number)
identifier = PatientIdentifier.create_with(nhs_number)

assert identifier.system == "https://fhir.nhs.uk/Id/nhs-number"
assert identifier.value == nhs_number
Expand All @@ -180,7 +161,7 @@ class _TestContainer(BaseModel):

def test_create_with_patient_identifier(self) -> None:
nhs_number = "nhs_number"
patient_id = PatientIdentifier.from_nhs_number(nhs_number)
patient_id = PatientIdentifier.create_with(nhs_number)

reference = LogicalReference(identifier=patient_id)

Expand All @@ -190,11 +171,11 @@ def test_create_with_patient_identifier(self) -> None:

def test_serialization(self) -> None:
nhs_number = "nhs_number"
patient_id = PatientIdentifier.from_nhs_number(nhs_number)
patient_id = PatientIdentifier.create_with(nhs_number)
reference = LogicalReference(identifier=patient_id)

container = self._TestContainer(reference=reference)
serialized = container.model_dump(by_alias=True)
serialized = container.model_dump(by_alias=True, exclude_none=True)

expected = {
"reference": {
Expand All @@ -206,6 +187,25 @@ def test_serialization(self) -> None:
}
assert serialized == expected

def test_serialization_with_reference(self) -> None:
nhs_number = "nhs_number"
patient_id = PatientIdentifier.create_with(nhs_number)
reference = LogicalReference(identifier=patient_id, reference=nhs_number)

container = self._TestContainer(reference=reference)
serialized = container.model_dump(by_alias=True)

expected = {
"reference": {
"identifier": {
"system": "https://fhir.nhs.uk/Id/nhs-number",
"value": "nhs_number",
},
"reference": nhs_number,
}
}
assert serialized == expected

def test_deserialization(self) -> None:
data = {
"reference": {
Expand All @@ -218,7 +218,30 @@ def test_deserialization(self) -> None:

container = self._TestContainer.model_validate(data)

assert container.reference.reference is None
created_identifier = container.reference.identifier

assert isinstance(created_identifier, PatientIdentifier)
assert created_identifier.system == "https://fhir.nhs.uk/Id/nhs-number"
assert created_identifier.value == "nhs_number"

def test_deserialization_with_reference(self) -> None:
expected_reference = "expected-reference"
data = {
"reference": {
"identifier": {
"system": "https://fhir.nhs.uk/Id/nhs-number",
"value": "nhs_number",
},
"reference": expected_reference,
}
}

container = self._TestContainer.model_validate(data)

assert container.reference.reference == expected_reference
created_identifier = container.reference.identifier

assert isinstance(created_identifier, PatientIdentifier)
assert created_identifier.system == "https://fhir.nhs.uk/Id/nhs-number"
assert created_identifier.value == "nhs_number"
Expand Down
Loading
Loading