fixed resource fullUrl in _history bundles (#5815)

This commit is contained in:
Emre Dincturk 2024-04-01 15:21:18 -04:00 committed by GitHub
parent 6ce3b17460
commit 70843cdf45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 109 additions and 0 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 5088
title: "Previously, the fullUrl for resources in _history bundles was not generated correctly when using a client
provided id. The same problem started to happen for the resources with server generated ids more recently
(after 6.9.10). This has now been fixed"

View File

@ -167,6 +167,14 @@ public class HistoryBuilder {
Optional<String> forcedId = pidToForcedId.get(JpaPid.fromId(nextResourceId));
if (forcedId.isPresent()) {
resourceId = forcedId.get();
// IdHelperService returns a forcedId with the '<resourceType>/' prefix
// but the transientForcedId is expected to be just the idPart (without the <resourceType>/ prefix).
// For that reason, strip the prefix before setting the transientForcedId below.
// If not stripped this messes up the id of the resource as the resourceType would be repeated
// twice like Patient/Patient/1234 in the resource constructed
if (resourceId.startsWith(myResourceType + "/")) {
resourceId = resourceId.substring(myResourceType.length() + 1);
}
} else {
resourceId = nextResourceId.toString();
}

View File

@ -18,6 +18,7 @@ import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.term.ZipCollectionBuilder;
import ca.uhn.fhir.jpa.test.config.TestR4Config;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.QueryParameterUtils;
import ca.uhn.fhir.model.api.StorageResponseCodeEnum;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@ -2404,6 +2405,100 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(idValues, hasSize(0));
}
@ParameterizedTest
@CsvSource({
"false,PatientWithServerGeneratedId1",
"true,PatientWithServerGeneratedId2"
})
public void testHistoryOnInstanceWithServerGeneratedId(boolean theInvalidateCacheBeforeHistory,
String thePatientFamilyName) {
Patient patient = new Patient();
patient.addName().setFamily(thePatientFamilyName);
IIdType id = myClient.create().resource(patient).execute().getId().toVersionless();
ourLog.info("Res ID: {}", id);
final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart();
if (theInvalidateCacheBeforeHistory) {
// the reason for this test parameterization to invalidate the cache is that
// when a resource is created/updated, its id mapping is cached for 1 minute so
// retrieving the history right after creating the resource will use the cached value.
// By invalidating the cache here and getting the history bundle again,
// we test the scenario where the id mapping needs to be read from the db,
// hence testing a different code path.
myMemoryCacheService.invalidateCaches(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID);
}
Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).execute();
assertEquals(1, history.getEntry().size());
BundleEntryComponent historyEntry0 = history.getEntry().get(0);
// validate entry.fullUrl
assertEquals(expectedFullUrl, historyEntry0.getFullUrl());
//validate entry.request
assertEquals(HTTPVerb.POST, historyEntry0.getRequest().getMethod());
assertEquals("Patient/" + id.getIdPart() + "/_history/1", historyEntry0.getRequest().getUrl());
//validate entry.response
assertEquals("201 Created", historyEntry0.getResponse().getStatus());
assertNotNull(historyEntry0.getResponse().getEtag());
//validate patient resource details in the entry
Patient historyEntry0Patient = (Patient) historyEntry0.getResource();
assertEquals(id.withVersion("1").getValue(), historyEntry0Patient.getId());
assertEquals(1, historyEntry0Patient.getName().size());
assertEquals(thePatientFamilyName, historyEntry0Patient.getName().get(0).getFamily());
}
@ParameterizedTest
@CsvSource({
"false,PatientWithForcedId1",
"true,PatientWithForcedId2"
})
public void testHistoryOnInstanceWithForcedId(boolean theInvalidateCacheBeforeHistory,
String thePatientFamilyName) {
final String patientForcedId = thePatientFamilyName + "-ForcedId";
Patient patient = new Patient();
patient.addName().setFamily(thePatientFamilyName);
patient.setId(patientForcedId);
IIdType id = myClient.update().resource(patient).execute().getId().toVersionless();
ourLog.info("Res ID: {}", id);
assertEquals(patientForcedId, id.getIdPart());
final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart();
if (theInvalidateCacheBeforeHistory) {
// the reason for this test parameterization to invalidate the cache is that
// when a resource is created/updated, its id mapping is cached for 1 minute so
// retrieving the history right after creating the resource will use the cached value.
// By invalidating the cache here and getting the history bundle again,
// we test the scenario where the id mapping needs to be read from the db,
// hence testing a different code path.
myMemoryCacheService.invalidateCaches(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID);
}
Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).execute();
assertEquals(1, history.getEntry().size());
BundleEntryComponent historyEntry0 = history.getEntry().get(0);
// validate entry.fullUrl
assertEquals(expectedFullUrl, historyEntry0.getFullUrl());
//validate entry.request
assertEquals(HTTPVerb.POST, historyEntry0.getRequest().getMethod());
assertEquals("Patient/" + id.getIdPart() + "/_history/1", historyEntry0.getRequest().getUrl());
//validate entry.response
assertEquals("201 Created", historyEntry0.getResponse().getStatus());
assertNotNull(historyEntry0.getResponse().getEtag());
//validate patient resource details in the entry
Patient historyEntry0Patient = (Patient) historyEntry0.getResource();
assertEquals(id.withVersion("1").getValue(), historyEntry0Patient.getId());
assertEquals(1, historyEntry0Patient.getName().size());
assertEquals(thePatientFamilyName, historyEntry0Patient.getName().get(0).getFamily());
}
@Test
public void testHistoryWithDeletedResource() {
String methodName = "testHistoryWithDeletedResource";