Skip to content

Commit

Permalink
Validate QuestionnaireItem.Group with repeats as repeating group (#2755)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
LZRS authored Jan 21, 2025
1 parent 7af811e commit 87fa2ae
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 Google LLC
* Copyright 2022-2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -151,10 +151,12 @@ object QuestionnaireResponseValidator {
questionnaireResponseItemValidator: QuestionnaireResponseItemValidator,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>,
): Map<String, List<ValidationResult>> {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Questionnaire.QuestionnaireItemType.DISPLAY,
Questionnaire.QuestionnaireItemType.NULL, -> Unit
Questionnaire.QuestionnaireItemType.GROUP ->
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }
when {
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
!questionnaireItem.repeats ->
// Nested items under group
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
validateQuestionnaireResponseItems(
Expand Down Expand Up @@ -262,10 +264,13 @@ object QuestionnaireResponseValidator {
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
) {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Questionnaire.QuestionnaireItemType.DISPLAY,
Questionnaire.QuestionnaireItemType.NULL, -> Unit
Questionnaire.QuestionnaireItemType.GROUP ->
checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }

when {
questionnaireItem.type == Questionnaire.QuestionnaireItemType.DISPLAY ||
questionnaireItem.type == Questionnaire.QuestionnaireItemType.NULL -> Unit
questionnaireItem.type == Questionnaire.QuestionnaireItemType.GROUP &&
!questionnaireItem.repeats ->
// Nested items under group
// http://www.hl7.org/fhir/questionnaireresponse-definitions.html#QuestionnaireResponse.item.item
checkQuestionnaireResponseItems(questionnaireItem.item, questionnaireResponseItem.item)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2024 Google LLC
* Copyright 2022-2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@ import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.google.android.fhir.datacapture.extensions.EXTENSION_HIDDEN_URL
import com.google.android.fhir.datacapture.extensions.packRepeatedGroups
import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import kotlinx.coroutines.test.runTest
Expand Down Expand Up @@ -596,6 +597,79 @@ class QuestionnaireResponseValidatorTest {
)
}

@Test
fun `validation fails for required item in a questionnaire repeating group item with answer value`() {
val questionnaire1 =
Questionnaire().apply {
url = "questionnaire-1"
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("group-1"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.GROUP,
),
)
.apply {
repeats = true
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("question-0"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.INTEGER,
),
)
.apply { required = true },
)
},
)
}

val questionnaireResponse1 =
QuestionnaireResponse()
.apply {
questionnaire = "questionnaire-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
.apply {
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = IntegerType(1)
},
)
},
)
},
)

addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0")),
)
},
)
}
.apply { packRepeatedGroups(questionnaire1) }

runTest {
val result =
QuestionnaireResponseValidator.validateQuestionnaireResponse(
questionnaire1,
questionnaireResponse1,
context,
)

assertThat(result.keys).containsExactly("question-0", "group-1")
assertThat(result["question-0"]!!.first()).isInstanceOf(Invalid::class.java)
assertThat((result["question-0"]!!.first() as Invalid).getSingleStringValidationMessage())
.isEqualTo("Missing answer for required field.")
}
}

@Test
fun `check passes if questionnaire response matches questionnaire`() {
QuestionnaireResponseValidator.checkQuestionnaireResponse(
Expand Down Expand Up @@ -1653,6 +1727,69 @@ class QuestionnaireResponseValidatorTest {
)
}

@Test
fun `check fails for wrong answer type to a nested question in repeating group`() {
assertException_checkQuestionnaireResponse_throwsIllegalArgumentException(
Questionnaire().apply {
url = "questionnaire-1"
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("group-1"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.GROUP,
),
)
.apply {
repeats = true
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("question-0"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.INTEGER,
),
),
)
},
)
},
QuestionnaireResponse().apply {
questionnaire = "questionnaire-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
.apply {
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = IntegerType(1)
},
)
},
)
},
)

addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("group-1")).apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("question-0"))
.apply {
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = DecimalType(2.0)
},
)
},
)
},
)
},
"Mismatching question type INTEGER and answer type decimal for question-0",
)
}

private fun assertException_checkQuestionnaireResponse_throwsIllegalArgumentException(
questionnaire: Questionnaire,
questionnaireResponse: QuestionnaireResponse,
Expand Down

0 comments on commit 87fa2ae

Please sign in to comment.