Skip to content

Commit

Permalink
Validation API for SDC (#548)
Browse files Browse the repository at this point in the history
* Added link id to validation result

* Added function to validate Questionnaire response items before submitting

* Fixed UT

* Update datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintValidator.kt

Co-authored-by: Jing Tang <jing.t86@gmail.com>

* Reverted all changes to validation package

* Reverted changes and added function to return the current Questionnaire

* Reverted changes to questionnaireFragment

* Added new questionnaire Validator

* Ran spotless

* UT fix

* Added unit tests

* Renamed Class to QuestionnaireResponseValidator.kt

* Removed function and made questionnaire a property

* Changed to isEqualTo

* Using assertThat from truth instead of assertEquals

* Ran spotless

* removed getQuestionnaire function

* added Todo note to cover in the next PR

* Updated test

Co-authored-by: Jing Tang <jing.t86@gmail.com>

Co-authored-by: Jing Tang <jing.t86@gmail.com>
  • Loading branch information
joiskash and jingtang10 committed Jun 22, 2021
1 parent 3ea3035 commit b6950ae
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class QuestionnaireFragment : Fragment() {
// Returns the current questionnaire response
fun getQuestionnaireResponse() = viewModel.getQuestionnaireResponse()

// The current questionnaire
internal val questionnaire = viewModel.questionnaire

companion object {
const val BUNDLE_KEY_QUESTIONNAIRE = "questionnaire"
const val BUNDLE_KEY_QUESTIONNAIRE_RESPONSE = "questionnaire-response"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse

internal class QuestionnaireViewModel(state: SavedStateHandle) : ViewModel() {
/** The current questionnaire as questions are being answered. */
private val questionnaire: Questionnaire
internal val questionnaire: Questionnaire

init {
val questionnaireJson: String = state[QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE]!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ internal object QuestionnaireResponseItemValidator {
}
}

internal data class ValidationResult(var isValid: Boolean, val validationMessages: List<String>)
data class ValidationResult(var isValid: Boolean, val validationMessages: List<String>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.datacapture.validation

import com.google.android.fhir.datacapture.hasNestedItemsWithinAnswers
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

object QuestionnaireResponseValidator {

/** Maps linkId to [ValidationResult]. */
private val linkIdToValidationResultMap = mutableMapOf<String, MutableList<ValidationResult>>()

/**
* Validates [questionnaireResponseItemList] using the constraints defined in the
* [questionnaireItemList].
*/
fun validate(
questionnaireItemList: List<Questionnaire.QuestionnaireItemComponent>,
questionnaireResponseItemList: List<QuestionnaireResponse.QuestionnaireResponseItemComponent>
): Map<String, List<ValidationResult>> {
/* TODO create an iterator for questionnaire item + questionnaire response item refer to the
questionnaire view model */
val questionnaireItemListIterator = questionnaireItemList.iterator()
val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator()
while (questionnaireItemListIterator.hasNext() &&
questionnaireResponseItemListIterator.hasNext()) {
val questionnaireItem = questionnaireItemListIterator.next()
val questionnaireResponseItem = questionnaireResponseItemListIterator.next()
linkIdToValidationResultMap[questionnaireItem.linkId] = mutableListOf()
linkIdToValidationResultMap[questionnaireItem.linkId]?.add(
QuestionnaireResponseItemValidator.validate(questionnaireItem, questionnaireResponseItem)
)
if (questionnaireItem.hasNestedItemsWithinAnswers) {
// TODO(https://github.com/google/android-fhir/issues/487): Validates all answers.
validate(questionnaireItem.item, questionnaireResponseItem.answer[0].item)
}
validate(questionnaireItem.item, questionnaireResponseItem.item)
}
return linkIdToValidationResultMap
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class MaxValueConstraintValidatorTest {
MaxValueConstraintValidator.validate(questionnaireItem, questionnaireResponseItem)

assertThat(validationResult.isValid).isFalse()
assertThat(validationResult.message.equals("Maximum value allowed is:200000")).isTrue()
assertThat(validationResult.message).isEqualTo("Maximum value allowed is:200000")
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class QuestionnaireResponseItemValidatorTest {
fun exceededMaxMinValue_shouldReturnInvalidResultWithMessages() {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "a-question"
addExtension(
Extension().apply {
url = MIN_VALUE_EXTENSION_URL
Expand All @@ -84,6 +85,7 @@ class QuestionnaireResponseItemValidatorTest {
}
val questionnaireResponseItem =
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "a-question"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = IntegerType(550)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.datacapture.validation

import android.os.Build
import com.google.common.truth.Truth.assertThat
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.StringType
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class QuestionnaireResponseValidatorTest {

@Test
fun shouldReturnValidResult() {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent()
.setLinkId("a-question")
.setMaxLength(3)
.setType(Questionnaire.QuestionnaireItemType.INTEGER)
.setText("Age in years?")
)
val questionnaireResponse =
QuestionnaireResponse()
.addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent()
.setLinkId("a-question")
.setAnswer(
listOf(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(IntegerType(3))
)
)
)
val result =
QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item)
assertThat(result.get("a-question")).isEqualTo(listOf(ValidationResult(true, listOf())))
}

@Test
fun shouldReturnInvalidResultWithMessages() {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent()
.setLinkId("a-question")
.setMaxLength(3)
.setType(Questionnaire.QuestionnaireItemType.INTEGER)
.setText("Age in years?")
)
val questionnaireResponse =
QuestionnaireResponse()
.addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent()
.setLinkId("a-question")
.setAnswer(
listOf(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(IntegerType(1000))
)
)
)
val result =
QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item)
assertThat(result.get("a-question"))
.isEqualTo(
listOf(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
)
}

@Test
fun shouldReturnInvalidResultWithMessages_forNestedItems() {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent()
.setLinkId("a-question")
.setMaxLength(3)
.setType(Questionnaire.QuestionnaireItemType.INTEGER)
.setText("Age in years?")
.addItem(
Questionnaire.QuestionnaireItemComponent()
.setLinkId("a-nested-question")
.setMaxLength(3)
.setType(Questionnaire.QuestionnaireItemType.STRING)
.setText("Country code")
)
)
val questionnaireResponse =
QuestionnaireResponse()
.addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent()
.setLinkId("a-question")
.setAnswer(
listOf(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(IntegerType(1000))
.addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent()
.setLinkId("a-nested-question")
.setAnswer(
listOf(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(StringType("ABCD"))
)
)
)
)
)
)
val result =
QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item)
assertThat(result.get("a-question"))
.containsExactly(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
assertThat(result.get("a-nested-question"))
.containsExactly(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
}
}

0 comments on commit b6950ae

Please sign in to comment.