1import unittest
2import json
3import fhirspec_pb2
4from fhir_spec_extractor import FhirSpecExtractor
5from google.protobuf import text_format
6
7
8class FhirSpecExtractorTest(unittest.TestCase):
9    BUNDLE_WITH_IMMUNIZATION_AND_PATIENT_STRUCTURE_DEFINITION = json.loads("""
10    {
11        "resourceType" : "Bundle",
12        "id" : "resources",
13        "meta" : {
14          "lastUpdated" : "2019-11-01T09:29:23.356+11:00"
15        },
16        "type" : "collection",
17        "entry" : [
18          {
19            "fullUrl" : "http://hl7.org/fhir/CompartmentDefinition/relatedPerson"
20          },
21          {
22            "fullUrl" : "http://hl7.org/fhir/StructureDefinition/Immunization",
23            "resource" : {
24              "resourceType" : "StructureDefinition",
25              "id" : "Immunization",
26              "meta" : {
27                "lastUpdated" : "2019-11-01T09:29:23.356+11:00"
28              },
29              "fhirVersion" : "4.0.1",
30              "kind" : "resource",
31              "type" : "Immunization",
32              "baseDefinition" : "http://hl7.org/fhir/StructureDefinition/DomainResource",
33              "snapshot" : {
34                "element" : [{
35                  "id" : "Immunization",
36                  "path" : "Immunization",
37                  "min" : 0,
38                  "max" : "*",
39                  "base" : {
40                    "path" : "Immunization",
41                    "min" : 0,
42                    "max" : "*"
43                  }
44                },
45                {
46                  "id" : "Immunization.id",
47                  "path" : "Immunization.id",
48                  "min" : 0,
49                  "max" : "1",
50                  "base" : {
51                    "path" : "Resource.id",
52                    "min" : 0,
53                    "max" : "1"
54                  },
55                  "type" : [{
56                    "extension" : [{
57                      "url" : "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
58                      "valueUrl" : "string"
59                    }],
60                    "code" : "http://hl7.org/fhirpath/System.String"
61                  }]
62                },
63                {
64                  "id" : "Immunization.status",
65                  "path" : "Immunization.status",
66                  "min" : 1,
67                  "max" : "1",
68                  "base" : {
69                    "path" : "Immunization.status",
70                    "min" : 1,
71                    "max" : "1"
72                  },
73                  "type" : [{
74                    "code" : "code"
75                  }]
76                },
77                {
78                  "id" : "Immunization.vaccineCode",
79                  "path" : "Immunization.vaccineCode",
80                  "min" : 1,
81                  "max" : "1",
82                  "base" : {
83                    "path" : "Immunization.vaccineCode",
84                    "min" : 1,
85                    "max" : "1"
86                  },
87                  "type" : [{
88                    "code" : "CodeableConcept"
89                  }]
90                },
91                {
92                  "id" : "Immunization.exampleFieldToTestOneToMany",
93                  "path" : "Immunization.exampleFieldToTestOneToMany",
94                  "min" : 1,
95                  "max" : "*",
96                  "base" : {
97                    "path" : "Immunization.exampleFieldToTestOneToMany",
98                    "min" : 1,
99                    "max" : "*"
100                  },
101                  "type" : [{
102                    "code" : "CodeableConcept"
103                  }]
104                },
105                {
106                  "id" : "Immunization.occurrence[x]",
107                  "path" : "Immunization.occurrence[x]",
108                  "min" : 1,
109                  "max" : "1",
110                  "base" : {
111                    "path" : "Immunization.occurrence[x]",
112                    "min" : 1,
113                    "max" : "1"
114                  },
115                  "type" : [{
116                    "code" : "dateTime"
117                  },
118                  {
119                    "code" : "string"
120                  }]
121                },
122                {
123                  "id" : "Immunization.performer",
124                  "path" : "Immunization.performer",
125                  "min" : 0,
126                  "max" : "*",
127                  "base" : {
128                    "path" : "Immunization.performer",
129                    "min" : 0,
130                    "max" : "*"
131                  },
132                  "type" : [{
133                    "code" : "BackboneElement"
134                  }]
135                },
136                {
137                  "id" : "Immunization.performer.id",
138                  "path" : "Immunization.performer.id",
139                  "min" : 0,
140                  "max" : "1",
141                  "base" : {
142                    "path" : "Element.id",
143                    "min" : 0,
144                    "max" : "1"
145                  },
146                  "type" : [{
147                    "extension" : [{
148                      "url" : "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
149                      "valueUrl" : "string"
150                    }],
151                    "code" : "http://hl7.org/fhirpath/System.String"
152                  }]
153                },
154                {
155                  "id" : "Immunization.performer.extension",
156                  "path" : "Immunization.performer.extension",
157                  "min" : 0,
158                  "max" : "*",
159                  "base" : {
160                    "path" : "Element.extension",
161                    "min" : 0,
162                    "max" : "*"
163                  },
164                  "type" : [{
165                    "code" : "Extension"
166                  }]
167                }
168              ]
169              }
170            }
171          },
172          {
173            "fullUrl" : "http://hl7.org/fhir/StructureDefinition/Patient",
174            "resource" : {
175              "resourceType" : "StructureDefinition",
176              "id" : "Patient",
177              "meta" : {
178                "lastUpdated" : "2019-11-01T09:29:23.356+11:00"
179              },
180              "url" : "http://hl7.org/fhir/StructureDefinition/Patient",
181              "fhirVersion" : "4.0.1",
182              "kind" : "resource",
183              "type" : "Patient",
184              "snapshot" : {
185                "element" : [{
186                  "id" : "Patient.id",
187                  "path" : "Patient.id",
188                  "min" : 0,
189                  "max" : "1",
190                  "base" : {
191                    "path" : "Resource.id",
192                    "min" : 0,
193                    "max" : "1"
194                  },
195                  "type" : [{
196                    "extension" : [{
197                      "url" : "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
198                      "valueUrl" : "string"
199                    }],
200                    "code" : "http://hl7.org/fhirpath/System.String"
201                  }]
202                }]
203              }
204            }
205          }
206        ]
207    }
208    """)
209
210    IMMUNIZATION_RESOURCE_TYPE_INT = 1
211
212    PATIENT_RESOURCE_TYPE_INT = 9
213
214    def test_fhir_spec_extractor_immunization_resource_produces_expected(self):
215        fhir_spec_extractor = FhirSpecExtractor(
216            self.BUNDLE_WITH_IMMUNIZATION_AND_PATIENT_STRUCTURE_DEFINITION,
217            {"Immunization"})
218        # we expect each top level field to be present, and one ofs that are represented in the spec
219        # as e.g. occurrence[x] should be expanded into the individual fields such as
220        # occurrenceDateTime and occurrenceString. Fields with a cardinality of 0..* or 1..* should
221        # have is_array = true set in their config.
222        expected_required_fields = {"status", "vaccineCode", "exampleFieldToTestOneToMany"}
223        expected_multi_type_config = fhirspec_pb2.MultiTypeFieldConfig(
224            name="occurrence[x]",
225            typed_field_names=["occurrenceDateTime", "occurrenceString"],
226            is_required=True
227        )
228        expected_field_names_to_config = {
229            "resourceType": fhirspec_pb2.FhirFieldConfig(
230                is_array=False,
231                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_STRING,
232                kind=fhirspec_pb2.Kind.KIND_PRIMITIVE_TYPE
233            ),
234            "id": fhirspec_pb2.FhirFieldConfig(
235                is_array=False,
236                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_SYSTEM_STRING,
237                kind=fhirspec_pb2.Kind.KIND_PRIMITIVE_TYPE
238            ),
239            "status": fhirspec_pb2.FhirFieldConfig(
240                is_array=False,
241                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_CODE,
242                kind=fhirspec_pb2.Kind.KIND_PRIMITIVE_TYPE
243            ),
244            "vaccineCode": fhirspec_pb2.FhirFieldConfig(
245                is_array=False,
246                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_COMPLEX,
247                kind=fhirspec_pb2.Kind.KIND_COMPLEX_TYPE
248            ),
249            "exampleFieldToTestOneToMany": fhirspec_pb2.FhirFieldConfig(
250                is_array=True,
251                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_COMPLEX,
252                kind=fhirspec_pb2.Kind.KIND_COMPLEX_TYPE
253            ),
254            "occurrenceDateTime": fhirspec_pb2.FhirFieldConfig(
255                is_array=False,
256                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_DATE_TIME,
257                kind=fhirspec_pb2.Kind.KIND_PRIMITIVE_TYPE
258            ),
259            "occurrenceString": fhirspec_pb2.FhirFieldConfig(
260                is_array=False,
261                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_STRING,
262                kind=fhirspec_pb2.Kind.KIND_PRIMITIVE_TYPE
263            ),
264            "performer": fhirspec_pb2.FhirFieldConfig(
265                is_array=True,
266                r4_type=fhirspec_pb2.R4FhirType.R4_FHIR_TYPE_COMPLEX,
267                kind=fhirspec_pb2.Kind.KIND_COMPLEX_TYPE
268            ),
269        }
270
271        generated_spec = fhir_spec_extractor.generate_r4_fhir_spec_proto_message()
272
273        # Check that exactly one Immunization config is present
274        self.assertEqual(len(generated_spec.resource_type_to_config.keys()), 1)
275        immunization_config = (
276            generated_spec.resource_type_to_config[self.IMMUNIZATION_RESOURCE_TYPE_INT])
277        # Check that the list of required fields is as expected
278        self.assertEquals(set(immunization_config.required_fields), expected_required_fields)
279        # Check that the list of multi type configs is as expected
280        self.assertEquals(len(immunization_config.multi_type_fields), 1)
281        received_multi_type_config = immunization_config.multi_type_fields[0]
282        self.assertEqual(received_multi_type_config.name, expected_multi_type_config.name)
283        self.assertEqual(received_multi_type_config.typed_field_names,
284                         expected_multi_type_config.typed_field_names)
285        self.assertEqual(received_multi_type_config.is_required,
286                         expected_multi_type_config.is_required)
287        # Check that the field names to config map is as expected
288        self.assertEqual(set(expected_field_names_to_config.keys()),
289                         set(immunization_config.allowed_field_names_to_config.keys()))
290        for expected_field, expected_config in expected_field_names_to_config.items():
291            self.assertEqual(
292                immunization_config.allowed_field_names_to_config[expected_field],
293                expected_config,
294                "Mismatching config for field " + expected_field
295            )
296
297    def test_fhir_spec_extractor_immunization_and_patient_contains_two_entries(self):
298        fhir_spec_extractor = FhirSpecExtractor(
299            self.BUNDLE_WITH_IMMUNIZATION_AND_PATIENT_STRUCTURE_DEFINITION,
300            {"Immunization", "Patient"})
301
302        generated_spec = fhir_spec_extractor.generate_r4_fhir_spec_proto_message()
303
304        self.assertEqual(len(generated_spec.resource_type_to_config), 2)
305
306    def test_fhir_spec_extractor_unsupported_resource_raises_exception(self):
307        with self.assertRaises(ValueError):
308            FhirSpecExtractor(
309                self.BUNDLE_WITH_IMMUNIZATION_AND_PATIENT_STRUCTURE_DEFINITION,
310                {"UnsupportedResource"})
311
312    def test_fhir_spec_extractor_missing_resource_raises_exception(self):
313        with self.assertRaises(ValueError):
314            FhirSpecExtractor(
315                self.BUNDLE_WITH_IMMUNIZATION_AND_PATIENT_STRUCTURE_DEFINITION,
316                {"Observation"})
317
318
319if __name__ == '__main__':
320    unittest.main()
321