Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation API for SDC #548

Merged
merged 23 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
14ad806
Added link id to validation result
joiskash Jun 14, 2021
bc96a50
Merge branch 'master' of https://github.com/google/android-fhir
joiskash Jun 15, 2021
962a864
Added function to validate Questionnaire response items before submit…
joiskash Jun 14, 2021
a9f7bf3
Fixed UT
joiskash Jun 14, 2021
8f7be59
Update datacapture/src/main/java/com/google/android/fhir/datacapture/…
joiskash Jun 16, 2021
6b59724
Reverted all changes to validation package
joiskash Jun 16, 2021
4049c4a
Reverted changes and added function to return the current Questionnaire
joiskash Jun 16, 2021
87fec8e
Reverted changes to questionnaireFragment
joiskash Jun 16, 2021
5d99679
Added new questionnaire Validator
joiskash Jun 16, 2021
5c3855d
Ran spotless
joiskash Jun 16, 2021
41d335d
UT fix
joiskash Jun 16, 2021
167ad15
Added unit tests
joiskash Jun 16, 2021
20e0ad2
Renamed Class to QuestionnaireResponseValidator.kt
joiskash Jun 21, 2021
70ce98a
Removed function and made questionnaire a property
joiskash Jun 21, 2021
803120f
Changed to isEqualTo
joiskash Jun 21, 2021
932681e
Using assertThat from truth instead of assertEquals
joiskash Jun 21, 2021
6a498c1
Ran spotless
joiskash Jun 21, 2021
379a1e4
Merge branch 'master' into kj/validation-api
joiskash Jun 21, 2021
c01b055
Merge branch 'master' into kj/validation-api
joiskash Jun 22, 2021
f1a0062
removed getQuestionnaire function
joiskash Jun 22, 2021
2c9f422
added Todo note to cover in the next PR
joiskash Jun 22, 2021
6f50d8b
Updated test
joiskash Jun 22, 2021
7e515ce
Merge branch 'master' into kj/validation-api
joiskash Jun 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Expand Up @@ -88,6 +88,9 @@ internal class QuestionnaireViewModel(state: SavedStateHandle) : ViewModel() {
internal val questionnaireItemViewItemListFlow: Flow<List<QuestionnaireItemViewItem>> =
modificationCount.map { questionnaireItemViewItemList }

/** The current [Questionnaire] . */
fun getQuestionnaire(): Questionnaire = questionnaire
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just change the property to internal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove this getter function altogether!


/** The current [QuestionnaireResponse] captured by the UI. */
fun getQuestionnaireResponse(): QuestionnaireResponse = questionnaireResponse

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,54 @@
/*
* 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>> {
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
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't have to do this in this PR - but you can probably create an iterator for questionnaire item + questionnaire response item. this logic is repeated in the questionnaire view model as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure ill put a comment so that it is done while handling repeated answers

}
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,159 @@
/*
* 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")
)
)
)
}
Comment on lines +87 to +96
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assertThat(result.get("a-question"))
.isEqualTo(
listOf(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
)
}
assertThat(result.get("a-question"))
.containsExactly(
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"))
.isEqualTo(
listOf(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
)
assertThat(result.get("a-nested-question"))
.isEqualTo(
listOf(
ValidationResult(
false,
listOf("The maximum number of characters that are permitted in the answer is: 3")
)
)
)
joiskash marked this conversation as resolved.
Show resolved Hide resolved
}
}