package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.hapi.validation.FhirQuestionnaireResponseValidator;
import org.hl7.fhir.instance.model.Coding;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.IntegerType;
import org.hl7.fhir.instance.model.Questionnaire;
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
import org.hl7.fhir.instance.model.QuestionnaireResponse;
import org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.hl7.fhir.instance.model.Reference;
import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
public class QuestionnaireResponseValidatorIntegrationTest {
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorIntegrationTest.class);
private IResourceLoader myResourceLoaderMock;
private FhirValidator myVal;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before
public void before() {
myResourceLoaderMock = mock(IResourceLoader.class);
FhirQuestionnaireResponseValidator qaVal = new FhirQuestionnaireResponseValidator();
qaVal.setResourceLoader(myResourceLoaderMock);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
myVal.registerValidatorModule(qaVal);
}
@Test
public void testAnswerWithWrongType() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
}
@Test
public void testRequiredOnlyTestedForFinishedQuestionnaires() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
GroupComponent qg = q.getGroup().addGroup().setLinkId("link1").setRequired(true);
qg.addQuestion().setLinkId("link2").setRequired(false).setType(AnswerFormat.BOOLEAN);
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
// Wrong type
{
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new IntegerType(123));
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("Answer to question with linkId[link0] found of type [IntegerType] but this is invalid for question of type [boolean]"));
}
// Not populated, no status
{
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("myMessage=Missing required group with linkId[link1],mySeverity=information"));
}
// Not populated, partial status
{
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("myMessage=Missing required group with linkId[link1],mySeverity=information"));
}
// Not populated, finished status
{
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("Missing answer to required question with linkId[link0],mySeverity=error"));
}
}
@Test
public void testGroupWithNoAnswer() throws Exception {
Questionnaire q = ourCtx.newJsonParser().parseResource(Questionnaire.class, IOUtils.toString(getClass().getResourceAsStream("/questionnaire-20160712.json")));
QuestionnaireResponse qa = ourCtx.newJsonParser().parseResource(QuestionnaireResponse.class, IOUtils.toString(getClass().getResourceAsStream("/questionnaire-20160712-response.json")));
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.any(IdType.class))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertEquals(true, errors.isSuccessful());
}
@Test
public void testCodedAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset/ValueSet/123"));
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
ValueSet options = new ValueSet();
options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
when(myResourceLoaderMock.load(Mockito.eq(ValueSet.class), Mockito.eq(new IdType("http://somevalueset/ValueSet/123")))).thenReturn(options);
QuestionnaireResponse qa;
// Good code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
ValidationResult result = myVal.validateWithResult(qa);
assertEquals(result.getMessages().toString(), 0, result.getMessages().size());
// Bad code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("myLocationString=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(result.getMessages().toString(),
containsString("myMessage=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset/ValueSet/123]"));
result.toOperationOutcome();
}
@Test
public void testInvalidReference() {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("someReference"); // not relative
ValidationResult result = myVal.validateWithResult(qa);
assertEquals(result.getMessages().toString(), 1, result.getMessages().size());
assertThat(result.getMessages().toString(), containsString("Invalid reference 'someReference"));
}
@Test
public void testUnknownValueSet() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset/ValueSet/123"));
when(myResourceLoaderMock.load(Mockito.eq(Questionnaire.class), Mockito.eq(new IdType("http://example.com/Questionnaire/q1")))).thenReturn(q);
when(myResourceLoaderMock.load(Mockito.eq(ValueSet.class), Mockito.eq(new IdType("http://somevalueset/ValueSet/123")))).thenThrow(new ResourceNotFoundException("Unknown"));
QuestionnaireResponse qa;
// Bad code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
ValidationResult result = myVal.validateWithResult(qa);
assertThat(result.getMessages().toString(), containsString("myMessage=Reference could not be found: http://some"));
}
@Test
public void testContainedQuestionnaireAndValueSet() {
ValueSet vs = new ValueSet();
vs.getCodeSystem().setSystem("urn:system").addConcept().setCode("code1");
vs.setId("#VVV");
Questionnaire q = new Questionnaire();
q.setId("#QQQ");
Reference vsRef = new Reference("#VVV");
vsRef.setResource(vs);
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(vsRef);
QuestionnaireResponse qa;
// Bad code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("#QQQ");
qa.getQuestionnaire().setResource(q);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
ValidationResult result = myVal.validateWithResult(qa);
assertEquals(result.getMessages().toString(), 0, result.getMessages().size());
}
/**
* Sample provided by Eric van der Zwan
*/
@SuppressWarnings("unchecked")
@Test
public void testSampleQuestionnaire() {
when(myResourceLoaderMock.load(Mockito.any(Class.class), Mockito.any(IdType.class))).thenAnswer(new Answer<IBaseResource>() {
@Override
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IdType id = (IdType) theInvocation.getArguments()[1];
String name = "/nice/" + id.getIdPart() + ".xml";
InputStream in = getClass().getResourceAsStream(name);
if (in == null) {
throw new IllegalArgumentException(name);
}
InputStreamReader reader = new InputStreamReader(in);
String body = IOUtils.toString(reader);
if (Questionnaire.class.equals(theInvocation.getArguments()[0])) {
return ourCtx.newXmlParser().parseResource(Questionnaire.class, body);
} else if (ValueSet.class.equals(theInvocation.getArguments()[0])) {
return ourCtx.newXmlParser().parseResource(ValueSet.class, body);
} else {
throw new IllegalArgumentException(id.getValue());
}
}
});
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, new InputStreamReader(getClass().getResourceAsStream("/nice/answer-1-admission.xml")));
ValidationResult result = myVal.validateWithResult(qa);
ourLog.info(result.getMessages().toString());
assertThat(result.getMessages().toString(), containsString("Answer to question with linkId[partialBSN] found of type [IntegerType] but this is invalid for question of type [string]"));
}
}