diff --git a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs index d072424c3c..0a62a8cc18 100644 --- a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs @@ -29,6 +29,7 @@ public partial class BaseFhirClient : IDisposable { internal readonly ModelInspector Inspector; private readonly IFhirSerializationEngine _serializationEngine; + private readonly Lazy> _200responses = new(() => Enum.GetValues(typeof(HttpStatusCode)).Cast().Where(n => (int)n > 199 && (int)n < 300).ToList()); /// /// Creates a new client using a default endpoint @@ -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(tx, HttpStatusCode.OK, ct); + //operation responses are expected to return 2xx codes. + return executeAsync(tx, _200responses.Value, ct); } [Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")] @@ -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(tx, HttpStatusCode.OK, ct); + //operation responses are expected to return 2xx codes. + return executeAsync(tx, _200responses.Value, ct); } [Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")] @@ -944,7 +947,7 @@ public virtual void Delete(string resourceType, SearchParams condition) var tx = new TransactionBuilder(Endpoint).ProcessMessage(bundle, async, responseUrl).ToBundle(); - return executeAsync(tx, new [] {HttpStatusCode.OK, HttpStatusCode.Accepted, HttpStatusCode.NoContent}, ct); + return executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.Accepted, HttpStatusCode.NoContent }, ct); } [Obsolete("Synchronous use of the FhirClient is strongly discouraged, use the asynchronous call instead.")] @@ -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(tx, new[] { HttpStatusCode.OK, HttpStatusCode.Accepted }, ct); + //operation responses are expected to return 2xx codes. + return executeAsync(tx, _200responses.Value, ct); } private Resource? internalOperation(string operationName, string? type = null, string? id = null, diff --git a/src/Hl7.Fhir.Support.Poco.Tests/Rest/FhirClientMockTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/Rest/FhirClientMockTests.cs index 945d77833d..abe3ca6d62 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/Rest/FhirClientMockTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/Rest/FhirClientMockTests.cs @@ -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; @@ -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; }); @@ -347,6 +346,55 @@ public async Task WillReturnOperationOutcomeOnOtherEndpoint() client.LastResult!.Outcome.Should().BeOfType().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().WithMessage("Operation was unsuccessful because of a client error (NotFound). Body contains a Parameters."); + + } + private static BaseFhirClient sendBack(string resourceType) { var mock = new Mock();