Skip to content

Commit

Permalink
Merge pull request #2683 from FirelyTeam/bugfix/2295-http-status-chec…
Browse files Browse the repository at this point in the history
…ks-in-operation-handling-too-strict

Allow all 2xx http response codes for FHIR operations
  • Loading branch information
mmsmits authored Jan 24, 2024
2 parents 11a97cc + 4c9bbca commit 5efde1f
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 6 deletions.
12 changes: 8 additions & 4 deletions src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public partial class BaseFhirClient : IDisposable
{
internal readonly ModelInspector Inspector;
private readonly IFhirSerializationEngine _serializationEngine;
private readonly Lazy<List<HttpStatusCode>> _200responses = new(() => Enum.GetValues(typeof(HttpStatusCode)).Cast<HttpStatusCode>().Where(n => (int)n > 199 && (int)n < 300).ToList());

/// <summary>
/// Creates a new client using a default endpoint
Expand Down Expand Up @@ -914,7 +915,8 @@ public virtual void Delete(string resourceType, SearchParams condition)

var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(location), operationName, parameters, useGet).ToBundle();

return executeAsync<Resource>(tx, HttpStatusCode.OK, ct);
//operation responses are expected to return 2xx codes.
return executeAsync<Resource>(tx, _200responses.Value, ct);
}

[Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")]
Expand All @@ -929,7 +931,8 @@ public virtual void Delete(string resourceType, SearchParams condition)

var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(operation), parameters, useGet).ToBundle();

return executeAsync<Resource>(tx, HttpStatusCode.OK, ct);
//operation responses are expected to return 2xx codes.
return executeAsync<Resource>(tx, _200responses.Value, ct);
}

[Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")]
Expand All @@ -944,7 +947,7 @@ public virtual void Delete(string resourceType, SearchParams condition)

var tx = new TransactionBuilder(Endpoint).ProcessMessage(bundle, async, responseUrl).ToBundle();

return executeAsync<Bundle>(tx, new [] {HttpStatusCode.OK, HttpStatusCode.Accepted, HttpStatusCode.NoContent}, ct);
return executeAsync<Bundle>(tx, new[] { HttpStatusCode.OK, HttpStatusCode.Accepted, HttpStatusCode.NoContent }, ct);
}

[Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")]
Expand Down Expand Up @@ -973,7 +976,8 @@ public virtual void Delete(string resourceType, SearchParams condition)
else
tx = new TransactionBuilder(Endpoint).ResourceOperation(type, id, vid, operationName, parameters, useGet).ToBundle();

return executeAsync<Resource>(tx, new[] { HttpStatusCode.OK, HttpStatusCode.Accepted }, ct);
//operation responses are expected to return 2xx codes.
return executeAsync<Resource>(tx, _200responses.Value, ct);
}

private Resource? internalOperation(string operationName, string? type = null, string? id = null,
Expand Down
52 changes: 50 additions & 2 deletions src/Hl7.Fhir.Support.Poco.Tests/Rest/FhirClientMockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Hl7.Fhir.Introspection;
using Hl7.Fhir.Model;
using Hl7.Fhir.Rest;
using Hl7.Fhir.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
Expand Down Expand Up @@ -147,7 +146,7 @@ public async T.Task AcceptHeaderTest(bool useFhirVersionHeader)
};

using var client = new MoqBuilder()
.Send(response,
.Send(response,
h => h.RequestUri == new Uri("http://example.com/Patient?name=henry") &&
findInAcceptHeader(h.Headers.Accept, "fhirVersion", useFhirVersionHeader))
.AsClient(s => { s.VerifyFhirVersion = false; s.UseFhirVersionInAcceptHeader = useFhirVersionHeader; });
Expand Down Expand Up @@ -347,6 +346,55 @@ public async Task WillReturnOperationOutcomeOnOtherEndpoint()
client.LastResult!.Outcome.Should().BeOfType<OperationOutcome>().Which.Id.Should().Be("example");
}


[TestMethod]
public async T.Task TestOperationResponseCodes()
{
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Created,
Content = new StringContent(@"{""resourceType"": ""Parameters"", ""parameter"": [ { ""name"": ""result"", ""valueString"": ""connected""}] }", Encoding.UTF8, "application/json"),
RequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.com/fhir/$ping")
};

using var client = new MoqBuilder()
.Send(response, h => h.RequestUri == new Uri("http://example.com/fhir/$ping"))
.AsClient();

var parameters = await client.OperationAsync(new Uri("http://example.com/fhir/$ping")) as Parameters;
client.LastResult?.Status.Should().Be("201");

response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Accepted,
Content = new StringContent(@"{""resourceType"": ""Parameters"", ""parameter"": [ { ""name"": ""result"", ""valueString"": ""connected""}] }", Encoding.UTF8, "application/json"),
RequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.com/fhir/$ping")
};

using var client2 = new MoqBuilder()
.Send(response, h => h.RequestUri == new Uri("http://example.com/fhir/$ping"))
.AsClient();

parameters = await client2.OperationAsync(new Uri("http://example.com/fhir/$ping")) as Parameters;
client2.LastResult?.Status.Should().Be("202");


response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound,
Content = new StringContent(@"{""resourceType"": ""Parameters"", ""parameter"": [ { ""name"": ""result"", ""valueString"": ""connected""}] }", Encoding.UTF8, "application/json"),
RequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://example.com/fhir/$ping")
};

using var client3 = new MoqBuilder()
.Send(response, h => h.RequestUri == new Uri("http://example.com/fhir/$ping"))
.AsClient();

var function = () => client3.OperationAsync(new Uri("http://example.com/fhir/$ping"));
await function.Should().ThrowAsync<FhirOperationException>().WithMessage("Operation was unsuccessful because of a client error (NotFound). Body contains a Parameters.");

}

private static BaseFhirClient sendBack(string resourceType)
{
var mock = new Mock<HttpMessageHandler>();
Expand Down

0 comments on commit 5efde1f

Please sign in to comment.