diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt index 9c3af08f8d..e5c9d3f9f8 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2022-2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import androidx.test.core.app.ApplicationProvider import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.context.FhirVersionEnum import ca.uhn.fhir.parser.IParser +import com.google.android.fhir.datacapture.extensions.EXTENSION_LAUNCH_CONTEXT +import com.google.android.fhir.datacapture.extensions.EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT import com.google.android.fhir.datacapture.views.factories.localDate import com.google.common.truth.Truth.assertThat import java.math.BigDecimal @@ -32,23 +34,21 @@ import kotlin.test.assertFailsWith import kotlinx.coroutines.runBlocking import org.hl7.fhir.exceptions.FHIRException import org.hl7.fhir.r4.model.Address -import org.hl7.fhir.r4.model.Annotation import org.hl7.fhir.r4.model.Base import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.ContactPoint import org.hl7.fhir.r4.model.DateType +import org.hl7.fhir.r4.model.Encounter import org.hl7.fhir.r4.model.Enumerations import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.HumanName import org.hl7.fhir.r4.model.Immunization -import org.hl7.fhir.r4.model.MarkdownType import org.hl7.fhir.r4.model.Observation import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse -import org.hl7.fhir.r4.model.RelatedPerson import org.hl7.fhir.r4.model.ResourceFactory import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.model.codesystems.AdministrativeGender @@ -801,6 +801,16 @@ class ResourceMapperTest { runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "mother", "Mother")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-dob" @@ -820,7 +830,7 @@ class ResourceMapperTest { val patientId = UUID.randomUUID().toString() val patient = Patient().apply { id = "Patient/$patientId/_history/2" } - val questionnaireResponse = ResourceMapper.populate(questionnaire, patient) + val questionnaireResponse = ResourceMapper.populate(questionnaire, mapOf("mother" to patient)) assertThat((questionnaireResponse.item[0].answer[0].value as DateType).localDate) .isEqualTo((DateType(Date())).localDate) @@ -1173,6 +1183,25 @@ class ResourceMapperTest { { "resourceType": "Questionnaire", "id": "client-registration-sample", + "extension": [ + { + "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext", + "extension": [ + { + "url": "name", + "valueCoding": { + "system": "http://hl7.org/fhir/uv/sdc/CodeSystem/launchContext", + "code": "father", + "display": "Patient" + } + }, + { + "url": "type", + "valueCode": "Patient" + } + ] + } + ], "status": "active", "date": "2020-11-18T07:24:47.111Z", "subjectType": [ @@ -1194,7 +1223,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.name.given", + "expression": "%father.name.given", "name": "patientName" } } @@ -1210,7 +1239,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.name.family", + "expression": "%father.name.family", "name": "patientFamily" } } @@ -1228,7 +1257,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.birthDate", + "expression": "%father.birthDate", "name": "patientBirthDate" } } @@ -1245,7 +1274,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.gender.value", + "expression": "%father.gender.value", "name": "patientGender" } } @@ -1283,7 +1312,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.telecom.value", + "expression": "%father.telecom.value", "name": "patientTelecom" } } @@ -1306,7 +1335,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.address.city", + "expression": "%father.address.city", "name": "patientCity" } } @@ -1322,7 +1351,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.address.country", + "expression": "%father.address.country", "name": "patientCity" } } @@ -1340,7 +1369,7 @@ class ResourceMapperTest { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "Patient.active", + "expression": "%father.active", "name": "patientActive" } } @@ -1361,7 +1390,7 @@ class ResourceMapperTest { as Questionnaire val patient = createPatientResource() - val response = ResourceMapper.populate(uriTestQuestionnaire, patient) + val response = ResourceMapper.populate(uriTestQuestionnaire, mapOf("father" to patient)) val responseItem = response.item[0] assertThat(((responseItem.item[0].item[0].answer[0]).value as StringType).valueAsString) @@ -1385,29 +1414,44 @@ class ResourceMapperTest { @Test fun `populate() should fill QuestionnaireResponse with values when given multiple Resources`() = runBlocking { - val relatedPerson = - RelatedPerson().apply { - name = - listOf( - HumanName().apply { - given = listOf(StringType("John")) - family = "Doe" - } - ) - birthDate = "1990-05-20".toDateFromFormatYyyyMmDd() - } - - val observation = - Observation().apply { - value = StringType("Allergic to dairy products and proteins") - note = listOf(Annotation(MarkdownType("Patient Registration Comments"))) - } - val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "father", "Father")), + Extension("type", StringType("Patient")) + ) + } + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "mother", "Mother")), + Extension("type", StringType("Patient")) + ) + } + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension( + "name", + Coding( + EXTENSION_LAUNCH_CONTEXT, + "registration-encounter", + "Registration Encounter" + ) + ), + Extension("type", StringType("Encounter")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { - linkId = "first-name" + linkId = "first-name-father" type = Questionnaire.QuestionnaireItemType.TEXT extension = listOf( @@ -1415,7 +1459,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.name.given" + expression = "%father.name.given" } ) ) @@ -1423,38 +1467,15 @@ class ResourceMapperTest { ) .addItem( Questionnaire.QuestionnaireItemComponent().apply { - linkId = "related-person-details" - type = Questionnaire.QuestionnaireItemType.GROUP - item = - listOf( - Questionnaire.QuestionnaireItemComponent().apply { - linkId = "rp-family-name" - type = Questionnaire.QuestionnaireItemType.TEXT - extension = - listOf( - Extension( - ITEM_INITIAL_EXPRESSION_URL, - Expression().apply { - language = "text/fhirpath" - expression = "RelatedPerson.name.family" - } - ) - ) - } - ) - } - ) - .addItem( - Questionnaire.QuestionnaireItemComponent().apply { - linkId = "related-person-dob" - type = Questionnaire.QuestionnaireItemType.DATE + linkId = "first-name-mother" + type = Questionnaire.QuestionnaireItemType.TEXT extension = listOf( Extension( ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "RelatedPerson.birthDate" + expression = "%mother.name.given" } ) ) @@ -1462,7 +1483,7 @@ class ResourceMapperTest { ) .addItem( Questionnaire.QuestionnaireItemComponent().apply { - linkId = "comments" + linkId = "encounter-reason" type = Questionnaire.QuestionnaireItemType.TEXT extension = listOf( @@ -1470,33 +1491,140 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Observation.value" + expression = "%registration-encounter.reasonCode[0].text" } ) ) } ) - val patient = createPatientResource() + val patientFather = + Patient().apply { + active = true + gender = Enumerations.AdministrativeGender.MALE + name = listOf(HumanName().apply { given = mutableListOf(StringType("Salman")) }) + } + + val patientMother = + Patient().apply { + active = true + gender = Enumerations.AdministrativeGender.FEMALE + name = listOf(HumanName().apply { given = mutableListOf(StringType("Fatima")) }) + } + + val encounter = + Encounter().apply { + addReasonCode().apply { addCoding().apply { text = "Registration Task" } } + } + val questionnaireResponse = - ResourceMapper.populate(questionnaire, patient, relatedPerson, observation) + ResourceMapper.populate( + questionnaire, + mapOf( + "father" to patientFather, + "mother" to patientMother, + "registration-encounter" to encounter + ) + ) assertThat((questionnaireResponse.item[0].answer[0].value as StringType).valueAsString) .isEqualTo("Salman") - assertThat( - ((questionnaireResponse.item[1].item[0].answer[0]).value as StringType).valueAsString + assertThat(((questionnaireResponse.item[1].answer[0]).value as StringType).valueAsString) + .isEqualTo("Fatima") + assertThat(((questionnaireResponse.item[2].answer[0]).value as StringType).valueAsString) + .isEqualTo("Registration Task") + } + + @Test + fun `populate() should not fill QuestionnaireResponse with values if the intended launch context extension is not declared`(): + Unit = runBlocking { + val questionnaire = + Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "father", "Father")), + Extension("type", StringType("Patient")) + ) + } + } + .addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "first-name-father" + type = Questionnaire.QuestionnaireItemType.TEXT + extension = + listOf( + Extension( + ITEM_INITIAL_EXPRESSION_URL, + Expression().apply { + language = "text/fhirpath" + expression = "%father.name.given" + } + ) + ) + } ) - .isEqualTo("Doe") - assertThat(((questionnaireResponse.item[2].answer[0]).value as DateType).valueAsString) - .isEqualTo("1990-05-20") - assertThat(((questionnaireResponse.item[3].answer[0]).value as StringType).valueAsString) - .isEqualTo("Allergic to dairy products and proteins") + .addItem( + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "first-name-mother" + type = Questionnaire.QuestionnaireItemType.TEXT + extension = + listOf( + Extension( + ITEM_INITIAL_EXPRESSION_URL, + Expression().apply { + language = "text/fhirpath" + expression = "%mother.name.given" + } + ) + ) + } + ) + + val patientFather = + Patient().apply { + active = true + gender = Enumerations.AdministrativeGender.MALE + name = listOf(HumanName().apply { given = mutableListOf(StringType("Salman")) }) + } + + val patientMother = + Patient().apply { + active = true + gender = Enumerations.AdministrativeGender.FEMALE + name = listOf(HumanName().apply { given = mutableListOf(StringType("Fatima")) }) + } + + val questionnaireResponse = + ResourceMapper.populate( + questionnaire, + mapOf("father" to patientFather, "mother" to patientMother) + ) + + assertThat((questionnaireResponse.item[0].answer[0].value as StringType).valueAsString) + .isEqualTo("Salman") + assertFailsWith { + assertThat(((questionnaireResponse.item[1].answer[0]).value as StringType).valueAsString) + .isEqualTo("Fatima") } + } @Test fun `populate() should correctly populate IdType value in QuestionnaireResponse`() = runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "father", "Father")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-id" @@ -1507,7 +1635,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.id" + expression = "%father.id" } ) ) @@ -1516,7 +1644,7 @@ class ResourceMapperTest { val patientId = UUID.randomUUID().toString() val patient = Patient().apply { id = "Patient/$patientId" } - val questionnaireResponse = ResourceMapper.populate(questionnaire, patient) + val questionnaireResponse = ResourceMapper.populate(questionnaire, mapOf("father" to patient)) assertThat((questionnaireResponse.item[0].answer[0].value as StringType).value) .isEqualTo(patientId) @@ -1527,6 +1655,16 @@ class ResourceMapperTest { runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "father", "Father")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-id" @@ -1537,7 +1675,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.id" + expression = "%father.id" } ) ) @@ -1546,7 +1684,7 @@ class ResourceMapperTest { val patientId = UUID.randomUUID().toString() val patient = Patient().apply { id = "Patient/$patientId/_history/2" } - val questionnaireResponse = ResourceMapper.populate(questionnaire, patient) + val questionnaireResponse = ResourceMapper.populate(questionnaire, mapOf("father" to patient)) assertThat((questionnaireResponse.item[0].answer[0].value as StringType).value) .isEqualTo(patientId) @@ -1557,6 +1695,16 @@ class ResourceMapperTest { runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "mother", "Mother")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-gender" @@ -1567,7 +1715,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.gender" + expression = "%mother.gender" } ) ) @@ -1590,7 +1738,7 @@ class ResourceMapperTest { ) val patient = Patient().apply { gender = Enumerations.AdministrativeGender.FEMALE } - val questionnaireResponse = ResourceMapper.populate(questionnaire, patient) + val questionnaireResponse = ResourceMapper.populate(questionnaire, mapOf("mother" to patient)) assertThat((questionnaireResponse.item[0].answer[0].value as Coding).code).isEqualTo("female") assertThat((questionnaireResponse.item[0].answer[0].value as Coding).display) @@ -1601,6 +1749,16 @@ class ResourceMapperTest { fun `populate() should populate nested non-group questions`() = runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "mother", "Mother")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-gender" @@ -1611,7 +1769,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.gender" + expression = "%mother.gender" } ) ) @@ -1641,7 +1799,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.id" + expression = "%mother.id" } ) ) @@ -1656,7 +1814,7 @@ class ResourceMapperTest { gender = Enumerations.AdministrativeGender.FEMALE id = "Patient/$patientId/_history/2" } - val questionnaireResponse = ResourceMapper.populate(questionnaire, patient) + val questionnaireResponse = ResourceMapper.populate(questionnaire, mapOf("mother" to patient)) assertThat((questionnaireResponse.item[0].answer[0].value as Coding).code).isEqualTo("female") assertThat((questionnaireResponse.item[0].answer[0].value as Coding).display) @@ -2533,6 +2691,16 @@ class ResourceMapperTest { Unit = runBlocking { val questionnaire = Questionnaire() + .apply { + addExtension().apply { + url = EXTENSION_SDC_QUESTIONNAIRE_LAUNCH_CONTEXT + extension = + listOf( + Extension("name", Coding(EXTENSION_LAUNCH_CONTEXT, "father", "Father")), + Extension("type", StringType("Patient")) + ) + } + } .addItem( Questionnaire.QuestionnaireItemComponent().apply { linkId = "patient-gender" @@ -2543,7 +2711,7 @@ class ResourceMapperTest { ITEM_INITIAL_EXPRESSION_URL, Expression().apply { language = "text/fhirpath" - expression = "Patient.gender" + expression = "%father.gender" } ) ) @@ -2553,7 +2721,9 @@ class ResourceMapperTest { val patient = Patient().apply { gender = Enumerations.AdministrativeGender.MALE } val errorMessage = - assertFailsWith { ResourceMapper.populate(questionnaire, patient) } + assertFailsWith { + ResourceMapper.populate(questionnaire, mapOf("father" to patient)) + } .localizedMessage assertThat(errorMessage) .isEqualTo(