Error on missing profile validation (#1742)

* Error on missing profile validation

* Add changelog

* Test fixes
This commit is contained in:
James Agnew 2020-03-03 08:28:26 -05:00 committed by GitHub
parent ac9a371f18
commit 3c2c1dab2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 163 additions and 39 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1742
title: "When validating a resource, the validator will now report an error if the resource declares conformance
to an unknown or invalid profile URL via the `Resource.meta.profile` declaration. Previously this was a warning
and did not block successful validation."

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1742
title: When performing a terminology delta ADD operation, if the number of codes being added is large
the codes will be added in small batches via an asynchronous scheduled task in order to avoid overwhelming
the database with a large operation.

View File

@ -102,7 +102,7 @@ public class DaoConfig {
/**
* update setter javadoc if default changes
*/
private int myDeferIndexingForCodesystemsOfSize = 2000;
private int myDeferIndexingForCodesystemsOfSize = 100;
private boolean myDeleteStaleSearches = true;
private boolean myEnforceReferentialIntegrityOnDelete = true;
private boolean myUniqueIndexesEnabled = true;
@ -392,7 +392,7 @@ public class DaoConfig {
* the code system will be indexed later in an incremental process in order to
* avoid overwhelming Lucene with a huge number of codes in a single operation.
* <p>
* Defaults to 2000
* Defaults to 100
* </p>
*/
public int getDeferIndexingForCodesystemsOfSize() {
@ -404,7 +404,7 @@ public class DaoConfig {
* the code system will be indexed later in an incremental process in order to
* avoid overwhelming Lucene with a huge number of codes in a single operation.
* <p>
* Defaults to 2000
* Defaults to 100
* </p>
*/
public void setDeferIndexingForCodesystemsOfSize(int theDeferIndexingForCodesystemsOfSize) {

View File

@ -50,4 +50,8 @@ public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest);
@Modifying
@Query("DELETE FROM TermConcept t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
}

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@ -39,4 +40,9 @@ public interface ITermConceptParentChildLinkDao extends JpaRepository<TermConcep
@Query("SELECT t.myPid FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Modifying
@Query("DELETE FROM TermConceptParentChildLink t WHERE t.myChildPid = :pid OR t.myParentPid = :pid")
void deleteByConceptPid(@Param("pid") Long theId);
}

View File

@ -47,7 +47,7 @@ public class TermCodeSystem implements Serializable {
@Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH)
private String myCodeSystemUri;
@OneToOne()
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CURRENT_VERSION_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_CURVER"))
private TermCodeSystemVersion myCurrentVersion;
@Column(name = "CURRENT_VERSION_PID", nullable = true, insertable = false, updatable = false)
@ -57,7 +57,7 @@ public class TermCodeSystem implements Serializable {
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODESYSTEM_PID")
@Column(name = "PID")
private Long myPid;
@OneToOne()
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_RES"))
private ResourceTable myResource;
@Column(name = "RES_ID", insertable = false, updatable = false)

View File

@ -50,7 +50,7 @@ public class TermCodeSystemVersion implements Serializable {
@Column(name = "PID")
private Long myId;
@OneToOne()
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CODESYSVER_RES_ID"))
private ResourceTable myResource;
@ -64,7 +64,7 @@ public class TermCodeSystemVersion implements Serializable {
* This was added in HAPI FHIR 3.3.0 and is nullable just to avoid migration
* issued. It should be made non-nullable at some point.
*/
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_CODESYSVER_CS_ID"))
private TermCodeSystem myCodeSystem;
@ -72,7 +72,7 @@ public class TermCodeSystemVersion implements Serializable {
private Long myCodeSystemPid;
@SuppressWarnings("unused")
@OneToOne(mappedBy = "myCurrentVersion", optional = true)
@OneToOne(mappedBy = "myCurrentVersion", optional = true, fetch = FetchType.LAZY)
private TermCodeSystem myCodeSystemHavingThisVersionAsCurrentVersionIfAny;
@Column(name = "CS_DISPLAY", nullable = true, updatable = false, length = MAX_VERSION_LENGTH)

View File

@ -65,7 +65,7 @@ public class TermConcept implements Serializable {
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CONCEPT_UPDATED", nullable = true)
private Date myUpdated;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
private TermCodeSystemVersion myCodeSystem;
@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
@ -79,11 +79,11 @@ public class TermConcept implements Serializable {
@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
})
private String myDisplay;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
@Field(name = "PROPmyProperties", analyzer = @Analyzer(definition = "termConceptPropertyAnalyzer"))
@FieldBridge(impl = TermConceptPropertyFieldBridge.class)
private Collection<TermConceptProperty> myProperties;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
private Collection<TermConceptDesignation> myDesignations;
@Id()
@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")

View File

@ -39,7 +39,7 @@ public class TermConceptDesignation implements Serializable {
public static final int MAX_LENGTH = 500;
public static final int MAX_VAL_LENGTH = 2000;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT"))
private TermConcept myConcept;
@Id()
@ -62,7 +62,7 @@ public class TermConceptDesignation implements Serializable {
*
* @since 3.5.0
*/
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CSV"))
private TermCodeSystemVersion myCodeSystemVersion;

View File

@ -32,14 +32,14 @@ import java.io.Serializable;
public class TermConceptParentChildLink implements Serializable {
private static final long serialVersionUID = 1L;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CHILD_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CHILD"))
private TermConcept myChild;
@Column(name = "CHILD_PID", insertable = false, updatable = false)
private Long myChildPid;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODESYSTEM_PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CS"))
private TermCodeSystemVersion myCodeSystem;
@ -47,7 +47,7 @@ public class TermConceptParentChildLink implements Serializable {
@Fields({@Field(name = "myCodeSystemVersionPid")})
private long myCodeSystemVersionPid;
@ManyToOne(cascade = {})
@ManyToOne(fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "PARENT_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_PARENT"))
private TermConcept myParent;

View File

@ -44,7 +44,7 @@ public class TermConceptProperty implements Serializable {
private static final int MAX_LENGTH = 500;
static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
private TermConcept myConcept;
/**
@ -52,7 +52,7 @@ public class TermConceptProperty implements Serializable {
*
* @since 3.5.0
*/
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CSV"))
private TermCodeSystemVersion myCodeSystemVersion;
@Id()

View File

@ -1246,6 +1246,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return retVal;
}
@Transactional
@Override
public List<VersionIndependentConcept> findCodesAbove(String theSystem, String theCode) {
TermCodeSystem cs = getCodeSystem(theSystem);
@ -1277,6 +1278,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return retVal;
}
@Transactional
@Override
public List<VersionIndependentConcept> findCodesBelow(String theSystem, String theCode) {
TermCodeSystem cs = getCodeSystem(theSystem);

View File

@ -223,7 +223,15 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
* save parent concepts first (it's way too slow to do that)
*/
if (theConcept.getId() == null) {
retVal += ensureParentsSaved(theConcept.getParents());
boolean needToSaveParents = false;
for (TermConceptParentChildLink next : theConcept.getParents()) {
if (next.getParent().getId() == null) {
needToSaveParents = true;
}
}
if (needToSaveParents) {
retVal += ensureParentsSaved(theConcept.getParents());
}
}
if (theConcept.getId() == null || theConcept.getIndexStatus() == null) {
@ -485,7 +493,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
conceptToAdd.setCodeSystemVersion(theCsv);
if (theStatisticsTracker.getUpdatedConceptCount() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) {
conceptToAdd = myConceptDao.save(conceptToAdd);
saveConcept(conceptToAdd);
Long nextConceptPid = conceptToAdd.getId();
Validate.notNull(nextConceptPid);
} else {
@ -508,7 +516,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
ourLog.info("Saving parent/child link - Parent[{}] Child[{}]", parentLink.getParent().getCode(), parentLink.getChild().getCode());
if (theStatisticsTracker.getUpdatedConceptCount() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) {
myConceptParentChildLinkDao.save(parentLink);
myConceptParentChildLinkDao.save(parentLink);
} else {
myDeferredStorageSvc.addConceptLinkToStorageQueue(parentLink);
}
@ -655,12 +663,15 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
private void deleteConceptChildrenAndConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) {
for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) {
deleteConceptChildrenAndConcept(nextChildLink.getChild(), theRemoveCounter);
myConceptParentChildLinkDao.delete(nextChildLink);
}
myConceptParentChildLinkDao.deleteByConceptPid(theConcept.getId());
myConceptDesignationDao.deleteAll(theConcept.getDesignations());
myConceptPropertyDao.deleteAll(theConcept.getProperties());
myConceptDao.delete(theConcept);
ourLog.info("Deleting concept {} - Code {}", theConcept.getId(), theConcept.getCode());
myConceptDao.deleteByPid(theConcept.getId());
theRemoveCounter.incrementAndGet();
}

View File

@ -98,6 +98,13 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
myDeferredValueSets.addAll(theValueSets);
}
@Override
public void saveAllDeferred() {
while (!isStorageQueueEmpty()) {
saveDeferred();
}
}
@Override
public void setProcessDeferred(boolean theProcessDeferred) {
myProcessDeferred = theProcessDeferred;

View File

@ -50,4 +50,9 @@ public interface ITermDeferredStorageSvc {
void addConceptMapsToStorageQueue(List<ConceptMap> theConceptMaps);
void addValueSetsToStorageQueue(List<ValueSet> theValueSets);
/**
* This is mostly here for unit tests - Saves any and all deferred concepts and links
*/
void saveAllDeferred();
}

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
@ -134,8 +135,13 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
}
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermDeferredStorageSvc.saveAllDeferred();
}
@Autowired
private ITermDeferredStorageSvc myTermDeferredStorageSvc;
private void createExternalCsAndLocalVs() {
CodeSystem codeSystem = createExternalCs();

View File

@ -15,7 +15,6 @@ import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.utils.IResourceValidator;
@ -323,11 +322,15 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
ValidationModeEnum mode = ValidationModeEnum.CREATE;
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input);
MethodOutcome output = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
OperationOutcome oo = (OperationOutcome) output.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
try {
myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
}
}
@Test

View File

@ -164,6 +164,8 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
myTerminologyDeferredStorageSvc.saveAllDeferred();
}
private void createLocalCsAndVs() {

View File

@ -147,6 +147,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd);
myTerminologyDeferredStorageSvc.saveAllDeferred();
// Create a valueset
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
@ -395,12 +397,15 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
ValidationModeEnum mode = ValidationModeEnum.CREATE;
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input);
MethodOutcome output = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) output.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
try {
myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
}
}
@Test
@ -614,6 +619,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
upload("/r4/uscore/ValueSet-omb-race-category.json");
upload("/r4/uscore/ValueSet-us-core-usps-state.json");
myTerminologyDeferredStorageSvc.saveAllDeferred();
{
String resource = loadResource("/r4/uscore/patient-resource-badcode.json");
IBaseResource parsedResource = myFhirCtx.newJsonParser().parseResource(resource);

View File

@ -15,6 +15,7 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
@ -26,6 +27,9 @@ import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
@ -52,6 +56,7 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils;
@ -2260,6 +2265,28 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
}
@Test
public void testValidateResourceContainingProfileDeclarationDoesntResolve() throws IOException {
Observation input = new Observation();
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
input.getMeta().addProfile("http://foo/structuredefinition/myprofile");
input.getCode().setText("Hello");
input.setStatus(ObservationStatus.FINAL);
HttpPost post = new HttpPost(ourServerBase + "/Observation/$validate?_pretty=true");
post.setEntity(new ResourceEntity(myFhirCtx, input));
try (CloseableHttpResponse resp = ourHttpClient.execute(post)) {
String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(respString);
assertEquals(412, resp.getStatusLine().getStatusCode());
assertThat(respString, containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked"));
}
}
@SuppressWarnings("unused")
@Test
public void testFullTextSearch() throws Exception {

View File

@ -31,8 +31,10 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@ -126,17 +128,23 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
ourLog.info("All concepts: {}", myTermConceptDao.findAll());
});
myCaptureQueriesListener.clear();
delta = new CustomTerminologySet();
TermConcept root = delta.addRootConcept("RootA", "Root A");
root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA");
root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAB").setDisplay("Child AB");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
myCaptureQueriesListener.logAllQueriesForCurrentThread();
assertHierarchyContains(
"RootA seq=0",
" ChildAA seq=0",
" ChildAB seq=1",
"RootB seq=0"
);
}
@Test
@ -175,6 +183,12 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
);
assertEquals(2, outcome.getUpdatedConceptCount());
runInTransaction(() -> {
TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA").orElseThrow(() -> new IllegalStateException());
assertEquals(2, concept.getParents().size());
assertThat(concept.getParentPidsAsString(), matchesPattern("^[0-9]+ [0-9]+$"));
});
}
@Test
@ -538,9 +552,15 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAAA").isPresent()));
// Remove CodeA
delta = new CustomTerminologySet();
delta.addRootConcept("codeA");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta);
myCaptureQueriesListener.clear();
runInTransaction(()->{
CustomTerminologySet delta2 = new CustomTerminologySet();
delta2.addRootConcept("codeA");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta2);
});
myCaptureQueriesListener.logAllQueriesForCurrentThread();
ourLog.info("*** Done removing");
assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeB").isPresent()));
assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeA").isPresent()));

View File

@ -100,6 +100,8 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
myTerminologyDeferredStorageSvc.saveAllDeferred();
return id;
}

View File

@ -171,11 +171,18 @@ public class ValidatorWrapper {
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
String message = next.getMessage();
// TODO: are these still needed?
if ("Binding has no source, so can't be checked".equals(message) ||
"ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) {
messages.remove(i);
i--;
}
if (message.endsWith("' could not be resolved, so has not been checked") && next.getLevel() == ValidationMessage.IssueSeverity.WARNING) {
next.setLevel(ValidationMessage.IssueSeverity.ERROR);
}
}
return messages;

View File

@ -1067,7 +1067,10 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
myInstanceVal.setValidationSupport(myMockSupport);
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked"));
assertEquals(1, errors.size());
assertEquals("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked", errors.get(0).getMessage());
assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity());
}
@Test