Skip to content

Commit 38497e9

Browse files
issue-4097 - Conditional update should reject updates when resource b… (#4125)
* issue-4097 - Conditional update should reject updates when resource body contains id that already exists on server Signed-off-by: PrasannaHegde1 <phegde@merative.com> * issue-4097 - fixing unit tests Signed-off-by: PrasannaHegde1 <phegde@merative.com> * updating the r4b package Signed-off-by: PrasannaHegde1 <phegde@merative.com> * reverting the update to r4b package Signed-off-by: PrasannaHegde1 <phegde@merative.com> * issue-4097 - fixinf review comments Signed-off-by: PrasannaHegde1 <phegde@merative.com> Signed-off-by: PrasannaHegde1 <phegde@merative.com>
1 parent 68237b0 commit 38497e9

3 files changed

Lines changed: 299 additions & 2 deletions

File tree

fhir-server-test/src/test/java/org/linuxforhealth/fhir/server/test/ServerSpecTest.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
import java.net.URI;
1818
import java.time.ZoneOffset;
19+
import java.util.List;
20+
import java.util.Properties;
1921
import java.util.UUID;
2022

2123
import javax.ws.rs.client.Entity;
2224
import javax.ws.rs.client.WebTarget;
2325
import javax.ws.rs.core.Response;
2426

27+
import org.testng.annotations.BeforeClass;
2528
import org.testng.annotations.Test;
2629

2730
import org.linuxforhealth.fhir.client.FHIRParameters;
@@ -30,6 +33,7 @@
3033
import org.linuxforhealth.fhir.model.resource.Bundle;
3134
import org.linuxforhealth.fhir.model.resource.Observation;
3235
import org.linuxforhealth.fhir.model.resource.OperationOutcome;
36+
import org.linuxforhealth.fhir.model.resource.OperationOutcome.Issue;
3337
import org.linuxforhealth.fhir.model.resource.Patient;
3438
import org.linuxforhealth.fhir.model.test.TestUtil;
3539
import org.linuxforhealth.fhir.model.type.Boolean;
@@ -44,6 +48,8 @@
4448
import org.linuxforhealth.fhir.model.type.Reference;
4549
import org.linuxforhealth.fhir.model.type.code.AdministrativeGender;
4650
import org.linuxforhealth.fhir.model.type.code.BundleType;
51+
import org.linuxforhealth.fhir.model.type.code.IssueSeverity;
52+
import org.linuxforhealth.fhir.model.type.code.IssueType;
4753
import org.linuxforhealth.fhir.model.type.code.ObservationStatus;
4854
import org.linuxforhealth.fhir.model.util.FHIRUtil;
4955

@@ -60,6 +66,15 @@ public class ServerSpecTest extends FHIRServerTestBase {
6066
private Patient savedPatient;
6167
@SuppressWarnings("unused")
6268
private Observation savedObservation;
69+
70+
private boolean updateCreateEnabled;
71+
72+
@BeforeClass
73+
public void setUp() throws Exception {
74+
Properties testProperties = TestUtil.readTestProperties("test.properties");
75+
setUp(testProperties);
76+
updateCreateEnabled = isUpdateCreateSupported();
77+
}
6378

6479
@Test(groups = { "server-spec" })
6580
public void testCreatePatient() throws Exception {
@@ -696,6 +711,82 @@ public void testConditionalUpdateObservation2() throws Exception {
696711
"Input resource 'id' attribute must match the id of the search result resource");
697712

698713
}
714+
715+
/**
716+
* Test conditional update when :
717+
* 1. No matches, no id provided.
718+
* 2. No matches, id provided and doesn't already exist and update/create is enabled.
719+
* 3. No matches, id provided and doesn't already exist and update/create is not enabled.
720+
* 4. No matches, id provided and id already exists.
721+
* @throws Exception
722+
*/
723+
@Test(groups = { "server-spec" }, dependsOnMethods = { "testConditionalCreateObservation" })
724+
public void testConditionalUpdateObservation3() throws Exception {
725+
String fakePatientRef = "Patient/" + UUID.randomUUID().toString();
726+
String obsId = UUID.randomUUID().toString();
727+
Observation obs = TestUtil.readLocalResource("Observation1.json");
728+
Observation obsWithNoId = obs.toBuilder()
729+
.subject(Reference.builder().reference(fakePatientRef).build())
730+
.build();
731+
Observation obsWithId = obs.toBuilder()
732+
.subject(Reference.builder().reference(fakePatientRef).build())
733+
.id(obsId)
734+
.build();
735+
Observation obsWithExistingId = obs.toBuilder()
736+
.subject(Reference.builder().reference(fakePatientRef).build())
737+
.id(savedObservation.getId())
738+
.build();
739+
String randomObsId = UUID.randomUUID().toString();
740+
// Test conditional update when : No matches, no id provided.
741+
// Expected output: The resource is created successfully.
742+
FHIRParameters query = new FHIRParameters().searchParam("_id", obsId);
743+
FHIRResponse response = client.conditionalUpdate(obsWithNoId, query);
744+
assertNotNull(response);
745+
assertResponse(response.getResponse(), Response.Status.CREATED.getStatusCode());
746+
String locationURI = response.getLocation();
747+
assertNotNull(locationURI);
748+
if (updateCreateEnabled) {
749+
// Test conditional update when : No matches, id provided and doesn't already exist and update/create is enabled.
750+
// Expected output : The resource is created successfully.The server treats the interaction as an Update as Create interaction.
751+
query = new FHIRParameters().searchParam("_id", obsId);
752+
response = client.conditionalUpdate(obsWithId, query);
753+
assertNotNull(response);
754+
assertResponse(response.getResponse(), Response.Status.CREATED.getStatusCode());
755+
locationURI = response.getLocation();
756+
assertNotNull(locationURI);
757+
} else {
758+
// No matches, id provided and doesn't already exist and update/create is not enabled.
759+
// Expected output : Should be rejected with error message.
760+
query = new FHIRParameters().searchParam("_id", randomObsId);
761+
response = client.conditionalUpdate(obsWithId, query);
762+
assertNotNull(response);
763+
assertResponse(response.getResponse(), Response.Status.METHOD_NOT_ALLOWED.getStatusCode());
764+
String expectedErrorMsg = "Resource 'Observation/" + obsWithId.getId() + "' not found.";
765+
OperationOutcome operationOutcome = response.getResource(OperationOutcome.class);
766+
assertNotNull(operationOutcome);
767+
List<Issue> issues = operationOutcome.getIssue();
768+
assertEquals(issues.size(), 1);
769+
assertEquals(issues.get(0).getDetails().getText().getValue(), expectedErrorMsg);
770+
assertEquals(issues.get(0).getSeverity(), IssueSeverity.FATAL);
771+
assertEquals(issues.get(0).getCode(), IssueType.NOT_FOUND);
772+
}
773+
774+
// Test conditional update when : No matches, id provided and id already exists.
775+
// Expected output : Should be rejected with 409 Conflict error message.
776+
query = new FHIRParameters().searchParam("_id", randomObsId);
777+
response = client.conditionalUpdate(obsWithExistingId, query);
778+
assertNotNull(response);
779+
assertResponse(response.getResponse(), Response.Status.CONFLICT.getStatusCode());
780+
OperationOutcome operationOutcome = response.getResource(OperationOutcome.class);
781+
assertNotNull(operationOutcome);
782+
List<Issue> issues = operationOutcome.getIssue();
783+
assertEquals(issues.size(), 1);
784+
String expectedErrorMsg = "Conflict error! The search criteria specified for a conditional update operation did not return any results"
785+
+ " but the input resource with id: " + obsWithExistingId.getId() + " already exists.";
786+
assertEquals(issues.get(0).getDetails().getText().getValue(), expectedErrorMsg);
787+
assertEquals(issues.get(0).getSeverity(), IssueSeverity.FATAL);
788+
assertEquals(issues.get(0).getCode(), IssueType.CONFLICT);
789+
}
699790

700791
// Test: retrieve Patient with _summary=true.
701792
@Test(groups = { "server-spec" }, dependsOnMethods={"testCreatePatient"})

fhir-server/src/main/java/org/linuxforhealth/fhir/server/util/FHIRRestHelper.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,20 @@ public FHIRRestOperationResponse doUpdateMeta(FHIRPersistenceEvent event, String
602602
SingleResourceResult<? extends Resource> srr = doRead(type, id, false, null, false);
603603
ior.setPrevResource(srr.getResource()); // might be null if resource is deleted
604604
isDeleted = srr.isDeleted();
605+
606+
607+
if (srr.getResource() == null && !persistence.isUpdateCreateEnabled()) {
608+
// Check that the resource exists, unless the updateCreate feature is enabled.
609+
// No matches, id provided and doesn't already exist and updateCreate feature is not enabled : Should be rejected with error message.
610+
String msg = "Resource '" + type + "/" + id + "' not found.";
611+
log.log(Level.SEVERE, msg);
612+
throw new FHIRResourceNotFoundException(msg);
613+
} else if (srr.getResource() != null && !isDeleted) {
614+
// No matches, id provided and already exist: The server rejects the update with a 409 Conflict error
615+
String msg = "Conflict error! The search criteria specified for a conditional update operation " +
616+
"did not return any results but the input resource with id: " + id + " already exists." ;
617+
throw buildRestException(msg, IssueType.CONFLICT);
618+
}
605619
currentVersion = srr.getVersion();
606620
currentLastUpdated = srr.getLastUpdated();
607621
}

0 commit comments

Comments
 (0)