Skip to content

Commit 87fa2ae

Browse files
authored
Validate QuestionnaireItem.Group with repeats as repeating group (#2755)
* Validate QuestionnaireItem.Group with repeats as repeating group Instead of just as group * Add tests for repeating groups in QuestionnaireResponseValidatorTest * Update licence year to fix spotless
1 parent 7af811e commit 87fa2ae

File tree

2 files changed

+152
-10
lines changed

2 files changed

+152
-10
lines changed

datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidator.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2024 Google LLC
2+
* Copyright 2022-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -151,10 +151,12 @@ object QuestionnaireResponseValidator {
151151
questionnaireResponseItemValidator: QuestionnaireResponseItemValidator,
152152
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>,
153153
): Map<String, List<ValidationResult>> {
154-
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
155-
Questionnaire.QuestionnaireItemType.DISPLAY,
156-
Questionnaire.QuestionnaireItemType.NULL, -> Unit
157-
Questionnaire.QuestionnaireItemType.GROUP ->
154+
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }
155+
when {
156+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
157+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
158+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
159+
!questionnaireItem.repeats ->
158160
// Nested items under group
159161
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
160162
validateQuestionnaireResponseItems(
@@ -262,10 +264,13 @@ object QuestionnaireResponseValidator {
262264
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
263265
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
264266
) {
265-
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
266-
Questionnaire.QuestionnaireItemType.DISPLAY,
267-
Questionnaire.QuestionnaireItemType.NULL, -> Unit
268-
Questionnaire.QuestionnaireItemType.GROUP ->
267+
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }
268+
269+
when {
270+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
271+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
272+
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
273+
!questionnaireItem.repeats ->
269274
// Nested items under group
270275
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
271276
checkQuestionnaireResponseItems(questionnaireItem.item, questionnaireResponseItem.item)

datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidatorTest.kt

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2024 Google LLC
2+
* Copyright 2022-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import android.content.Context
2020
import android.os.Build
2121
import androidx.test.core.app.ApplicationProvider
2222
import com.google.android.fhir.datacapture.extensions.EXTENSION_HIDDEN_URL
23+
import com.google.android.fhir.datacapture.extensions.packRepeatedGroups
2324
import com.google.common.truth.Truth.assertThat
2425
import java.math.BigDecimal
2526
import kotlinx.coroutines.test.runTest
@@ -596,6 +597,79 @@ class QuestionnaireResponseValidatorTest {
596597
)
597598
}
598599

600+
@Test
601+
fun `validation fails for required item in a questionnaire repeating group item with answer value`() {
602+
val questionnaire1 =
603+
Questionnaire().apply {
604+
url = "questionnaire-1"
605+
addItem(
606+
Questionnaire.QuestionnaireItemComponent(
607+
StringType("group-1"),
608+
Enumeration(
609+
Questionnaire.QuestionnaireItemTypeEnumFactory(),
610+
Questionnaire.QuestionnaireItemType.GROUP,
611+
),
612+
)
613+
.apply {
614+
repeats = true
615+
addItem(
616+
Questionnaire.QuestionnaireItemComponent(
617+
StringType("question-0"),
618+
Enumeration(
619+
Questionnaire.QuestionnaireItemTypeEnumFactory(),
620+
Questionnaire.QuestionnaireItemType.INTEGER,
621+
),
622+
)
623+
.apply { required = true },
624+
)
625+
},
626+
)
627+
}
628+
629+
val questionnaireResponse1 =
630+
QuestionnaireResponse()
631+
.apply {
632+
questionnaire = "questionnaire-1"
633+
addItem(
634+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
635+
addItem(
636+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
637+
.apply {
638+
addAnswer(
639+
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
640+
value = IntegerType(1)
641+
},
642+
)
643+
},
644+
)
645+
},
646+
)
647+
648+
addItem(
649+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
650+
addItem(
651+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0")),
652+
)
653+
},
654+
)
655+
}
656+
.apply { packRepeatedGroups(questionnaire1) }
657+
658+
runTest {
659+
val result =
660+
QuestionnaireResponseValidator.validateQuestionnaireResponse(
661+
questionnaire1,
662+
questionnaireResponse1,
663+
context,
664+
)
665+
666+
assertThat(result.keys).containsExactly("question-0", "group-1")
667+
assertThat(result["question-0"]!!.first()).isInstanceOf(Invalid::class.java)
668+
assertThat((result["question-0"]!!.first() as Invalid).getSingleStringValidationMessage())
669+
.isEqualTo("Missing answer for required field.")
670+
}
671+
}
672+
599673
@Test
600674
fun `check passes if questionnaire response matches questionnaire`() {
601675
QuestionnaireResponseValidator.checkQuestionnaireResponse(
@@ -1653,6 +1727,69 @@ class QuestionnaireResponseValidatorTest {
16531727
)
16541728
}
16551729

1730+
@Test
1731+
fun `check fails for wrong answer type to a nested question in repeating group`() {
1732+
assertException_checkQuestionnaireResponse_throwsIllegalArgumentException(
1733+
Questionnaire().apply {
1734+
url = "questionnaire-1"
1735+
addItem(
1736+
Questionnaire.QuestionnaireItemComponent(
1737+
StringType("group-1"),
1738+
Enumeration(
1739+
Questionnaire.QuestionnaireItemTypeEnumFactory(),
1740+
Questionnaire.QuestionnaireItemType.GROUP,
1741+
),
1742+
)
1743+
.apply {
1744+
repeats = true
1745+
addItem(
1746+
Questionnaire.QuestionnaireItemComponent(
1747+
StringType("question-0"),
1748+
Enumeration(
1749+
Questionnaire.QuestionnaireItemTypeEnumFactory(),
1750+
Questionnaire.QuestionnaireItemType.INTEGER,
1751+
),
1752+
),
1753+
)
1754+
},
1755+
)
1756+
},
1757+
QuestionnaireResponse().apply {
1758+
questionnaire = "questionnaire-1"
1759+
addItem(
1760+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
1761+
addItem(
1762+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
1763+
.apply {
1764+
addAnswer(
1765+
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
1766+
value = IntegerType(1)
1767+
},
1768+
)
1769+
},
1770+
)
1771+
},
1772+
)
1773+
1774+
addItem(
1775+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
1776+
addItem(
1777+
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
1778+
.apply {
1779+
addAnswer(
1780+
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
1781+
value = DecimalType(2.0)
1782+
},
1783+
)
1784+
},
1785+
)
1786+
},
1787+
)
1788+
},
1789+
"Mismatching question type INTEGER and answer type decimal for question-0",
1790+
)
1791+
}
1792+
16561793
private fun assertException_checkQuestionnaireResponse_throwsIllegalArgumentException(
16571794
questionnaire: Questionnaire,
16581795
questionnaireResponse: QuestionnaireResponse,

0 commit comments

Comments
 (0)