Adding more tests for endpoint validation and repoitory validation with javascript configuration. Some changes in existing tests.

This commit is contained in:
Martha Mitran 2024-01-12 08:26:39 -08:00
parent d3b1d75dce
commit 80519bf0c4
5 changed files with 600 additions and 333 deletions

View File

@ -0,0 +1,171 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class EndpointResourceValidationR4Test extends BaseResourceProviderR4Test {
private static final String myResourceType = "Patient";
private final String myProfile = "http://example.org/fhir/StructureDefinition/TestPatient";
private PrePopulatedValidationSupport myPrePopulatedValidationSupport;
@BeforeEach
public void before() {
CachingValidationSupport myValidationSupport = createCachingValidationSupport();
FhirInstanceValidator fhirInstanceValidator = new FhirInstanceValidator(myValidationSupport);
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.addValidatorModule(fhirInstanceValidator);
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
myServer.registerInterceptor(interceptor);
}
@Nonnull
private CachingValidationSupport createCachingValidationSupport() {
myPrePopulatedValidationSupport = new PrePopulatedValidationSupport(myFhirContext);
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(myFhirContext),
new SnapshotGeneratingValidationSupport(myFhirContext),
new CommonCodeSystemsTerminologyService(myFhirContext),
new InMemoryTerminologyServerValidationSupport(myFhirContext),
myPrePopulatedValidationSupport);
return new CachingValidationSupport(chain, true);
}
@Test
public void testCreatePatientRequest_withProfileNotRegistered_unknownProfile() {
createProfile(myProfile, "1", "Patient.identifier");
final Patient patient = new Patient();
patient.setMeta(new Meta().addProfile(myProfile));
try {
myClient.create().resource(patient).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Profile reference '" + myProfile + "' has not been checked because it is unknown", e.getMessage());
}
}
@Test
public void testCreatePatientRequest_withProfileNoVersion_throwsExceptionWithLatestVersion() {
createAndRegisterProfile("1", "Patient.identifier");
createAndRegisterProfile("2", "Patient.name");
final Patient patient = new Patient();
patient.setMeta(new Meta().addProfile(myProfile));
try {
myClient.create().resource(patient).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Patient.name: minimum required = 1, but only found 0 (from " + myProfile + "|2)", e.getMessage());
}
}
@Test
public void testCreatePatientRequest_withProfileWithVersion_throwsExceptionWithSpecifiedVersion() {
createAndRegisterProfile("1", "Patient.identifier");
createAndRegisterProfile("2", "Patient.name");
final Patient patient = new Patient();
patient.setMeta(new Meta().addProfile(myProfile + "|1"));
try {
myClient.create().resource(patient).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Patient.identifier: minimum required = 1, but only found 0 (from " + myProfile + "|1)", e.getMessage());
}
}
@Test
public void testCreatePatientRequest_withMultipleProfiles_throwsExceptionWithFirstDeclaredProfile() {
final String sdIdentifier = myProfile + "-identifier";
final String sdName = myProfile + "-name";
createAndRegisterProfile(sdIdentifier, "1", "Patient.identifier");
createAndRegisterProfile(sdName, "1", "Patient.name");
final Patient patient = new Patient();
patient.setMeta(new Meta().addProfile(sdIdentifier).addProfile(sdName));
try {
myClient.create().resource(patient).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Patient.identifier: minimum required = 1, but only found 0 (from " + sdIdentifier + "|1)", e.getMessage());
}
}
@Test
public void testCreatePatientRequest_withMultipleVersions_throwsExceptionWithFirstDeclaredProfile() {
createAndRegisterProfile(myProfile, "1", "Patient.identifier");
createAndRegisterProfile(myProfile, "2", "Patient.name");
createAndRegisterProfile(myProfile, "3", "Patient.birthDate");
final Patient patient = new Patient();
patient.setMeta(new Meta()
.addProfile(myProfile + "|2")
.addProfile(myProfile + "|1")
.addProfile(myProfile + "|3"));
try {
myClient.create().resource(patient).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Patient.name: minimum required = 1, but only found 0 (from " + myProfile + "|2)", e.getMessage());
}
}
private void createAndRegisterProfile(String theVersion, String thePath) {
createAndRegisterProfile(myProfile, theVersion, thePath);
}
private void createAndRegisterProfile(String theUrl, String theVersion, String thePath) {
StructureDefinition sd = createProfile(theUrl, theVersion, thePath);
myPrePopulatedValidationSupport.addStructureDefinition(sd);
}
private StructureDefinition createProfile(String theUrl, String theVersion, String thePath) {
final String baseProfile = "http://hl7.org/fhir/StructureDefinition/Patient";
final String profileId = "TestProfile";
StructureDefinition sd = new StructureDefinition()
.setUrl(theUrl).setVersion(theVersion)
.setBaseDefinition(baseProfile)
.setType(myResourceType)
.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setId(profileId);
sd.getDifferential().addElement()
.setPath(thePath)
.setMin(1)
.setId(thePath);
DaoMethodOutcome outcome = myStructureDefinitionDao.update(sd, new SystemRequestDetails());
assertNotNull(outcome.getResource());
return (StructureDefinition) outcome.getResource();
}
}

View File

@ -0,0 +1,180 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class OnDemandResourceValidationR4Test extends BaseResourceProviderR4Test {
private static final FhirContext ourCtx = FhirContext.forR4();
private static final String myResourceType = "Patient";
private final String myProfile = "http://example.org/fhir/StructureDefinition/TestPatient";
@Test
public void testCreateStructureDefinition_createMultipleWithWithSameUrl_isStoredSuccessfully() {
createProfile("1", "Patient.identifier");
createProfile("2", "Patient.name");
createProfile("3", "Patient.birthdate");
}
private static FhirValidator myFhirValidator;
private static PrePopulatedValidationSupport ourValidationSupport;
@BeforeEach
public void before() {
myFhirValidator = ourCtx.newValidator();
myFhirValidator.setValidateAgainstStandardSchema(false);
myFhirValidator.setValidateAgainstStandardSchematron(false);
ourValidationSupport = new PrePopulatedValidationSupport(ourCtx);
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(ourCtx),
new SnapshotGeneratingValidationSupport(ourCtx),
new CommonCodeSystemsTerminologyService(ourCtx),
new InMemoryTerminologyServerValidationSupport(ourCtx),
ourValidationSupport);
CachingValidationSupport myValidationSupport = new CachingValidationSupport(chain, true);
FhirInstanceValidator myInstanceVal = new FhirInstanceValidator(myValidationSupport);
myFhirValidator.registerValidatorModule(myInstanceVal);
}
@Test
public void testValidatePatient_withProfileNotRegistered_unknownProfile() {
createProfile(myProfile, "1", "Patient.identifier");
IIdType idType = createPatient(withProfile(myProfile));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Profile reference '" + myProfile + "' has not been checked because it is unknown", message.getMessage());
}
@Test
public void testValidatePatient_withProfileNoVersion_shouldUsesLatestVersion() {
createAndRegisterProfile(myProfile, "1", "Patient.identifier");
createAndRegisterProfile(myProfile, "2", "Patient.name");
IIdType idType = createPatient(withProfile(myProfile));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.name: minimum required = 1, but only found 0 (from " + myProfile + "|2)", message.getMessage());
}
@Test
public void testValidatePatient_withProfileWithVersion_shouldUseSpecifiedVersion() {
createAndRegisterProfile(myProfile, "1", "Patient.identifier");
createAndRegisterProfile(myProfile, "2", "Patient.name");
IIdType idType = createPatient(withProfile(myProfile + "|1"));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + myProfile + "|1)", message.getMessage());
}
@Test
public void testValidatePatient_withMultipleProfiles_validatesAgainstAllProfiles() {
final String sdIdentifier = myProfile + "-identifier";
final String sdName = myProfile + "-name";
createAndRegisterProfile(sdIdentifier, "1", "Patient.identifier");
createAndRegisterProfile(sdName, "1", "Patient.name");
IIdType idType = createPatient(withProfile(sdIdentifier), withProfile(sdName));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(2, validationResult.getMessages().size());
Iterator<SingleValidationMessage> messageIterator = validationResult.getMessages().iterator();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + myProfile + "-identifier|1)", messageIterator.next().getMessage());
assertEquals("Patient.name: minimum required = 1, but only found 0 (from " + myProfile + "-name|1)", messageIterator.next().getMessage());
}
@Test
public void testValidatePatient_withMultipleVersions_validatesAgainstFirstVersion() {
createAndRegisterProfile(myProfile, "1", "Patient.identifier");
createAndRegisterProfile(myProfile, "2", "Patient.name");
createAndRegisterProfile(myProfile, "3", "Patient.birthDate");
IIdType idType = createPatient(
withProfile(myProfile + "|2"),
withProfile(myProfile + "|1"),
withProfile(myProfile + "|3"));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + myProfile + "|1)", message.getMessage());
}
private void createAndRegisterProfile(String theUrl, String theVersion, String thePath) {
StructureDefinition sd = createProfile(theUrl, theVersion, thePath);
ourValidationSupport.addStructureDefinition(sd);
}
private void createProfile(String theVersion, String theRequiredPath) {
createProfile(myProfile, theVersion, theRequiredPath);
}
private StructureDefinition createProfile(String theUrl, String theVersion, String theRequiredPath) {
final String myResourceTypeDefinition = "http://hl7.org/fhir/StructureDefinition/Patient";
final String myProfileId = "TestProfile";
StructureDefinition sd = new StructureDefinition()
.setUrl(theUrl).setVersion(theVersion)
.setBaseDefinition(myResourceTypeDefinition)
.setType(myResourceType)
.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setId(myProfileId);
sd.getDifferential().addElement()
.setPath(theRequiredPath)
.setMin(1)
.setId(theRequiredPath);
DaoMethodOutcome outcome = myStructureDefinitionDao.update(sd, mySrd);
assertNotNull(outcome.getResource());
return (StructureDefinition) outcome.getResource();
}
}

View File

@ -0,0 +1,153 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule;
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class RepositoryValidationOnCreateResourceR4Test extends BaseResourceProviderR4Test {
private static final String myResourceType = "Patient";
private final String myProfile = "http://example.org/fhir/StructureDefinition/TestPatient";
private static final FhirContext ourCtx = FhirContext.forR4();
private final RepositoryValidatingInterceptor myRepositoryValidatingInterceptor = new RepositoryValidatingInterceptor(ourCtx, Collections.emptyList());
@BeforeEach
public void before() {
myInterceptorRegistry.registerInterceptor(myRepositoryValidatingInterceptor);
}
@AfterEach
public void after() {
myInterceptorRegistry.unregisterInterceptor(myRepositoryValidatingInterceptor);
}
@Test
public void testCreatePatient_withValidationRuleNoVersion_validatesAgainstLatestVersion() {
createProfile("1", "Patient.identifier");
createProfile("2", "Patient.name");
setupRepositoryValidationRules(myProfile);
try {
createPatient(withProfile(myProfile));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(574)
+ "Patient.name: minimum required = 1, but only found 0 (from " + myProfile + "|2)", e.getMessage());
}
}
@Test
public void testCreatePatient_withValidationRuleWithVersion_unknownProfile() {
createProfile("1", "Patient.identifier");
createProfile("2", "Patient.name");
setupRepositoryValidationRules(myProfile + "|1");
try {
createPatient(withProfile(myProfile + "|1"));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(574) + "Profile reference '" + myProfile
+ "|1' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles", e.getMessage());
}
}
@Test
public void testCreatePatient_withProfileWithVersion_doesNotConform() {
setupRepositoryValidationRules(myProfile);
try {
createPatient(withProfile(myProfile + "|1"));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(575) + "Resource of type \"" + myResourceType
+ "\" does not declare conformance to profile from: [" + myProfile + "]", e.getMessage());
}
}
@Test
public void testCreatePatient_withValidationRuleWithVersion_doesNotConform() {
setupRepositoryValidationRules(myProfile + "|1");
try {
createPatient(withProfile(myProfile));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(575) + "Resource of type \"" + myResourceType
+ "\" does not declare conformance to profile from: [" + myProfile + "|1]", e.getMessage());
}
}
@Test
public void testCreatePatient_withMultipleProfiles_validatesAgainstLastProfile() {
final String sdIdentifier = myProfile + "-identifier";
final String sdName = myProfile + "-name";
createProfile(sdIdentifier, "1", "Patient.identifier");
createProfile(sdName, "1", "Patient.name");
setupRepositoryValidationRules(sdIdentifier, sdName);
try {
createPatient(withProfile(sdIdentifier), withProfile(sdName));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(574)
+ "Patient.name: minimum required = 1, but only found 0 (from " + sdName + "|1)", e.getMessage());
}
}
private void setupRepositoryValidationRules(String... theProfiles) {
List<IRepositoryValidatingRule> rules = myAppCtx
.getBean(RepositoryValidatingRuleBuilder.REPOSITORY_VALIDATING_RULE_BUILDER, RepositoryValidatingRuleBuilder.class)
.forResourcesOfType(myResourceType)
.requireAtLeastOneProfileOf(theProfiles)
.and()
.requireValidationToDeclaredProfiles()
.withBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore)
.rejectOnSeverity("error")
.build();
myRepositoryValidatingInterceptor.setRules(rules);
}
private void createProfile(String theVersion, String theRequiredPath) {
createProfile(myProfile, theVersion, theRequiredPath);
}
private void createProfile(String theUrl, String theVersion, String thePath) {
final String baseProfile = "http://hl7.org/fhir/StructureDefinition/Patient";
final String profileId = "TestProfile";
StructureDefinition sd = new StructureDefinition()
.setUrl(theUrl).setVersion(theVersion)
.setBaseDefinition(baseProfile)
.setType(myResourceType)
.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setId(profileId);
sd.getDifferential().addElement()
.setPath(thePath)
.setMin(1)
.setId(thePath);
DaoMethodOutcome outcome = myStructureDefinitionDao.update(sd, new SystemRequestDetails());
assertNotNull(outcome.getResource());
}
}

View File

@ -1,333 +0,0 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule;
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ResourceValidationR4Test extends BaseResourceProviderR4Test {
private static final FhirContext ourCtx = FhirContext.forR4();
private static FhirValidator myFhirValidator;
private static PrePopulatedValidationSupport ourValidationSupport;
private static final String myResourceType = "Patient";
private static final String myResourceTypeDefinition = "http://hl7.org/fhir/StructureDefinition/Patient";
private static final String PATIENT_STRUCTURE_DEFINITION_URL = "http://example.org/fhir/StructureDefinition/TestPatient";
private final RepositoryValidatingInterceptor myRepositoryValidatingInterceptor = new RepositoryValidatingInterceptor(ourCtx, Collections.emptyList());
@Autowired
private ApplicationContext myApplicationContext;
private final String profile = PATIENT_STRUCTURE_DEFINITION_URL;
@BeforeAll
public static void setup() {
myFhirValidator = ourCtx.newValidator();
myFhirValidator.setValidateAgainstStandardSchema(false);
myFhirValidator.setValidateAgainstStandardSchematron(false);
ourValidationSupport = new PrePopulatedValidationSupport(ourCtx);
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(ourCtx),
new SnapshotGeneratingValidationSupport(ourCtx),
new CommonCodeSystemsTerminologyService(ourCtx),
new InMemoryTerminologyServerValidationSupport(ourCtx),
ourValidationSupport);
CachingValidationSupport myValidationSupport = new CachingValidationSupport(chain, true);
FhirInstanceValidator myInstanceVal = new FhirInstanceValidator(myValidationSupport);
myFhirValidator.registerValidatorModule(myInstanceVal);
}
@BeforeEach
public void before() {
myInterceptorRegistry.registerInterceptor(myRepositoryValidatingInterceptor);
}
@AfterEach
public void after() {
myInterceptorRegistry.unregisterInterceptor(myRepositoryValidatingInterceptor);
}
@Test
public void testCreateStructureDefinition_createMultipleWithWithSameUrl_isStoredSuccessfully() {
createPatientStructureDefinitionWithMandatoryField(PATIENT_STRUCTURE_DEFINITION_URL, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(PATIENT_STRUCTURE_DEFINITION_URL, "2", "Patient.name");
createPatientStructureDefinitionWithMandatoryField(PATIENT_STRUCTURE_DEFINITION_URL, "3", "Patient.birthdate");
}
@Nested
public class OnDemandResourceValidationTests {
@Test
public void testValidatePatient_withProfileIncludingVersion_shouldUseSpecifiedVersion() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
IIdType idType = createPatient(withProfile(profile + "|1"));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + profile + "|1)", message.getMessage());
}
@Test
public void testValidatePatient_withProfileNoVersion_shouldUsesLatestVersion() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
IIdType idType = createPatient(withProfile(profile));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.name: minimum required = 1, but only found 0 (from " + profile + "|2)", message.getMessage());
}
@Test
public void testValidatePatient_withMultipleProfilesDifferentUrls_validatesAgainstAllProfiles() {
final String sdIdentifier = PATIENT_STRUCTURE_DEFINITION_URL + "-identifier";
final String sdName = PATIENT_STRUCTURE_DEFINITION_URL + "-name";
createPatientStructureDefinitionWithMandatoryField(sdIdentifier, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(sdName, "1", "Patient.name");
IIdType idType = createPatient(withProfile(sdIdentifier), withProfile(sdName));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(2, validationResult.getMessages().size());
Iterator<SingleValidationMessage> messageIterator = validationResult.getMessages().iterator();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + profile + "-identifier|1)", messageIterator.next().getMessage());
assertEquals("Patient.name: minimum required = 1, but only found 0 (from " + profile + "-name|1)", messageIterator.next().getMessage());
}
@Test
public void testValidatePatient_withMultipleProfilesSameUrlDifferentVersions_validatesAgainstFirstVersion() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
createPatientStructureDefinitionWithMandatoryField(profile, "3", "Patient.birthDate");
IIdType idType = createPatient(
withProfile(profile + "|2"),
withProfile(profile + "|1"),
withProfile(profile + "|3"));
Patient patient = myPatientDao.read(idType, mySrd);
ValidationResult validationResult = myFhirValidator.validateWithResult(patient);
assertFalse(validationResult.isSuccessful());
assertEquals(1, validationResult.getMessages().size());
SingleValidationMessage message = validationResult.getMessages().iterator().next();
assertEquals("Patient.identifier: minimum required = 1, but only found 0 (from " + profile + "|1)", message.getMessage());
}
}
@Nested
public class RepositoryValidationOnCreateTests {
@Test
public void testCreatePatient_withProfileNoVersionAndRepositoryValidationRuleNoVersion_validatesAgainstFirstVersion() {
setupRepositoryValidationRules(profile);
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(PATIENT_STRUCTURE_DEFINITION_URL, "2", "Patient.name");
try {
createPatient(withProfile(profile));
fail();
} catch (PreconditionFailedException e) {
ourLog.info(e.getMessage());
assertEquals(Msg.code(574)
+ "Patient.identifier: minimum required = 1, but only found 0 (from " + profile + "|1)", e.getMessage());
}
}
@Test
public void testCreatePatient_withProfileWithVersionAndRepositoryValidationRuleWithVersion_unknownProfile() {
setupRepositoryValidationRules(profile + "|1");
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
try {
createPatient(withProfile(profile + "|1"));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(574) + "Profile reference '" + profile
+ "|1' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles", e.getMessage());
}
}
@Test
public void testCreatePatient_withProfileWithVersionAndRepositoryValidationRuleNoVersion_doesNotConform() {
setupRepositoryValidationRules(profile);
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
try {
createPatient(withProfile(profile + "|1"));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(575) + "Resource of type \"" + myResourceType
+ "\" does not declare conformance to profile from: [" + profile + "]", e.getMessage());
}
}
@Test
public void testCreatePatient_withPatientProfileWithoutVersionAndRepositoryValidationRuleWithVersion_doesNotConform() {
setupRepositoryValidationRules(profile + "|1");
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatientStructureDefinitionWithMandatoryField(profile, "2", "Patient.name");
try {
createPatient(withProfile(profile));
fail();
} catch (PreconditionFailedException e) {
assertEquals(Msg.code(575) + "Resource of type \"" + myResourceType
+ "\" does not declare conformance to profile from: [" + profile + "|1]", e.getMessage());
}
}
private void setupRepositoryValidationRules(String... theProfiles) {
List<IRepositoryValidatingRule> rules = myApplicationContext
.getBean(RepositoryValidatingRuleBuilder.REPOSITORY_VALIDATING_RULE_BUILDER, RepositoryValidatingRuleBuilder.class)
.forResourcesOfType(myResourceType)
.requireAtLeastOneProfileOf(theProfiles)
.and()
.requireValidationToDeclaredProfiles()
.withBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore)
.rejectOnSeverity("error")
.build();
myRepositoryValidatingInterceptor.setRules(rules);
}
}
@Nested
public class SearchWithProfileTests {
@Test
public void testSearchPatient_withProfileBelowCriteria_returnsAllMatches() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
IIdType id1 = createPatient(withProfile(profile));
IIdType id2 = createPatient(withProfile(profile + "|1"));
SearchParameterMap params = new SearchParameterMap();
params.add("_profile", new UriParam(profile).setQualifier(UriParamQualifierEnum.BELOW));
IBundleProvider bundleProvider = myPatientDao.search(params, mySrd);
assertFalse(bundleProvider.isEmpty());
assertThat(toUnqualifiedVersionlessIdValues(bundleProvider),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue(), id2.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteriaWithoutVersionAndPatientProfileWithoutVersion_returnsExactMatch() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
IIdType id1 = createPatient(withProfile(profile));
IIdType id2 = createPatient(withProfile(profile));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(profile))
.returnBundle(Bundle.class).execute();
assertThat(toUnqualifiedVersionlessIdValues(outcome),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue(), id2.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteriaWithVersionAndPatientProfileWithVersion_returnsExactMatch() {
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
IIdType id1 = createPatient(withProfile(profile + "|1"));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(profile + "|1"))
.returnBundle(Bundle.class).execute();
assertThat(toUnqualifiedVersionlessIdValues(outcome),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteriaWithoutVersionAndPatientProfileWithVersion_returnsNoMatch() {
final String profile = PATIENT_STRUCTURE_DEFINITION_URL;
createPatientStructureDefinitionWithMandatoryField(profile, "1", "Patient.identifier");
createPatient(withProfile(profile + "|1"));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(profile))
.returnBundle(Bundle.class).execute();
assertTrue(outcome.getEntryFirstRep().isEmpty());
}
}
private void createPatientStructureDefinitionWithMandatoryField(String theUrl, String theVersion, String thePath) {
StructureDefinition sd = new StructureDefinition()
.setUrl(theUrl).setVersion(theVersion)
.setBaseDefinition(myResourceTypeDefinition)
.setType(myResourceType)
.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.getDifferential().addElement()
.setPath(thePath)
.setMin(1)
.setId(thePath);
DaoMethodOutcome outcome = myStructureDefinitionDao.create(sd, mySrd);
assertTrue(outcome.getCreated());
ourValidationSupport.addStructureDefinition(sd);
}
}

View File

@ -0,0 +1,96 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SearchWithResourceProfileR4Test extends BaseResourceProviderR4Test {
private static final String myResourceType = "Patient";
private final String myProfile = "http://example.org/fhir/StructureDefinition/TestPatient";
@Test
public void testSearchPatient_withProfileBelowCriteria_returnsAllMatches() {
createProfile();
IIdType id1 = createPatient(withProfile(myProfile));
IIdType id2 = createPatient(withProfile(myProfile + "|1"));
SearchParameterMap params = new SearchParameterMap();
params.add("_profile", new UriParam(myProfile).setQualifier(UriParamQualifierEnum.BELOW));
IBundleProvider bundleProvider = myPatientDao.search(params, mySrd);
assertFalse(bundleProvider.isEmpty());
assertThat(toUnqualifiedVersionlessIdValues(bundleProvider),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue(), id2.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteriaWithoutVersionAndPatientProfileWithoutVersion_returnsExactMatch() {
createProfile();
IIdType id1 = createPatient(withProfile(myProfile));
IIdType id2 = createPatient(withProfile(myProfile));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(myProfile))
.returnBundle(Bundle.class).execute();
assertThat(toUnqualifiedVersionlessIdValues(outcome),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue(), id2.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteriaWithVersionAndPatientProfileWithVersion_returnsExactMatch() {
createProfile();
IIdType id1 = createPatient(withProfile(myProfile + "|1"));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(myProfile + "|1"))
.returnBundle(Bundle.class).execute();
assertThat(toUnqualifiedVersionlessIdValues(outcome),
containsInAnyOrder(id1.toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchPatient_withProfileExactCriteria_returnsNoMatch() {
createProfile();
createPatient(withProfile(myProfile + "|1"));
Bundle outcome = myClient.search().forResource(myResourceType)
.where(new TokenClientParam("_profile").exactly().code(myProfile))
.returnBundle(Bundle.class).execute();
assertTrue(outcome.getEntryFirstRep().isEmpty());
}
private void createProfile() {
final String baseProfile = "http://hl7.org/fhir/StructureDefinition/Patient";
final String profileId = "TestProfile";
final String version = "1";
final String rulePath = "Patient.identifier";
StructureDefinition sd = new StructureDefinition()
.setUrl(myProfile).setVersion(version)
.setBaseDefinition(baseProfile)
.setType(myResourceType)
.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setId(profileId);
sd.getDifferential().addElement()
.setPath(rulePath)
.setMin(1)
.setId(rulePath);
DaoMethodOutcome outcome = myStructureDefinitionDao.update(sd, new SystemRequestDetails());
assertNotNull(outcome.getResource());
}
}