package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport.CodeValidationResult;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
public class QuestionnaireResponseValidatorDstu3Test {
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorDstu3Test.class);
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
private IValidationSupport myValSupport;
private IWorkerContext myWorkerCtx;
@Before
public void before() {
myValSupport = mock(IValidationSupport.class);
// new DefaultProfileValidationSupport();
myWorkerCtx = new HapiWorkerContext(ourCtx, myValSupport);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
private ValidationResult stripBindingHasNoSourceMessage(ValidationResult theErrors) {
List<SingleValidationMessage> messages = new ArrayList<SingleValidationMessage>(theErrors.getMessages());
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).getMessage().contains("has no source, so can't")) {
messages.remove(i);
i--;
}
}
return new ValidationResult(ourCtx, messages);
}
@Test
public void testAnswerWithWrongType() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Answer value must be of type boolean"));
}
@Test
public void testCodedAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setOptions(new Reference("http://somevalueset"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl("http://codesystems.com/system");
codeSystem.addConcept().setCode("code0");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
CodeSystem codeSystem2 = new CodeSystem();
codeSystem2.setContent(CodeSystemContentMode.COMPLETE);
codeSystem2.setUrl("http://codesystems.com/system2");
codeSystem2.addConcept().setCode("code2");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
QuestionnaireResponse qa;
ValidationResult errors;
// Good code
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code0"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
assertEquals(errors.toString(), 0, errors.getMessages().size());
// Bad code
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code1"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system2").setCode("code3"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system2::code3) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
}
@Test
public void testGroupWithNoLinkIdInQuestionnaireResponse() {
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent qGroup = q.addItem().setType(QuestionnaireItemType.GROUP);
qGroup.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
QuestionnaireResponseItemComponent qaGroup = qa.addItem();
qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("minimum required = 1, but only found 0 - QuestionnaireResponse.item"));
}
@Test
public void testItemWithNoType() {
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent qGroup = q.addItem();
qGroup.setLinkId("link0");
qGroup.addItem().setLinkId("link1").setType(QuestionnaireItemType.STRING);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
QuestionnaireResponseItemComponent qaGroup = qa.addItem().setLinkId("link0");
qaGroup.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Definition for item link0 does not contain a type"));
assertEquals(1, errors.getMessages().size());
}
@Test
public void testMissingRequiredQuestion() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.STRING);
q.addItem().setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
String reference = qa.getQuestionnaire().getReference();
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No response found for required item link0"));
}
@Test
public void testOpenchoiceAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent item = q.addItem();
item.setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.OPENCHOICE).setOptions(new Reference("http://somevalueset"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl("http://codesystems.com/system");
codeSystem.addConcept().setCode("code0");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
CodeSystem codeSystem2 = new CodeSystem();
codeSystem2.setContent(CodeSystemContentMode.COMPLETE);
codeSystem2.setUrl("http://codesystems.com/system2");
codeSystem2.addConcept().setCode("code2");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class))).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
QuestionnaireResponse qa;
ValidationResult errors;
// Good code
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code0"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
assertEquals(errors.toString(), 0, errors.getMessages().size());
// Bad code
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code1"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
// Partial code
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem(null).setCode("code1"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (null::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("").setCode("code1"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (null::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://system").setCode(null));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://system::null) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
// Wrong type
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new IntegerType(123));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Cannot validate integer answer option because no option list is provided"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item.answer"));
// String answer
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setDisplay("Hello"));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), empty());
// Missing String answer
qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setDisplay(""));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No response answer found for required item link0"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item"));
}
@Test
public void testUnexpectedAnswer() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString(" - QuestionnaireResponse"));
assertThat(errors.toString(), containsString("LinkId \"link1\" not found in questionnaire"));
}
@Test
public void testUnexpectedGroup() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addItem().setLinkId("link2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString(" - QuestionnaireResponse"));
assertThat(errors.toString(), containsString("LinkId \"link1\" not found in questionnaire"));
}
// @Test
public void validateHealthConnexExample() throws Exception {
String input = IOUtils.toString(QuestionnaireResponseValidatorDstu3Test.class.getResourceAsStream("/questionnaireanswers-0f431c50ddbe4fff8e0dd6b7323625fc.xml"));
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input);
ValidationResult errors = myVal.validateWithResult(qa);
assertEquals(errors.toString(), 0, errors.getMessages().size());
/*
* Now change a coded value
*/
//@formatter:off
input = input.replaceAll(
"<answer>\n" +
" <valueCoding>\n" +
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" +
" <code value=\"2\"/>\n" +
" <display value=\"Once/twice\"/>\n" +
" </valueCoding>\n" +
" </answer>",
"<answer>\n" +
" <valueCoding>\n" +
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" +
" <code value=\"GGG\"/>\n" +
" <display value=\"Once/twice\"/>\n" +
" </valueCoding>\n" +
" </answer>");
assertThat(input, containsString("GGG"));
//@formatter:on
qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input);
errors = myVal.validateWithResult(qa);
assertEquals(errors.toString(), 10, errors.getMessages().size());
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}