|
| 1 | +""" |
| 2 | +FHIR R4 Extension Profile Tests |
| 3 | +
|
| 4 | +Tests generated extension profile classes (Pydantic subclasses of Extension). |
| 5 | +""" |
| 6 | + |
| 7 | +import json |
| 8 | + |
| 9 | +import pytest |
| 10 | +from pydantic import ValidationError |
| 11 | + |
| 12 | +from fhir_types.hl7_fhir_r4_core import Address, CodeableConcept, Coding, Element, HumanName, Period |
| 13 | +from fhir_types.hl7_fhir_r4_core.base import Extension |
| 14 | +from fhir_types.hl7_fhir_r4_core.patient import Patient |
| 15 | +from fhir_types.hl7_fhir_r4_core.profiles.extension_birth_place import BirthPlaceExtension |
| 16 | +from fhir_types.hl7_fhir_r4_core.profiles.extension_birth_time import BirthTimeExtension |
| 17 | +from fhir_types.hl7_fhir_r4_core.profiles.extension_nationality import ( |
| 18 | + NationalityCodeExtension, |
| 19 | + NationalityExtension, |
| 20 | + NationalityPeriodExtension, |
| 21 | +) |
| 22 | +from fhir_types.hl7_fhir_r4_core.profiles.extension_own_prefix import OwnPrefixExtension |
| 23 | + |
| 24 | + |
| 25 | +# --------------------------------------------------------------------------- |
| 26 | +# Simple extensions: construction, url enforcement, required value, subclass |
| 27 | +# --------------------------------------------------------------------------- |
| 28 | + |
| 29 | + |
| 30 | +@pytest.mark.parametrize( |
| 31 | + ("cls", "kwargs", "expected_url"), |
| 32 | + [ |
| 33 | + (BirthPlaceExtension, {"value_address": Address(city="Bonn")}, "http://hl7.org/fhir/StructureDefinition/patient-birthPlace"), |
| 34 | + (BirthTimeExtension, {"value_date_time": "1990-03-15T08:22:00-05:00"}, "http://hl7.org/fhir/StructureDefinition/patient-birthTime"), |
| 35 | + (OwnPrefixExtension, {"value_string": "van"}, "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix"), |
| 36 | + ], |
| 37 | + ids=["birth_place", "birth_time", "own_prefix"], |
| 38 | +) |
| 39 | +class TestSimpleExtension: |
| 40 | + def test_construction_and_url(self, cls, kwargs, expected_url) -> None: |
| 41 | + ext = cls(**kwargs) |
| 42 | + assert ext.url == expected_url |
| 43 | + assert isinstance(ext, Extension) |
| 44 | + |
| 45 | + def test_rejects_wrong_url(self, cls, kwargs, expected_url) -> None: |
| 46 | + with pytest.raises(ValidationError): |
| 47 | + cls(url="http://wrong", **kwargs) |
| 48 | + |
| 49 | + def test_value_is_required(self, cls, kwargs, expected_url) -> None: |
| 50 | + with pytest.raises(ValidationError): |
| 51 | + cls() |
| 52 | + |
| 53 | + def test_round_trip(self, cls, kwargs, expected_url) -> None: |
| 54 | + original = cls(**kwargs) |
| 55 | + restored = cls.model_validate_json(original.model_dump_json(by_alias=True, exclude_none=True)) |
| 56 | + assert restored.url == expected_url |
| 57 | + |
| 58 | + |
| 59 | +# --------------------------------------------------------------------------- |
| 60 | +# Complex extension: NationalityExtension with discriminated sub-extensions |
| 61 | +# --------------------------------------------------------------------------- |
| 62 | + |
| 63 | + |
| 64 | +class TestNationalityExtension: |
| 65 | + def test_construction_no_sub_extensions(self) -> None: |
| 66 | + ext = NationalityExtension() |
| 67 | + assert ext.url == "http://hl7.org/fhir/StructureDefinition/patient-nationality" |
| 68 | + assert ext.extension is None |
| 69 | + |
| 70 | + def test_sub_extensions_construction_and_url(self) -> None: |
| 71 | + code_ext = NationalityCodeExtension( |
| 72 | + value_codeable_concept=CodeableConcept( |
| 73 | + coding=[Coding(system="urn:iso:std:iso:3166", code="DE")], |
| 74 | + ), |
| 75 | + ) |
| 76 | + period_ext = NationalityPeriodExtension(value_period=Period(start="1770-12-17")) |
| 77 | + assert code_ext.url == "code" |
| 78 | + assert period_ext.url == "period" |
| 79 | + assert isinstance(code_ext, Extension) |
| 80 | + |
| 81 | + def test_with_both_sub_extensions(self) -> None: |
| 82 | + ext = NationalityExtension(extension=[ |
| 83 | + NationalityCodeExtension( |
| 84 | + value_codeable_concept=CodeableConcept( |
| 85 | + coding=[Coding(system="urn:iso:std:iso:3166", code="DE")], |
| 86 | + ), |
| 87 | + ), |
| 88 | + NationalityPeriodExtension(value_period=Period(start="1770-12-17")), |
| 89 | + ]) |
| 90 | + assert len(ext.extension) == 2 |
| 91 | + assert isinstance(ext.extension[0], NationalityCodeExtension) |
| 92 | + assert isinstance(ext.extension[1], NationalityPeriodExtension) |
| 93 | + |
| 94 | + def test_sub_extension_rejects_wrong_url(self) -> None: |
| 95 | + with pytest.raises(ValidationError): |
| 96 | + NationalityCodeExtension(url="wrong", value_codeable_concept=CodeableConcept()) |
| 97 | + |
| 98 | + def test_sub_extension_value_is_required(self) -> None: |
| 99 | + with pytest.raises(ValidationError): |
| 100 | + NationalityCodeExtension() |
| 101 | + |
| 102 | + def test_round_trip(self) -> None: |
| 103 | + original = NationalityExtension(extension=[ |
| 104 | + NationalityCodeExtension( |
| 105 | + value_codeable_concept=CodeableConcept( |
| 106 | + coding=[Coding(system="urn:iso:std:iso:3166", code="DE")], |
| 107 | + ), |
| 108 | + ), |
| 109 | + NationalityPeriodExtension(value_period=Period(start="1770-12-17", end="1827-03-26")), |
| 110 | + ]) |
| 111 | + json_str = original.model_dump_json(by_alias=True, exclude_none=True) |
| 112 | + restored = NationalityExtension.model_validate_json(json_str) |
| 113 | + assert len(restored.extension) == 2 |
| 114 | + assert isinstance(restored.extension[0], NationalityCodeExtension) |
| 115 | + assert restored.extension[0].value_codeable_concept.coding[0].code == "DE" |
| 116 | + assert isinstance(restored.extension[1], NationalityPeriodExtension) |
| 117 | + assert restored.extension[1].value_period.start == "1770-12-17" |
| 118 | + |
| 119 | + def test_deserialization_from_fhir_json(self) -> None: |
| 120 | + """Discriminated union routes sub-extensions by url during deserialization.""" |
| 121 | + raw = json.dumps({ |
| 122 | + "url": "http://hl7.org/fhir/StructureDefinition/patient-nationality", |
| 123 | + "extension": [ |
| 124 | + {"url": "code", "valueCodeableConcept": {"coding": [{"system": "urn:iso:std:iso:3166", "code": "FR"}]}}, |
| 125 | + {"url": "period", "valuePeriod": {"start": "1990-01-01"}}, |
| 126 | + ], |
| 127 | + }) |
| 128 | + ext = NationalityExtension.model_validate_json(raw) |
| 129 | + assert isinstance(ext.extension[0], NationalityCodeExtension) |
| 130 | + assert ext.extension[0].value_codeable_concept.coding[0].code == "FR" |
| 131 | + assert isinstance(ext.extension[1], NationalityPeriodExtension) |
| 132 | + |
| 133 | + |
| 134 | +# --------------------------------------------------------------------------- |
| 135 | +# Serialization: FHIR-aliased JSON keys |
| 136 | +# --------------------------------------------------------------------------- |
| 137 | + |
| 138 | + |
| 139 | +def test_simple_extension_serializes_with_fhir_aliases() -> None: |
| 140 | + ext = OwnPrefixExtension(value_string="van") |
| 141 | + raw = json.loads(ext.model_dump_json(by_alias=True, exclude_none=True)) |
| 142 | + assert raw == { |
| 143 | + "url": "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix", |
| 144 | + "valueString": "van", |
| 145 | + } |
| 146 | + |
| 147 | + |
| 148 | +def test_complex_extension_serializes_with_fhir_aliases() -> None: |
| 149 | + ext = NationalityExtension(extension=[ |
| 150 | + NationalityCodeExtension( |
| 151 | + value_codeable_concept=CodeableConcept( |
| 152 | + coding=[Coding(system="urn:iso:std:iso:3166", code="DE")], |
| 153 | + ), |
| 154 | + ), |
| 155 | + NationalityPeriodExtension(value_period=Period(start="2000-01-01")), |
| 156 | + ]) |
| 157 | + data = json.loads(ext.model_dump_json(by_alias=True, exclude_none=True)) |
| 158 | + assert data["extension"][0]["url"] == "code" |
| 159 | + assert "valueCodeableConcept" in data["extension"][0] |
| 160 | + assert "value_codeable_concept" not in data["extension"][0] |
| 161 | + assert data["extension"][1]["valuePeriod"]["start"] == "2000-01-01" |
| 162 | + |
| 163 | + |
| 164 | +# --------------------------------------------------------------------------- |
| 165 | +# Patient integration: all extension placement types + full round-trip |
| 166 | +# --------------------------------------------------------------------------- |
| 167 | + |
| 168 | + |
| 169 | +def test_patient_with_all_extension_types() -> None: |
| 170 | + """Resource-level, element-level, and primitive-level extensions on one Patient.""" |
| 171 | + patient = Patient( |
| 172 | + resource_type="Patient", |
| 173 | + birth_date="1770-12-17", |
| 174 | + birth_date_extension=Element( |
| 175 | + extension=[BirthTimeExtension(value_date_time="1770-12-17T12:00:00+01:00")], |
| 176 | + ), |
| 177 | + extension=[ |
| 178 | + BirthPlaceExtension(value_address=Address(city="Bonn", country="DE")), |
| 179 | + NationalityExtension(extension=[ |
| 180 | + NationalityCodeExtension( |
| 181 | + value_codeable_concept=CodeableConcept( |
| 182 | + coding=[Coding(system="urn:iso:std:iso:3166", code="DE")], |
| 183 | + ), |
| 184 | + ), |
| 185 | + NationalityPeriodExtension(value_period=Period(start="1770-12-17")), |
| 186 | + ]), |
| 187 | + ], |
| 188 | + name=[ |
| 189 | + HumanName( |
| 190 | + family="van Beethoven", |
| 191 | + family_extension=Element(extension=[OwnPrefixExtension(value_string="van")]), |
| 192 | + given=["Ludwig"], |
| 193 | + ), |
| 194 | + ], |
| 195 | + ) |
| 196 | + |
| 197 | + # Resource-level |
| 198 | + assert patient.extension[0].url == "http://hl7.org/fhir/StructureDefinition/patient-birthPlace" |
| 199 | + assert patient.extension[1].url == "http://hl7.org/fhir/StructureDefinition/patient-nationality" |
| 200 | + # Primitive-level |
| 201 | + assert patient.birth_date_extension.extension[0].url == "http://hl7.org/fhir/StructureDefinition/patient-birthTime" |
| 202 | + # Element-level |
| 203 | + assert patient.name[0].family_extension.extension[0].url == "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix" |
| 204 | + |
| 205 | + # Round-trip through FHIR JSON |
| 206 | + json_str = patient.model_dump_json(by_alias=True, exclude_none=True) |
| 207 | + raw = json.loads(json_str) |
| 208 | + |
| 209 | + assert raw["_birthDate"]["extension"][0]["url"] == "http://hl7.org/fhir/StructureDefinition/patient-birthTime" |
| 210 | + assert raw["_birthDate"]["extension"][0]["valueDateTime"] == "1770-12-17T12:00:00+01:00" |
| 211 | + assert raw["extension"][0]["valueAddress"]["city"] == "Bonn" |
| 212 | + assert raw["name"][0]["_family"]["extension"][0]["valueString"] == "van" |
| 213 | + |
| 214 | + restored = Patient.model_validate_json(json_str) |
| 215 | + assert restored.birth_date == "1770-12-17" |
| 216 | + assert len(restored.extension) == 2 |
| 217 | + assert restored.name[0].family == "van Beethoven" |
0 commit comments