From 4e8b755da89e6e7daa96615ed6df2fe79f39ca69 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 20 Nov 2017 11:23:59 -0600 Subject: [PATCH 01/39] Initial work on converting from WebRequest to HttpClient. --- src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj | 1 + .../Rest/EntryToHttpExtensions.cs | 85 +++------- .../Rest/HttpToEntryExtensions.cs | 67 ++++---- src/Hl7.Fhir.Core/Rest/Requester.cs | 150 ++++++------------ 4 files changed, 105 insertions(+), 198 deletions(-) diff --git a/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj b/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj index c73f7761b9..605c4580b9 100644 --- a/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj +++ b/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 3f68c44eb6..60cef5f7b0 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -1,7 +1,7 @@ -/* +/* * Copyright (c) 2014, Furore (info@furore.com) and contributors * See the file CONTRIBUTORS for details. - * + * * This file is licensed under the BSD 3-Clause license * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE */ @@ -12,18 +12,18 @@ using System.Net; using System.Reflection; using Hl7.Fhir.Utility; +using System.Net.Http; namespace Hl7.Fhir.Rest { internal static class EntryToHttpExtensions { - public static HttpWebRequest ToHttpRequest(this Bundle.EntryComponent entry, - SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody, out byte[] body) + public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, + Prefer bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody, HttpMethod method) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); var interaction = entry.Request; - body = null; if (entry.Resource != null && !(interaction.Method == Bundle.HTTPVerb.POST || interaction.Method == Bundle.HTTPVerb.PUT)) throw Error.InvalidOperation("Cannot have a body on an Http " + interaction.Method.ToString()); @@ -33,80 +33,33 @@ public static HttpWebRequest ToHttpRequest(this Bundle.EntryComponent entry, if (useFormatParameter) location.AddParam(HttpUtil.RESTPARAM_FORMAT, Hl7.Fhir.Rest.ContentType.BuildFormatParam(format)); - var request = (HttpWebRequest)HttpWebRequest.Create(location.Uri); - request.Method = interaction.Method.ToString(); - setAgent(request, ".NET FhirClient for FHIR " + Model.ModelInfo.Version); + var request = new HttpRequestMessage(method, location.Uri); + request.Headers.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); if (!useFormatParameter) - request.Accept = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); + request.Headers.Add("Accept", Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false)); - if (interaction.IfMatch != null) request.Headers["If-Match"] = interaction.IfMatch; - if (interaction.IfNoneMatch != null) request.Headers["If-None-Match"] = interaction.IfNoneMatch; -#if DOTNETFW - if (interaction.IfModifiedSince != null) request.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; -#else - if (interaction.IfModifiedSince != null) request.Headers["If-Modified-Since"] = interaction.IfModifiedSince.Value.UtcDateTime.ToString(); -#endif - if (interaction.IfNoneExist != null) request.Headers["If-None-Exist"] = interaction.IfNoneExist; + if (interaction.IfMatch != null) request.Headers.TryAddWithoutValidation("If-Match", interaction.IfMatch); + if (interaction.IfNoneMatch != null) request.Headers.TryAddWithoutValidation("If-None-Match", interaction.IfNoneMatch); + if (interaction.IfModifiedSince != null) request.Headers.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; + if (interaction.IfNoneExist != null) request.Headers.TryAddWithoutValidation("If-None-Exist", interaction.IfNoneExist); - var interactionType = entry.Annotation(); - - if (interactionType == TransactionBuilder.InteractionType.Create && returnPreference != null) - request.Headers["Prefer"] = "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference); - else if(interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) - request.Headers["Prefer"] = "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference); + if (interaction.Method == Bundle.HTTPVerb.POST || interaction.Method == Bundle.HTTPVerb.PUT) + { + request.Headers.TryAddWithoutValidation("Prefer", bodyPreference == Prefer.ReturnMinimal ? "return=minimal" : "return=representation"); + } if (entry.Resource != null) setBodyAndContentType(request, entry.Resource, format, CompressRequestBody, out body); // PCL doesn't support setting the length (and in this case will be empty anyway) #if DOTNETFW else - request.ContentLength = 0; + request.Content.Headers.ContentLength = 0; #endif return request; } - /// - /// Flag to control the setting of the User Agent string (different platforms have different abilities) - /// - public static bool SetUserAgentUsingReflection = true; - public static bool SetUserAgentUsingDirectHeaderManipulation = true; - - private static void setAgent(HttpWebRequest request, string agent) - { - bool userAgentSet = false; - if (SetUserAgentUsingReflection) - { - try - { - System.Reflection.PropertyInfo prop = request.GetType().GetRuntimeProperty("UserAgent"); - - if (prop != null) - prop.SetValue(request, agent, null); - userAgentSet = true; - } - catch (Exception) - { - // This approach doesn't work on this platform, so don't try it again. - SetUserAgentUsingReflection = false; - } - } - if (!userAgentSet && SetUserAgentUsingDirectHeaderManipulation) - { - // platform does not support UserAgent property...too bad - try - { - request.Headers[HttpRequestHeader.UserAgent] = agent; - } - catch (ArgumentException) - { - SetUserAgentUsingDirectHeaderManipulation = false; - } - } - } - - - private static void setBodyAndContentType(HttpWebRequest request, Resource data, ResourceFormat format, bool CompressRequestBody, out byte[] body) + private static void setBodyAndContentType(HttpRequestMessage request, Resource data, ResourceFormat format, bool CompressRequestBody) { if (data == null) throw Error.ArgumentNull(nameof(data)); @@ -132,6 +85,6 @@ private static void setBodyAndContentType(HttpWebRequest request, Resource data, } } - + } } diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index 3b982a1312..abab3a5eb5 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -18,6 +18,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -31,7 +32,7 @@ public static class HttpToEntryExtensions private const string USERDATA_BODY = "$body"; private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; - internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) + internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) { var result = new Bundle.EntryComponent(); @@ -39,26 +40,31 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse respons result.Response.Status = ((int)response.StatusCode).ToString(); result.Response.SetHeaders(response.Headers); - var contentType = getContentType(response); - var charEncoding = getCharacterEncoding(response); + var contentType = response.Content.Headers.ContentType; - result.Response.Location = response.Headers[HttpUtil.LOCATION] ?? response.Headers[HttpUtil.CONTENTLOCATION]; + Encoding charEncoding; -#if !DOTNETFW - if (!String.IsNullOrEmpty(response.Headers[HttpUtil.LASTMODIFIED])) - result.Response.LastModified = DateTimeOffset.Parse(response.Headers[HttpUtil.LASTMODIFIED]); -#else - result.Response.LastModified = response.LastModified; -#endif - result.Response.Etag = getETag(response); + try + { + charEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); + } + catch(ArgumentException e) + { + charEncoding = Encoding.UTF8; + } + + result.Response.Location = response.Headers.Location.AbsoluteUri ?? response.Content.Headers.ContentLocation.AbsoluteUri; + + result.Response.LastModified = response.Content.Headers.LastModified; + result.Response.Etag = response.Headers.ETag.Tag; if (body != null) { result.Response.SetBody(body); - if (IsBinaryResponse(response.ResponseUri.OriginalString, contentType)) + if (IsBinaryResponse(response.Content.Headers.ContentLocation.AbsoluteUri, contentType.ToString())) { - result.Resource = makeBinaryResource(body, contentType); + result.Resource = makeBinaryResource(body, contentType.ToString()); if (result.Response.Location != null) { var ri = new ResourceIdentity(result.Response.Location); @@ -71,7 +77,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse respons else { var bodyText = DecodeBody(body, charEncoding); - var resource = parseResource(bodyText, contentType, parserSettings, throwOnFormatException); + var resource = parseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); result.Resource = resource; if (result.Response.Location != null) @@ -82,7 +88,6 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse respons return result; } - private static string getETag(HttpWebResponse response) { var result = response.Headers[HttpUtil.ETAG]; @@ -120,7 +125,7 @@ private static Encoding getCharacterEncoding(HttpWebResponse response) return result; } - private static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) + internal static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) { Resource result= null; @@ -151,11 +156,10 @@ private static Resource parseResource(string bodyText, string contentType, Parse return result; } - internal static bool IsBinaryResponse(string responseUri, string contentType) { - if (!string.IsNullOrEmpty(contentType) - && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) + if (!string.IsNullOrEmpty(contentType) + && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) || ContentType.JSON_CONTENT_HEADERS.Contains(contentType.ToLower()) ) ) @@ -170,11 +174,10 @@ internal static bool IsBinaryResponse(string responseUri, string contentType) if (id.Id != null && Id.IsValidValue(id.Id)) return true; if (id.VersionId != null && Id.IsValidValue(id.VersionId)) return true; } - + return false; } - internal static string DecodeBody(byte[] body, Encoding enc) { if (body == null) return null; @@ -189,7 +192,7 @@ internal static string DecodeBody(byte[] body, Encoding enc) } } - private static Binary makeBinaryResource(byte[] data, string contentType) + internal static Binary makeBinaryResource(byte[] data, string contentType) { var binary = new Binary(); @@ -199,18 +202,6 @@ private static Binary makeBinaryResource(byte[] data, string contentType) return binary; } - - public static string GetBodyAsText(this Bundle.ResponseComponent interaction) - { - var body = interaction.GetBody(); - - if (body != null) - return DecodeBody(body, Encoding.UTF8); - else - return null; - } - - private class Body { public byte[] Data; @@ -229,6 +220,14 @@ internal static void SetBody(this Bundle.ResponseComponent interaction, byte[] d interaction.AddAnnotation(new Body { Data = data }); } + internal static void SetHeaders(this Bundle.ResponseComponent interaction, System.Net.Http.Headers.HttpResponseHeaders headers) + { + foreach (var header in headers) + { + interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(header.Key + ":" + header.Value)); + } + } + internal static void SetHeaders(this Bundle.ResponseComponent interaction, WebHeaderCollection headers) { foreach (var key in headers.AllKeys) diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index a5a4ab5772..1dbb45330d 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -11,8 +11,12 @@ using Hl7.Fhir.Support; using Hl7.Fhir.Utility; using System; +using System.Collections.Generic; using System.IO.Compression; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Hl7.Fhir.Rest @@ -20,6 +24,7 @@ namespace Hl7.Fhir.Rest internal class Requester { public Uri BaseUrl { get; private set; } + public HttpClient Client { get; private set; } public bool UseFormatParameter { get; set; } public ResourceFormat PreferredFormat { get; set; } @@ -45,25 +50,26 @@ internal class Requester public Requester(Uri baseUrl) { BaseUrl = baseUrl; + Client = new HttpClient(); UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; - Timeout = 100 * 1000; // Default timeout is 100 seconds - PreferredReturn = Rest.Prefer.ReturnRepresentation; - PreferredParameterHandling = null; + Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds + Prefer = Rest.Prefer.ReturnRepresentation; ParserSettings = Hl7.Fhir.Serialization.ParserSettings.Default; } public Bundle.EntryComponent LastResult { get; private set; } - public HttpWebResponse LastResponse { get; private set; } - public HttpWebRequest LastRequest { get; private set; } - public Action BeforeRequest { get; set; } - public Action AfterResponse { get; set; } + public HttpResponseMessage LastResponse { get; private set; } + public HttpRequestMessage LastRequest { get; private set; } + public Action BeforeRequest { get; set; } + public Action AfterResponse { get; set; } public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) { return ExecuteAsync(interaction).WaitResult(); } + public async Task ExecuteAsync(Bundle.EntryComponent interaction) { if (interaction == null) throw Error.ArgumentNull(nameof(interaction)); @@ -71,121 +77,69 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - byte[] outBody; - var request = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody, out outBody); - -#if DOTNETFW - request.Timeout = Timeout; -#endif + var requestMessage = interaction.ToHttpRequest(Prefer, PreferredFormat, UseFormatParameter, compressRequestBody); if (PreferCompressedResponses) { - request.Headers["Accept-Encoding"] = "gzip, deflate"; + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); } - LastRequest = request; - if (BeforeRequest != null) BeforeRequest(request, outBody); + LastRequest = requestMessage; + var outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); + BeforeRequest?.Invoke(requestMessage, outgoingBody); - // Write the body to the output - if (outBody != null) - request.WriteBody(compressRequestBody, outBody); - - // Make sure the HttpResponse gets disposed! - using (HttpWebResponse webResponse = (HttpWebResponse)await request.GetResponseAsync(new TimeSpan(0, 0, 0, 0, Timeout)).ConfigureAwait(false)) - //using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponseNoEx()) + var response = await Client.SendAsync(requestMessage).ConfigureAwait(false); + try { + var body = await response.Content.ReadAsByteArrayAsync(); + //Read body before we call the hook, so the hook cannot read the body before we do + + LastResponse = response; + AfterResponse?.Invoke(response, body); + + // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() try { - //Read body before we call the hook, so the hook cannot read the body before we do - var inBody = readBody(webResponse); - - LastResponse = webResponse; - if (AfterResponse != null) AfterResponse(webResponse,inBody); + LastResult = null; - // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() - try + if (response.IsSuccessStatusCode) { - LastResult = null; - - if (webResponse.StatusCode.IsSuccessful()) - { - LastResult = webResponse.ToBundleEntry(inBody, ParserSettings, throwOnFormatException: true); - return LastResult; - } - else - { - LastResult = webResponse.ToBundleEntry(inBody, ParserSettings, throwOnFormatException: false); - throw buildFhirOperationException(webResponse.StatusCode, LastResult.Resource); - } + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: true); + return LastResult; } - catch(UnsupportedBodyTypeException bte) + else { - // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. - // Build a very minimal LastResult - var errorResult = new Bundle.EntryComponent(); - errorResult.Response = new Bundle.ResponseComponent(); - errorResult.Response.Status = ((int)webResponse.StatusCode).ToString(); - - OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); - - errorResult.Resource = operationOutcome; - LastResult = errorResult; - - throw buildFhirOperationException(webResponse.StatusCode, operationOutcome); + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: false); + throw buildFhirOperationException(response.StatusCode, LastResult.Resource); } } - catch (AggregateException ae) + catch (UnsupportedBodyTypeException bte) { - //EK: This code looks weird. Is this correct? - if (ae.GetBaseException() is WebException) - { - } - throw ae.GetBaseException(); + // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. + // Build a very minimal LastResult + var errorResult = new Bundle.EntryComponent(); + errorResult.Response = new Bundle.ResponseComponent(); + errorResult.Response.Status = ((int)response.StatusCode).ToString(); + + OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); + + errorResult.Resource = operationOutcome; + LastResult = errorResult; + + throw buildFhirOperationException(response.StatusCode, operationOutcome); } } - } - - private static byte[] readBody(HttpWebResponse response) - { - if (response.ContentLength != 0) + catch (AggregateException ae) { - byte[] body = null; - var respStream = response.GetResponseStream(); -#if !DOTNETFW - var contentEncoding = response.Headers["Content-Encoding"]; -#else - var contentEncoding = response.ContentEncoding; -#endif - if (contentEncoding == "gzip") - { - using (var decompressed = new GZipStream(respStream, CompressionMode.Decompress, true)) - { - body = HttpUtil.ReadAllFromStream(decompressed); - } - } - else if (contentEncoding == "deflate") + //EK: This code looks weird. Is this correct? + if (ae.GetBaseException() is WebException) { - using (var decompressed = new DeflateStream(respStream, CompressionMode.Decompress, true)) - { - body = HttpUtil.ReadAllFromStream(decompressed); - } - } - else - { - body = HttpUtil.ReadAllFromStream(respStream); } - respStream.Dispose(); - - if (body.Length > 0) - return body; - else - return null; + throw ae.GetBaseException(); } - else - return null; } - private static Exception buildFhirOperationException(HttpStatusCode status, Resource body) { string message; From b525bbc7e9bae1e0be1552de7a3c3969df802b39 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 20 Nov 2017 11:52:42 -0600 Subject: [PATCH 02/39] Body now getting written to request. --- .../Rest/EntryToHttpExtensions.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 60cef5f7b0..088c39db12 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -13,13 +13,14 @@ using System.Reflection; using Hl7.Fhir.Utility; using System.Net.Http; +using System.Net.Http.Headers; namespace Hl7.Fhir.Rest { internal static class EntryToHttpExtensions { - public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, - Prefer bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody, HttpMethod method) + public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, + Prefer bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); @@ -33,7 +34,7 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, if (useFormatParameter) location.AddParam(HttpUtil.RESTPARAM_FORMAT, Hl7.Fhir.Rest.ContentType.BuildFormatParam(format)); - var request = new HttpRequestMessage(method, location.Uri); + var request = new HttpRequestMessage(getMethod(interaction.Method), location.Uri); request.Headers.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); if (!useFormatParameter) @@ -50,7 +51,7 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, } if (entry.Resource != null) - setBodyAndContentType(request, entry.Resource, format, CompressRequestBody, out body); + setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); // PCL doesn't support setting the length (and in this case will be empty anyway) #if DOTNETFW else @@ -59,18 +60,36 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, return request; } + private static HttpMethod getMethod(Bundle.HTTPVerb? verb) + { + switch(verb) + { + case Bundle.HTTPVerb.GET: + return HttpMethod.Get; + case Bundle.HTTPVerb.POST: + return HttpMethod.Post; + case Bundle.HTTPVerb.PUT: + return HttpMethod.Put; + case Bundle.HTTPVerb.DELETE: + return HttpMethod.Delete; + } + throw new HttpRequestException($"Valid HttpVerb could not be found for verb type: [{verb}]"); + } + private static void setBodyAndContentType(HttpRequestMessage request, Resource data, ResourceFormat format, bool CompressRequestBody) { if (data == null) throw Error.ArgumentNull(nameof(data)); - if (data is Binary) + byte[] body; + string contentType; + + if (data is Binary bin) { - var bin = (Binary)data; body = bin.Content; // This is done by the caller after the OnBeforeRequest is called so that other properties // can be set before the content is committed // request.WriteBody(CompressRequestBody, bin.Content); - request.ContentType = bin.ContentType; + contentType = bin.ContentType; } else { @@ -81,8 +100,11 @@ private static void setBodyAndContentType(HttpRequestMessage request, Resource d // This is done by the caller after the OnBeforeRequest is called so that other properties // can be set before the content is committed // request.WriteBody(CompressRequestBody, body); - request.ContentType = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); + contentType = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); } + + request.Content = new ByteArrayContent(body); + request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); } From 4e04239abbefe49e3d7b13eca3171a5d339b53c2 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 20 Nov 2017 15:24:41 -0600 Subject: [PATCH 03/39] Fixed tests to use new .NET types and fixed some parsing issues. --- .../Rest/FhirClientTests.cs | 13 ++++++------- .../Rest/TransactionBuilderTests.cs | 4 +--- .../Rest/EntryToHttpExtensions.cs | 11 +++++------ src/Hl7.Fhir.Core/Rest/FhirClient.cs | 17 +++++++++-------- .../Rest/HttpToEntryExtensions.cs | 19 +++++++++++++++---- src/Hl7.Fhir.Core/Rest/IFhirClient.cs | 5 +++-- src/Hl7.Fhir.Core/Rest/Requester.cs | 15 +++++++++++++-- .../Source/ResourceResolverTests.cs | 5 +++-- 8 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 349be126d8..14c6744c54 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -203,7 +203,7 @@ public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestE { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip"); } } @@ -213,7 +213,7 @@ public static void Compression_OnBeforeRequestDeflate(object sender, BeforeReque { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "deflate"); } } @@ -223,7 +223,7 @@ public static void Compression_OnBeforeRequestZipOrDeflate(object sender, Before { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); } } @@ -282,8 +282,7 @@ public void Search() private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) { - // Test that the response was compressed - Assert.AreEqual("gzip", e.RawResponse.Headers[HttpResponseHeader.ContentEncoding]); + } #if NO_ASYNC_ANYMORE @@ -852,7 +851,7 @@ public void RequestFullResource() { var client = new FhirClient(testEndpoint); var minimal = false; - client.OnBeforeRequest += (object s, BeforeRequestEventArgs e) => e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; + client.OnBeforeRequest += (object s, BeforeRequestEventArgs e) => e.RawRequest.Headers.TryAddWithoutValidation("Prefer", minimal ? "return=minimal" : "return=representation"); var result = client.Read("Patient/glossy"); Assert.IsNotNull(result); @@ -1062,7 +1061,7 @@ public void TestAuthenticationOnBefore() FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => { - e.RawRequest.Headers["Authorization"] = "Bearer bad-bearer"; + e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); }; try { diff --git a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs index 29c6f77265..7f59bfe678 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs @@ -51,9 +51,7 @@ public void TestUrlEncoding() tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); var b = tx.ToBundle(); - byte[] body; - - var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false, body: out body); + var req = b.Entry[0].ToHttpRequest(Prefer.ReturnRepresentation, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); } diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 088c39db12..9268c07da2 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -14,6 +14,7 @@ using Hl7.Fhir.Utility; using System.Net.Http; using System.Net.Http.Headers; +using System.Linq; namespace Hl7.Fhir.Rest { @@ -52,11 +53,7 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, if (entry.Resource != null) setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); - // PCL doesn't support setting the length (and in this case will be empty anyway) -#if DOTNETFW - else - request.Content.Headers.ContentLength = 0; -#endif + return request; } @@ -104,7 +101,9 @@ private static void setBodyAndContentType(HttpRequestMessage request, Resource d } request.Content = new ByteArrayContent(body); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + var contentTypeList = contentType.Split(';'); + request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentTypeList.FirstOrDefault()); + request.Content.Headers.ContentType.CharSet = System.Text.Encoding.UTF8.WebName; } diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 6be14d0664..2f45f0409d 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; @@ -190,14 +191,14 @@ public ParserSettings ParserSettings /// /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient /// - public HttpWebRequest LastRequest { get { return _requester.LastRequest; } } + public HttpRequestMessage LastRequest { get { return _requester.LastRequest; } } /// /// Returns the HttpWebResponse as it was last received during a call on the FhirClient /// /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpWebResponse LastResponse { get { return _requester.LastResponse; } } + public HttpResponseMessage LastResponse { get { return _requester.LastResponse; } } /// /// The default endpoint for use with operations that use discrete id/version parameters @@ -1028,7 +1029,7 @@ private void setResourceBase(Resource resource, string baseUri) /// /// The request as it is about to be sent to the server /// The data in the body of the request as it is about to be sent to the server - protected virtual void BeforeRequest(HttpWebRequest rawRequest, byte[] body) + protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) { // Default implementation: call event OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); @@ -1039,7 +1040,7 @@ protected virtual void BeforeRequest(HttpWebRequest rawRequest, byte[] body) /// /// You cannot read the body from the HttpWebResponse, since it has /// already been read by the framework. Use the body parameter instead. - protected virtual void AfterResponse(HttpWebResponse webResponse, byte[] body) + protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) { // Default implementation: call event OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); @@ -1142,25 +1143,25 @@ private void verifyServerVersion() public class BeforeRequestEventArgs : EventArgs { - public BeforeRequestEventArgs(HttpWebRequest rawRequest, byte[] body) + public BeforeRequestEventArgs(HttpRequestMessage rawRequest, byte[] body) { this.RawRequest = rawRequest; this.Body = body; } - public HttpWebRequest RawRequest { get; internal set; } + public HttpRequestMessage RawRequest { get; internal set; } public byte[] Body { get; internal set; } } public class AfterResponseEventArgs : EventArgs { - public AfterResponseEventArgs(HttpWebResponse webResponse, byte[] body) + public AfterResponseEventArgs(HttpResponseMessage webResponse, byte[] body) { this.RawResponse = webResponse; this.Body = body; } - public HttpWebResponse RawResponse { get; internal set; } + public HttpResponseMessage RawResponse { get; internal set; } public byte[] Body { get; internal set; } } } diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index abab3a5eb5..f391427344 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -19,6 +19,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -53,16 +54,16 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res charEncoding = Encoding.UTF8; } - result.Response.Location = response.Headers.Location.AbsoluteUri ?? response.Content.Headers.ContentLocation.AbsoluteUri; + result.Response.Location = response.Headers.Location?.AbsoluteUri ?? response.Content.Headers.ContentLocation?.AbsoluteUri; result.Response.LastModified = response.Content.Headers.LastModified; - result.Response.Etag = response.Headers.ETag.Tag; + result.Response.Etag = response.Headers.ETag?.Tag; if (body != null) { result.Response.SetBody(body); - if (IsBinaryResponse(response.Content.Headers.ContentLocation.AbsoluteUri, contentType.ToString())) + if (IsBinaryResponse(result.Response.Location, contentType.ToString())) { result.Resource = makeBinaryResource(body, contentType.ToString()); if (result.Response.Location != null) @@ -202,6 +203,16 @@ internal static Binary makeBinaryResource(byte[] data, string contentType) return binary; } + public static string GetBodyAsText(this Bundle.ResponseComponent interaction) + { + var body = interaction.GetBody(); + + if (body != null) + return DecodeBody(body, Encoding.UTF8); + else + return null; + } + private class Body { public byte[] Data; @@ -220,7 +231,7 @@ internal static void SetBody(this Bundle.ResponseComponent interaction, byte[] d interaction.AddAnnotation(new Body { Data = data }); } - internal static void SetHeaders(this Bundle.ResponseComponent interaction, System.Net.Http.Headers.HttpResponseHeaders headers) + internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpResponseHeaders headers) { foreach (var header in headers) { diff --git a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs index ae81cb88ca..d82f515a8f 100644 --- a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using System.Net.Http; namespace Hl7.Fhir.Rest { @@ -17,8 +18,8 @@ public interface IFhirClient byte[] LastBody { get; } Resource LastBodyAsResource { get; } string LastBodyAsText { get; } - HttpWebRequest LastRequest { get; } - HttpWebResponse LastResponse { get; } + HttpRequestMessage LastRequest { get; } + HttpResponseMessage LastResponse { get; } Bundle.ResponseComponent LastResult { get; } ParserSettings ParserSettings { get; set; } ResourceFormat PreferredFormat { get; set; } diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 1dbb45330d..171d668e72 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -49,8 +49,13 @@ internal class Requester public Requester(Uri baseUrl) { + var clientHandler = new HttpClientHandler() + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + BaseUrl = baseUrl; - Client = new HttpClient(); + Client = new HttpClient(clientHandler); UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds @@ -86,7 +91,13 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) } LastRequest = requestMessage; - var outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); + + byte[] outgoingBody = null; + if(requestMessage.Method == HttpMethod.Post || requestMessage.Method == HttpMethod.Put) + { + outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); + } + BeforeRequest?.Invoke(requestMessage, outgoingBody); var response = await Client.SendAsync(requestMessage).ConfigureAwait(false); diff --git a/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs index 37cbcc2cd4..d4f4fb1c2d 100644 --- a/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs @@ -18,6 +18,7 @@ using Hl7.Fhir.Serialization; using System.Linq; using Hl7.Fhir.Utility; +using System.Net.Http; namespace Hl7.Fhir.Specification.Tests { @@ -94,13 +95,13 @@ public int Status public TestFhirClient(Uri endpoint) : base(endpoint) { Status = 1; } - protected override void BeforeRequest(HttpWebRequest rawRequest, byte[] body) + protected override void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) { Status = 2; base.BeforeRequest(rawRequest, body); } - protected override void AfterResponse(HttpWebResponse webResponse, byte[] body) + protected override void AfterResponse(HttpResponseMessage webResponse, byte[] body) { Status = 3; base.AfterResponse(webResponse, body); From 1e2e0f3aad1c504da514f52060a1ffe8ed8d9604 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 21 Nov 2017 10:05:55 -0600 Subject: [PATCH 04/39] Changed types for rebase onto develop-dstu3. --- src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs | 2 +- src/Hl7.Fhir.Core/Rest/Requester.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 9268c07da2..1221aa3632 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -21,7 +21,7 @@ namespace Hl7.Fhir.Rest internal static class EntryToHttpExtensions { public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, - Prefer bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) + Prefer? bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 171d668e72..3717942026 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -59,7 +59,7 @@ public Requester(Uri baseUrl) UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds - Prefer = Rest.Prefer.ReturnRepresentation; + PreferredReturn = Rest.Prefer.ReturnRepresentation; ParserSettings = Hl7.Fhir.Serialization.ParserSettings.Default; } @@ -82,7 +82,7 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - var requestMessage = interaction.ToHttpRequest(Prefer, PreferredFormat, UseFormatParameter, compressRequestBody); + var requestMessage = interaction.ToHttpRequest(PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody); if (PreferCompressedResponses) { From 2be2b92c794cbeb2f50a21ab4f3234b3c0a0f0a8 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 21 Nov 2017 13:40:35 -0600 Subject: [PATCH 05/39] Fixed missing search parameter argument from rebase and fixed tests using ToHttpEntry directly. --- .../Rest/RequesterTests.cs | 30 +++++++++---------- .../Rest/TransactionBuilderTests.cs | 2 +- .../Rest/EntryToHttpExtensions.cs | 12 ++++---- src/Hl7.Fhir.Core/Rest/Requester.cs | 3 +- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs index f3e9def4e8..6d29d8b941 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs @@ -12,6 +12,7 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; +using System.Linq; namespace Hl7.Fhir.Test { @@ -43,31 +44,30 @@ public void TestPreferSetting() var tx = new TransactionBuilder("http://myserver.org/fhir") .Create(p); var b = tx.ToBundle(); - byte[] dummy; - var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=minimal", request.Headers["Prefer"]); + var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("return=minimal", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=representation", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("return=representation", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=OperationOutcome", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false); + Assert.AreEqual("return=OperationOutcome", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false, out dummy); - Assert.IsNull(request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false); + Assert.IsFalse(request.Headers.TryGetValues("Prefer", out var headerValues)); tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); b = tx.ToBundle(); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("handling=lenient", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=lenient", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("handling=strict", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=strict", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.IsNull(request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.IsFalse(request.Headers.TryGetValues("Prefer", out headerValues)); } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs index 7f59bfe678..8bb2d9dda1 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs @@ -51,7 +51,7 @@ public void TestUrlEncoding() tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); var b = tx.ToBundle(); - var req = b.Entry[0].ToHttpRequest(Prefer.ReturnRepresentation, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); + var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); } diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 1221aa3632..a038e567fc 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -21,7 +21,7 @@ namespace Hl7.Fhir.Rest internal static class EntryToHttpExtensions { public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, - Prefer? bodyPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) + SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); @@ -46,10 +46,12 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, if (interaction.IfModifiedSince != null) request.Headers.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; if (interaction.IfNoneExist != null) request.Headers.TryAddWithoutValidation("If-None-Exist", interaction.IfNoneExist); - if (interaction.Method == Bundle.HTTPVerb.POST || interaction.Method == Bundle.HTTPVerb.PUT) - { - request.Headers.TryAddWithoutValidation("Prefer", bodyPreference == Prefer.ReturnMinimal ? "return=minimal" : "return=representation"); - } + var interactionType = entry.Annotation(); + + if (interactionType == TransactionBuilder.InteractionType.Create && returnPreference != null) + request.Headers.TryAddWithoutValidation("Prefer", "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference)); + else if (interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) + request.Headers.TryAddWithoutValidation("Prefer", "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference)); if (entry.Resource != null) setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 3717942026..3199a54b82 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -60,6 +60,7 @@ public Requester(Uri baseUrl) PreferredFormat = ResourceFormat.Xml; Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds PreferredReturn = Rest.Prefer.ReturnRepresentation; + PreferredParameterHandling = null; ParserSettings = Hl7.Fhir.Serialization.ParserSettings.Default; } @@ -82,7 +83,7 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - var requestMessage = interaction.ToHttpRequest(PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody); + var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody); if (PreferCompressedResponses) { From 3cc2a798b3bc9efd384ca0c89980a44ceb071030 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 21 Nov 2017 15:36:56 -0600 Subject: [PATCH 06/39] Ensured that we don't try to convert an empty body to a response bundle. --- src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index f391427344..a55116c54f 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -59,7 +59,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res result.Response.LastModified = response.Content.Headers.LastModified; result.Response.Etag = response.Headers.ETag?.Tag; - if (body != null) + if (body != null && body.Length != 0) { result.Response.SetBody(body); From 90eb3800ccb0410f2447f989fc3751739e59c610 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Wed, 22 Nov 2017 10:27:58 -0600 Subject: [PATCH 07/39] Fixed some comments referencing HttpWeb[Request/Response] and fixed a test that was adding headers twice. --- src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs | 5 ----- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 14c6744c54..05e3b105ff 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -850,8 +850,6 @@ public void TestBinaryDetection() public void RequestFullResource() { var client = new FhirClient(testEndpoint); - var minimal = false; - client.OnBeforeRequest += (object s, BeforeRequestEventArgs e) => e.RawRequest.Headers.TryAddWithoutValidation("Prefer", minimal ? "return=minimal" : "return=representation"); var result = client.Read("Patient/glossy"); Assert.IsNotNull(result); @@ -859,16 +857,13 @@ public void RequestFullResource() result.Meta = null; client.PreferredReturn = Prefer.ReturnRepresentation; - minimal = false; var posted = client.Create(result); Assert.IsNotNull(posted, "Patient example not found"); - minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true posted = client.Create(result); Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); client.PreferredReturn = Prefer.ReturnMinimal; - minimal = true; posted = client.Create(result); Assert.IsNull(posted); } diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 2f45f0409d..847fe050d0 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -1025,7 +1025,7 @@ private void setResourceBase(Resource resource, string baseUri) public event EventHandler OnAfterResponse; /// - /// Inspect or modify the HttpWebRequest just before the FhirClient issues a call to the server + /// Inspect or modify the just before the FhirClient issues a call to the server /// /// The request as it is about to be sent to the server /// The data in the body of the request as it is about to be sent to the server @@ -1036,9 +1036,9 @@ protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) } /// - /// Inspect the HttpWebResponse as it came back from the server + /// Inspect the as it came back from the server /// - /// You cannot read the body from the HttpWebResponse, since it has + /// You cannot read the body from the HttpResponseMessage, since it has /// already been read by the framework. Use the body parameter instead. protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) { From c24f53200d71342691b743bfea5be20d137aedb2 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Wed, 22 Nov 2017 16:34:02 -0600 Subject: [PATCH 08/39] Added comments and removed dead code. --- .../Rest/FhirClientTests.cs | 6 -- .../Rest/EntryToHttpExtensions.cs | 12 ++-- .../Rest/HttpToEntryExtensions.cs | 55 +------------------ src/Hl7.Fhir.Core/Rest/Requester.cs | 6 +- 4 files changed, 10 insertions(+), 69 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 05e3b105ff..3c93ef28ee 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -237,10 +237,8 @@ public void Search() client.CompressRequestBody = true; client.OnBeforeRequest += Compression_OnBeforeRequestGZip; - client.OnAfterResponse += Client_OnAfterResponse; result = client.Search(); - client.OnAfterResponse -= Client_OnAfterResponse; Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); @@ -280,10 +278,6 @@ public void Search() Assert.IsTrue(result.Entry.Count > 0); } - private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) - { - - } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index a038e567fc..b529c07c11 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -8,9 +8,6 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using System; -using System.Net; -using System.Reflection; using Hl7.Fhir.Utility; using System.Net.Http; using System.Net.Http.Headers; @@ -18,7 +15,7 @@ namespace Hl7.Fhir.Rest { - internal static class EntryToHttpExtensions + internal static class EntryToHttpExtensions { public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) @@ -59,6 +56,11 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, return request; } + /// + /// Converts bundle http verb to corresponding . + /// + /// specified by input bundle. + /// corresponding to verb specified in input bundle. private static HttpMethod getMethod(Bundle.HTTPVerb? verb) { switch(verb) @@ -103,6 +105,8 @@ private static void setBodyAndContentType(HttpRequestMessage request, Resource d } request.Content = new ByteArrayContent(body); + + // MediaTypeHeaderValue cannot accept a content type that contains charset at the end, so that value must be split out. var contentTypeList = contentType.Split(';'); request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentTypeList.FirstOrDefault()); request.Content.Headers.ContentType.CharSet = System.Text.Encoding.UTF8.WebName; diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index a55116c54f..3d890b6b53 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -9,26 +9,18 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; -using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; namespace Hl7.Fhir.Rest { - public static class HttpToEntryExtensions + public static class HttpToEntryExtensions { private const string USERDATA_BODY = "$body"; private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; @@ -86,43 +78,6 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res } } - return result; - } - - private static string getETag(HttpWebResponse response) - { - var result = response.Headers[HttpUtil.ETAG]; - - if(result != null) - { - if(result.StartsWith(@"W/")) result = result.Substring(2); - result = result.Trim('\"'); - } - - return result; - } - - private static string getContentType(HttpWebResponse response) - { - if (!String.IsNullOrEmpty(response.ContentType)) - { - return ContentType.GetMediaTypeFromHeaderValue(response.ContentType); - } - else - return null; - } - - private static Encoding getCharacterEncoding(HttpWebResponse response) - { - Encoding result = null; - - if (!String.IsNullOrEmpty(response.ContentType)) - { - var charset = ContentType.GetCharSetFromHeaderValue(response.ContentType); - - if (!String.IsNullOrEmpty(charset)) - result = Encoding.GetEncoding(charset); - } return result; } @@ -239,14 +194,6 @@ internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpR } } - internal static void SetHeaders(this Bundle.ResponseComponent interaction, WebHeaderCollection headers) - { - foreach (var key in headers.AllKeys) - { - interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(key + ":" + headers[key])); - } - } - public static IEnumerable> GetHeaders(this Bundle.ResponseComponent interaction) { foreach (var headerExt in interaction.GetExtensions(EXTENSION_RESPONSE_HEADER)) diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 3199a54b82..de8b7bbc25 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -8,12 +8,8 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using Hl7.Fhir.Support; using Hl7.Fhir.Utility; using System; -using System.Collections.Generic; -using System.IO.Compression; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,7 +17,7 @@ namespace Hl7.Fhir.Rest { - internal class Requester + internal class Requester { public Uri BaseUrl { get; private set; } public HttpClient Client { get; private set; } From 2ae3d35edec76a002ec40a3c80778e14f8c84a3f Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Wed, 22 Nov 2017 16:44:27 -0600 Subject: [PATCH 09/39] Fixed formatting. --- src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index b529c07c11..0905b92676 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -15,7 +15,7 @@ namespace Hl7.Fhir.Rest { - internal static class EntryToHttpExtensions + internal static class EntryToHttpExtensions { public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) From 6d8872054eb5246966e760700ead9f912c72a1b0 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 27 Nov 2017 10:09:31 -0600 Subject: [PATCH 10/39] All classes wrapping HttpClient are now IDisposables --- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 24 ++++++++++++++++++++- src/Hl7.Fhir.Core/Rest/IFhirClient.cs | 2 +- src/Hl7.Fhir.Core/Rest/Requester.cs | 30 +++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 847fe050d0..f84041a4e4 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -1138,7 +1138,29 @@ private void verifyServerVersion() throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); } } - } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this._requester.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } public class BeforeRequestEventArgs : EventArgs diff --git a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs index d82f515a8f..4022d6d53a 100644 --- a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs @@ -7,7 +7,7 @@ namespace Hl7.Fhir.Rest { - public interface IFhirClient + public interface IFhirClient : IDisposable { #if NET_COMPRESSION bool PreferCompressedResponses { get; set; } diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index de8b7bbc25..da0d1c73c1 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.Rest { - internal class Requester + internal class Requester : IDisposable { public Uri BaseUrl { get; private set; } public HttpClient Client { get; private set; } @@ -168,5 +168,31 @@ private static Exception buildFhirOperationException(HttpStatusCode status, Reso else return new FhirOperationException($"{message}. Body has no content.", status); } - } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.Client.Dispose(); + } + + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } } From bf59ad44efb17181ac8459b9a644db38564c1334 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 27 Nov 2017 10:41:36 -0600 Subject: [PATCH 11/39] Removed generated docstrings. --- src/Hl7.Fhir.Core/Rest/Requester.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index da0d1c73c1..40c32e2aba 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -185,13 +185,9 @@ protected virtual void Dispose(bool disposing) } } - // This code added to correctly implement the disposable pattern. public void Dispose() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); } #endregion } From d7ef495b06a51b1d5070cfedef4b39c11e531656 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 27 Nov 2017 11:24:45 -0600 Subject: [PATCH 12/39] Moved setting of user agent to default request headers. --- src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs | 1 - src/Hl7.Fhir.Core/Rest/Requester.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index 0905b92676..cd3db407b3 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -33,7 +33,6 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, location.AddParam(HttpUtil.RESTPARAM_FORMAT, Hl7.Fhir.Rest.ContentType.BuildFormatParam(format)); var request = new HttpRequestMessage(getMethod(interaction.Method), location.Uri); - request.Headers.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); if (!useFormatParameter) request.Headers.Add("Accept", Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false)); diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 40c32e2aba..2bc9348bc5 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -52,6 +52,7 @@ public Requester(Uri baseUrl) BaseUrl = baseUrl; Client = new HttpClient(clientHandler); + Client.DefaultRequestHeaders.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds From 8bf076e6fc3d2fee50f7e2e73635d8133403afe1 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 27 Nov 2017 17:20:08 -0600 Subject: [PATCH 13/39] Dispose of fhir clients during tests. --- .../Rest/FhirClientTests.cs | 1226 +++++++++-------- .../Rest/OperationsTests.cs | 150 +- .../Rest/ReadAsyncTests.cs | 38 +- 3 files changed, 737 insertions(+), 677 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 3c93ef28ee..068909363e 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -43,7 +43,7 @@ public void TestInitialize() public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) { System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); - + if (b.Entry != null) { foreach (var item in b.Entry) @@ -66,30 +66,32 @@ public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FetchConformance() { - FhirClient client = new FhirClient(testEndpoint); - client.ParserSettings.AllowUnrecognizedEnums = true; - - var entry = client.CapabilityStatement(); - - Assert.IsNotNull(entry.Text); - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - entry = client.CapabilityStatement(SummaryType.True); - - Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); - Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); - Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.ParserSettings.AllowUnrecognizedEnums = true; + + var entry = client.CapabilityStatement(); + + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + entry = client.CapabilityStatement(SummaryType.True); + + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + } } @@ -114,87 +116,90 @@ public void VerifyFormatParamProcessing() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadWithFormat() { - FhirClient client = new FhirClient(testEndpoint); - - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; - var loc = client.Read("Patient/example"); - Assert.IsNotNull(loc); + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Read() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.Read("Location/1"); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); - Assert.AreEqual("1", loc.Id); - Assert.IsNotNull(loc.Meta.VersionId); + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); - var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc2); - Assert.AreEqual(loc2.Id, loc.Id); - Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); - try - { - var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); - Assert.AreEqual("404", client.LastResult.Status); - } + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } - var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc3); - var jsonSer = new FhirJsonSerializer(); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc3)); + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); - var loc4 = client.Read(loc.ResourceIdentity()); - Assert.IsNotNull(loc4); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc4)); + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); + } } - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadRelative() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.Read(new Uri("Location/1", UriKind.Relative)); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.Read(ri); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + } } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] public void ReadRelativeAsync() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.ReadAsync(ri).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - } + using(FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } + } #endif public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) @@ -228,54 +233,56 @@ public static void Compression_OnBeforeRequestZipOrDeflate(object sender, Before } [TestMethod, Ignore] // Something does not work with the gzip - [TestCategory("FhirClient"), + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Search() { - FhirClient client = new FhirClient(testEndpoint); - Bundle result; + using (FhirClient client = new FhirClient(testEndpoint)) + { + Bundle result; - client.CompressRequestBody = true; - client.OnBeforeRequest += Compression_OnBeforeRequestGZip; + client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestGZip; - result = client.Search(); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.Search(); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; + client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = withSubject.ResourceIdentity(); + ResourceIdentity ri = withSubject.ResourceIdentity(); - // TODO: The include on Grahame's server doesn't currently work - //result = client.SearchById(ri.Id, - // includes: new string[] { "DiagnosticReport:subject" }); - //Assert.IsNotNull(result); + // TODO: The include on Grahame's server doesn't currently work + //result = client.SearchById(ri.Id, + // includes: new string[] { "DiagnosticReport:subject" }); + //Assert.IsNotNull(result); - //Assert.AreEqual(2, result.Entry.Count); // should have subject too + //Assert.AreEqual(2, result.Entry.Count); // should have subject too - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(DiagnosticReport).GetCollectionName())); - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(Patient).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(DiagnosticReport).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(Patient).GetCollectionName())); - client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; - result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); + result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } } @@ -283,38 +290,40 @@ public void Search() [TestMethod, TestCategory("FhirClient")] public void SearchAsync() { - FhirClient client = new FhirClient(testEndpoint); - Bundle result; + using(FhirClient client = new FhirClient(testEndpoint)) + { + Bundle result; - result = client.SearchAsync().Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - result = client.SearchAsync(pageSize: 10).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); - result = client.SearchByIdAsync(ri.Id, - includes: new string[] { "DiagnosticReport.subject" }).Result; - Assert.IsNotNull(result); + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); - Assert.AreEqual(2, result.Entry.Count); // should have subject too + Assert.AreEqual(2, result.Entry.Count); // should have subject too - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(DiagnosticReport).GetCollectionName())); - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(Patient).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); - result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } } #endif @@ -322,66 +331,69 @@ public void SearchAsync() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Paging() { - FhirClient client = new FhirClient(testEndpoint); - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void PagingInJson() { - FhirClient client = new FhirClient(testEndpoint); - client.PreferredFormat = ResourceFormat.Json; - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.PreferredFormat = ResourceFormat.Json; + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } } @@ -389,39 +401,39 @@ public void PagingInJson() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateAndFullRepresentation() { - FhirClient client = new FhirClient(testEndpoint); - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - var pat = client.Read("Patient/glossy"); - ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); - pat.Id = null; - pat.Identifier.Clear(); - var patC = client.Create(pat); - Assert.IsNotNull(patC); + var pat = client.Read("Patient/glossy"); + ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); + pat.Id = null; + pat.Identifier.Clear(); + var patC = client.Create(pat); + Assert.IsNotNull(patC); - client.PreferredReturn = Prefer.ReturnMinimal; - patC = client.Create(pat); + client.PreferredReturn = Prefer.ReturnMinimal; + patC = client.Create(pat); - Assert.IsNull(patC); + Assert.IsNull(patC); - if (client.LastBody != null) - { - var returned = client.LastBodyAsResource; - Assert.IsTrue(returned is OperationOutcome); - } + if (client.LastBody != null) + { + var returned = client.LastBodyAsResource; + Assert.IsTrue(returned is OperationOutcome); + } - // Now validate this resource - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - Parameters p = new Parameters(); - // p.Add("mode", new FhirString("create")); - p.Add("resource", pat); - OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); - Assert.IsNotNull(ooI); + // Now validate this resource + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + Parameters p = new Parameters(); + // p.Add("mode", new FhirString("create")); + p.Add("resource", pat); + OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); + Assert.IsNotNull(ooI); + } } - - private Uri createdTestPatientUrl = null; /// @@ -432,49 +444,51 @@ public void CreateAndFullRepresentation() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateEditDelete() { - FhirClient client = new FhirClient(testEndpoint); + using (FhirClient client = new FhirClient(testEndpoint)) + { - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - // client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + // client.CompressRequestBody = true; - var pat = client.Read("Patient/example"); - pat.Id = null; - pat.Identifier.Clear(); - pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); + var pat = client.Read("Patient/example"); + pat.Id = null; + pat.Identifier.Clear(); + pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); - var fe = client.Create(pat); // Create as we are not providing the ID to be used. - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.Meta.VersionId); - createdTestPatientUrl = fe.ResourceIdentity(); + var fe = client.Create(pat); // Create as we are not providing the ID to be used. + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.Meta.VersionId); + createdTestPatientUrl = fe.ResourceIdentity(); - fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.Update(fe); + fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.Update(fe); - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); - Assert.AreEqual(2, fe2.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); + Assert.AreEqual(2, fe2.Identifier.Count); - fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.Update(fe); - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Identifier.Count); + fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.Update(fe); + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Identifier.Count); - client.Delete(fe3); + client.Delete(fe3); - try - { - // Get most recent version - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.Fail(); - } - catch(FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); - Assert.AreEqual("410", client.LastResult.Status); + try + { + // Get most recent version + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); + Assert.AreEqual("410", client.LastResult.Status); + } } } @@ -483,21 +497,23 @@ public void CreateEditDelete() //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() { - FhirClient client = new FhirClient(testEndpoint); - var observation = new Observation(); - observation.Status = ObservationStatus.Preliminary; - observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); - observation.Value = new SimpleQuantity() + using (FhirClient client = new FhirClient(testEndpoint)) { - System = "http://unitsofmeasure.org", - Value = 23, - Code = "mg", - Unit = "miligram" - }; - observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); - var fe = client.Create(observation); - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + var observation = new Observation(); + observation.Status = ObservationStatus.Preliminary; + observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); + observation.Value = new SimpleQuantity() + { + System = "http://unitsofmeasure.org", + Value = 23, + Code = "mg", + Unit = "miligram" + }; + observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); + var fe = client.Create(observation); + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + } } #if NO_ASYNC_ANYMORE @@ -515,50 +531,52 @@ public void CreateEditDeleteAsync() Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } }; - FhirClient client = new FhirClient(testEndpoint); - var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; + using(FhirClient client = new FhirClient(testEndpoint)) + { + var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; - var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; + var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; - Assert.IsNotNull(furore); - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.SelfLink); - Assert.AreNotEqual(fe.Id, fe.SelfLink); - Assert.IsNotNull(fe.Tags); - Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); - Assert.AreEqual(fe.Tags.First(), tags[0]); - createdTestOrganizationUrl = fe.Id; + Assert.IsNotNull(furore); + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.SelfLink); + Assert.AreNotEqual(fe.Id, fe.SelfLink); + Assert.IsNotNull(fe.Tags); + Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); + Assert.AreEqual(fe.Tags.First(), tags[0]); + createdTestOrganizationUrl = fe.Id; - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.UpdateAsync(fe, refresh: true).Result; + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.UpdateAsync(fe, refresh: true).Result; - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); - Assert.AreEqual(2, fe2.Resource.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); + Assert.AreEqual(2, fe2.Resource.Identifier.Count); - Assert.IsNotNull(fe2.Tags); - Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); - Assert.AreEqual(fe2.Tags.First(), tags[0]); + Assert.IsNotNull(fe2.Tags); + Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); + Assert.AreEqual(fe2.Tags.First(), tags[0]); - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Resource.Identifier.Count); + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Resource.Identifier.Count); - client.DeleteAsync(fe3).Wait(); + client.DeleteAsync(fe3).Wait(); - try - { - // Get most recent version - fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; - Assert.Fail(); - } - catch - { - Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); - } + try + { + // Get most recent version + fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; + Assert.Fail(); + } + catch + { + Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); + } + } } #endif @@ -566,7 +584,7 @@ public void CreateEditDeleteAsync() /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? public void History() { System.Threading.Thread.Sleep(500); @@ -574,49 +592,51 @@ public void History() CreateEditDelete(); // this test does a create, update, update, delete (4 operations) - FhirClient client = new FhirClient(testEndpoint); - - System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); + using (FhirClient client = new FhirClient(testEndpoint)) + { - Bundle history = client.History(createdTestPatientUrl); - Assert.IsNotNull(history); - DebugDumpBundle(history); + System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + Bundle history = client.History(createdTestPatientUrl); + Assert.IsNotNull(history); + DebugDumpBundle(history); - //// Now, assume no one is quick enough to insert something between now and the next - //// tests.... + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + //// Now, assume no one is quick enough to insert something between now and the next + //// tests.... - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); - history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); + history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); - history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) - { - System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); - history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); + history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); Assert.IsNotNull(history); DebugDumpBundle(history); - Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); - // Check that the number of patients that have been created is what we expected - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) + { + System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); + history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); + // Check that the number of patients that have been created is what we expected + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + } } } @@ -625,102 +645,106 @@ public void History() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestWithParam() { - var client = new FhirClient(testEndpoint); - var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); - Assert.IsNotNull(res); + using (var client = new FhirClient(testEndpoint)) + { + var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); + Assert.IsNotNull(res); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ManipulateMeta() { - FhirClient client = new FhirClient(testEndpoint); + using (FhirClient client = new FhirClient(testEndpoint)) + { + + var pat = new Patient(); + pat.Meta = new Meta(); + var key = new Random().Next(); + pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); + pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); - var pat = new Patient(); - pat.Meta = new Meta(); - var key = new Random().Next(); - pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); - pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); + Assert.IsNotNull(wsm); - //Before we begin, ensure that our new tags are not actually used when doing System Meta() - var wsm = client.Meta(); - Assert.IsNotNull(wsm); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); - // First, create a patient with the first set of meta - var pat2 = client.Create(pat); - var loc = pat2.ResourceIdentity(testEndpoint); + // Meta should be present on created patient + verifyMeta(pat2.Meta, false, key); - // Meta should be present on created patient - verifyMeta(pat2.Meta, false, key); + // Should be present when doing instance Meta() + var par = client.Meta(loc); + verifyMeta(par, false, key); - // Should be present when doing instance Meta() - var par = client.Meta(loc); - verifyMeta(par, false, key); + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // Should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); - // Should be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); + // Now add some additional meta to the patient - // Now add some additional meta to the patient + var newMeta = new Meta(); + newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - var newMeta = new Meta(); - newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); - newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - - client.AddMeta(loc, newMeta); - var pat3 = client.Read(loc); + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); - // New and old meta should be present on instance - verifyMeta(pat3.Meta, true, key); + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); - // New and old meta should be present on Meta() - par = client.Meta(loc); - verifyMeta(par, true, key); + // New and old meta should be present on Meta() + par = client.Meta(loc); + verifyMeta(par, true, key); - // New and old meta should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, true, key); + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, true, key); - // New and old meta should be present when doing system Meta() - par = client.Meta(); - verifyMeta(par, true, key); + // New and old meta should be present when doing system Meta() + par = client.Meta(); + verifyMeta(par, true, key); - // Now, remove those new meta tags - client.DeleteMeta(loc, newMeta); + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); - // Should no longer be present on instance - var pat4 = client.Read(loc); - verifyMeta(pat4.Meta, false, key); + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); - // Should no longer be present when doing instance Meta() - par = client.Meta(loc); - verifyMeta(par, false, key); + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); + verifyMeta(par, false, key); - // Should no longer be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // clear out the client that we created, no point keeping it around - client.Delete(pat4); + // clear out the client that we created, no point keeping it around + client.Delete(pat4); - // Should no longer be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); + // Should no longer be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + } } @@ -750,12 +774,13 @@ private void verifyMeta(Meta meta, bool hasNew, int key) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestSearchByPersonaCode() { - var client = new FhirClient(testEndpoint); - - var pats = - client.Search( - new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); - var pat = (Patient)pats.Entry.First().Resource; + using (var client = new FhirClient(testEndpoint)) + { + var pats = + client.Search( + new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); + var pat = (Patient)pats.Entry.First().Resource; + } } @@ -767,59 +792,63 @@ public void CreateDynamic() { Name = "Furore", Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { + Telecom = new List { new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, - new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } } }; - FhirClient client = new FhirClient(testEndpoint); - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); + using (FhirClient client = new FhirClient(testEndpoint)) + { + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); - var fe = client.Create(furore); - Assert.IsNotNull(fe); + var fe = client.Create(furore); + Assert.IsNotNull(fe); + } } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CallsCallbacks() { - FhirClient client = new FhirClient(testEndpoint); - client.ParserSettings.AllowUnrecognizedEnums = true; + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.ParserSettings.AllowUnrecognizedEnums = true; - bool calledBefore = false; - HttpStatusCode? status = null; - byte[] body = null; - byte[] bodyOut = null; + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; - client.OnBeforeRequest += (sender, e) => - { - calledBefore = true; - bodyOut = e.Body; - }; + client.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; - client.OnAfterResponse += (sender, e) => - { - body = e.Body; - status = e.RawResponse.StatusCode; - }; + client.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; - var pat = client.Read("Patient/glossy"); - Assert.IsTrue(calledBefore); - Assert.IsNotNull(status); - Assert.IsNotNull(body); + var pat = client.Read("Patient/glossy"); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); - var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); + var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); - Assert.IsTrue(bodyText.Contains("("Patient/glossy"); - Assert.IsNotNull(result); - result.Id = null; - result.Meta = null; + var result = client.Read("Patient/glossy"); + Assert.IsNotNull(result); + result.Id = null; + result.Meta = null; - client.PreferredReturn = Prefer.ReturnRepresentation; - var posted = client.Create(result); - Assert.IsNotNull(posted, "Patient example not found"); + client.PreferredReturn = Prefer.ReturnRepresentation; + var posted = client.Create(result); + Assert.IsNotNull(posted, "Patient example not found"); - posted = client.Create(result); - Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); + posted = client.Create(result); + Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); - client.PreferredReturn = Prefer.ReturnMinimal; - posted = client.Create(result); - Assert.IsNull(posted); + client.PreferredReturn = Prefer.ReturnMinimal; + posted = client.Create(result); + Assert.IsNull(posted); + } } void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) @@ -871,76 +902,80 @@ void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. public void TestReceiveHtmlIsHandled() { - var client = new FhirClient("http://spark.furore.com/"); // an address that returns html - - try + using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns html { - var pat = client.Read("Patient/1"); - } - catch (FhirOperationException fe) - { - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); - } + try + { + var pat = client.Read("Patient/1"); + } + catch (FhirOperationException fe) + { + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + } } + } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestRefresh() { - var client = new FhirClient(testEndpoint); - var result = client.Read("Patient/example"); + using (var client = new FhirClient(testEndpoint)) + { + var result = client.Read("Patient/example"); - var orig = result.Name[0].FamilyElement.Value; + var orig = result.Name[0].FamilyElement.Value; - result.Name[0].FamilyElement.Value = "overwritten name"; + result.Name[0].FamilyElement.Value = "overwritten name"; - result = client.Refresh(result); + result = client.Refresh(result); - Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + } } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithHtmlIsHandled() { - var client = new FhirClient("http://spark.furore.com/"); // an address that returns Status 500 with HTML in its body - - try + using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns Status 500 with HTML in its body { - var pat = client.Read("Patient/1"); - Assert.Fail("Failed to throw an Exception on status 500"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.InternalServerError) - Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); + try + { + var pat = client.Read("Patient/1"); + Assert.Fail("Failed to throw an Exception on status 500"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.InternalServerError) + Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - if (client.LastResult.Status != "500") - Assert.Fail("LastResult.Status is not 500."); + if (client.LastResult.Status != "500") + Assert.Fail("LastResult.Status is not 500."); - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - string message = operationOutcome.Issue[0].Diagnostics; - if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to carry error message over into OperationOutcome"); - } - catch (Exception) - { - Assert.Fail("Failed to throw FhirOperationException on status 500"); + string message = operationOutcome.Issue[0].Diagnostics; + if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to carry error message over into OperationOutcome"); + } + catch (Exception) + { + Assert.Fail("Failed to throw FhirOperationException on status 500"); + } } } @@ -949,44 +984,45 @@ public void TestReceiveErrorStatusWithHtmlIsHandled() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() { - var client = new FhirClient("http://test.fhir.org/r3"); // an address that returns Status 404 with an OperationOutcome - - try - { - var pat = client.Read("Patient/doesnotexist"); - Assert.Fail("Failed to throw an Exception on status 404"); - } - catch (FhirOperationException fe) + using (var client = new FhirClient("http://test.fhir.org/r3")) // an address that returns Status 404 with an OperationOutcome { - // Expected exception happened - if (fe.Status != HttpStatusCode.NotFound) - Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); + try + { + var pat = client.Read("Patient/doesnotexist"); + Assert.Fail("Failed to throw an Exception on status 404"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.NotFound) + Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - Bundle.ResponseComponent entryComponent = client.LastResult; + Bundle.ResponseComponent entryComponent = client.LastResult; - if (entryComponent.Status != "404") - Assert.Fail("LastResult.Status is not 404."); + if (entryComponent.Status != "404") + Assert.Fail("LastResult.Status is not 404."); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - } - catch (Exception e) - { - Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + } + catch (Exception e) + { + Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + } } } - [TestMethod,Ignore] + [TestMethod, Ignore] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FhirVersionIsChecked() { @@ -996,100 +1032,100 @@ public void FhirVersionIsChecked() var testEndpointDSTU22 = new Uri("http://fhirtest.uhn.ca/baseDstu2"); var testEndpointDSTU23 = new Uri("http://test.fhir.org/r3"); - var client = new FhirClient(testEndpointDSTU1); - client.ParserSettings.AllowUnrecognizedEnums = true; + using(var clientDSTU1 = new FhirClient(testEndpointDSTU1)) + using(var clientDSTU12 = new FhirClient(testEndpointDSTU12)) + using(var clientVerifiedDSTU23 = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true)) + using(var clientDSTU23 = new FhirClient(testEndpointDSTU23)) + { + clientDSTU1.ParserSettings.AllowUnrecognizedEnums = true; - CapabilityStatement p; + CapabilityStatement p; - try - { - client = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true); - client.ParserSettings.AllowUnrecognizedEnums = true; - p = client.CapabilityStatement(); - } - catch (FhirOperationException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } - catch (NotSupportedException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } + try + { + clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; + p = clientDSTU23.CapabilityStatement(); + } + catch (FhirOperationException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } + catch (NotSupportedException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } - client = new FhirClient(testEndpointDSTU23); - client.ParserSettings.AllowUnrecognizedEnums = true; - p = client.CapabilityStatement(); + clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; + p = clientDSTU23.CapabilityStatement(); - //client = new FhirClient(testEndpointDSTU2); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU2); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); - //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); + clientDSTU12.ParserSettings.AllowUnrecognizedEnums = true; - client = new FhirClient(testEndpointDSTU12); - client.ParserSettings.AllowUnrecognizedEnums = true; - - try - { - p = client.CapabilityStatement(); - Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); - } - catch (Exception) - { - // OK + try + { + p = clientDSTU12.CapabilityStatement(); + Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); + } + catch (Exception) + { + // OK + } } - } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestAuthenticationOnBefore() { - FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); - validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => - { - e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); - }; - try + using (FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir")) { - var output = validationFhirClient.ValidateResource(new Patient()); + validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => + { + e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); + }; + try + { + var output = validationFhirClient.ValidateResource(new Patient()); - } - catch(FhirOperationException ex) - { - Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } + catch (FhirOperationException ex) + { + Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } } } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestOperationEverything() { - FhirClient client = new FhirClient(testEndpoint) + using (FhirClient client = new FhirClient(testEndpoint)) { - UseFormatParam = true, - PreferredFormat = ResourceFormat.Json - }; + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; - // GET operation $everything without parameters - var loc = client.TypeOperation("everything", null, true); - Assert.IsNotNull(loc); + // GET operation $everything without parameters + var loc = client.TypeOperation("everything", null, true); + Assert.IsNotNull(loc); - // POST operation $everything without parameters - loc = client.TypeOperation("everything", null, false); - Assert.IsNotNull(loc); + // POST operation $everything without parameters + loc = client.TypeOperation("everything", null, false); + Assert.IsNotNull(loc); - // GET operation $everything with 1 parameter - // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. - // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); - // Assert.IsNotNull(loc); + // GET operation $everything with 1 parameter + // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. + // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); + // Assert.IsNotNull(loc); - // POST operation $everything with 1 parameter - loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); - Assert.IsNotNull(loc); + // POST operation $everything with 1 parameter + loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); + Assert.IsNotNull(loc); + } } - } - } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs index ee03c198e4..aa3763eb3b 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs @@ -23,28 +23,32 @@ public class OperationsTests { string testEndpoint = FhirClientTests.testEndpoint.OriginalString; - [TestMethod] + [TestMethod] [TestCategory("IntegrationTest")] public void InvokeTestPatientGetEverything() { - var client = new FhirClient(testEndpoint); - var start = new FhirDateTime(2014,11,1); - var end = new FhirDateTime(2015,1,1); - var par = new Parameters().Add("start", start).Add("end", end); - var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); - Assert.IsTrue(bundle.Entry.Any()); - - var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient","example"), start, end); - Assert.IsTrue(bundle2.Entry.Any()); + using (var client = new FhirClient(testEndpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2015, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); + var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient", "example"), start, end); + Assert.IsTrue(bundle2.Entry.Any()); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeExpandExistingValueSet() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet","administrative-gender")); - Assert.IsTrue(vs.Expansion.Contains.Any()); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet", "administrative-gender")); + Assert.IsTrue(vs.Expansion.Contains.Any()); + } } @@ -53,12 +57,14 @@ public void InvokeExpandExistingValueSet() [TestCategory("IntegrationTest")] public void InvokeExpandParameterValueSet() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { - var vs = client.Read("ValueSet/administrative-gender"); - var vsX = client.ExpandValueSet(vs); + var vs = client.Read("ValueSet/administrative-gender"); + var vsX = client.ExpandValueSet(vs); - Assert.IsTrue(vsX.Expansion.Contains.Any()); + Assert.IsTrue(vsX.Expansion.Contains.Any()); + } } // [WMR 20170927] Chris Munro @@ -81,61 +87,71 @@ public void InvokeExpandParameterValueSet() [TestCategory("IntegrationTest")] public void InvokeLookupCoding() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); - var expansion = client.ConceptLookup(coding: coding); + var expansion = client.ConceptLookup(coding: coding); - // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } } [TestMethod] // Server returns internal server error [TestCategory("IntegrationTest")] public void InvokeLookupCode() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); - //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeById() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeByCanonical() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), - coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), + coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeWithVS() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var vs = client.Read("ValueSet/c80-facilitycodes"); - Assert.IsNotNull(vs); + var vs = client.Read("ValueSet/c80-facilitycodes"); + Assert.IsNotNull(vs); - var result = client.ValidateCode(valueSet: vs, coding: coding); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode(valueSet: vs, coding: coding); + Assert.IsTrue(result.Result?.Value == true); + } } @@ -143,20 +159,22 @@ public void InvokeValidateCodeWithVS() [TestCategory("IntegrationTest"), Ignore] public void InvokeResourceValidation() { - var client = new FhirClient(testEndpoint); - - var pat = client.Read("Patient/patient-uslab-example1"); - - try - { - var vresult = client.ValidateResource(pat, null, - new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); - Assert.Fail("Should have resulted in 400"); - } - catch(FhirOperationException fe) + using (var client = new FhirClient(testEndpoint)) { - Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); - Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + + var pat = client.Read("Patient/patient-uslab-example1"); + + try + { + var vresult = client.ValidateResource(pat, null, + new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); + Assert.Fail("Should have resulted in 400"); + } + catch (FhirOperationException fe) + { + Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); + Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + } } } @@ -166,21 +184,23 @@ public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() { string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - var client = new FhirClient(_endpoint); - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2020, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); + using (var client = new FhirClient(_endpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2020, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); - var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); - var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); + var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); + var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); - await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); + await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); - var bundle = (Bundle)bundleTask.Result; - Assert.IsTrue(bundle.Entry.Any()); + var bundle = (Bundle)bundleTask.Result; + Assert.IsTrue(bundle.Entry.Any()); - var bundle2 = (Bundle)bundle2Task.Result; - Assert.IsTrue(bundle2.Entry.Any()); + var bundle2 = (Bundle)bundle2Task.Result; + Assert.IsTrue(bundle2.Entry.Any()); + } } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs index 94fac8a41e..ce2b6311d3 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs @@ -16,36 +16,40 @@ public class ReadAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); + }) + { + + Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - Patient p = await client.ReadAsync("/Patient/SMART-1288992"); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); + Patient p = await client.ReadAsync("/Patient/SMART-1288992"); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } } } } \ No newline at end of file From 65fd4ebf9065ffcfb988cfb0854db9975d4adcab Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 27 Nov 2017 17:23:52 -0600 Subject: [PATCH 14/39] Finished adding using statements to REST tests. --- .../Rest/SearchAsyncTests.cs | 195 +++++++++--------- .../Rest/UpdateRefreshDeleteAsyncTests.cs | 57 ++--- 2 files changed, 131 insertions(+), 121 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs index c1f98dc942..c2412dbb3f 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs @@ -16,69 +16,73 @@ public class SearchAsyncTests [TestCategory("IntegrationTest")] public async Task Search_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = await client.SearchAsync(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + var result1 = await client.SearchAsync(srch); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); + + Console.WriteLine("Test Completed"); } - - Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public void SearchSync_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = client.Search(srch); + var result1 = client.Search(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } @@ -86,97 +90,102 @@ public void SearchSync_UsingSearchParams_SearchReturned() [TestCategory("IntegrationTest")] public async Task SearchMultiple_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var srchParams = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var task1 = client.SearchAsync(srchParams); - var task2 = client.SearchAsync(srchParams); - var task3 = client.SearchAsync(srchParams); - - await Task.WhenAll(task1, task2, task3); - var result1 = task1.Result; - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) + }) { - foreach (var e in result1.Entry) + var srchParams = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var task1 = client.SearchAsync(srchParams); + var task2 = client.SearchAsync(srchParams); + var task3 = client.SearchAsync(srchParams); + + await Task.WhenAll(task1, task2, task3); + var result1 = task1.Result; + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_SyncContinue_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var result1 = await client.SearchAsync(new []{"family=clark"}); + }) + { - Assert.IsTrue(result1.Entry.Count >= 1); + var result1 = await client.SearchAsync(new[] { "family=clark" }); - while (result1 != null) - { - foreach (var e in result1.Entry) + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_AsyncContinue_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var result1 = await client.SearchAsync(new[] { "family=clark" },null,1); + var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + Console.WriteLine("Fetching more results..."); + result1 = await client.ContinueAsync(result1); } - Console.WriteLine("Fetching more results..."); - result1 = await client.ContinueAsync(result1); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs index 0b0d811413..da3339bd4b 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs @@ -16,15 +16,16 @@ public class UpdateRefreshDeleteAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var pat = new Patient() + }) { - Name = new List() + + var pat = new Patient() + { + Name = new List() { new HumanName() { @@ -32,33 +33,33 @@ public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_Resu Family = "test_family", } }, - Id = "async-test-patient" - }; - // Create the patient - Console.WriteLine("Creating patient..."); - Patient p = await client.UpdateAsync(pat); - Assert.IsNotNull(p); + Id = "async-test-patient" + }; + // Create the patient + Console.WriteLine("Creating patient..."); + Patient p = await client.UpdateAsync(pat); + Assert.IsNotNull(p); - // Refresh the patient - Console.WriteLine("Refreshing patient..."); - await client.RefreshAsync(p); + // Refresh the patient + Console.WriteLine("Refreshing patient..."); + await client.RefreshAsync(p); - // Delete the patient - Console.WriteLine("Deleting patient..."); - await client.DeleteAsync(p); + // Delete the patient + Console.WriteLine("Deleting patient..."); + await client.DeleteAsync(p); + + Console.WriteLine("Reading patient..."); + Func act = async () => + { + await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); + }; + + // VERIFY // + Assert.ThrowsException(act, "the patient is no longer on the server"); - Console.WriteLine("Reading patient..."); - Func act = async () => - { - await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); - }; - // VERIFY // - Assert.ThrowsException(act, "the patient is no longer on the server"); - - - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } - } } \ No newline at end of file From f09beccd5902094cdc4826c867eb5b5c99ba63e6 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 28 Nov 2017 09:47:55 -0600 Subject: [PATCH 15/39] Added using statements for Requesters HttpResponseMessage and HttpRequestMessage objects. --- src/Hl7.Fhir.Core/Rest/Requester.cs | 112 ++++++++++++++-------------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 2bc9348bc5..622612ad57 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -80,72 +80,74 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody); - - if (PreferCompressedResponses) - { - requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); - requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); - } - - LastRequest = requestMessage; - - byte[] outgoingBody = null; - if(requestMessage.Method == HttpMethod.Post || requestMessage.Method == HttpMethod.Put) + using (var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody)) { - outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); - } + if (PreferCompressedResponses) + { + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); + } - BeforeRequest?.Invoke(requestMessage, outgoingBody); + LastRequest = requestMessage; - var response = await Client.SendAsync(requestMessage).ConfigureAwait(false); - try - { - var body = await response.Content.ReadAsByteArrayAsync(); - //Read body before we call the hook, so the hook cannot read the body before we do + byte[] outgoingBody = null; + if (requestMessage.Method == HttpMethod.Post || requestMessage.Method == HttpMethod.Put) + { + outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); + } - LastResponse = response; - AfterResponse?.Invoke(response, body); + BeforeRequest?.Invoke(requestMessage, outgoingBody); - // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() - try + using (var response = await Client.SendAsync(requestMessage).ConfigureAwait(false)) { - LastResult = null; - - if (response.IsSuccessStatusCode) + try { - LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: true); - return LastResult; + var body = await response.Content.ReadAsByteArrayAsync(); + + LastResponse = response; + AfterResponse?.Invoke(response, body); + + // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() + try + { + LastResult = null; + + if (response.IsSuccessStatusCode) + { + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: true); + return LastResult; + } + else + { + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: false); + throw buildFhirOperationException(response.StatusCode, LastResult.Resource); + } + } + catch (UnsupportedBodyTypeException bte) + { + // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. + // Build a very minimal LastResult + var errorResult = new Bundle.EntryComponent(); + errorResult.Response = new Bundle.ResponseComponent(); + errorResult.Response.Status = ((int)response.StatusCode).ToString(); + + OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); + + errorResult.Resource = operationOutcome; + LastResult = errorResult; + + throw buildFhirOperationException(response.StatusCode, operationOutcome); + } } - else + catch (AggregateException ae) { - LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: false); - throw buildFhirOperationException(response.StatusCode, LastResult.Resource); + //EK: This code looks weird. Is this correct? + if (ae.GetBaseException() is WebException) + { + } + throw ae.GetBaseException(); } } - catch (UnsupportedBodyTypeException bte) - { - // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. - // Build a very minimal LastResult - var errorResult = new Bundle.EntryComponent(); - errorResult.Response = new Bundle.ResponseComponent(); - errorResult.Response.Status = ((int)response.StatusCode).ToString(); - - OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); - - errorResult.Resource = operationOutcome; - LastResult = errorResult; - - throw buildFhirOperationException(response.StatusCode, operationOutcome); - } - } - catch (AggregateException ae) - { - //EK: This code looks weird. Is this correct? - if (ae.GetBaseException() is WebException) - { - } - throw ae.GetBaseException(); } } From 0b9104d7ba5c4eff994fd5ed3df7c52d0161da61 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 09:32:37 -0600 Subject: [PATCH 16/39] Moved common code to IFhirCompatibleClient --- src/Hl7.Fhir.Core/Rest/IFhirClient.cs | 86 +--------------- .../Rest/IFhirCompatibleClient.cs | 98 +++++++++++++++++++ 2 files changed, 100 insertions(+), 84 deletions(-) create mode 100644 src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs diff --git a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs index 4022d6d53a..8d8472078f 100644 --- a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs @@ -7,98 +7,16 @@ namespace Hl7.Fhir.Rest { - public interface IFhirClient : IDisposable + public interface IFhirClient : IDisposable, IFhirCompatibleClient { -#if NET_COMPRESSION - bool PreferCompressedResponses { get; set; } - bool CompressRequestBody { get; set; } -#endif - - Uri Endpoint { get; } byte[] LastBody { get; } Resource LastBodyAsResource { get; } string LastBodyAsText { get; } HttpRequestMessage LastRequest { get; } HttpResponseMessage LastResponse { get; } Bundle.ResponseComponent LastResult { get; } - ParserSettings ParserSettings { get; set; } - ResourceFormat PreferredFormat { get; set; } - bool ReturnFullResource { get; set; } - int Timeout { get; set; } - bool UseFormatParam { get; set; } - bool VerifyFhirVersion { get; set; } event EventHandler OnAfterResponse; - event EventHandler OnBeforeRequest; - - CapabilityStatement CapabilityStatement(SummaryType? summary = default(SummaryType?)); - Task CapabilityStatementAsync(SummaryType? summary = default(SummaryType?)); - Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next); - Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next); - TResource Create(TResource resource) where TResource : Resource; - TResource Create(TResource resource, SearchParams condition) where TResource : Resource; - Task CreateAsync(TResource resource) where TResource : Resource; - Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource; - void Delete(Resource resource); - void Delete(string location); - void Delete(string resourceType, SearchParams condition); - void Delete(Uri location); - System.Threading.Tasks.Task DeleteAsync(Resource resource); - System.Threading.Tasks.Task DeleteAsync(string location); - System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition); - System.Threading.Tasks.Task DeleteAsync(Uri location); - Task executeAsync(Bundle tx, HttpStatusCode expect) where TResource : Resource; - Resource Get(string url); - Resource Get(Uri url); - Task GetAsync(string url); - Task GetAsync(Uri url); - Bundle History(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Bundle History(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task HistoryAsync(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task HistoryAsync(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false); - Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false); - Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - TResource Read(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - TResource Read(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - TResource Refresh(TResource current) where TResource : Resource; - Task RefreshAsync(TResource current) where TResource : Resource; - Bundle Search(SearchParams q, string resourceType = null); - Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); - Bundle Search(SearchParams q) where TResource : Resource; - Task SearchAsync(SearchParams q, string resourceType = null); - Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); - Task SearchAsync(SearchParams q) where TResource : Resource; - Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); - Bundle SearchById(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); - Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); - Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); - Bundle Transaction(Bundle bundle); - Task TransactionAsync(Bundle bundle); - Bundle TypeHistory(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Bundle TypeHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); - Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task TypeHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); - Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false); - Resource TypeOperation(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; - Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false); - Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; - TResource Update(TResource resource, bool versionAware = false) where TResource : Resource; - TResource Update(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; - Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource; - Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; - Bundle WholeSystemHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task WholeSystemHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false); - Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false); - Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + event EventHandler OnBeforeRequest; } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs new file mode 100644 index 0000000000..8850fe39c2 --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using System.Net; + +namespace Hl7.Fhir.Rest +{ + public interface IFhirCompatibleClient + { +#if NET_COMPRESSION + bool PreferCompressedResponses { get; set; } + bool CompressRequestBody { get; set; } +#endif + + Uri Endpoint { get; } + + ParserSettings ParserSettings { get; set; } + ResourceFormat PreferredFormat { get; set; } + bool ReturnFullResource { get; set; } + int Timeout { get; set; } + bool UseFormatParam { get; set; } + bool VerifyFhirVersion { get; set; } + + CapabilityStatement CapabilityStatement(SummaryType? summary = default(SummaryType?)); + Task CapabilityStatementAsync(SummaryType? summary = default(SummaryType?)); + Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next); + Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next); + TResource Create(TResource resource) where TResource : Resource; + TResource Create(TResource resource, SearchParams condition) where TResource : Resource; + Task CreateAsync(TResource resource) where TResource : Resource; + Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource; + void Delete(Resource resource); + void Delete(string location); + void Delete(string resourceType, SearchParams condition); + void Delete(Uri location); + System.Threading.Tasks.Task DeleteAsync(Resource resource); + System.Threading.Tasks.Task DeleteAsync(string location); + System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition); + System.Threading.Tasks.Task DeleteAsync(Uri location); + Task executeAsync(Bundle tx, HttpStatusCode expect) where TResource : Resource; + Resource Get(string url); + Resource Get(Uri url); + Task GetAsync(string url); + Task GetAsync(Uri url); + Bundle History(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Bundle History(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task HistoryAsync(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task HistoryAsync(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false); + Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false); + Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + TResource Read(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + TResource Read(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + TResource Refresh(TResource current) where TResource : Resource; + Task RefreshAsync(TResource current) where TResource : Resource; + Bundle Search(SearchParams q, string resourceType = null); + Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); + Bundle Search(SearchParams q) where TResource : Resource; + Task SearchAsync(SearchParams q, string resourceType = null); + Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); + Task SearchAsync(SearchParams q) where TResource : Resource; + Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); + Bundle SearchById(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); + Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); + Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); + Bundle Transaction(Bundle bundle); + Task TransactionAsync(Bundle bundle); + Bundle TypeHistory(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Bundle TypeHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); + Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task TypeHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); + Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false); + Resource TypeOperation(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; + Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false); + Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; + TResource Update(TResource resource, bool versionAware = false) where TResource : Resource; + TResource Update(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; + Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource; + Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; + Bundle WholeSystemHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task WholeSystemHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false); + Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false); + Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + } +} From 3de96320b1142ec7acec40ecd38796f29157177e Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 09:38:35 -0600 Subject: [PATCH 17/39] Added stub for new fhir client. --- src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs | 1518 ++++++++++++++++++++++ 1 file changed, 1518 insertions(+) create mode 100644 src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs diff --git a/src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs b/src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs new file mode 100644 index 0000000000..7a5e6531ce --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs @@ -0,0 +1,1518 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + + +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + + +namespace Hl7.Fhir.Rest +{ + public partial class FhirHttpClient : IFhirClient + { + private Requester _requester; + + /// + /// Creates a new client using a default endpoint + /// If the endpoint does not end with a slash (/), it will be added. + /// + /// + /// The URL of the server to connect to.
+ /// If the trailing '/' is not present, then it will be appended automatically + /// + /// + /// If parameter is set to true the first time a request is made to the server a + /// conformance check will be made to check that the FHIR versions are compatible. + /// When they are not compatible, a FhirException will be thrown. + /// + public FhirHttpClient(Uri endpoint, bool verifyFhirVersion = false) + { + if (endpoint == null) throw new ArgumentNullException("endpoint"); + + if (!endpoint.OriginalString.EndsWith("/")) + endpoint = new Uri(endpoint.OriginalString + "/"); + + if (!endpoint.IsAbsoluteUri) throw new ArgumentException("endpoint", "Endpoint must be absolute"); + + Endpoint = endpoint; + + _requester = new Requester(Endpoint) + { + BeforeRequest = this.BeforeRequest, + AfterResponse = this.AfterResponse + }; + + VerifyFhirVersion = verifyFhirVersion; + } + + + /// + /// Creates a new client using a default endpoint + /// If the endpoint does not end with a slash (/), it will be added. + /// + /// + /// The URL of the server to connect to.
+ /// If the trailing '/' is not present, then it will be appended automatically + /// + /// + /// If parameter is set to true the first time a request is made to the server a + /// conformance check will be made to check that the FHIR versions are compatible. + /// When they are not compatible, a FhirException will be thrown. + /// + public FhirHttpClient(string endpoint, bool verifyFhirVersion = false) + : this(new Uri(endpoint), verifyFhirVersion) + { + } + + #region << Client Communication Defaults (PreferredFormat, UseFormatParam, Timeout, ReturnFullResource) >> + public bool VerifyFhirVersion + { + get; + set; + } + + /// + /// The preferred format of the content to be used when communicating with the FHIR server (XML or JSON) + /// + public ResourceFormat PreferredFormat + { + get { return _requester.PreferredFormat; } + set { _requester.PreferredFormat = value; } + } + + /// + /// When passing the content preference, use the _format parameter instead of the request header + /// + public bool UseFormatParam + { + get { return _requester.UseFormatParameter; } + set { _requester.UseFormatParameter = value; } + } + + /// + /// The timeout (in milliseconds) to be used when making calls to the FHIR server + /// + public int Timeout + { + get { return _requester.Timeout; } + set { _requester.Timeout = value; } + } + + + //private bool _returnFullResource = false; + + /// + /// Should calls to Create, Update and transaction operations return the whole updated content? + /// + /// Refer to specification section 2.1.0.5 (Managing Return Content) + [Obsolete("In STU3 this is no longer a true/false option, use the PreferredReturn property instead")] + public bool ReturnFullResource + { + get => _requester.PreferredReturn == Prefer.ReturnRepresentation; + set => _requester.PreferredReturn = value ? Prefer.ReturnRepresentation : Prefer.ReturnMinimal; + } + + /// + /// Should calls to Create, Update and transaction operations return the whole updated content, + /// or an OperationOutcome? + /// + /// Refer to specification section 2.1.0.5 (Managing Return Content) + + public Prefer? PreferredReturn + { + get => _requester.PreferredReturn; + set => _requester.PreferredReturn = value; + } + + /// + /// Should server return which search parameters were supported after executing a search? + /// If true, the server should return an error for any unknown or unsupported parameter, otherwise + /// the server may ignore any unknown or unsupported parameter. + /// + public SearchParameterHandling? PreferredParameterHandling + { + get => _requester.PreferredParameterHandling; + set => _requester.PreferredParameterHandling = value; + } + + +#if NET_COMPRESSION + /// + /// This will do 2 things: + /// 1. Add the header Accept-Encoding: gzip, deflate + /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) + /// + public bool PreferCompressedResponses + { + get { return _requester.PreferCompressedResponses; } + set { _requester.PreferCompressedResponses = value; } + } + /// + /// Compress any Request bodies + /// (warning, if a server does not handle compressed requests you will get a 415 response) + /// + public bool CompressRequestBody + { + get { return _requester.CompressRequestBody; } + set { _requester.CompressRequestBody = value; } + } +#endif + + + /// + /// The last transaction result that was executed on this connection to the FHIR server + /// + public Bundle.ResponseComponent LastResult => _requester.LastResult?.Response; + + public ParserSettings ParserSettings + { + get { return _requester.ParserSettings; } + set { _requester.ParserSettings = value; } + } + + + public byte[] LastBody => LastResult?.GetBody(); + public string LastBodyAsText => LastResult?.GetBodyAsText(); + public Resource LastBodyAsResource => _requester.LastResult?.Resource; + + /// + /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient + /// + public HttpRequestMessage LastRequest { get { return _requester.LastRequest; } } + + /// + /// Returns the HttpWebResponse as it was last received during a call on the FhirClient + /// + /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is + /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) + public HttpResponseMessage LastResponse { get { return _requester.LastResponse; } } + + /// + /// The default endpoint for use with operations that use discrete id/version parameters + /// instead of explicit uri endpoints. This will always have a trailing "/" + /// + public Uri Endpoint + { + get; + private set; + } + + #endregion + + #region Read + + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// + /// The requested resource. This operation will throw an exception + /// if the resource has been deleted or does not exist. + /// The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + /// + /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. + /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome + public Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + Bundle tx; + + if (!id.HasVersion) + { + var ri = new TransactionBuilder(Endpoint).Read(id.ResourceType, id.Id, ifNoneMatch, ifModifiedSince); + tx = ri.ToBundle(); + } + else + { + tx = new TransactionBuilder(Endpoint).VRead(id.ResourceType, id.Id, id.VersionId).ToBundle(); + } + + return executeAsync(tx, HttpStatusCode.OK); + } + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// + /// The requested resource. This operation will throw an exception + /// if the resource has been deleted or does not exist. + /// The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + /// + /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. + /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome + public TResource Read(Uri location, string ifNoneMatch = null, + DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); + } + + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// The requested resource + /// This operation will throw an exception + /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + public Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(new Uri(location, UriKind.RelativeOrAbsolute), ifNoneMatch, ifModifiedSince); + } + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// The requested resource + /// This operation will throw an exception + /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + public TResource Read(string location, string ifNoneMatch = null, + DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); + } + + #endregion + + #region Refresh + + /// + /// Refreshes the data in the resource passed as an argument by re-reading it from the server + /// + /// + /// The resource for which you want to get the most recent version. + /// A new instance of the resource, containing the most up-to-date data + /// This function will not overwrite the argument with new data, rather it will return a new instance + /// which will have the newest data, leaving the argument intact. + public Task RefreshAsync(TResource current) where TResource : Resource + { + if (current == null) throw Error.ArgumentNull(nameof(current)); + + return ReadAsync(ResourceIdentity.Build(current.TypeName, current.Id)); + } + /// + /// Refreshes the data in the resource passed as an argument by re-reading it from the server + /// + /// + /// The resource for which you want to get the most recent version. + /// A new instance of the resource, containing the most up-to-date data + /// This function will not overwrite the argument with new data, rather it will return a new instance + /// which will have the newest data, leaving the argument intact. + public TResource Refresh(TResource current) where TResource : Resource + { + return RefreshAsync(current).WaitResult(); + } + + #endregion + + #region Update + + /// + /// Update (or create) a resource + /// + /// The resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be + /// created. + public Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (resource.Id == null) throw Error.Argument(nameof(resource), "Resource needs a non-null Id to send the update to"); + + var upd = new TransactionBuilder(Endpoint); + + if (versionAware && resource.HasVersionId) + upd.Update(resource.Id, resource, versionId: resource.VersionId); + else + upd.Update(resource.Id, resource); + + return internalUpdateAsync(resource, upd.ToBundle()); + } + /// + /// Update (or create) a resource + /// + /// The resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be + /// created. + public TResource Update(TResource resource, bool versionAware = false) where TResource : Resource + { + return UpdateAsync(resource, versionAware).WaitResult(); + } + + /// + /// Conditionally update (or create) a resource + /// + /// The resource to update + /// Criteria used to locate the resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. + public Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var upd = new TransactionBuilder(Endpoint); + + if (versionAware && resource.HasVersionId) + upd.Update(condition, resource, versionId: resource.VersionId); + else + upd.Update(condition, resource); + + return internalUpdateAsync(resource, upd.ToBundle()); + } + /// + /// Conditionally update (or create) a resource + /// + /// The resource to update + /// Criteria used to locate the resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. + public TResource Update(TResource resource, SearchParams condition, bool versionAware = false) + where TResource : Resource + { + return UpdateAsync(resource, condition, versionAware).WaitResult(); + } + private Task internalUpdateAsync(TResource resource, Bundle tx) where TResource : Resource + { + resource.ResourceBase = Endpoint; + + // This might be an update of a resource that doesn't yet exist, so accept a status Created too + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + private TResource internalUpdate(TResource resource, Bundle tx) where TResource : Resource + { + return internalUpdateAsync(resource, tx).WaitResult(); + } + #endregion + + #region Delete + + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public async System.Threading.Tasks.Task DeleteAsync(Uri location) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + var tx = new TransactionBuilder(Endpoint).Delete(id.ResourceType, id.Id).ToBundle(); + + await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public void Delete(Uri location) + { + DeleteAsync(location).WaitNoResult(); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public System.Threading.Tasks.Task DeleteAsync(string location) + { + return DeleteAsync(new Uri(location, UriKind.Relative)); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public void Delete(string location) + { + DeleteAsync(location).WaitNoResult(); + } + + + /// + /// Delete a resource + /// + /// The resource to delete + public async System.Threading.Tasks.Task DeleteAsync(Resource resource) + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (resource.Id == null) throw Error.Argument(nameof(resource), "Entry must have an id"); + + await DeleteAsync(resource.ResourceIdentity(Endpoint).WithoutVersion()).ConfigureAwait(false); + } + /// + /// Delete a resource + /// + /// The resource to delete + public void Delete(Resource resource) + { + DeleteAsync(resource).WaitNoResult(); + } + + /// + /// Conditionally delete a resource + /// + /// The type of resource to delete + /// Criteria to use to match the resource to delete. + public async System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition) + { + if (resourceType == null) throw Error.ArgumentNull(nameof(resourceType)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var tx = new TransactionBuilder(Endpoint).Delete(resourceType, condition).ToBundle(); + await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); + } + /// + /// Conditionally delete a resource + /// + /// The type of resource to delete + /// Criteria to use to match the resource to delete. + public void Delete(string resourceType, SearchParams condition) + { + DeleteAsync(resourceType, condition).WaitNoResult(); + } + + #endregion + + #region Create + + /// + /// Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public Task CreateAsync(TResource resource) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + + var tx = new TransactionBuilder(Endpoint).Create(resource).ToBundle(); + + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + /// + /// Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public TResource Create(TResource resource) where TResource : Resource + { + return CreateAsync(resource).WaitResult(); + } + + /// + /// Conditionally Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The criteria + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var tx = new TransactionBuilder(Endpoint).Create(resource, condition).ToBundle(); + + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + public TResource Create(TResource resource, SearchParams condition) where TResource : Resource + { + return CreateAsync(resource, condition).WaitResult(); + } + + #endregion + + #region Conformance + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + [Obsolete("The Conformance operation has been replaced by the CapabilityStatement", false)] + public CapabilityStatement Conformance(SummaryType? summary = null) + { + return CapabilityStatement(summary); + } + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + public Task CapabilityStatementAsync(SummaryType? summary = null) + { + var tx = new TransactionBuilder(Endpoint).CapabilityStatement(summary).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + public CapabilityStatement CapabilityStatement(SummaryType? summary = null) + { + return CapabilityStatementAsync(summary).WaitResult(); + } + #endregion + + #region History + + /// + /// Retrieve the version history for a specific resource type + /// + /// The type of Resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(resourceType, null, since, pageSize, summary); + } + /// + /// Retrieve the version history for a specific resource type + /// + /// The type of Resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle TypeHistory(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return TypeHistoryAsync(resourceType, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a specific resource type + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// The type of Resource to get the history for + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task TypeHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() + { + string collection = typeof(TResource).GetCollectionName(); + return internalHistoryAsync(collection, null, since, pageSize, summary); + } + /// + /// Retrieve the version history for a specific resource type + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// The type of Resource to get the history for + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle TypeHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() + { + return TypeHistoryAsync(since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task HistoryAsync(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + return internalHistoryAsync(id.ResourceType, id.Id, since, pageSize, summary); + } + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle History(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(location, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task HistoryAsync(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(new Uri(location, UriKind.Relative), since, pageSize, summary); + } + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle History(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(location, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the full version history of the server + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Indicates whether the returned resources should just contain the minimal set of elements + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task WholeSystemHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(null, null, since, pageSize, summary); + } + /// + /// Retrieve the full version history of the server + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Indicates whether the returned resources should just contain the minimal set of elements + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle WholeSystemHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return WholeSystemHistoryAsync(since, pageSize, summary).WaitResult(); + } + private Task internalHistoryAsync(string resourceType = null, string id = null, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + TransactionBuilder history; + + if (resourceType == null) + history = new TransactionBuilder(Endpoint).ServerHistory(summary, pageSize, since); + else if (id == null) + history = new TransactionBuilder(Endpoint).CollectionHistory(resourceType, summary, pageSize, since); + else + history = new TransactionBuilder(Endpoint).ResourceHistory(resourceType, id, summary, pageSize, since); + + return executeAsync(history.ToBundle(), HttpStatusCode.OK); + } + private Bundle internalHistory(string resourceType = null, string id = null, DateTimeOffset? since = null, + int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(resourceType, id, since, pageSize, summary).WaitResult(); + } + + #endregion + + #region Transaction + + /// + /// Send a set of creates, updates and deletes to the server to be processed in one transaction + /// + /// The bundled creates, updates and deleted + /// A bundle as returned by the server after it has processed the transaction, or null + /// if an error occurred. + public Task TransactionAsync(Bundle bundle) + { + if (bundle == null) throw new ArgumentNullException(nameof(bundle)); + + var tx = new TransactionBuilder(Endpoint).Transaction(bundle).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + /// + /// Send a set of creates, updates and deletes to the server to be processed in one transaction + /// + /// The bundled creates, updates and deleted + /// A bundle as returned by the server after it has processed the transaction, or null + /// if an error occurred. + public Bundle Transaction(Bundle bundle) + { + return TransactionAsync(bundle).WaitResult(); + } + + #endregion + + #region Operation + + public Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + return internalOperationAsync(operationName, parameters: parameters, useGet: useGet); + } + + public Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false) + { + return WholeSystemOperationAsync(operationName, parameters, useGet).WaitResult(); + } + + + public Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) + where TResource : Resource + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + // [WMR 20160421] GetResourceNameForType is obsolete + // var typeName = ModelInfo.GetResourceNameForType(typeof(TResource)); + var typeName = ModelInfo.GetFhirTypeNameForType(typeof(TResource)); + + return TypeOperationAsync(operationName, typeName, parameters, useGet: useGet); + } + public Resource TypeOperation(string operationName, Parameters parameters = null, + bool useGet = false) where TResource : Resource + { + return TypeOperationAsync(operationName, parameters, useGet).WaitResult(); + } + + + + public Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false) + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + if (typeName == null) throw Error.ArgumentNull(nameof(typeName)); + + return internalOperationAsync(operationName, typeName, parameters: parameters, useGet: useGet); + } + public Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false) + { + return TypeOperationAsync(operationName, typeName, parameters, useGet).WaitResult(); + } + + + + public Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + + return internalOperationAsync(operationName, id.ResourceType, id.Id, id.VersionId, parameters, useGet); + } + public Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + return InstanceOperationAsync(location, operationName, parameters, useGet).WaitResult(); + } + + + + public Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(location), operationName, parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + public Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + return OperationAsync(location, operationName, parameters, useGet).WaitResult(); + } + + + public Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false) + { + if (operation == null) throw Error.ArgumentNull(nameof(operation)); + + var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(operation), parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + public Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false) + { + return OperationAsync(operation, parameters, useGet).WaitResult(); + } + + + + private Task internalOperationAsync(string operationName, string type = null, string id = null, string vid = null, + Parameters parameters = null, bool useGet = false) + { + // Brian: Not sure why we would create this parameters object as empty. + // I would imagine that a null parameters object is different to an empty one? + // EK: What else could we do? POST an empty body? We cannot use GET unless the caller indicates this is an + // idempotent call.... + // MV: (related to issue #419): we only provide an empty parameter when we are not performing a GET operation. In r4 it will be allowed + // to provide an empty body in POST operations. In that case the line of code can be deleted. + if (parameters == null && !useGet) parameters = new Parameters(); + + Bundle tx; + + if (type == null) + tx = new TransactionBuilder(Endpoint).ServerOperation(operationName, parameters, useGet).ToBundle(); + else if (id == null) + tx = new TransactionBuilder(Endpoint).TypeOperation(type, operationName, parameters, useGet).ToBundle(); + else + tx = new TransactionBuilder(Endpoint).ResourceOperation(type, id, vid, operationName, parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + + private Resource internalOperation(string operationName, string type = null, string id = null, + string vid = null, Parameters parameters = null, bool useGet = false) + { + return internalOperationAsync(operationName, type, id, vid, parameters, useGet).WaitResult(); + } + + #endregion + + #region Get + + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Resource Get(Uri url) + { + return GetAsync(url).WaitResult(); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public async Task GetAsync(Uri url) + { + if (url == null) throw Error.ArgumentNull(nameof(url)); + + var tx = new TransactionBuilder(Endpoint).Get(url).ToBundle(); + return await executeAsync(tx, HttpStatusCode.OK).ConfigureAwait(false); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Resource Get(string url) + { + return GetAsync(url).WaitResult(); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Task GetAsync(string url) + { + if (url == null) throw Error.ArgumentNull(nameof(url)); + + return GetAsync(new Uri(url, UriKind.RelativeOrAbsolute)); + } + + #endregion + + + + + private ResourceIdentity verifyResourceIdentity(Uri location, bool needId, bool needVid) + { + var result = new ResourceIdentity(location); + + if (result.ResourceType == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the resource type in its path"); + if (needId && result.Id == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the logical id in its path"); + if (needVid && !result.HasVersion) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the version id in its path"); + + return result; + } + + + // TODO: Depending on type of response, update identity & always update lastupdated? + + private void updateIdentity(Resource resource, ResourceIdentity identity) + { + if (resource.Meta == null) resource.Meta = new Meta(); + + if (resource.Id == null) + { + resource.Id = identity.Id; + resource.VersionId = identity.VersionId; + } + } + + + private void setResourceBase(Resource resource, string baseUri) + { + resource.ResourceBase = new Uri(baseUri); + + if (resource is Bundle) + { + var bundle = resource as Bundle; + foreach (var entry in bundle.Entry.Where(e => e.Resource != null)) + entry.Resource.ResourceBase = new Uri(baseUri, UriKind.RelativeOrAbsolute); + } + } + + + /// + /// Called just before the Http call is done + /// + public event EventHandler OnBeforeRequest; + + /// + /// Called just after the response was received + /// + public event EventHandler OnAfterResponse; + + /// + /// Inspect or modify the just before the FhirClient issues a call to the server + /// + /// The request as it is about to be sent to the server + /// The data in the body of the request as it is about to be sent to the server + protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) + { + // Default implementation: call event + OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); + } + + /// + /// Inspect the as it came back from the server + /// + /// You cannot read the body from the HttpResponseMessage, since it has + /// already been read by the framework. Use the body parameter instead. + protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) + { + // Default implementation: call event + OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); + } + + // Original + private TResource execute(Bundle tx, HttpStatusCode expect) where TResource : Model.Resource + { + return executeAsync(tx, new[] { expect }).WaitResult(); + } + public Task executeAsync(Model.Bundle tx, HttpStatusCode expect) where TResource : Model.Resource + { + return executeAsync(tx, new[] { expect }); + } + // Original + private TResource execute(Bundle tx, IEnumerable expect) where TResource : Resource + { + return executeAsync(tx, expect).WaitResult(); + } + + private async Task executeAsync(Bundle tx, IEnumerable expect) where TResource : Resource + { + verifyServerVersion(); + + var request = tx.Entry[0]; + var response = await _requester.ExecuteAsync(request).ConfigureAwait(false); + + if (!expect.Select(sc => ((int)sc).ToString()).Contains(response.Response.Status)) + { + Enum.TryParse(response.Response.Status, out HttpStatusCode code); + throw new FhirOperationException("Operation concluded successfully, but the return status {0} was unexpected".FormatWith(response.Response.Status), code); + } + + Resource result; + + // Special feature: if ReturnFullResource was requested (using the Prefer header), but the server did not return the resource + // (or it returned an OperationOutcome) - explicitly go out to the server to get the resource and return it. + // This behavior is only valid for PUT and POST requests, where the server may device whether or not to return the full body of the alterend resource. + var noRealBody = response.Resource == null || (response.Resource is OperationOutcome && string.IsNullOrEmpty(response.Resource.Id)); + if (noRealBody && isPostOrPut(request) + && PreferredReturn == Prefer.ReturnRepresentation && response.Response.Location != null + && new ResourceIdentity(response.Response.Location).IsRestResourceIdentity()) // Check that it isn't an operation too + { + result = await GetAsync(response.Response.Location).ConfigureAwait(false); + } + else + result = response.Resource; + + if (result == null) return null; + + // We have a success code (2xx), we have a body, but the body may not be of the type we expect. + if (!(result is TResource)) + { + // If this is an operationoutcome, that may still be allright. Keep the OperationOutcome in + // the LastResult, and return null as the result. Otherwise, throw. + if (result is OperationOutcome) + return null; + + var message = String.Format("Operation {0} on {1} expected a body of type {2} but a {3} was returned", response.Request.Method, + response.Request.Url, typeof(TResource).Name, result.GetType().Name); + throw new FhirOperationException(message, _requester.LastResponse.StatusCode); + } + else + return result as TResource; + } + private bool isPostOrPut(Bundle.EntryComponent interaction) + { + var method = interaction.Request.Method; + return method == Bundle.HTTPVerb.POST || method == Bundle.HTTPVerb.PUT; + } + + + private bool versionChecked = false; + + private void verifyServerVersion() + { + if (!VerifyFhirVersion) return; + + if (versionChecked) return; + versionChecked = true; // So we can now start calling Conformance() without getting into a loop + + CapabilityStatement conf = null; + try + { + conf = CapabilityStatement(SummaryType.True); // don't get the full version as its huge just to read the fhir version + } + catch (FormatException) + { + // Mmmm...cannot even read the body. Probably not so good. + throw Error.NotSupported("Cannot read the conformance statement of the server to verify FHIR version compatibility"); + } + + if (!conf.FhirVersion.StartsWith(ModelInfo.Version)) + { + throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this._requester.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + + #region Search Execution + + /// + /// Search for Resources based on criteria specified in a Query resource + /// + /// The Query resource containing the search parameters + /// The type of resource to filter on (optional). If not specified, will search on all resource types. + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + public Task SearchAsync(SearchParams q, string resourceType = null) + { + var tx = new TransactionBuilder(Endpoint).Search(q, resourceType).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + /// + /// Search for Resources based on criteria specified in a Query resource + /// + /// The Query resource containing the search parameters + /// The type of resource to filter on (optional). If not specified, will search on all resource types. + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + public Bundle Search(SearchParams q, string resourceType = null) + { + return SearchAsync(q, resourceType).WaitResult(); + } + + #endregion + + #region Search by Parameters + + /// + /// Search for Resources based on criteria specified in a Query resource + /// + /// The Query resource containing the search parameters + /// The type of resource to filter on + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + public Task SearchAsync(SearchParams q) + where TResource : Resource + { + // [WMR 20160421] GetResourceNameForType is obsolete + // return Search(q, ModelInfo.GetResourceNameForType(typeof(TResource))); + return SearchAsync(q, ModelInfo.GetFhirTypeNameForType(typeof(TResource))); + } + /// + /// Search for Resources based on criteria specified in a Query resource + /// + /// The Query resource containing the search parameters + /// The type of resource to filter on + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + public Bundle Search(SearchParams q) where TResource : Resource + { + return SearchAsync(q).WaitResult(); + } + + #endregion + + #region Generic Criteria Search + + /// + /// Search for Resources of a certain type that match the given criteria + /// + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// The type of resource to list + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + where TResource : Resource, new() + { + // [WMR 20160421] GetResourceNameForType is obsolete + // return Search(ModelInfo.GetResourceNameForType(typeof(TResource)), criteria, includes, pageSize, summary); + return SearchAsync(ModelInfo.GetFhirTypeNameForType(typeof(TResource)), criteria, includes, pageSize, summary, revIncludes); + } + /// + /// Search for Resources of a certain type that match the given criteria + /// + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// The type of resource to list + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + where TResource : Resource, new() + { + return SearchAsync(criteria, includes, pageSize, summary, revIncludes).WaitResult(); + } + + #endregion + + #region Non-Generic Criteria Search + + /// + /// Search for Resources of a certain type that match the given criteria + /// + /// The type of resource to search for + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + + return SearchAsync(toQuery(criteria, includes, pageSize, summary, revIncludes), resource); + } + /// + /// Search for Resources of a certain type that match the given criteria + /// + /// The type of resource to search for + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + { + return SearchAsync(resource, criteria, includes, pageSize, summary, revIncludes).WaitResult(); + } + + #endregion + + #region Whole system search + + /// + /// Search for Resources across the whole server that match the given criteria + /// + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + { + return SearchAsync(toQuery(criteria, includes, pageSize, summary, revIncludes)); + } + + /// + /// Search for Resources across the whole server that match the given criteria + /// + /// Optional. The search parameters to filter the resources on. Each + /// given string is a combined key/value pair (separated by '=') + /// Optional. A list of include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Whether to include only return a summary of the resources in the Bundle + /// Optional. A list of reverse include paths + /// A Bundle with all resources found by the search, or an empty Bundle if none were found. + /// All parameters are optional, leaving all parameters empty will return an unfiltered list + /// of all resources of the given Resource type + public Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = null, + SummaryType? summary = null, string[] revIncludes = null) + { + return WholeSystemSearchAsync(criteria, includes, pageSize, summary, revIncludes).WaitResult(); + } + + #endregion + + #region Generic Search by ID + + /// + /// Search for resources based on a resource's id. + /// + /// The id of the resource to search for + /// Zero or more include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. A list of reverse include paths + /// The type of resource to search for + /// A Bundle with the BundleEntry as identified by the id parameter or an empty + /// Bundle if the resource wasn't found. + /// This operation is similar to Read, but additionally, + /// it is possible to specify include parameters to include resources in the bundle that the + /// returned resource refers to. + public Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = null, + string[] revIncludes = null) where TResource : Resource, new() + { + if (id == null) throw Error.ArgumentNull(nameof(id)); + + return SearchByIdAsync(typeof(TResource).GetCollectionName(), id, includes, pageSize, revIncludes); + } + + /// + /// Search for resources based on a resource's id. + /// + /// The id of the resource to search for + /// Zero or more include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. A list of reverse include paths + /// The type of resource to search for + /// A Bundle with the BundleEntry as identified by the id parameter or an empty + /// Bundle if the resource wasn't found. + /// This operation is similar to Read, but additionally, + /// it is possible to specify include parameters to include resources in the bundle that the + /// returned resource refers to. + public Bundle SearchById(string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) where TResource : Resource, new() + { + return SearchByIdAsync(id, includes, pageSize, revIncludes).WaitResult(); + } + + #endregion + + #region Non-Generic Search by Id + + /// + /// Search for resources based on a resource's id. + /// + /// The type of resource to search for + /// The id of the resource to search for + /// Zero or more include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. A list of reverse include paths + /// A Bundle with the BundleEntry as identified by the id parameter or an empty + /// Bundle if the resource wasn't found. + /// This operation is similar to Read, but additionally, + /// it is possible to specify include parameters to include resources in the bundle that the + /// returned resource refers to. + public Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (id == null) throw Error.ArgumentNull(nameof(id)); + + string criterium = "_id=" + id; + return SearchAsync(toQuery(new string[] { criterium }, includes, pageSize, summary: null, revIncludes: revIncludes), resource); + } + /// + /// Search for resources based on a resource's id. + /// + /// The type of resource to search for + /// The id of the resource to search for + /// Zero or more include paths + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. A list of reverse include paths + /// A Bundle with the BundleEntry as identified by the id parameter or an empty + /// Bundle if the resource wasn't found. + /// This operation is similar to Read, but additionally, + /// it is possible to specify include parameters to include resources in the bundle that the + /// returned resource refers to. + public Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) + { + return SearchByIdAsync(resource, id, includes, pageSize, revIncludes).WaitResult(); + } + + #endregion + + #region Continue + + /// + /// Uses the FHIR paging mechanism to go navigate around a series of paged result Bundles + /// + /// The bundle as received from the last response + /// Optional. Direction to browse to, default is the next page of results. + /// A bundle containing a new page of results based on the browse direction, or null if + /// the server did not have more results in that direction. + public Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next) + { + if (current == null) throw Error.ArgumentNull(nameof(current)); + if (current.Link == null) return null; + + Uri continueAt = null; + + switch (direction) + { + case PageDirection.First: + continueAt = current.FirstLink; break; + case PageDirection.Previous: + continueAt = current.PreviousLink; break; + case PageDirection.Next: + continueAt = current.NextLink; break; + case PageDirection.Last: + continueAt = current.LastLink; break; + } + + if (continueAt != null) + { + var tx = new TransactionBuilder(Endpoint).Get(continueAt).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + else + { + // Return a null bundle, can not return simply null because this is a task + Bundle nullValue = null; + return System.Threading.Tasks.Task.FromResult(nullValue); + } + } + /// + /// Uses the FHIR paging mechanism to go navigate around a series of paged result Bundles + /// + /// The bundle as received from the last response + /// Optional. Direction to browse to, default is the next page of results. + /// A bundle containing a new page of results based on the browse direction, or null if + /// the server did not have more results in that direction. + public Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next) + { + return ContinueAsync(current, direction).WaitResult(); + } + + #endregion + + #region Private Methods + + private SearchParams toQuery(string[] criteria, string[] includes, int? pageSize, SummaryType? summary, string[] revIncludes) + { + var q = new SearchParams() + { + Count = pageSize + }; + + if (includes != null) + foreach (var inc in includes) q.Include.Add(inc); + + if (revIncludes != null) + foreach (var revInc in revIncludes) q.RevInclude.Add(revInc); + + if (criteria != null) + { + foreach (var crit in criteria) + { + var keyVal = crit.SplitLeft('='); + q.Add(keyVal.Item1, keyVal.Item2); + } + } + + if (summary != null) + q.Summary = summary.Value; + + return q; + } + + #endregion + } +} From 31d4b966306ac26ab38a054b82f5d44cf9f447fb Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 09:44:16 -0600 Subject: [PATCH 18/39] Added stub for httpclient specific requester --- src/Hl7.Fhir.Core/Rest/HttpRequester.cs | 197 ++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/Hl7.Fhir.Core/Rest/HttpRequester.cs diff --git a/src/Hl7.Fhir.Core/Rest/HttpRequester.cs b/src/Hl7.Fhir.Core/Rest/HttpRequester.cs new file mode 100644 index 0000000000..5279eceb5b --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/HttpRequester.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Hl7.Fhir.Rest +{ + internal class HttpRequester : IDisposable + { + public Uri BaseUrl { get; private set; } + public HttpClient Client { get; private set; } + + public bool UseFormatParameter { get; set; } + public ResourceFormat PreferredFormat { get; set; } + public int Timeout { get; set; } // In milliseconds + + public Prefer? PreferredReturn { get; set; } + public SearchParameterHandling? PreferredParameterHandling { get; set; } + + /// + /// This will do 2 things: + /// 1. Add the header Accept-Encoding: gzip, deflate + /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) + /// + public bool PreferCompressedResponses { get; set; } + /// + /// Compress any Request bodies + /// (warning, if a server does not handle compressed requests you will get a 415 response) + /// + public bool CompressRequestBody { get; set; } + + public ParserSettings ParserSettings { get; set; } + + public HttpRequester(Uri baseUrl) + { + var clientHandler = new HttpClientHandler() + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + + BaseUrl = baseUrl; + Client = new HttpClient(clientHandler); + Client.DefaultRequestHeaders.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); + UseFormatParameter = false; + PreferredFormat = ResourceFormat.Xml; + Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds + PreferredReturn = Rest.Prefer.ReturnRepresentation; + PreferredParameterHandling = null; + ParserSettings = Hl7.Fhir.Serialization.ParserSettings.Default; + } + + + public Bundle.EntryComponent LastResult { get; private set; } + public HttpResponseMessage LastResponse { get; private set; } + public HttpRequestMessage LastRequest { get; private set; } + public Action BeforeRequest { get; set; } + public Action AfterResponse { get; set; } + + public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) + { + return ExecuteAsync(interaction).WaitResult(); + } + + public async Task ExecuteAsync(Bundle.EntryComponent interaction) + { + if (interaction == null) throw Error.ArgumentNull(nameof(interaction)); + bool compressRequestBody = false; + + compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment + + using (var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody)) + { + if (PreferCompressedResponses) + { + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); + } + + LastRequest = requestMessage; + + byte[] outgoingBody = null; + if (requestMessage.Method == HttpMethod.Post || requestMessage.Method == HttpMethod.Put) + { + outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); + } + + BeforeRequest?.Invoke(requestMessage, outgoingBody); + + using (var response = await Client.SendAsync(requestMessage).ConfigureAwait(false)) + { + try + { + var body = await response.Content.ReadAsByteArrayAsync(); + + LastResponse = response; + AfterResponse?.Invoke(response, body); + + // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() + try + { + LastResult = null; + + if (response.IsSuccessStatusCode) + { + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: true); + return LastResult; + } + else + { + LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: false); + throw buildFhirOperationException(response.StatusCode, LastResult.Resource); + } + } + catch (UnsupportedBodyTypeException bte) + { + // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. + // Build a very minimal LastResult + var errorResult = new Bundle.EntryComponent(); + errorResult.Response = new Bundle.ResponseComponent(); + errorResult.Response.Status = ((int)response.StatusCode).ToString(); + + OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); + + errorResult.Resource = operationOutcome; + LastResult = errorResult; + + throw buildFhirOperationException(response.StatusCode, operationOutcome); + } + } + catch (AggregateException ae) + { + //EK: This code looks weird. Is this correct? + if (ae.GetBaseException() is WebException) + { + } + throw ae.GetBaseException(); + } + } + } + } + + private static Exception buildFhirOperationException(HttpStatusCode status, Resource body) + { + string message; + + if (status.IsInformational()) + message = $"Operation resulted in an informational response ({status})"; + else if (status.IsRedirection()) + message = $"Operation resulted in a redirection response ({status})"; + else if (status.IsClientError()) + message = $"Operation was unsuccessful because of a client error ({status})"; + else + message = $"Operation was unsuccessful, and returned status {status}"; + + if (body is OperationOutcome outcome) + return new FhirOperationException($"{message}. OperationOutcome: {outcome.ToString()}.", status, outcome); + else if (body != null) + return new FhirOperationException($"{message}. Body contains a {body.TypeName}.", status); + else + return new FhirOperationException($"{message}. Body has no content.", status); + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.Client.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} From ed73dd95ed53bd70da21d14cd82b13e91b8912f7 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 09:59:20 -0600 Subject: [PATCH 19/39] Moved new implementations to new namespace, restored old implementations. --- .../Rest/FhirClientTests.cs | 1252 ++++++++--------- .../Rest/OperationsTests.cs | 150 +- .../Rest/ReadAsyncTests.cs | 38 +- .../Rest/RequesterTests.cs | 30 +- .../Rest/SearchAsyncTests.cs | 195 ++- .../Rest/TransactionBuilderTests.cs | 4 +- .../Rest/UpdateRefreshDeleteAsyncTests.cs | 57 +- .../Rest/EntryToHttpExtensions.cs | 115 +- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 47 +- .../Rest/Http/EntryToHttpExtensions.cs | 116 ++ .../{FhirHttpClient.cs => Http/FhirClient.cs} | 44 +- .../Rest/Http/HttpToEntryExtensions.cs | 232 +++ .../{HttpRequester.cs => Http/Requester.cs} | 6 +- .../Rest/HttpToEntryExtensions.cs | 105 +- src/Hl7.Fhir.Core/Rest/IFhirClient.cs | 9 +- src/Hl7.Fhir.Core/Rest/Requester.cs | 191 +-- 16 files changed, 1451 insertions(+), 1140 deletions(-) create mode 100644 src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs rename src/Hl7.Fhir.Core/Rest/{FhirHttpClient.cs => Http/FhirClient.cs} (97%) create mode 100644 src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs rename src/Hl7.Fhir.Core/Rest/{HttpRequester.cs => Http/Requester.cs} (98%) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 068909363e..349be126d8 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -43,7 +43,7 @@ public void TestInitialize() public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) { System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); - + if (b.Entry != null) { foreach (var item in b.Entry) @@ -66,32 +66,30 @@ public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FetchConformance() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.ParserSettings.AllowUnrecognizedEnums = true; - - var entry = client.CapabilityStatement(); - - Assert.IsNotNull(entry.Text); - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - entry = client.CapabilityStatement(SummaryType.True); - - Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); - Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); - Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary - } + FhirClient client = new FhirClient(testEndpoint); + client.ParserSettings.AllowUnrecognizedEnums = true; + + var entry = client.CapabilityStatement(); + + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + entry = client.CapabilityStatement(SummaryType.True); + + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary } @@ -116,90 +114,87 @@ public void VerifyFormatParamProcessing() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadWithFormat() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; + FhirClient client = new FhirClient(testEndpoint); - var loc = client.Read("Patient/example"); - Assert.IsNotNull(loc); - } + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; + + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Read() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.Read("Location/1"); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + FhirClient client = new FhirClient(testEndpoint); - Assert.AreEqual("1", loc.Id); - Assert.IsNotNull(loc.Meta.VersionId); + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); - var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc2); - Assert.AreEqual(loc2.Id, loc.Id); - Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); - try - { - var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); - Assert.AreEqual("404", client.LastResult.Status); - } + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); - var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc3); - var jsonSer = new FhirJsonSerializer(); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc3)); - - var loc4 = client.Read(loc.ResourceIdentity()); - Assert.IsNotNull(loc4); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc4)); + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } + + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); + + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadRelative() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.Read(new Uri("Location/1", UriKind.Relative)); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.Read(ri); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - } + FhirClient client = new FhirClient(testEndpoint); + + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] public void ReadRelativeAsync() { - using(FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.ReadAsync(ri).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - } - } + FhirClient client = new FhirClient(testEndpoint); + + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } #endif public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) @@ -208,7 +203,7 @@ public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestE { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip"; } } @@ -218,7 +213,7 @@ public static void Compression_OnBeforeRequestDeflate(object sender, BeforeReque { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "deflate"); + e.RawRequest.Headers["Accept-Encoding"] = "deflate"; } } @@ -228,102 +223,105 @@ public static void Compression_OnBeforeRequestZipOrDeflate(object sender, Before { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; } } [TestMethod, Ignore] // Something does not work with the gzip - [TestCategory("FhirClient"), + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Search() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - Bundle result; + FhirClient client = new FhirClient(testEndpoint); + Bundle result; - client.CompressRequestBody = true; - client.OnBeforeRequest += Compression_OnBeforeRequestGZip; + client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestGZip; + client.OnAfterResponse += Client_OnAfterResponse; - result = client.Search(); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.Search(); + client.OnAfterResponse -= Client_OnAfterResponse; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; + client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = withSubject.ResourceIdentity(); + ResourceIdentity ri = withSubject.ResourceIdentity(); - // TODO: The include on Grahame's server doesn't currently work - //result = client.SearchById(ri.Id, - // includes: new string[] { "DiagnosticReport:subject" }); - //Assert.IsNotNull(result); + // TODO: The include on Grahame's server doesn't currently work + //result = client.SearchById(ri.Id, + // includes: new string[] { "DiagnosticReport:subject" }); + //Assert.IsNotNull(result); - //Assert.AreEqual(2, result.Entry.Count); // should have subject too + //Assert.AreEqual(2, result.Entry.Count); // should have subject too - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(DiagnosticReport).GetCollectionName())); - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(Patient).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(DiagnosticReport).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(Patient).GetCollectionName())); - client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; - result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); + result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); - } + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); } + private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) + { + // Test that the response was compressed + Assert.AreEqual("gzip", e.RawResponse.Headers[HttpResponseHeader.ContentEncoding]); + } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] public void SearchAsync() { - using(FhirClient client = new FhirClient(testEndpoint)) - { - Bundle result; + FhirClient client = new FhirClient(testEndpoint); + Bundle result; - result = client.SearchAsync().Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - result = client.SearchAsync(pageSize: 10).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); - result = client.SearchByIdAsync(ri.Id, - includes: new string[] { "DiagnosticReport.subject" }).Result; - Assert.IsNotNull(result); + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); - Assert.AreEqual(2, result.Entry.Count); // should have subject too + Assert.AreEqual(2, result.Entry.Count); // should have subject too - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(DiagnosticReport).GetCollectionName())); - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(Patient).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); - result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); - } + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); } #endif @@ -331,69 +329,66 @@ public void SearchAsync() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Paging() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - } + FhirClient client = new FhirClient(testEndpoint); + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void PagingInJson() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.PreferredFormat = ResourceFormat.Json; - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - } + FhirClient client = new FhirClient(testEndpoint); + client.PreferredFormat = ResourceFormat.Json; + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); } @@ -401,39 +396,39 @@ public void PagingInJson() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateAndFullRepresentation() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + FhirClient client = new FhirClient(testEndpoint); + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - var pat = client.Read("Patient/glossy"); - ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); - pat.Id = null; - pat.Identifier.Clear(); - var patC = client.Create(pat); - Assert.IsNotNull(patC); + var pat = client.Read("Patient/glossy"); + ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); + pat.Id = null; + pat.Identifier.Clear(); + var patC = client.Create(pat); + Assert.IsNotNull(patC); - client.PreferredReturn = Prefer.ReturnMinimal; - patC = client.Create(pat); + client.PreferredReturn = Prefer.ReturnMinimal; + patC = client.Create(pat); - Assert.IsNull(patC); + Assert.IsNull(patC); - if (client.LastBody != null) - { - var returned = client.LastBodyAsResource; - Assert.IsTrue(returned is OperationOutcome); - } - - // Now validate this resource - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - Parameters p = new Parameters(); - // p.Add("mode", new FhirString("create")); - p.Add("resource", pat); - OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); - Assert.IsNotNull(ooI); + if (client.LastBody != null) + { + var returned = client.LastBodyAsResource; + Assert.IsTrue(returned is OperationOutcome); } + + // Now validate this resource + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + Parameters p = new Parameters(); + // p.Add("mode", new FhirString("create")); + p.Add("resource", pat); + OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); + Assert.IsNotNull(ooI); } + + private Uri createdTestPatientUrl = null; /// @@ -444,51 +439,49 @@ public void CreateAndFullRepresentation() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateEditDelete() { - using (FhirClient client = new FhirClient(testEndpoint)) - { + FhirClient client = new FhirClient(testEndpoint); - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - // client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + // client.CompressRequestBody = true; - var pat = client.Read("Patient/example"); - pat.Id = null; - pat.Identifier.Clear(); - pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); + var pat = client.Read("Patient/example"); + pat.Id = null; + pat.Identifier.Clear(); + pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); - var fe = client.Create(pat); // Create as we are not providing the ID to be used. - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.Meta.VersionId); - createdTestPatientUrl = fe.ResourceIdentity(); + var fe = client.Create(pat); // Create as we are not providing the ID to be used. + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.Meta.VersionId); + createdTestPatientUrl = fe.ResourceIdentity(); - fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.Update(fe); + fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.Update(fe); - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); - Assert.AreEqual(2, fe2.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); + Assert.AreEqual(2, fe2.Identifier.Count); - fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.Update(fe); - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Identifier.Count); + fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.Update(fe); + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Identifier.Count); - client.Delete(fe3); + client.Delete(fe3); - try - { - // Get most recent version - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); - Assert.AreEqual("410", client.LastResult.Status); - } + try + { + // Get most recent version + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.Fail(); + } + catch(FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); + Assert.AreEqual("410", client.LastResult.Status); } } @@ -497,23 +490,21 @@ public void CreateEditDelete() //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() { - using (FhirClient client = new FhirClient(testEndpoint)) + FhirClient client = new FhirClient(testEndpoint); + var observation = new Observation(); + observation.Status = ObservationStatus.Preliminary; + observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); + observation.Value = new SimpleQuantity() { - var observation = new Observation(); - observation.Status = ObservationStatus.Preliminary; - observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); - observation.Value = new SimpleQuantity() - { - System = "http://unitsofmeasure.org", - Value = 23, - Code = "mg", - Unit = "miligram" - }; - observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); - var fe = client.Create(observation); - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); - } + System = "http://unitsofmeasure.org", + Value = 23, + Code = "mg", + Unit = "miligram" + }; + observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); + var fe = client.Create(observation); + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); } #if NO_ASYNC_ANYMORE @@ -531,52 +522,50 @@ public void CreateEditDeleteAsync() Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } }; - using(FhirClient client = new FhirClient(testEndpoint)) - { - var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; + FhirClient client = new FhirClient(testEndpoint); + var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; - var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; + var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; - Assert.IsNotNull(furore); - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.SelfLink); - Assert.AreNotEqual(fe.Id, fe.SelfLink); - Assert.IsNotNull(fe.Tags); - Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); - Assert.AreEqual(fe.Tags.First(), tags[0]); - createdTestOrganizationUrl = fe.Id; + Assert.IsNotNull(furore); + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.SelfLink); + Assert.AreNotEqual(fe.Id, fe.SelfLink); + Assert.IsNotNull(fe.Tags); + Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); + Assert.AreEqual(fe.Tags.First(), tags[0]); + createdTestOrganizationUrl = fe.Id; - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.UpdateAsync(fe, refresh: true).Result; + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.UpdateAsync(fe, refresh: true).Result; - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); - Assert.AreEqual(2, fe2.Resource.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); + Assert.AreEqual(2, fe2.Resource.Identifier.Count); - Assert.IsNotNull(fe2.Tags); - Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); - Assert.AreEqual(fe2.Tags.First(), tags[0]); + Assert.IsNotNull(fe2.Tags); + Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); + Assert.AreEqual(fe2.Tags.First(), tags[0]); - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Resource.Identifier.Count); + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Resource.Identifier.Count); - client.DeleteAsync(fe3).Wait(); + client.DeleteAsync(fe3).Wait(); - try - { - // Get most recent version - fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; - Assert.Fail(); - } - catch - { - Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); - } - } + try + { + // Get most recent version + fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; + Assert.Fail(); + } + catch + { + Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); + } } #endif @@ -584,7 +573,7 @@ public void CreateEditDeleteAsync() /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? public void History() { System.Threading.Thread.Sleep(500); @@ -592,51 +581,49 @@ public void History() CreateEditDelete(); // this test does a create, update, update, delete (4 operations) - using (FhirClient client = new FhirClient(testEndpoint)) - { + FhirClient client = new FhirClient(testEndpoint); - System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); + System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); - Bundle history = client.History(createdTestPatientUrl); - Assert.IsNotNull(history); - DebugDumpBundle(history); + Bundle history = client.History(createdTestPatientUrl); + Assert.IsNotNull(history); + DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - //// Now, assume no one is quick enough to insert something between now and the next - //// tests.... + //// Now, assume no one is quick enough to insert something between now and the next + //// tests.... - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); - history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); + history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); + history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); - history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); + if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) + { + System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); + history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); Assert.IsNotNull(history); DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - - if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) - { - System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); - history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); - // Check that the number of patients that have been created is what we expected - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); - } + Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); + // Check that the number of patients that have been created is what we expected + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); } } @@ -645,106 +632,102 @@ public void History() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestWithParam() { - using (var client = new FhirClient(testEndpoint)) - { - var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); - Assert.IsNotNull(res); - } + var client = new FhirClient(testEndpoint); + var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); + Assert.IsNotNull(res); } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ManipulateMeta() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - - var pat = new Patient(); - pat.Meta = new Meta(); - var key = new Random().Next(); - pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); - pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); + FhirClient client = new FhirClient(testEndpoint); - //Before we begin, ensure that our new tags are not actually used when doing System Meta() - var wsm = client.Meta(); - Assert.IsNotNull(wsm); + var pat = new Patient(); + pat.Meta = new Meta(); + var key = new Random().Next(); + pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); + pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); + Assert.IsNotNull(wsm); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - // First, create a patient with the first set of meta - var pat2 = client.Create(pat); - var loc = pat2.ResourceIdentity(testEndpoint); - // Meta should be present on created patient - verifyMeta(pat2.Meta, false, key); + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); - // Should be present when doing instance Meta() - var par = client.Meta(loc); - verifyMeta(par, false, key); + // Meta should be present on created patient + verifyMeta(pat2.Meta, false, key); - // Should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should be present when doing instance Meta() + var par = client.Meta(loc); + verifyMeta(par, false, key); - // Should be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // Now add some additional meta to the patient + // Should be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); - var newMeta = new Meta(); - newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); - newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); + // Now add some additional meta to the patient + var newMeta = new Meta(); + newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - client.AddMeta(loc, newMeta); - var pat3 = client.Read(loc); + + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); - // New and old meta should be present on instance - verifyMeta(pat3.Meta, true, key); + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); - // New and old meta should be present on Meta() - par = client.Meta(loc); - verifyMeta(par, true, key); + // New and old meta should be present on Meta() + par = client.Meta(loc); + verifyMeta(par, true, key); - // New and old meta should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, true, key); + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, true, key); - // New and old meta should be present when doing system Meta() - par = client.Meta(); - verifyMeta(par, true, key); + // New and old meta should be present when doing system Meta() + par = client.Meta(); + verifyMeta(par, true, key); - // Now, remove those new meta tags - client.DeleteMeta(loc, newMeta); + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); - // Should no longer be present on instance - var pat4 = client.Read(loc); - verifyMeta(pat4.Meta, false, key); + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); - // Should no longer be present when doing instance Meta() - par = client.Meta(loc); - verifyMeta(par, false, key); + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); + verifyMeta(par, false, key); - // Should no longer be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // clear out the client that we created, no point keeping it around - client.Delete(pat4); + // clear out the client that we created, no point keeping it around + client.Delete(pat4); - // Should no longer be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); - } + // Should no longer be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); } @@ -774,13 +757,12 @@ private void verifyMeta(Meta meta, bool hasNew, int key) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestSearchByPersonaCode() { - using (var client = new FhirClient(testEndpoint)) - { - var pats = - client.Search( - new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); - var pat = (Patient)pats.Entry.First().Resource; - } + var client = new FhirClient(testEndpoint); + + var pats = + client.Search( + new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); + var pat = (Patient)pats.Entry.First().Resource; } @@ -792,63 +774,59 @@ public void CreateDynamic() { Name = "Furore", Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { + Telecom = new List { new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, - new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } } }; - using (FhirClient client = new FhirClient(testEndpoint)) - { - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); + FhirClient client = new FhirClient(testEndpoint); + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); - var fe = client.Create(furore); - Assert.IsNotNull(fe); - } + var fe = client.Create(furore); + Assert.IsNotNull(fe); } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CallsCallbacks() { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.ParserSettings.AllowUnrecognizedEnums = true; + FhirClient client = new FhirClient(testEndpoint); + client.ParserSettings.AllowUnrecognizedEnums = true; - bool calledBefore = false; - HttpStatusCode? status = null; - byte[] body = null; - byte[] bodyOut = null; + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; - client.OnBeforeRequest += (sender, e) => - { - calledBefore = true; - bodyOut = e.Body; - }; + client.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; - client.OnAfterResponse += (sender, e) => - { - body = e.Body; - status = e.RawResponse.StatusCode; - }; + client.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; - var pat = client.Read("Patient/glossy"); - Assert.IsTrue(calledBefore); - Assert.IsNotNull(status); - Assert.IsNotNull(body); + var pat = client.Read("Patient/glossy"); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); - var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); + var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); - Assert.IsTrue(bodyText.Contains("("Patient/glossy"); - Assert.IsNotNull(result); - result.Id = null; - result.Meta = null; - - client.PreferredReturn = Prefer.ReturnRepresentation; - var posted = client.Create(result); - Assert.IsNotNull(posted, "Patient example not found"); - - posted = client.Create(result); - Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); - - client.PreferredReturn = Prefer.ReturnMinimal; - posted = client.Create(result); - Assert.IsNull(posted); - } + var client = new FhirClient(testEndpoint); + var minimal = false; + client.OnBeforeRequest += (object s, BeforeRequestEventArgs e) => e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; + + var result = client.Read("Patient/glossy"); + Assert.IsNotNull(result); + result.Id = null; + result.Meta = null; + + client.PreferredReturn = Prefer.ReturnRepresentation; + minimal = false; + var posted = client.Create(result); + Assert.IsNotNull(posted, "Patient example not found"); + + minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true + posted = client.Create(result); + Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); + + client.PreferredReturn = Prefer.ReturnMinimal; + minimal = true; + posted = client.Create(result); + Assert.IsNull(posted); } void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) @@ -902,80 +883,76 @@ void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. public void TestReceiveHtmlIsHandled() { - using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns html + var client = new FhirClient("http://spark.furore.com/"); // an address that returns html + + try { - try - { - var pat = client.Read("Patient/1"); - } - catch (FhirOperationException fe) - { - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); - } + var pat = client.Read("Patient/1"); + } + catch (FhirOperationException fe) + { + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + } } - } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestRefresh() { - using (var client = new FhirClient(testEndpoint)) - { - var result = client.Read("Patient/example"); + var client = new FhirClient(testEndpoint); + var result = client.Read("Patient/example"); - var orig = result.Name[0].FamilyElement.Value; + var orig = result.Name[0].FamilyElement.Value; - result.Name[0].FamilyElement.Value = "overwritten name"; + result.Name[0].FamilyElement.Value = "overwritten name"; - result = client.Refresh(result); + result = client.Refresh(result); - Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); - } + Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithHtmlIsHandled() { - using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns Status 500 with HTML in its body + var client = new FhirClient("http://spark.furore.com/"); // an address that returns Status 500 with HTML in its body + + try { - try - { - var pat = client.Read("Patient/1"); - Assert.Fail("Failed to throw an Exception on status 500"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.InternalServerError) - Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); + var pat = client.Read("Patient/1"); + Assert.Fail("Failed to throw an Exception on status 500"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.InternalServerError) + Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - if (client.LastResult.Status != "500") - Assert.Fail("LastResult.Status is not 500."); + if (client.LastResult.Status != "500") + Assert.Fail("LastResult.Status is not 500."); - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - string message = operationOutcome.Issue[0].Diagnostics; - if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to carry error message over into OperationOutcome"); - } - catch (Exception) - { - Assert.Fail("Failed to throw FhirOperationException on status 500"); - } + string message = operationOutcome.Issue[0].Diagnostics; + if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to carry error message over into OperationOutcome"); + } + catch (Exception) + { + Assert.Fail("Failed to throw FhirOperationException on status 500"); } } @@ -984,45 +961,44 @@ public void TestReceiveErrorStatusWithHtmlIsHandled() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() { - using (var client = new FhirClient("http://test.fhir.org/r3")) // an address that returns Status 404 with an OperationOutcome + var client = new FhirClient("http://test.fhir.org/r3"); // an address that returns Status 404 with an OperationOutcome + + try { - try - { - var pat = client.Read("Patient/doesnotexist"); - Assert.Fail("Failed to throw an Exception on status 404"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.NotFound) - Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); + var pat = client.Read("Patient/doesnotexist"); + Assert.Fail("Failed to throw an Exception on status 404"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.NotFound) + Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - Bundle.ResponseComponent entryComponent = client.LastResult; + Bundle.ResponseComponent entryComponent = client.LastResult; - if (entryComponent.Status != "404") - Assert.Fail("LastResult.Status is not 404."); + if (entryComponent.Status != "404") + Assert.Fail("LastResult.Status is not 404."); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - } - catch (Exception e) - { - Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); - } + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + } + catch (Exception e) + { + Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); } } - [TestMethod, Ignore] + [TestMethod,Ignore] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FhirVersionIsChecked() { @@ -1032,100 +1008,100 @@ public void FhirVersionIsChecked() var testEndpointDSTU22 = new Uri("http://fhirtest.uhn.ca/baseDstu2"); var testEndpointDSTU23 = new Uri("http://test.fhir.org/r3"); - using(var clientDSTU1 = new FhirClient(testEndpointDSTU1)) - using(var clientDSTU12 = new FhirClient(testEndpointDSTU12)) - using(var clientVerifiedDSTU23 = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true)) - using(var clientDSTU23 = new FhirClient(testEndpointDSTU23)) - { - clientDSTU1.ParserSettings.AllowUnrecognizedEnums = true; + var client = new FhirClient(testEndpointDSTU1); + client.ParserSettings.AllowUnrecognizedEnums = true; - CapabilityStatement p; + CapabilityStatement p; - try - { - clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; - p = clientDSTU23.CapabilityStatement(); - } - catch (FhirOperationException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } - catch (NotSupportedException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } + try + { + client = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true); + client.ParserSettings.AllowUnrecognizedEnums = true; + p = client.CapabilityStatement(); + } + catch (FhirOperationException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } + catch (NotSupportedException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } - clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; - p = clientDSTU23.CapabilityStatement(); + client = new FhirClient(testEndpointDSTU23); + client.ParserSettings.AllowUnrecognizedEnums = true; + p = client.CapabilityStatement(); - //client = new FhirClient(testEndpointDSTU2); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU2); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); - //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); - clientDSTU12.ParserSettings.AllowUnrecognizedEnums = true; - try - { - p = clientDSTU12.CapabilityStatement(); - Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); - } - catch (Exception) - { - // OK - } + client = new FhirClient(testEndpointDSTU12); + client.ParserSettings.AllowUnrecognizedEnums = true; + + try + { + p = client.CapabilityStatement(); + Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); + } + catch (Exception) + { + // OK } + } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestAuthenticationOnBefore() { - using (FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir")) + FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); + validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => { - validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => - { - e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); - }; - try - { - var output = validationFhirClient.ValidateResource(new Patient()); + e.RawRequest.Headers["Authorization"] = "Bearer bad-bearer"; + }; + try + { + var output = validationFhirClient.ValidateResource(new Patient()); - } - catch (FhirOperationException ex) - { - Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); - } + } + catch(FhirOperationException ex) + { + Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); } } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestOperationEverything() { - using (FhirClient client = new FhirClient(testEndpoint)) + FhirClient client = new FhirClient(testEndpoint) { - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; + UseFormatParam = true, + PreferredFormat = ResourceFormat.Json + }; - // GET operation $everything without parameters - var loc = client.TypeOperation("everything", null, true); - Assert.IsNotNull(loc); + // GET operation $everything without parameters + var loc = client.TypeOperation("everything", null, true); + Assert.IsNotNull(loc); - // POST operation $everything without parameters - loc = client.TypeOperation("everything", null, false); - Assert.IsNotNull(loc); + // POST operation $everything without parameters + loc = client.TypeOperation("everything", null, false); + Assert.IsNotNull(loc); - // GET operation $everything with 1 parameter - // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. - // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); - // Assert.IsNotNull(loc); + // GET operation $everything with 1 parameter + // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. + // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); + // Assert.IsNotNull(loc); - // POST operation $everything with 1 parameter - loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); - Assert.IsNotNull(loc); - } + // POST operation $everything with 1 parameter + loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); + Assert.IsNotNull(loc); } + } + } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs index aa3763eb3b..ee03c198e4 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs @@ -23,32 +23,28 @@ public class OperationsTests { string testEndpoint = FhirClientTests.testEndpoint.OriginalString; - [TestMethod] + [TestMethod] [TestCategory("IntegrationTest")] public void InvokeTestPatientGetEverything() { - using (var client = new FhirClient(testEndpoint)) - { - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2015, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); - var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); - Assert.IsTrue(bundle.Entry.Any()); - - var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient", "example"), start, end); - Assert.IsTrue(bundle2.Entry.Any()); - } + var client = new FhirClient(testEndpoint); + var start = new FhirDateTime(2014,11,1); + var end = new FhirDateTime(2015,1,1); + var par = new Parameters().Add("start", start).Add("end", end); + var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient","example"), start, end); + Assert.IsTrue(bundle2.Entry.Any()); } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeExpandExistingValueSet() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet", "administrative-gender")); - Assert.IsTrue(vs.Expansion.Contains.Any()); - } + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet","administrative-gender")); + Assert.IsTrue(vs.Expansion.Contains.Any()); } @@ -57,14 +53,12 @@ public void InvokeExpandExistingValueSet() [TestCategory("IntegrationTest")] public void InvokeExpandParameterValueSet() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var vs = client.Read("ValueSet/administrative-gender"); - var vsX = client.ExpandValueSet(vs); + var vs = client.Read("ValueSet/administrative-gender"); + var vsX = client.ExpandValueSet(vs); - Assert.IsTrue(vsX.Expansion.Contains.Any()); - } + Assert.IsTrue(vsX.Expansion.Contains.Any()); } // [WMR 20170927] Chris Munro @@ -87,71 +81,61 @@ public void InvokeExpandParameterValueSet() [TestCategory("IntegrationTest")] public void InvokeLookupCoding() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); - var expansion = client.ConceptLookup(coding: coding); + var expansion = client.ConceptLookup(coding: coding); - // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); - } + // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); } [TestMethod] // Server returns internal server error [TestCategory("IntegrationTest")] public void InvokeLookupCode() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); - //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); - } + //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeById() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); - } + var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeByCanonical() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), - coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); - } + var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), + coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeWithVS() { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); - var vs = client.Read("ValueSet/c80-facilitycodes"); - Assert.IsNotNull(vs); + var vs = client.Read("ValueSet/c80-facilitycodes"); + Assert.IsNotNull(vs); - var result = client.ValidateCode(valueSet: vs, coding: coding); - Assert.IsTrue(result.Result?.Value == true); - } + var result = client.ValidateCode(valueSet: vs, coding: coding); + Assert.IsTrue(result.Result?.Value == true); } @@ -159,22 +143,20 @@ public void InvokeValidateCodeWithVS() [TestCategory("IntegrationTest"), Ignore] public void InvokeResourceValidation() { - using (var client = new FhirClient(testEndpoint)) - { + var client = new FhirClient(testEndpoint); - var pat = client.Read("Patient/patient-uslab-example1"); - - try - { - var vresult = client.ValidateResource(pat, null, - new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); - Assert.Fail("Should have resulted in 400"); - } - catch (FhirOperationException fe) - { - Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); - Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); - } + var pat = client.Read("Patient/patient-uslab-example1"); + + try + { + var vresult = client.ValidateResource(pat, null, + new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); + Assert.Fail("Should have resulted in 400"); + } + catch(FhirOperationException fe) + { + Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); + Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); } } @@ -184,23 +166,21 @@ public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() { string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - using (var client = new FhirClient(_endpoint)) - { - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2020, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); + var client = new FhirClient(_endpoint); + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2020, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); - var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); - var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); + var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); + var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); - await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); + await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); - var bundle = (Bundle)bundleTask.Result; - Assert.IsTrue(bundle.Entry.Any()); + var bundle = (Bundle)bundleTask.Result; + Assert.IsTrue(bundle.Entry.Any()); - var bundle2 = (Bundle)bundle2Task.Result; - Assert.IsTrue(bundle2.Entry.Any()); - } + var bundle2 = (Bundle)bundle2Task.Result; + Assert.IsTrue(bundle2.Entry.Any()); } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs index ce2b6311d3..94fac8a41e 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs @@ -16,40 +16,36 @@ public class ReadAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { - - Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); - } + }; + + Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; - Patient p = await client.ReadAsync("/Patient/SMART-1288992"); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); - } + Patient p = await client.ReadAsync("/Patient/SMART-1288992"); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs index 6d29d8b941..f3e9def4e8 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs @@ -12,7 +12,6 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; -using System.Linq; namespace Hl7.Fhir.Test { @@ -44,30 +43,31 @@ public void TestPreferSetting() var tx = new TransactionBuilder("http://myserver.org/fhir") .Create(p); var b = tx.ToBundle(); + byte[] dummy; - var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); - Assert.AreEqual("return=minimal", request.Headers.GetValues("Prefer").FirstOrDefault()); + var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=minimal", request.Headers["Prefer"]); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.AreEqual("return=representation", request.Headers.GetValues("Prefer").FirstOrDefault()); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=representation", request.Headers["Prefer"]); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false); - Assert.AreEqual("return=OperationOutcome", request.Headers.GetValues("Prefer").FirstOrDefault()); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=OperationOutcome", request.Headers["Prefer"]); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false); - Assert.IsFalse(request.Headers.TryGetValues("Prefer", out var headerValues)); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false, out dummy); + Assert.IsNull(request.Headers["Prefer"]); tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); b = tx.ToBundle(); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); - Assert.AreEqual("handling=lenient", request.Headers.GetValues("Prefer").FirstOrDefault()); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("handling=lenient", request.Headers["Prefer"]); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.AreEqual("handling=strict", request.Headers.GetValues("Prefer").FirstOrDefault()); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("handling=strict", request.Headers["Prefer"]); - request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.IsFalse(request.Headers.TryGetValues("Prefer", out headerValues)); + request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.IsNull(request.Headers["Prefer"]); } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs index c2412dbb3f..c1f98dc942 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs @@ -16,73 +16,69 @@ public class SearchAsyncTests [TestCategory("IntegrationTest")] public async Task Search_UsingSearchParams_SearchReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = await client.SearchAsync(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + var result1 = await client.SearchAsync(srch); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) + while (result1 != null) + { + foreach (var e in result1.Entry) { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); } - - Console.WriteLine("Test Completed"); + result1 = client.Continue(result1, PageDirection.Next); } + + Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public void SearchSync_UsingSearchParams_SearchReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = client.Search(srch); + var result1 = client.Search(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) + while (result1 != null) + { + foreach (var e in result1.Entry) { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); } - - Console.WriteLine("Test Completed"); + result1 = client.Continue(result1, PageDirection.Next); } + + Console.WriteLine("Test Completed"); } @@ -90,102 +86,97 @@ public void SearchSync_UsingSearchParams_SearchReturned() [TestCategory("IntegrationTest")] public async Task SearchMultiple_UsingSearchParams_SearchReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) + }; + + var srchParams = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var task1 = client.SearchAsync(srchParams); + var task2 = client.SearchAsync(srchParams); + var task3 = client.SearchAsync(srchParams); + + await Task.WhenAll(task1, task2, task3); + var result1 = task1.Result; + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) { - var srchParams = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var task1 = client.SearchAsync(srchParams); - var task2 = client.SearchAsync(srchParams); - var task3 = client.SearchAsync(srchParams); - - await Task.WhenAll(task1, task2, task3); - var result1 = task1.Result; - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) + foreach (var e in result1.Entry) { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); } - - Console.WriteLine("Test Completed"); + result1 = client.Continue(result1, PageDirection.Next); } + + Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_SyncContinue_SearchReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; + + var result1 = await client.SearchAsync(new []{"family=clark"}); - var result1 = await client.SearchAsync(new[] { "family=clark" }); + Assert.IsTrue(result1.Entry.Count >= 1); - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) + while (result1 != null) + { + foreach (var e in result1.Entry) { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); } - - Console.WriteLine("Test Completed"); + result1 = client.Continue(result1, PageDirection.Next); } + + Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_AsyncContinue_SearchReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; - var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); + var result1 = await client.SearchAsync(new[] { "family=clark" },null,1); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) + while (result1 != null) + { + foreach (var e in result1.Entry) { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - Console.WriteLine("Fetching more results..."); - result1 = await client.ContinueAsync(result1); + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); } - - Console.WriteLine("Test Completed"); + Console.WriteLine("Fetching more results..."); + result1 = await client.ContinueAsync(result1); } + + Console.WriteLine("Test Completed"); } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs index 8bb2d9dda1..29c6f77265 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/TransactionBuilderTests.cs @@ -51,7 +51,9 @@ public void TestUrlEncoding() tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); var b = tx.ToBundle(); - var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); + byte[] body; + + var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false, body: out body); Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs index da3339bd4b..0b0d811413 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs @@ -16,16 +16,15 @@ public class UpdateRefreshDeleteAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() { - using (var client = new FhirClient(_endpoint) + var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }) - { + }; - var pat = new Patient() - { - Name = new List() + var pat = new Patient() + { + Name = new List() { new HumanName() { @@ -33,33 +32,33 @@ public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_Resu Family = "test_family", } }, - Id = "async-test-patient" - }; - // Create the patient - Console.WriteLine("Creating patient..."); - Patient p = await client.UpdateAsync(pat); - Assert.IsNotNull(p); + Id = "async-test-patient" + }; + // Create the patient + Console.WriteLine("Creating patient..."); + Patient p = await client.UpdateAsync(pat); + Assert.IsNotNull(p); - // Refresh the patient - Console.WriteLine("Refreshing patient..."); - await client.RefreshAsync(p); + // Refresh the patient + Console.WriteLine("Refreshing patient..."); + await client.RefreshAsync(p); - // Delete the patient - Console.WriteLine("Deleting patient..."); - await client.DeleteAsync(p); - - Console.WriteLine("Reading patient..."); - Func act = async () => - { - await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); - }; - - // VERIFY // - Assert.ThrowsException(act, "the patient is no longer on the server"); + // Delete the patient + Console.WriteLine("Deleting patient..."); + await client.DeleteAsync(p); + Console.WriteLine("Reading patient..."); + Func act = async () => + { + await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); + }; - Console.WriteLine("Test Completed"); - } + // VERIFY // + Assert.ThrowsException(act, "the patient is no longer on the server"); + + + Console.WriteLine("Test Completed"); } + } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs index cd3db407b3..3f68c44eb6 100644 --- a/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/EntryToHttpExtensions.cs @@ -1,28 +1,29 @@ -/* +/* * Copyright (c) 2014, Furore (info@furore.com) and contributors * See the file CONTRIBUTORS for details. - * + * * This file is licensed under the BSD 3-Clause license * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE */ using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using System; +using System.Net; +using System.Reflection; using Hl7.Fhir.Utility; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Linq; namespace Hl7.Fhir.Rest { internal static class EntryToHttpExtensions { - public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, - SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) + public static HttpWebRequest ToHttpRequest(this Bundle.EntryComponent entry, + SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody, out byte[] body) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); var interaction = entry.Request; + body = null; if (entry.Resource != null && !(interaction.Method == Bundle.HTTPVerb.POST || interaction.Method == Bundle.HTTPVerb.PUT)) throw Error.InvalidOperation("Cannot have a body on an Http " + interaction.Method.ToString()); @@ -32,64 +33,91 @@ public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, if (useFormatParameter) location.AddParam(HttpUtil.RESTPARAM_FORMAT, Hl7.Fhir.Rest.ContentType.BuildFormatParam(format)); - var request = new HttpRequestMessage(getMethod(interaction.Method), location.Uri); + var request = (HttpWebRequest)HttpWebRequest.Create(location.Uri); + request.Method = interaction.Method.ToString(); + setAgent(request, ".NET FhirClient for FHIR " + Model.ModelInfo.Version); if (!useFormatParameter) - request.Headers.Add("Accept", Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false)); + request.Accept = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); - if (interaction.IfMatch != null) request.Headers.TryAddWithoutValidation("If-Match", interaction.IfMatch); - if (interaction.IfNoneMatch != null) request.Headers.TryAddWithoutValidation("If-None-Match", interaction.IfNoneMatch); - if (interaction.IfModifiedSince != null) request.Headers.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; - if (interaction.IfNoneExist != null) request.Headers.TryAddWithoutValidation("If-None-Exist", interaction.IfNoneExist); + if (interaction.IfMatch != null) request.Headers["If-Match"] = interaction.IfMatch; + if (interaction.IfNoneMatch != null) request.Headers["If-None-Match"] = interaction.IfNoneMatch; +#if DOTNETFW + if (interaction.IfModifiedSince != null) request.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; +#else + if (interaction.IfModifiedSince != null) request.Headers["If-Modified-Since"] = interaction.IfModifiedSince.Value.UtcDateTime.ToString(); +#endif + if (interaction.IfNoneExist != null) request.Headers["If-None-Exist"] = interaction.IfNoneExist; var interactionType = entry.Annotation(); if (interactionType == TransactionBuilder.InteractionType.Create && returnPreference != null) - request.Headers.TryAddWithoutValidation("Prefer", "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference)); - else if (interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) - request.Headers.TryAddWithoutValidation("Prefer", "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference)); + request.Headers["Prefer"] = "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference); + else if(interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) + request.Headers["Prefer"] = "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference); if (entry.Resource != null) - setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); - + setBodyAndContentType(request, entry.Resource, format, CompressRequestBody, out body); + // PCL doesn't support setting the length (and in this case will be empty anyway) +#if DOTNETFW + else + request.ContentLength = 0; +#endif return request; } /// - /// Converts bundle http verb to corresponding . + /// Flag to control the setting of the User Agent string (different platforms have different abilities) /// - /// specified by input bundle. - /// corresponding to verb specified in input bundle. - private static HttpMethod getMethod(Bundle.HTTPVerb? verb) + public static bool SetUserAgentUsingReflection = true; + public static bool SetUserAgentUsingDirectHeaderManipulation = true; + + private static void setAgent(HttpWebRequest request, string agent) { - switch(verb) + bool userAgentSet = false; + if (SetUserAgentUsingReflection) + { + try + { + System.Reflection.PropertyInfo prop = request.GetType().GetRuntimeProperty("UserAgent"); + + if (prop != null) + prop.SetValue(request, agent, null); + userAgentSet = true; + } + catch (Exception) + { + // This approach doesn't work on this platform, so don't try it again. + SetUserAgentUsingReflection = false; + } + } + if (!userAgentSet && SetUserAgentUsingDirectHeaderManipulation) { - case Bundle.HTTPVerb.GET: - return HttpMethod.Get; - case Bundle.HTTPVerb.POST: - return HttpMethod.Post; - case Bundle.HTTPVerb.PUT: - return HttpMethod.Put; - case Bundle.HTTPVerb.DELETE: - return HttpMethod.Delete; + // platform does not support UserAgent property...too bad + try + { + request.Headers[HttpRequestHeader.UserAgent] = agent; + } + catch (ArgumentException) + { + SetUserAgentUsingDirectHeaderManipulation = false; + } } - throw new HttpRequestException($"Valid HttpVerb could not be found for verb type: [{verb}]"); } - private static void setBodyAndContentType(HttpRequestMessage request, Resource data, ResourceFormat format, bool CompressRequestBody) + + private static void setBodyAndContentType(HttpWebRequest request, Resource data, ResourceFormat format, bool CompressRequestBody, out byte[] body) { if (data == null) throw Error.ArgumentNull(nameof(data)); - byte[] body; - string contentType; - - if (data is Binary bin) + if (data is Binary) { + var bin = (Binary)data; body = bin.Content; // This is done by the caller after the OnBeforeRequest is called so that other properties // can be set before the content is committed // request.WriteBody(CompressRequestBody, bin.Content); - contentType = bin.ContentType; + request.ContentType = bin.ContentType; } else { @@ -100,17 +128,10 @@ private static void setBodyAndContentType(HttpRequestMessage request, Resource d // This is done by the caller after the OnBeforeRequest is called so that other properties // can be set before the content is committed // request.WriteBody(CompressRequestBody, body); - contentType = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); + request.ContentType = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); } - - request.Content = new ByteArrayContent(body); - - // MediaTypeHeaderValue cannot accept a content type that contains charset at the end, so that value must be split out. - var contentTypeList = contentType.Split(';'); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentTypeList.FirstOrDefault()); - request.Content.Headers.ContentType.CharSet = System.Text.Encoding.UTF8.WebName; } - + } } diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index f84041a4e4..6be14d0664 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -15,7 +15,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; @@ -191,14 +190,14 @@ public ParserSettings ParserSettings /// /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient /// - public HttpRequestMessage LastRequest { get { return _requester.LastRequest; } } + public HttpWebRequest LastRequest { get { return _requester.LastRequest; } } /// /// Returns the HttpWebResponse as it was last received during a call on the FhirClient /// /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpResponseMessage LastResponse { get { return _requester.LastResponse; } } + public HttpWebResponse LastResponse { get { return _requester.LastResponse; } } /// /// The default endpoint for use with operations that use discrete id/version parameters @@ -1025,22 +1024,22 @@ private void setResourceBase(Resource resource, string baseUri) public event EventHandler OnAfterResponse; /// - /// Inspect or modify the just before the FhirClient issues a call to the server + /// Inspect or modify the HttpWebRequest just before the FhirClient issues a call to the server /// /// The request as it is about to be sent to the server /// The data in the body of the request as it is about to be sent to the server - protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) + protected virtual void BeforeRequest(HttpWebRequest rawRequest, byte[] body) { // Default implementation: call event OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); } /// - /// Inspect the as it came back from the server + /// Inspect the HttpWebResponse as it came back from the server /// - /// You cannot read the body from the HttpResponseMessage, since it has + /// You cannot read the body from the HttpWebResponse, since it has /// already been read by the framework. Use the body parameter instead. - protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) + protected virtual void AfterResponse(HttpWebResponse webResponse, byte[] body) { // Default implementation: call event OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); @@ -1138,52 +1137,30 @@ private void verifyServerVersion() throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); } } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - this._requester.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - } - #endregion - } + } public class BeforeRequestEventArgs : EventArgs { - public BeforeRequestEventArgs(HttpRequestMessage rawRequest, byte[] body) + public BeforeRequestEventArgs(HttpWebRequest rawRequest, byte[] body) { this.RawRequest = rawRequest; this.Body = body; } - public HttpRequestMessage RawRequest { get; internal set; } + public HttpWebRequest RawRequest { get; internal set; } public byte[] Body { get; internal set; } } public class AfterResponseEventArgs : EventArgs { - public AfterResponseEventArgs(HttpResponseMessage webResponse, byte[] body) + public AfterResponseEventArgs(HttpWebResponse webResponse, byte[] body) { this.RawResponse = webResponse; this.Body = body; } - public HttpResponseMessage RawResponse { get; internal set; } + public HttpWebResponse RawResponse { get; internal set; } public byte[] Body { get; internal set; } } } diff --git a/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs new file mode 100644 index 0000000000..ec532047dc --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Linq; + +namespace Hl7.Fhir.Rest.Http +{ + internal static class EntryToHttpExtensions + { + public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, + SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) + { + System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); + + var interaction = entry.Request; + + if (entry.Resource != null && !(interaction.Method == Bundle.HTTPVerb.POST || interaction.Method == Bundle.HTTPVerb.PUT)) + throw Error.InvalidOperation("Cannot have a body on an Http " + interaction.Method.ToString()); + + var location = new RestUrl(interaction.Url); + + if (useFormatParameter) + location.AddParam(HttpUtil.RESTPARAM_FORMAT, Hl7.Fhir.Rest.ContentType.BuildFormatParam(format)); + + var request = new HttpRequestMessage(getMethod(interaction.Method), location.Uri); + + if (!useFormatParameter) + request.Headers.Add("Accept", Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false)); + + if (interaction.IfMatch != null) request.Headers.TryAddWithoutValidation("If-Match", interaction.IfMatch); + if (interaction.IfNoneMatch != null) request.Headers.TryAddWithoutValidation("If-None-Match", interaction.IfNoneMatch); + if (interaction.IfModifiedSince != null) request.Headers.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; + if (interaction.IfNoneExist != null) request.Headers.TryAddWithoutValidation("If-None-Exist", interaction.IfNoneExist); + + var interactionType = entry.Annotation(); + + if (interactionType == TransactionBuilder.InteractionType.Create && returnPreference != null) + request.Headers.TryAddWithoutValidation("Prefer", "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference)); + else if (interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) + request.Headers.TryAddWithoutValidation("Prefer", "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference)); + + if (entry.Resource != null) + setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); + + return request; + } + + /// + /// Converts bundle http verb to corresponding . + /// + /// specified by input bundle. + /// corresponding to verb specified in input bundle. + private static HttpMethod getMethod(Bundle.HTTPVerb? verb) + { + switch(verb) + { + case Bundle.HTTPVerb.GET: + return HttpMethod.Get; + case Bundle.HTTPVerb.POST: + return HttpMethod.Post; + case Bundle.HTTPVerb.PUT: + return HttpMethod.Put; + case Bundle.HTTPVerb.DELETE: + return HttpMethod.Delete; + } + throw new HttpRequestException($"Valid HttpVerb could not be found for verb type: [{verb}]"); + } + + private static void setBodyAndContentType(HttpRequestMessage request, Resource data, ResourceFormat format, bool CompressRequestBody) + { + if (data == null) throw Error.ArgumentNull(nameof(data)); + + byte[] body; + string contentType; + + if (data is Binary bin) + { + body = bin.Content; + // This is done by the caller after the OnBeforeRequest is called so that other properties + // can be set before the content is committed + // request.WriteBody(CompressRequestBody, bin.Content); + contentType = bin.ContentType; + } + else + { + body = format == ResourceFormat.Xml ? + new FhirXmlSerializer().SerializeToBytes(data, summary: Fhir.Rest.SummaryType.False) : + new FhirJsonSerializer().SerializeToBytes(data, summary: Fhir.Rest.SummaryType.False); + + // This is done by the caller after the OnBeforeRequest is called so that other properties + // can be set before the content is committed + // request.WriteBody(CompressRequestBody, body); + contentType = Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false); + } + + request.Content = new ByteArrayContent(body); + + // MediaTypeHeaderValue cannot accept a content type that contains charset at the end, so that value must be split out. + var contentTypeList = contentType.Split(';'); + request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentTypeList.FirstOrDefault()); + request.Content.Headers.ContentType.CharSet = System.Text.Encoding.UTF8.WebName; + } + + + } +} diff --git a/src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs similarity index 97% rename from src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs rename to src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index 7a5e6531ce..0986f610fb 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirHttpClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -19,9 +19,9 @@ using System.Threading.Tasks; -namespace Hl7.Fhir.Rest +namespace Hl7.Fhir.Rest.Http { - public partial class FhirHttpClient : IFhirClient + public partial class FhirClient : IFhirCompatibleClient { private Requester _requester; @@ -38,7 +38,7 @@ public partial class FhirHttpClient : IFhirClient /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirHttpClient(Uri endpoint, bool verifyFhirVersion = false) + public FhirClient(Uri endpoint, bool verifyFhirVersion = false) { if (endpoint == null) throw new ArgumentNullException("endpoint"); @@ -51,8 +51,7 @@ public FhirHttpClient(Uri endpoint, bool verifyFhirVersion = false) _requester = new Requester(Endpoint) { - BeforeRequest = this.BeforeRequest, - AfterResponse = this.AfterResponse + }; VerifyFhirVersion = verifyFhirVersion; @@ -72,7 +71,7 @@ public FhirHttpClient(Uri endpoint, bool verifyFhirVersion = false) /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirHttpClient(string endpoint, bool verifyFhirVersion = false) + public FhirClient(string endpoint, bool verifyFhirVersion = false) : this(new Uri(endpoint), verifyFhirVersion) { } @@ -1013,39 +1012,6 @@ private void setResourceBase(Resource resource, string baseUri) } } - - /// - /// Called just before the Http call is done - /// - public event EventHandler OnBeforeRequest; - - /// - /// Called just after the response was received - /// - public event EventHandler OnAfterResponse; - - /// - /// Inspect or modify the just before the FhirClient issues a call to the server - /// - /// The request as it is about to be sent to the server - /// The data in the body of the request as it is about to be sent to the server - protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) - { - // Default implementation: call event - OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); - } - - /// - /// Inspect the as it came back from the server - /// - /// You cannot read the body from the HttpResponseMessage, since it has - /// already been read by the framework. Use the body parameter instead. - protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) - { - // Default implementation: call event - OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); - } - // Original private TResource execute(Bundle tx, HttpStatusCode expect) where TResource : Model.Resource { diff --git a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs new file mode 100644 index 0000000000..92dac72348 --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; + +namespace Hl7.Fhir.Rest.Http +{ + public static class HttpToEntryExtensions + { + private const string USERDATA_BODY = "$body"; + private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; + + internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) + { + var result = new Bundle.EntryComponent(); + + result.Response = new Bundle.ResponseComponent(); + result.Response.Status = ((int)response.StatusCode).ToString(); + result.Response.SetHeaders(response.Headers); + + var contentType = response.Content.Headers.ContentType; + + Encoding charEncoding; + + try + { + charEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); + } + catch(ArgumentException e) + { + charEncoding = Encoding.UTF8; + } + + result.Response.Location = response.Headers.Location?.AbsoluteUri ?? response.Content.Headers.ContentLocation?.AbsoluteUri; + + result.Response.LastModified = response.Content.Headers.LastModified; + result.Response.Etag = response.Headers.ETag?.Tag; + + if (body != null && body.Length != 0) + { + result.Response.SetBody(body); + + if (IsBinaryResponse(result.Response.Location, contentType.ToString())) + { + result.Resource = makeBinaryResource(body, contentType.ToString()); + if (result.Response.Location != null) + { + var ri = new ResourceIdentity(result.Response.Location); + result.Resource.Id = ri.Id; + result.Resource.Meta = new Meta(); + result.Resource.Meta.VersionId = ri.VersionId; + result.Resource.ResourceBase = ri.BaseUri; + } + } + else + { + var bodyText = DecodeBody(body, charEncoding); + var resource = parseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); + result.Resource = resource; + + if (result.Response.Location != null) + result.Resource.ResourceBase = new ResourceIdentity(result.Response.Location).BaseUri; + } + } + + return result; + } + + internal static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) + { + Resource result= null; + + var fhirType = ContentType.GetResourceFormatFromContentType(contentType); + + if (fhirType == ResourceFormat.Unknown) + throw new UnsupportedBodyTypeException( + "Endpoint returned a body with contentType '{0}', while a valid FHIR xml/json body type was expected. Is this a FHIR endpoint?" + .FormatWith(contentType), contentType, bodyText); + + if (!SerializationUtil.ProbeIsJson(bodyText) && !SerializationUtil.ProbeIsXml(bodyText)) + throw new UnsupportedBodyTypeException( + "Endpoint said it returned '{0}', but the body is not recognized as either xml or json.".FormatWith(contentType), contentType, bodyText); + + try + { + if (fhirType == ResourceFormat.Json) + result = new FhirJsonParser(settings).Parse(bodyText); + else + result = new FhirXmlParser(settings).Parse(bodyText); + } + catch(FormatException fe) + { + if (throwOnFormatException) throw fe; + return null; + } + + return result; + } + + internal static bool IsBinaryResponse(string responseUri, string contentType) + { + if (!string.IsNullOrEmpty(contentType) + && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) + || ContentType.JSON_CONTENT_HEADERS.Contains(contentType.ToLower()) + ) + ) + return false; + + if (ResourceIdentity.IsRestResourceIdentity(responseUri)) + { + var id = new ResourceIdentity(responseUri); + + if (id.ResourceType != ResourceType.Binary.ToString()) return false; + + if (id.Id != null && Id.IsValidValue(id.Id)) return true; + if (id.VersionId != null && Id.IsValidValue(id.VersionId)) return true; + } + + return false; + } + + internal static string DecodeBody(byte[] body, Encoding enc) + { + if (body == null) return null; + if (enc == null) enc = Encoding.UTF8; + + // [WMR 20160421] Explicit disposal + // return (new StreamReader(new MemoryStream(body), enc, true)).ReadToEnd(); + using (var stream = new MemoryStream(body)) + using (var reader = new StreamReader(stream, enc, true)) + { + return reader.ReadToEnd(); + } + } + + internal static Binary makeBinaryResource(byte[] data, string contentType) + { + var binary = new Binary(); + + binary.Content = data; + binary.ContentType = contentType; + + return binary; + } + + public static string GetBodyAsText(this Bundle.ResponseComponent interaction) + { + var body = interaction.GetBody(); + + if (body != null) + return DecodeBody(body, Encoding.UTF8); + else + return null; + } + + private class Body + { + public byte[] Data; + } + + + public static byte[] GetBody(this Bundle.ResponseComponent interaction) + { + var body = interaction.Annotation(); + return body != null ? body.Data : null; + } + + internal static void SetBody(this Bundle.ResponseComponent interaction, byte[] data) + { + interaction.RemoveAnnotations(); + interaction.AddAnnotation(new Body { Data = data }); + } + + internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpResponseHeaders headers) + { + foreach (var header in headers) + { + interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(header.Key + ":" + header.Value)); + } + } + + public static IEnumerable> GetHeaders(this Bundle.ResponseComponent interaction) + { + foreach (var headerExt in interaction.GetExtensions(EXTENSION_RESPONSE_HEADER)) + { + if(headerExt.Value != null && headerExt.Value is FhirString) + { + var header = ((FhirString)headerExt.Value).Value; + + if (header != null) + { + yield return header.SplitLeft(':'); + } + } + } + } + + + public static IEnumerable GetHeader(this Bundle.ResponseComponent interaction, string header) + { + return interaction.GetHeaders().Where(h => h.Item1 == header).Select(h => h.Item2); + } + } + + + public class UnsupportedBodyTypeException : Exception + { + public string BodyType { get; set; } + + public string Body { get; set; } + public UnsupportedBodyTypeException(string message, string mimeType, string body) : base(message) + { + BodyType = mimeType; + Body = body; + } + } +} diff --git a/src/Hl7.Fhir.Core/Rest/HttpRequester.cs b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs similarity index 98% rename from src/Hl7.Fhir.Core/Rest/HttpRequester.cs rename to src/Hl7.Fhir.Core/Rest/Http/Requester.cs index 5279eceb5b..ee09c931c5 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpRequester.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs @@ -15,9 +15,9 @@ using System.Net.Http.Headers; using System.Threading.Tasks; -namespace Hl7.Fhir.Rest +namespace Hl7.Fhir.Rest.Http { - internal class HttpRequester : IDisposable + internal class Requester : IDisposable { public Uri BaseUrl { get; private set; } public HttpClient Client { get; private set; } @@ -43,7 +43,7 @@ internal class HttpRequester : IDisposable public ParserSettings ParserSettings { get; set; } - public HttpRequester(Uri baseUrl) + public Requester(Uri baseUrl) { var clientHandler = new HttpClientHandler() { diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index 3d890b6b53..3b982a1312 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -9,23 +9,29 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; +using Hl7.Fhir.Support; using Hl7.Fhir.Utility; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; +using System.Net; using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; namespace Hl7.Fhir.Rest { - public static class HttpToEntryExtensions + public static class HttpToEntryExtensions { private const string USERDATA_BODY = "$body"; private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; - internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) + internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) { var result = new Bundle.EntryComponent(); @@ -33,31 +39,26 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res result.Response.Status = ((int)response.StatusCode).ToString(); result.Response.SetHeaders(response.Headers); - var contentType = response.Content.Headers.ContentType; + var contentType = getContentType(response); + var charEncoding = getCharacterEncoding(response); - Encoding charEncoding; + result.Response.Location = response.Headers[HttpUtil.LOCATION] ?? response.Headers[HttpUtil.CONTENTLOCATION]; - try - { - charEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); - } - catch(ArgumentException e) - { - charEncoding = Encoding.UTF8; - } - - result.Response.Location = response.Headers.Location?.AbsoluteUri ?? response.Content.Headers.ContentLocation?.AbsoluteUri; +#if !DOTNETFW + if (!String.IsNullOrEmpty(response.Headers[HttpUtil.LASTMODIFIED])) + result.Response.LastModified = DateTimeOffset.Parse(response.Headers[HttpUtil.LASTMODIFIED]); +#else + result.Response.LastModified = response.LastModified; +#endif + result.Response.Etag = getETag(response); - result.Response.LastModified = response.Content.Headers.LastModified; - result.Response.Etag = response.Headers.ETag?.Tag; - - if (body != null && body.Length != 0) + if (body != null) { result.Response.SetBody(body); - if (IsBinaryResponse(result.Response.Location, contentType.ToString())) + if (IsBinaryResponse(response.ResponseUri.OriginalString, contentType)) { - result.Resource = makeBinaryResource(body, contentType.ToString()); + result.Resource = makeBinaryResource(body, contentType); if (result.Response.Location != null) { var ri = new ResourceIdentity(result.Response.Location); @@ -70,7 +71,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res else { var bodyText = DecodeBody(body, charEncoding); - var resource = parseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); + var resource = parseResource(bodyText, contentType, parserSettings, throwOnFormatException); result.Resource = resource; if (result.Response.Location != null) @@ -78,10 +79,48 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res } } + return result; + } + + + private static string getETag(HttpWebResponse response) + { + var result = response.Headers[HttpUtil.ETAG]; + + if(result != null) + { + if(result.StartsWith(@"W/")) result = result.Substring(2); + result = result.Trim('\"'); + } + + return result; + } + + private static string getContentType(HttpWebResponse response) + { + if (!String.IsNullOrEmpty(response.ContentType)) + { + return ContentType.GetMediaTypeFromHeaderValue(response.ContentType); + } + else + return null; + } + + private static Encoding getCharacterEncoding(HttpWebResponse response) + { + Encoding result = null; + + if (!String.IsNullOrEmpty(response.ContentType)) + { + var charset = ContentType.GetCharSetFromHeaderValue(response.ContentType); + + if (!String.IsNullOrEmpty(charset)) + result = Encoding.GetEncoding(charset); + } return result; } - internal static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) + private static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) { Resource result= null; @@ -112,10 +151,11 @@ internal static Resource parseResource(string bodyText, string contentType, Pars return result; } + internal static bool IsBinaryResponse(string responseUri, string contentType) { - if (!string.IsNullOrEmpty(contentType) - && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) + if (!string.IsNullOrEmpty(contentType) + && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) || ContentType.JSON_CONTENT_HEADERS.Contains(contentType.ToLower()) ) ) @@ -130,10 +170,11 @@ internal static bool IsBinaryResponse(string responseUri, string contentType) if (id.Id != null && Id.IsValidValue(id.Id)) return true; if (id.VersionId != null && Id.IsValidValue(id.VersionId)) return true; } - + return false; } + internal static string DecodeBody(byte[] body, Encoding enc) { if (body == null) return null; @@ -148,7 +189,7 @@ internal static string DecodeBody(byte[] body, Encoding enc) } } - internal static Binary makeBinaryResource(byte[] data, string contentType) + private static Binary makeBinaryResource(byte[] data, string contentType) { var binary = new Binary(); @@ -158,6 +199,7 @@ internal static Binary makeBinaryResource(byte[] data, string contentType) return binary; } + public static string GetBodyAsText(this Bundle.ResponseComponent interaction) { var body = interaction.GetBody(); @@ -168,6 +210,7 @@ public static string GetBodyAsText(this Bundle.ResponseComponent interaction) return null; } + private class Body { public byte[] Data; @@ -186,11 +229,11 @@ internal static void SetBody(this Bundle.ResponseComponent interaction, byte[] d interaction.AddAnnotation(new Body { Data = data }); } - internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpResponseHeaders headers) + internal static void SetHeaders(this Bundle.ResponseComponent interaction, WebHeaderCollection headers) { - foreach (var header in headers) + foreach (var key in headers.AllKeys) { - interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(header.Key + ":" + header.Value)); + interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(key + ":" + headers[key])); } } diff --git a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs index 8d8472078f..058123dc0a 100644 --- a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs @@ -3,20 +3,19 @@ using System.Threading.Tasks; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using System.Net.Http; namespace Hl7.Fhir.Rest { - public interface IFhirClient : IDisposable, IFhirCompatibleClient + public interface IFhirClient : IFhirCompatibleClient { byte[] LastBody { get; } Resource LastBodyAsResource { get; } string LastBodyAsText { get; } - HttpRequestMessage LastRequest { get; } - HttpResponseMessage LastResponse { get; } + HttpWebRequest LastRequest { get; } + HttpWebResponse LastResponse { get; } Bundle.ResponseComponent LastResult { get; } event EventHandler OnAfterResponse; - event EventHandler OnBeforeRequest; + event EventHandler OnBeforeRequest; } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index 622612ad57..a5a4ab5772 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -8,19 +8,18 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using Hl7.Fhir.Support; using Hl7.Fhir.Utility; using System; +using System.IO.Compression; using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; using System.Threading.Tasks; namespace Hl7.Fhir.Rest { - internal class Requester : IDisposable + internal class Requester { public Uri BaseUrl { get; private set; } - public HttpClient Client { get; private set; } public bool UseFormatParameter { get; set; } public ResourceFormat PreferredFormat { get; set; } @@ -45,17 +44,10 @@ internal class Requester : IDisposable public Requester(Uri baseUrl) { - var clientHandler = new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; - BaseUrl = baseUrl; - Client = new HttpClient(clientHandler); - Client.DefaultRequestHeaders.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; - Client.Timeout = new TimeSpan(0, 0, 100); // Default timeout is 100 seconds + Timeout = 100 * 1000; // Default timeout is 100 seconds PreferredReturn = Rest.Prefer.ReturnRepresentation; PreferredParameterHandling = null; ParserSettings = Hl7.Fhir.Serialization.ParserSettings.Default; @@ -63,16 +55,15 @@ public Requester(Uri baseUrl) public Bundle.EntryComponent LastResult { get; private set; } - public HttpResponseMessage LastResponse { get; private set; } - public HttpRequestMessage LastRequest { get; private set; } - public Action BeforeRequest { get; set; } - public Action AfterResponse { get; set; } + public HttpWebResponse LastResponse { get; private set; } + public HttpWebRequest LastRequest { get; private set; } + public Action BeforeRequest { get; set; } + public Action AfterResponse { get; set; } public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) { return ExecuteAsync(interaction).WaitResult(); } - public async Task ExecuteAsync(Bundle.EntryComponent interaction) { if (interaction == null) throw Error.ArgumentNull(nameof(interaction)); @@ -80,77 +71,121 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - using (var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody)) + byte[] outBody; + var request = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody, out outBody); + +#if DOTNETFW + request.Timeout = Timeout; +#endif + + if (PreferCompressedResponses) { - if (PreferCompressedResponses) - { - requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); - requestMessage.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); - } + request.Headers["Accept-Encoding"] = "gzip, deflate"; + } + + LastRequest = request; + if (BeforeRequest != null) BeforeRequest(request, outBody); - LastRequest = requestMessage; + // Write the body to the output + if (outBody != null) + request.WriteBody(compressRequestBody, outBody); - byte[] outgoingBody = null; - if (requestMessage.Method == HttpMethod.Post || requestMessage.Method == HttpMethod.Put) + // Make sure the HttpResponse gets disposed! + using (HttpWebResponse webResponse = (HttpWebResponse)await request.GetResponseAsync(new TimeSpan(0, 0, 0, 0, Timeout)).ConfigureAwait(false)) + //using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponseNoEx()) + { + try { - outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); - } + //Read body before we call the hook, so the hook cannot read the body before we do + var inBody = readBody(webResponse); - BeforeRequest?.Invoke(requestMessage, outgoingBody); + LastResponse = webResponse; + if (AfterResponse != null) AfterResponse(webResponse,inBody); - using (var response = await Client.SendAsync(requestMessage).ConfigureAwait(false)) - { + // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() try { - var body = await response.Content.ReadAsByteArrayAsync(); + LastResult = null; - LastResponse = response; - AfterResponse?.Invoke(response, body); - - // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() - try + if (webResponse.StatusCode.IsSuccessful()) { - LastResult = null; - - if (response.IsSuccessStatusCode) - { - LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: true); - return LastResult; - } - else - { - LastResult = response.ToBundleEntry(body, ParserSettings, throwOnFormatException: false); - throw buildFhirOperationException(response.StatusCode, LastResult.Resource); - } + LastResult = webResponse.ToBundleEntry(inBody, ParserSettings, throwOnFormatException: true); + return LastResult; } - catch (UnsupportedBodyTypeException bte) + else { - // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. - // Build a very minimal LastResult - var errorResult = new Bundle.EntryComponent(); - errorResult.Response = new Bundle.ResponseComponent(); - errorResult.Response.Status = ((int)response.StatusCode).ToString(); + LastResult = webResponse.ToBundleEntry(inBody, ParserSettings, throwOnFormatException: false); + throw buildFhirOperationException(webResponse.StatusCode, LastResult.Resource); + } + } + catch(UnsupportedBodyTypeException bte) + { + // The server responded with HTML code. Still build a FhirOperationException and set a LastResult. + // Build a very minimal LastResult + var errorResult = new Bundle.EntryComponent(); + errorResult.Response = new Bundle.ResponseComponent(); + errorResult.Response.Status = ((int)webResponse.StatusCode).ToString(); - OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); + OperationOutcome operationOutcome = OperationOutcome.ForException(bte, OperationOutcome.IssueType.Invalid); - errorResult.Resource = operationOutcome; - LastResult = errorResult; + errorResult.Resource = operationOutcome; + LastResult = errorResult; - throw buildFhirOperationException(response.StatusCode, operationOutcome); - } + throw buildFhirOperationException(webResponse.StatusCode, operationOutcome); } - catch (AggregateException ae) + } + catch (AggregateException ae) + { + //EK: This code looks weird. Is this correct? + if (ae.GetBaseException() is WebException) { - //EK: This code looks weird. Is this correct? - if (ae.GetBaseException() is WebException) - { - } - throw ae.GetBaseException(); } + throw ae.GetBaseException(); } } } + private static byte[] readBody(HttpWebResponse response) + { + if (response.ContentLength != 0) + { + byte[] body = null; + var respStream = response.GetResponseStream(); +#if !DOTNETFW + var contentEncoding = response.Headers["Content-Encoding"]; +#else + var contentEncoding = response.ContentEncoding; +#endif + if (contentEncoding == "gzip") + { + using (var decompressed = new GZipStream(respStream, CompressionMode.Decompress, true)) + { + body = HttpUtil.ReadAllFromStream(decompressed); + } + } + else if (contentEncoding == "deflate") + { + using (var decompressed = new DeflateStream(respStream, CompressionMode.Decompress, true)) + { + body = HttpUtil.ReadAllFromStream(decompressed); + } + } + else + { + body = HttpUtil.ReadAllFromStream(respStream); + } + respStream.Dispose(); + + if (body.Length > 0) + return body; + else + return null; + } + else + return null; + } + + private static Exception buildFhirOperationException(HttpStatusCode status, Resource body) { string message; @@ -171,27 +206,5 @@ private static Exception buildFhirOperationException(HttpStatusCode status, Reso else return new FhirOperationException($"{message}. Body has no content.", status); } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - this.Client.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - } - #endregion - } + } } From 349bb4a418d9c92084aa60fb74dcaf91e0554cd8 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 13:46:36 -0600 Subject: [PATCH 20/39] Refactored common code to base class for FhirClient and Requester. --- src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs | 1065 ++++++++++++ src/Hl7.Fhir.Core/Rest/FhirClient.cs | 1037 +----------- src/Hl7.Fhir.Core/Rest/FhirClientSearch.cs | 2 +- .../Rest/FhirOperationException.cs | 10 +- .../Rest/Http/EntryToHttpExtensions.cs | 2 +- src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs | 1438 +---------------- .../Rest/Http/HttpToEntryExtensions.cs | 148 +- src/Hl7.Fhir.Core/Rest/Http/Requester.cs | 15 +- .../Rest/HttpToEntryExtensions.cs | 8 +- src/Hl7.Fhir.Core/Rest/IRequester.cs | 40 + src/Hl7.Fhir.Core/Rest/Requester.cs | 3 +- .../Source/ResourceResolverTests.cs | 5 +- 12 files changed, 1173 insertions(+), 2600 deletions(-) create mode 100644 src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs create mode 100644 src/Hl7.Fhir.Core/Rest/IRequester.cs diff --git a/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs new file mode 100644 index 0000000000..0c5ef88280 --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs @@ -0,0 +1,1065 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Hl7.Fhir.Rest +{ + public abstract partial class BaseFhirClient : IDisposable + { + protected IRequester Requester { get; set; } + + /// + /// The default endpoint for use with operations that use discrete id/version parameters + /// instead of explicit uri endpoints. This will always have a trailing "/" + /// + public Uri Endpoint + { + get; + protected set; + } + + #region << Client Communication Defaults (PreferredFormat, UseFormatParam, Timeout, ReturnFullResource) >> + public bool VerifyFhirVersion + { + get; + set; + } + + /// + /// The preferred format of the content to be used when communicating with the FHIR server (XML or JSON) + /// + public ResourceFormat PreferredFormat + { + get { return Requester.PreferredFormat; } + set { Requester.PreferredFormat = value; } + } + + /// + /// When passing the content preference, use the _format parameter instead of the request header + /// + public bool UseFormatParam + { + get { return Requester.UseFormatParameter; } + set { Requester.UseFormatParameter = value; } + } + + /// + /// The timeout (in milliseconds) to be used when making calls to the FHIR server + /// + public int Timeout + { + get { return Requester.Timeout; } + set { Requester.Timeout = value; } + } + + + //private bool _returnFullResource = false; + + /// + /// Should calls to Create, Update and transaction operations return the whole updated content? + /// + /// Refer to specification section 2.1.0.5 (Managing Return Content) + [Obsolete("In STU3 this is no longer a true/false option, use the PreferredReturn property instead")] + public bool ReturnFullResource + { + get => Requester.PreferredReturn == Prefer.ReturnRepresentation; + set => Requester.PreferredReturn = value ? Prefer.ReturnRepresentation : Prefer.ReturnMinimal; + } + + /// + /// Should calls to Create, Update and transaction operations return the whole updated content, + /// or an OperationOutcome? + /// + /// Refer to specification section 2.1.0.5 (Managing Return Content) + + public Prefer? PreferredReturn + { + get => Requester.PreferredReturn; + set => Requester.PreferredReturn = value; + } + + /// + /// Should server return which search parameters were supported after executing a search? + /// If true, the server should return an error for any unknown or unsupported parameter, otherwise + /// the server may ignore any unknown or unsupported parameter. + /// + public SearchParameterHandling? PreferredParameterHandling + { + get => Requester.PreferredParameterHandling; + set => Requester.PreferredParameterHandling = value; + } +#endregion + + +#if NET_COMPRESSION + /// + /// This will do 2 things: + /// 1. Add the header Accept-Encoding: gzip, deflate + /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) + /// + public bool PreferCompressedResponses + { + get { return Requester.PreferCompressedResponses; } + set { Requester.PreferCompressedResponses = value; } + } + /// + /// Compress any Request bodies + /// (warning, if a server does not handle compressed requests you will get a 415 response) + /// + public bool CompressRequestBody + { + get { return Requester.CompressRequestBody; } + set { Requester.CompressRequestBody = value; } + } +#endif + + + /// + /// The last transaction result that was executed on this connection to the FHIR server + /// + public Bundle.ResponseComponent LastResult => Requester.LastResult?.Response; + + public ParserSettings ParserSettings + { + get { return Requester.ParserSettings; } + set { Requester.ParserSettings = value; } + } + + protected static Uri GetValidatedEndpoint(Uri endpoint) + { + if (endpoint == null) throw new ArgumentNullException("endpoint"); + + if (!endpoint.OriginalString.EndsWith("/")) + endpoint = new Uri(endpoint.OriginalString + "/"); + + if (!endpoint.IsAbsoluteUri) throw new ArgumentException("endpoint", "Endpoint must be absolute"); + + return endpoint; + } + + #region Read + + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// + /// The requested resource. This operation will throw an exception + /// if the resource has been deleted or does not exist. + /// The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + /// + /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. + /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome + public Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + Bundle tx; + + if (!id.HasVersion) + { + var ri = new TransactionBuilder(Endpoint).Read(id.ResourceType, id.Id, ifNoneMatch, ifModifiedSince); + tx = ri.ToBundle(); + } + else + { + tx = new TransactionBuilder(Endpoint).VRead(id.ResourceType, id.Id, id.VersionId).ToBundle(); + } + + return executeAsync(tx, HttpStatusCode.OK); + } + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// + /// The requested resource. This operation will throw an exception + /// if the resource has been deleted or does not exist. + /// The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + /// + /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. + /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome + public TResource Read(Uri location, string ifNoneMatch = null, + DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); + } + + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// The requested resource + /// This operation will throw an exception + /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + public Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(new Uri(location, UriKind.RelativeOrAbsolute), ifNoneMatch, ifModifiedSince); + } + /// + /// Fetches a typed resource from a FHIR resource endpoint. + /// + /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific + /// Resource url. + /// The (weak) ETag to use in a conditional read. Optional. + /// Last modified since date in a conditional read. Optional. + /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown + /// The requested resource + /// This operation will throw an exception + /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute + /// url, it must reference an address within the endpoint. + public TResource Read(string location, string ifNoneMatch = null, + DateTimeOffset? ifModifiedSince = null) where TResource : Resource + { + return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); + } + + #endregion + + #region Refresh + + /// + /// Refreshes the data in the resource passed as an argument by re-reading it from the server + /// + /// + /// The resource for which you want to get the most recent version. + /// A new instance of the resource, containing the most up-to-date data + /// This function will not overwrite the argument with new data, rather it will return a new instance + /// which will have the newest data, leaving the argument intact. + public Task RefreshAsync(TResource current) where TResource : Resource + { + if (current == null) throw Error.ArgumentNull(nameof(current)); + + return ReadAsync(ResourceIdentity.Build(current.TypeName, current.Id)); + } + /// + /// Refreshes the data in the resource passed as an argument by re-reading it from the server + /// + /// + /// The resource for which you want to get the most recent version. + /// A new instance of the resource, containing the most up-to-date data + /// This function will not overwrite the argument with new data, rather it will return a new instance + /// which will have the newest data, leaving the argument intact. + public TResource Refresh(TResource current) where TResource : Resource + { + return RefreshAsync(current).WaitResult(); + } + + #endregion + + #region Update + + /// + /// Update (or create) a resource + /// + /// The resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be + /// created. + public Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (resource.Id == null) throw Error.Argument(nameof(resource), "Resource needs a non-null Id to send the update to"); + + var upd = new TransactionBuilder(Endpoint); + + if (versionAware && resource.HasVersionId) + upd.Update(resource.Id, resource, versionId: resource.VersionId); + else + upd.Update(resource.Id, resource); + + return internalUpdateAsync(resource, upd.ToBundle()); + } + /// + /// Update (or create) a resource + /// + /// The resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be + /// created. + public TResource Update(TResource resource, bool versionAware = false) where TResource : Resource + { + return UpdateAsync(resource, versionAware).WaitResult(); + } + + /// + /// Conditionally update (or create) a resource + /// + /// The resource to update + /// Criteria used to locate the resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. + public Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var upd = new TransactionBuilder(Endpoint); + + if (versionAware && resource.HasVersionId) + upd.Update(condition, resource, versionId: resource.VersionId); + else + upd.Update(condition, resource); + + return internalUpdateAsync(resource, upd.ToBundle()); + } + /// + /// Conditionally update (or create) a resource + /// + /// The resource to update + /// Criteria used to locate the resource to update + /// If true, asks the server to verify we are updating the latest version + /// The type of resource that is being updated + /// The body of the updated resource, unless ReturnFullResource is set to "false" + /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. + /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. + public TResource Update(TResource resource, SearchParams condition, bool versionAware = false) + where TResource : Resource + { + return UpdateAsync(resource, condition, versionAware).WaitResult(); + } + private Task internalUpdateAsync(TResource resource, Bundle tx) where TResource : Resource + { + resource.ResourceBase = Endpoint; + + // This might be an update of a resource that doesn't yet exist, so accept a status Created too + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + private TResource internalUpdate(TResource resource, Bundle tx) where TResource : Resource + { + return internalUpdateAsync(resource, tx).WaitResult(); + } + #endregion + + #region Delete + + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public async System.Threading.Tasks.Task DeleteAsync(Uri location) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + var tx = new TransactionBuilder(Endpoint).Delete(id.ResourceType, id.Id).ToBundle(); + + await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public void Delete(Uri location) + { + DeleteAsync(location).WaitNoResult(); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public System.Threading.Tasks.Task DeleteAsync(string location) + { + return DeleteAsync(new Uri(location, UriKind.Relative)); + } + /// + /// Delete a resource at the given endpoint. + /// + /// endpoint of the resource to delete + /// Throws an exception when the delete failed, though this might + /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was + /// already deleted). + public void Delete(string location) + { + DeleteAsync(location).WaitNoResult(); + } + + + /// + /// Delete a resource + /// + /// The resource to delete + public async System.Threading.Tasks.Task DeleteAsync(Resource resource) + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (resource.Id == null) throw Error.Argument(nameof(resource), "Entry must have an id"); + + await DeleteAsync(resource.ResourceIdentity(Endpoint).WithoutVersion()).ConfigureAwait(false); + } + /// + /// Delete a resource + /// + /// The resource to delete + public void Delete(Resource resource) + { + DeleteAsync(resource).WaitNoResult(); + } + + /// + /// Conditionally delete a resource + /// + /// The type of resource to delete + /// Criteria to use to match the resource to delete. + public async System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition) + { + if (resourceType == null) throw Error.ArgumentNull(nameof(resourceType)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var tx = new TransactionBuilder(Endpoint).Delete(resourceType, condition).ToBundle(); + await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); + } + /// + /// Conditionally delete a resource + /// + /// The type of resource to delete + /// Criteria to use to match the resource to delete. + public void Delete(string resourceType, SearchParams condition) + { + DeleteAsync(resourceType, condition).WaitNoResult(); + } + + #endregion + + #region Create + + /// + /// Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public Task CreateAsync(TResource resource) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + + var tx = new TransactionBuilder(Endpoint).Create(resource).ToBundle(); + + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + /// + /// Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public TResource Create(TResource resource) where TResource : Resource + { + return CreateAsync(resource).WaitResult(); + } + + /// + /// Conditionally Create a resource on a FHIR endpoint + /// + /// The resource instance to create + /// The criteria + /// The resource as created on the server, or an exception if the create failed. + /// The type of resource to create + public Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource + { + if (resource == null) throw Error.ArgumentNull(nameof(resource)); + if (condition == null) throw Error.ArgumentNull(nameof(condition)); + + var tx = new TransactionBuilder(Endpoint).Create(resource, condition).ToBundle(); + + return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); + } + public TResource Create(TResource resource, SearchParams condition) where TResource : Resource + { + return CreateAsync(resource, condition).WaitResult(); + } + + #endregion + + #region Conformance + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + [Obsolete("The Conformance operation has been replaced by the CapabilityStatement", false)] + public CapabilityStatement Conformance(SummaryType? summary = null) + { + return CapabilityStatement(summary); + } + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + public Task CapabilityStatementAsync(SummaryType? summary = null) + { + var tx = new TransactionBuilder(Endpoint).CapabilityStatement(summary).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + + /// + /// Get a conformance statement for the system + /// + /// A Conformance resource. Throws an exception if the operation failed. + public CapabilityStatement CapabilityStatement(SummaryType? summary = null) + { + return CapabilityStatementAsync(summary).WaitResult(); + } + #endregion + + #region History + + /// + /// Retrieve the version history for a specific resource type + /// + /// The type of Resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(resourceType, null, since, pageSize, summary); + } + /// + /// Retrieve the version history for a specific resource type + /// + /// The type of Resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle TypeHistory(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return TypeHistoryAsync(resourceType, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a specific resource type + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// The type of Resource to get the history for + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task TypeHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() + { + string collection = typeof(TResource).GetCollectionName(); + return internalHistoryAsync(collection, null, since, pageSize, summary); + } + /// + /// Retrieve the version history for a specific resource type + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// The type of Resource to get the history for + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle TypeHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() + { + return TypeHistoryAsync(since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task HistoryAsync(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + return internalHistoryAsync(id.ResourceType, id.Id, since, pageSize, summary); + } + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle History(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(location, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task HistoryAsync(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(new Uri(location, UriKind.Relative), since, pageSize, summary); + } + /// + /// Retrieve the version history for a resource at a given location + /// + /// The address of the resource to get the history for + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Optional. Asks the server to only provide the fields defined for the summary + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle History(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return HistoryAsync(location, since, pageSize, summary).WaitResult(); + } + + /// + /// Retrieve the full version history of the server + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Indicates whether the returned resources should just contain the minimal set of elements + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Task WholeSystemHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(null, null, since, pageSize, summary); + } + /// + /// Retrieve the full version history of the server + /// + /// Optional. Returns only changes after the given date + /// Optional. Asks server to limit the number of entries per page returned + /// Indicates whether the returned resources should just contain the minimal set of elements + /// A bundle with the history for the indicated instance, may contain both + /// ResourceEntries and DeletedEntries. + public Bundle WholeSystemHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + return WholeSystemHistoryAsync(since, pageSize, summary).WaitResult(); + } + private Task internalHistoryAsync(string resourceType = null, string id = null, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) + { + TransactionBuilder history; + + if (resourceType == null) + history = new TransactionBuilder(Endpoint).ServerHistory(summary, pageSize, since); + else if (id == null) + history = new TransactionBuilder(Endpoint).CollectionHistory(resourceType, summary, pageSize, since); + else + history = new TransactionBuilder(Endpoint).ResourceHistory(resourceType, id, summary, pageSize, since); + + return executeAsync(history.ToBundle(), HttpStatusCode.OK); + } + private Bundle internalHistory(string resourceType = null, string id = null, DateTimeOffset? since = null, + int? pageSize = null, SummaryType summary = SummaryType.False) + { + return internalHistoryAsync(resourceType, id, since, pageSize, summary).WaitResult(); + } + + #endregion + + #region Transaction + + /// + /// Send a set of creates, updates and deletes to the server to be processed in one transaction + /// + /// The bundled creates, updates and deleted + /// A bundle as returned by the server after it has processed the transaction, or null + /// if an error occurred. + public Task TransactionAsync(Bundle bundle) + { + if (bundle == null) throw new ArgumentNullException(nameof(bundle)); + + var tx = new TransactionBuilder(Endpoint).Transaction(bundle).ToBundle(); + return executeAsync(tx, HttpStatusCode.OK); + } + /// + /// Send a set of creates, updates and deletes to the server to be processed in one transaction + /// + /// The bundled creates, updates and deleted + /// A bundle as returned by the server after it has processed the transaction, or null + /// if an error occurred. + public Bundle Transaction(Bundle bundle) + { + return TransactionAsync(bundle).WaitResult(); + } + + #endregion + + #region Operation + + public Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + return internalOperationAsync(operationName, parameters: parameters, useGet: useGet); + } + + public Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false) + { + return WholeSystemOperationAsync(operationName, parameters, useGet).WaitResult(); + } + + + public Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) + where TResource : Resource + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + // [WMR 20160421] GetResourceNameForType is obsolete + // var typeName = ModelInfo.GetResourceNameForType(typeof(TResource)); + var typeName = ModelInfo.GetFhirTypeNameForType(typeof(TResource)); + + return TypeOperationAsync(operationName, typeName, parameters, useGet: useGet); + } + public Resource TypeOperation(string operationName, Parameters parameters = null, + bool useGet = false) where TResource : Resource + { + return TypeOperationAsync(operationName, parameters, useGet).WaitResult(); + } + + + + public Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false) + { + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + if (typeName == null) throw Error.ArgumentNull(nameof(typeName)); + + return internalOperationAsync(operationName, typeName, parameters: parameters, useGet: useGet); + } + public Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false) + { + return TypeOperationAsync(operationName, typeName, parameters, useGet).WaitResult(); + } + + + + public Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + var id = verifyResourceIdentity(location, needId: true, needVid: false); + + return internalOperationAsync(operationName, id.ResourceType, id.Id, id.VersionId, parameters, useGet); + } + public Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + return InstanceOperationAsync(location, operationName, parameters, useGet).WaitResult(); + } + + + + public Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + if (location == null) throw Error.ArgumentNull(nameof(location)); + if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); + + var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(location), operationName, parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + public Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) + { + return OperationAsync(location, operationName, parameters, useGet).WaitResult(); + } + + + public Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false) + { + if (operation == null) throw Error.ArgumentNull(nameof(operation)); + + var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(operation), parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + public Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false) + { + return OperationAsync(operation, parameters, useGet).WaitResult(); + } + + + + private Task internalOperationAsync(string operationName, string type = null, string id = null, string vid = null, + Parameters parameters = null, bool useGet = false) + { + // Brian: Not sure why we would create this parameters object as empty. + // I would imagine that a null parameters object is different to an empty one? + // EK: What else could we do? POST an empty body? We cannot use GET unless the caller indicates this is an + // idempotent call.... + // MV: (related to issue #419): we only provide an empty parameter when we are not performing a GET operation. In r4 it will be allowed + // to provide an empty body in POST operations. In that case the line of code can be deleted. + if (parameters == null && !useGet) parameters = new Parameters(); + + Bundle tx; + + if (type == null) + tx = new TransactionBuilder(Endpoint).ServerOperation(operationName, parameters, useGet).ToBundle(); + else if (id == null) + tx = new TransactionBuilder(Endpoint).TypeOperation(type, operationName, parameters, useGet).ToBundle(); + else + tx = new TransactionBuilder(Endpoint).ResourceOperation(type, id, vid, operationName, parameters, useGet).ToBundle(); + + return executeAsync(tx, HttpStatusCode.OK); + } + + private Resource internalOperation(string operationName, string type = null, string id = null, + string vid = null, Parameters parameters = null, bool useGet = false) + { + return internalOperationAsync(operationName, type, id, vid, parameters, useGet).WaitResult(); + } + + #endregion + + #region Get + + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Resource Get(Uri url) + { + return GetAsync(url).WaitResult(); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public async Task GetAsync(Uri url) + { + if (url == null) throw Error.ArgumentNull(nameof(url)); + + var tx = new TransactionBuilder(Endpoint).Get(url).ToBundle(); + return await executeAsync(tx, HttpStatusCode.OK).ConfigureAwait(false); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Resource Get(string url) + { + return GetAsync(url).WaitResult(); + } + /// + /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception + /// + /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. + /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url + /// parameters to the method are simple, and are in the URL, and this is a GET operation + public Task GetAsync(string url) + { + if (url == null) throw Error.ArgumentNull(nameof(url)); + + return GetAsync(new Uri(url, UriKind.RelativeOrAbsolute)); + } + + #endregion + + + + + private ResourceIdentity verifyResourceIdentity(Uri location, bool needId, bool needVid) + { + var result = new ResourceIdentity(location); + + if (result.ResourceType == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the resource type in its path"); + if (needId && result.Id == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the logical id in its path"); + if (needVid && !result.HasVersion) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the version id in its path"); + + return result; + } + + + // TODO: Depending on type of response, update identity & always update lastupdated? + + private void updateIdentity(Resource resource, ResourceIdentity identity) + { + if (resource.Meta == null) resource.Meta = new Meta(); + + if (resource.Id == null) + { + resource.Id = identity.Id; + resource.VersionId = identity.VersionId; + } + } + + + private void setResourceBase(Resource resource, string baseUri) + { + resource.ResourceBase = new Uri(baseUri); + + if (resource is Bundle) + { + var bundle = resource as Bundle; + foreach (var entry in bundle.Entry.Where(e => e.Resource != null)) + entry.Resource.ResourceBase = new Uri(baseUri, UriKind.RelativeOrAbsolute); + } + } + + // Original + private TResource execute(Bundle tx, HttpStatusCode expect) where TResource : Model.Resource + { + return executeAsync(tx, new[] { expect }).WaitResult(); + } + public Task executeAsync(Model.Bundle tx, HttpStatusCode expect) where TResource : Model.Resource + { + return executeAsync(tx, new[] { expect }); + } + // Original + private TResource execute(Bundle tx, IEnumerable expect) where TResource : Resource + { + return executeAsync(tx, expect).WaitResult(); + } + + private async Task executeAsync(Bundle tx, IEnumerable expect) where TResource : Resource + { + verifyServerVersion(); + + var request = tx.Entry[0]; + var response = await Requester.ExecuteAsync(request).ConfigureAwait(false); + + if (!expect.Select(sc => ((int)sc).ToString()).Contains(response.Response.Status)) + { + Enum.TryParse(response.Response.Status, out HttpStatusCode code); + throw new FhirOperationException("Operation concluded successfully, but the return status {0} was unexpected".FormatWith(response.Response.Status), code); + } + + Resource result; + + // Special feature: if ReturnFullResource was requested (using the Prefer header), but the server did not return the resource + // (or it returned an OperationOutcome) - explicitly go out to the server to get the resource and return it. + // This behavior is only valid for PUT and POST requests, where the server may device whether or not to return the full body of the alterend resource. + var noRealBody = response.Resource == null || (response.Resource is OperationOutcome && string.IsNullOrEmpty(response.Resource.Id)); + if (noRealBody && isPostOrPut(request) + && PreferredReturn == Prefer.ReturnRepresentation && response.Response.Location != null + && new ResourceIdentity(response.Response.Location).IsRestResourceIdentity()) // Check that it isn't an operation too + { + result = await GetAsync(response.Response.Location).ConfigureAwait(false); + } + else + result = response.Resource; + + if (result == null) return null; + + // We have a success code (2xx), we have a body, but the body may not be of the type we expect. + if (!(result is TResource)) + { + // If this is an operationoutcome, that may still be allright. Keep the OperationOutcome in + // the LastResult, and return null as the result. Otherwise, throw. + if (result is OperationOutcome) + return null; + + var message = String.Format("Operation {0} on {1} expected a body of type {2} but a {3} was returned", response.Request.Method, + response.Request.Url, typeof(TResource).Name, result.GetType().Name); + throw new FhirOperationException(message, Requester.LastStatusCode); + } + else + return result as TResource; + } + private bool isPostOrPut(Bundle.EntryComponent interaction) + { + var method = interaction.Request.Method; + return method == Bundle.HTTPVerb.POST || method == Bundle.HTTPVerb.PUT; + } + + + private bool versionChecked = false; + + private void verifyServerVersion() + { + if (!VerifyFhirVersion) return; + + if (versionChecked) return; + versionChecked = true; // So we can now start calling Conformance() without getting into a loop + + CapabilityStatement conf = null; + try + { + conf = CapabilityStatement(SummaryType.True); // don't get the full version as its huge just to read the fhir version + } + catch (FormatException) + { + // Mmmm...cannot even read the body. Probably not so good. + throw Error.NotSupported("Cannot read the conformance statement of the server to verify FHIR version compatibility"); + } + + if (!conf.FhirVersion.StartsWith(ModelInfo.Version)) + { + throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); + } + } + + #region IDisposable Support + protected bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if(Requester is IDisposable disposableRequester) + { + disposableRequester.Dispose(); + } + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 6be14d0664..ff0af76d38 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -20,10 +20,8 @@ namespace Hl7.Fhir.Rest { - public partial class FhirClient : IFhirClient + public partial class FhirClient : BaseFhirClient, IFhirClient { - private Requester _requester; - /// /// Creates a new client using a default endpoint /// If the endpoint does not end with a slash (/), it will be added. @@ -39,16 +37,9 @@ public partial class FhirClient : IFhirClient /// public FhirClient(Uri endpoint, bool verifyFhirVersion = false) { - if (endpoint == null) throw new ArgumentNullException("endpoint"); - - if (!endpoint.OriginalString.EndsWith("/")) - endpoint = new Uri(endpoint.OriginalString + "/"); - - if (!endpoint.IsAbsoluteUri) throw new ArgumentException("endpoint", "Endpoint must be absolute"); - - Endpoint = endpoint; + Endpoint = GetValidatedEndpoint(endpoint); - _requester = new Requester(Endpoint) + Requester = new Requester(Endpoint) { BeforeRequest = this.BeforeRequest, AfterResponse = this.AfterResponse @@ -76,942 +67,22 @@ public FhirClient(string endpoint, bool verifyFhirVersion = false) { } - #region << Client Communication Defaults (PreferredFormat, UseFormatParam, Timeout, ReturnFullResource) >> - public bool VerifyFhirVersion - { - get; - set; - } - - /// - /// The preferred format of the content to be used when communicating with the FHIR server (XML or JSON) - /// - public ResourceFormat PreferredFormat - { - get { return _requester.PreferredFormat; } - set { _requester.PreferredFormat = value; } - } - - /// - /// When passing the content preference, use the _format parameter instead of the request header - /// - public bool UseFormatParam - { - get { return _requester.UseFormatParameter; } - set { _requester.UseFormatParameter = value; } - } - - /// - /// The timeout (in milliseconds) to be used when making calls to the FHIR server - /// - public int Timeout - { - get { return _requester.Timeout; } - set { _requester.Timeout = value; } - } - - - //private bool _returnFullResource = false; - - /// - /// Should calls to Create, Update and transaction operations return the whole updated content? - /// - /// Refer to specification section 2.1.0.5 (Managing Return Content) - [Obsolete("In STU3 this is no longer a true/false option, use the PreferredReturn property instead")] - public bool ReturnFullResource - { - get => _requester.PreferredReturn == Prefer.ReturnRepresentation; - set => _requester.PreferredReturn = value ? Prefer.ReturnRepresentation : Prefer.ReturnMinimal; - } - - /// - /// Should calls to Create, Update and transaction operations return the whole updated content, - /// or an OperationOutcome? - /// - /// Refer to specification section 2.1.0.5 (Managing Return Content) - - public Prefer? PreferredReturn - { - get => _requester.PreferredReturn; - set => _requester.PreferredReturn = value; - } - - /// - /// Should server return which search parameters were supported after executing a search? - /// If true, the server should return an error for any unknown or unsupported parameter, otherwise - /// the server may ignore any unknown or unsupported parameter. - /// - public SearchParameterHandling? PreferredParameterHandling - { - get => _requester.PreferredParameterHandling; - set => _requester.PreferredParameterHandling = value; - } - - -#if NET_COMPRESSION - /// - /// This will do 2 things: - /// 1. Add the header Accept-Encoding: gzip, deflate - /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) - /// - public bool PreferCompressedResponses - { - get { return _requester.PreferCompressedResponses; } - set { _requester.PreferCompressedResponses = value; } - } - /// - /// Compress any Request bodies - /// (warning, if a server does not handle compressed requests you will get a 415 response) - /// - public bool CompressRequestBody - { - get { return _requester.CompressRequestBody; } - set { _requester.CompressRequestBody = value; } - } -#endif - - - /// - /// The last transaction result that was executed on this connection to the FHIR server - /// - public Bundle.ResponseComponent LastResult => _requester.LastResult?.Response; - - public ParserSettings ParserSettings - { - get { return _requester.ParserSettings; } - set { _requester.ParserSettings = value; } - } - public byte[] LastBody => LastResult?.GetBody(); public string LastBodyAsText => LastResult?.GetBodyAsText(); - public Resource LastBodyAsResource => _requester.LastResult?.Resource; + public Resource LastBodyAsResource => Requester.LastResult?.Resource; /// /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient /// - public HttpWebRequest LastRequest { get { return _requester.LastRequest; } } + public HttpWebRequest LastRequest { get { return (Requester as Requester)?.LastRequest; } } /// /// Returns the HttpWebResponse as it was last received during a call on the FhirClient /// /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpWebResponse LastResponse { get { return _requester.LastResponse; } } - - /// - /// The default endpoint for use with operations that use discrete id/version parameters - /// instead of explicit uri endpoints. This will always have a trailing "/" - /// - public Uri Endpoint - { - get; - private set; - } - - #endregion - - #region Read - - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// - /// The requested resource. This operation will throw an exception - /// if the resource has been deleted or does not exist. - /// The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - /// - /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. - /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome - public Task ReadAsync(Uri location, string ifNoneMatch=null, DateTimeOffset? ifModifiedSince=null) where TResource : Resource - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - Bundle tx; - - if (!id.HasVersion) - { - var ri = new TransactionBuilder(Endpoint).Read(id.ResourceType, id.Id, ifNoneMatch, ifModifiedSince); - tx = ri.ToBundle(); - } - else - { - tx = new TransactionBuilder(Endpoint).VRead(id.ResourceType, id.Id, id.VersionId).ToBundle(); - } - - return executeAsync(tx, HttpStatusCode.OK); - } - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// - /// The requested resource. This operation will throw an exception - /// if the resource has been deleted or does not exist. - /// The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - /// - /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. - /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome - public TResource Read(Uri location, string ifNoneMatch = null, - DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); - } - - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// The requested resource - /// This operation will throw an exception - /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - public Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(new Uri(location, UriKind.RelativeOrAbsolute), ifNoneMatch, ifModifiedSince); - } - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// The requested resource - /// This operation will throw an exception - /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - public TResource Read(string location, string ifNoneMatch = null, - DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); - } - - #endregion - - #region Refresh - - /// - /// Refreshes the data in the resource passed as an argument by re-reading it from the server - /// - /// - /// The resource for which you want to get the most recent version. - /// A new instance of the resource, containing the most up-to-date data - /// This function will not overwrite the argument with new data, rather it will return a new instance - /// which will have the newest data, leaving the argument intact. - public Task RefreshAsync(TResource current) where TResource : Resource - { - if (current == null) throw Error.ArgumentNull(nameof(current)); - - return ReadAsync(ResourceIdentity.Build(current.TypeName, current.Id)); - } - /// - /// Refreshes the data in the resource passed as an argument by re-reading it from the server - /// - /// - /// The resource for which you want to get the most recent version. - /// A new instance of the resource, containing the most up-to-date data - /// This function will not overwrite the argument with new data, rather it will return a new instance - /// which will have the newest data, leaving the argument intact. - public TResource Refresh(TResource current) where TResource : Resource - { - return RefreshAsync(current).WaitResult(); - } - - #endregion - - #region Update - - /// - /// Update (or create) a resource - /// - /// The resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be - /// created. - public Task UpdateAsync(TResource resource, bool versionAware=false) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (resource.Id == null) throw Error.Argument(nameof(resource), "Resource needs a non-null Id to send the update to"); - - var upd = new TransactionBuilder(Endpoint); - - if (versionAware && resource.HasVersionId) - upd.Update(resource.Id, resource, versionId: resource.VersionId); - else - upd.Update(resource.Id, resource); - - return internalUpdateAsync(resource, upd.ToBundle()); - } - /// - /// Update (or create) a resource - /// - /// The resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be - /// created. - public TResource Update(TResource resource, bool versionAware = false) where TResource : Resource - { - return UpdateAsync(resource, versionAware).WaitResult(); - } - - /// - /// Conditionally update (or create) a resource - /// - /// The resource to update - /// Criteria used to locate the resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. - public Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var upd = new TransactionBuilder(Endpoint); - - if (versionAware && resource.HasVersionId) - upd.Update(condition, resource, versionId: resource.VersionId); - else - upd.Update(condition, resource); - - return internalUpdateAsync(resource, upd.ToBundle()); - } - /// - /// Conditionally update (or create) a resource - /// - /// The resource to update - /// Criteria used to locate the resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. - public TResource Update(TResource resource, SearchParams condition, bool versionAware = false) - where TResource : Resource - { - return UpdateAsync(resource, condition, versionAware).WaitResult(); - } - private Task internalUpdateAsync(TResource resource, Bundle tx) where TResource : Resource - { - resource.ResourceBase = Endpoint; - - // This might be an update of a resource that doesn't yet exist, so accept a status Created too - return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - private TResource internalUpdate(TResource resource, Bundle tx) where TResource : Resource - { - return internalUpdateAsync(resource, tx).WaitResult(); - } - #endregion - - #region Delete - - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public async System.Threading.Tasks.Task DeleteAsync(Uri location) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - var tx = new TransactionBuilder(Endpoint).Delete(id.ResourceType, id.Id).ToBundle(); - - await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public void Delete(Uri location) - { - DeleteAsync(location).WaitNoResult(); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public System.Threading.Tasks.Task DeleteAsync(string location) - { - return DeleteAsync(new Uri(location, UriKind.Relative)); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public void Delete(string location) - { - DeleteAsync(location).WaitNoResult(); - } - - - /// - /// Delete a resource - /// - /// The resource to delete - public async System.Threading.Tasks.Task DeleteAsync(Resource resource) - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (resource.Id == null) throw Error.Argument(nameof(resource), "Entry must have an id"); - - await DeleteAsync(resource.ResourceIdentity(Endpoint).WithoutVersion()).ConfigureAwait(false); - } - /// - /// Delete a resource - /// - /// The resource to delete - public void Delete(Resource resource) - { - DeleteAsync(resource).WaitNoResult(); - } - - /// - /// Conditionally delete a resource - /// - /// The type of resource to delete - /// Criteria to use to match the resource to delete. - public async System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition) - { - if (resourceType == null) throw Error.ArgumentNull(nameof(resourceType)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var tx = new TransactionBuilder(Endpoint).Delete(resourceType, condition).ToBundle(); - await executeAsync(tx,new[]{ HttpStatusCode.OK, HttpStatusCode.NoContent}).ConfigureAwait(false); - } - /// - /// Conditionally delete a resource - /// - /// The type of resource to delete - /// Criteria to use to match the resource to delete. - public void Delete(string resourceType, SearchParams condition) - { - DeleteAsync(resourceType, condition).WaitNoResult(); - } - - #endregion - - #region Create - - /// - /// Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public Task CreateAsync(TResource resource) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - - var tx = new TransactionBuilder(Endpoint).Create(resource).ToBundle(); - - return executeAsync(tx,new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - /// - /// Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public TResource Create(TResource resource) where TResource : Resource - { - return CreateAsync(resource).WaitResult(); - } - - /// - /// Conditionally Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The criteria - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var tx = new TransactionBuilder(Endpoint).Create(resource, condition).ToBundle(); - - return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - public TResource Create(TResource resource, SearchParams condition) where TResource : Resource - { - return CreateAsync(resource, condition).WaitResult(); - } - - #endregion - - #region Conformance - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - [Obsolete("The Conformance operation has been replaced by the CapabilityStatement", false)] - public CapabilityStatement Conformance(SummaryType? summary = null) - { - return CapabilityStatement(summary); - } - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - public Task CapabilityStatementAsync(SummaryType? summary = null) - { - var tx = new TransactionBuilder(Endpoint).CapabilityStatement(summary).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - public CapabilityStatement CapabilityStatement(SummaryType? summary = null) - { - return CapabilityStatementAsync(summary).WaitResult(); - } - #endregion - - #region History - - /// - /// Retrieve the version history for a specific resource type - /// - /// The type of Resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(resourceType, null, since, pageSize, summary); - } - /// - /// Retrieve the version history for a specific resource type - /// - /// The type of Resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle TypeHistory(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return TypeHistoryAsync(resourceType, since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a specific resource type - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// The type of Resource to get the history for - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task TypeHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() - { - string collection = typeof(TResource).GetCollectionName(); - return internalHistoryAsync(collection, null, since, pageSize, summary); - } - /// - /// Retrieve the version history for a specific resource type - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// The type of Resource to get the history for - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle TypeHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() - { - return TypeHistoryAsync(since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task HistoryAsync(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - return internalHistoryAsync(id.ResourceType, id.Id, since, pageSize, summary); - } - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle History(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(location, since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task HistoryAsync(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(new Uri(location, UriKind.Relative), since, pageSize, summary); - } - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle History(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(location, since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the full version history of the server - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Indicates whether the returned resources should just contain the minimal set of elements - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task WholeSystemHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(null, null, since, pageSize, summary); - } - /// - /// Retrieve the full version history of the server - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Indicates whether the returned resources should just contain the minimal set of elements - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle WholeSystemHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return WholeSystemHistoryAsync(since, pageSize, summary).WaitResult(); - } - private Task internalHistoryAsync(string resourceType = null, string id = null, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - TransactionBuilder history; - - if(resourceType == null) - history = new TransactionBuilder(Endpoint).ServerHistory(summary,pageSize,since); - else if(id == null) - history = new TransactionBuilder(Endpoint).CollectionHistory(resourceType, summary,pageSize,since); - else - history = new TransactionBuilder(Endpoint).ResourceHistory(resourceType,id, summary,pageSize,since); - - return executeAsync(history.ToBundle(), HttpStatusCode.OK); - } - private Bundle internalHistory(string resourceType = null, string id = null, DateTimeOffset? since = null, - int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(resourceType, id, since, pageSize, summary).WaitResult(); - } - - #endregion - - #region Transaction - - /// - /// Send a set of creates, updates and deletes to the server to be processed in one transaction - /// - /// The bundled creates, updates and deleted - /// A bundle as returned by the server after it has processed the transaction, or null - /// if an error occurred. - public Task TransactionAsync(Bundle bundle) - { - if (bundle == null) throw new ArgumentNullException(nameof(bundle)); - - var tx = new TransactionBuilder(Endpoint).Transaction(bundle).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - /// - /// Send a set of creates, updates and deletes to the server to be processed in one transaction - /// - /// The bundled creates, updates and deleted - /// A bundle as returned by the server after it has processed the transaction, or null - /// if an error occurred. - public Bundle Transaction(Bundle bundle) - { - return TransactionAsync(bundle).WaitResult(); - } - - #endregion - - #region Operation - - public Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - return internalOperationAsync(operationName, parameters: parameters, useGet: useGet); - } - - public Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false) - { - return WholeSystemOperationAsync(operationName, parameters, useGet).WaitResult(); - } - - - public Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) - where TResource : Resource - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - // [WMR 20160421] GetResourceNameForType is obsolete - // var typeName = ModelInfo.GetResourceNameForType(typeof(TResource)); - var typeName = ModelInfo.GetFhirTypeNameForType(typeof(TResource)); - - return TypeOperationAsync(operationName, typeName, parameters, useGet: useGet); - } - public Resource TypeOperation(string operationName, Parameters parameters = null, - bool useGet = false) where TResource : Resource - { - return TypeOperationAsync(operationName, parameters, useGet).WaitResult(); - } - - - - public Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false) - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - if (typeName == null) throw Error.ArgumentNull(nameof(typeName)); - - return internalOperationAsync(operationName, typeName, parameters: parameters, useGet: useGet); - } - public Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false) - { - return TypeOperationAsync(operationName, typeName, parameters, useGet).WaitResult(); - } - - - - public Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - - return internalOperationAsync(operationName, id.ResourceType, id.Id, id.VersionId, parameters, useGet); - } - public Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - return InstanceOperationAsync(location, operationName, parameters, useGet).WaitResult(); - } - - - - public Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(location), operationName, parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - public Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - return OperationAsync(location, operationName, parameters, useGet).WaitResult(); - } - - - public Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false) - { - if (operation == null) throw Error.ArgumentNull(nameof(operation)); - - var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(operation), parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - public Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false) - { - return OperationAsync(operation, parameters, useGet).WaitResult(); - } - - - - private Task internalOperationAsync(string operationName, string type = null, string id = null, string vid = null, - Parameters parameters = null, bool useGet = false) - { - // Brian: Not sure why we would create this parameters object as empty. - // I would imagine that a null parameters object is different to an empty one? - // EK: What else could we do? POST an empty body? We cannot use GET unless the caller indicates this is an - // idempotent call.... - // MV: (related to issue #419): we only provide an empty parameter when we are not performing a GET operation. In r4 it will be allowed - // to provide an empty body in POST operations. In that case the line of code can be deleted. - if (parameters == null && !useGet) parameters = new Parameters(); - - Bundle tx; - - if (type == null) - tx = new TransactionBuilder(Endpoint).ServerOperation(operationName, parameters, useGet).ToBundle(); - else if (id == null) - tx = new TransactionBuilder(Endpoint).TypeOperation(type, operationName, parameters, useGet).ToBundle(); - else - tx = new TransactionBuilder(Endpoint).ResourceOperation(type, id, vid, operationName, parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - - private Resource internalOperation(string operationName, string type = null, string id = null, - string vid = null, Parameters parameters = null, bool useGet = false) - { - return internalOperationAsync(operationName, type, id, vid, parameters, useGet).WaitResult(); - } - - #endregion - - #region Get - - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Resource Get(Uri url) - { - return GetAsync(url).WaitResult(); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public async Task GetAsync(Uri url) - { - if (url == null) throw Error.ArgumentNull(nameof(url)); - - var tx = new TransactionBuilder(Endpoint).Get(url).ToBundle(); - return await executeAsync(tx, HttpStatusCode.OK).ConfigureAwait(false); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Resource Get(string url) - { - return GetAsync(url).WaitResult(); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Task GetAsync(string url) - { - if (url == null) throw Error.ArgumentNull(nameof(url)); - - return GetAsync(new Uri(url, UriKind.RelativeOrAbsolute)); - } - - #endregion - - - - - private ResourceIdentity verifyResourceIdentity(Uri location, bool needId, bool needVid) - { - var result = new ResourceIdentity(location); - - if (result.ResourceType == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the resource type in its path"); - if (needId && result.Id == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the logical id in its path"); - if (needVid && !result.HasVersion) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the version id in its path"); - - return result; - } - - - // TODO: Depending on type of response, update identity & always update lastupdated? - - private void updateIdentity(Resource resource, ResourceIdentity identity) - { - if (resource.Meta == null) resource.Meta = new Meta(); - - if (resource.Id == null) - { - resource.Id = identity.Id; - resource.VersionId = identity.VersionId; - } - } - - - private void setResourceBase(Resource resource, string baseUri) - { - resource.ResourceBase = new Uri(baseUri); - - if (resource is Bundle) - { - var bundle = resource as Bundle; - foreach (var entry in bundle.Entry.Where(e => e.Resource != null)) - entry.Resource.ResourceBase = new Uri(baseUri, UriKind.RelativeOrAbsolute); - } - } - + public HttpWebResponse LastResponse { get { return (Requester as Requester)?.LastResponse; } } /// /// Called just before the Http call is done @@ -1028,7 +99,7 @@ private void setResourceBase(Resource resource, string baseUri) /// /// The request as it is about to be sent to the server /// The data in the body of the request as it is about to be sent to the server - protected virtual void BeforeRequest(HttpWebRequest rawRequest, byte[] body) + protected virtual void BeforeRequest(HttpWebRequest rawRequest, byte[] body) { // Default implementation: call event OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); @@ -1044,102 +115,8 @@ protected virtual void AfterResponse(HttpWebResponse webResponse, byte[] body) // Default implementation: call event OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); } - - // Original - private TResource execute(Bundle tx, HttpStatusCode expect) where TResource : Model.Resource - { - return executeAsync(tx, new[] { expect }).WaitResult(); - } - public Task executeAsync(Model.Bundle tx, HttpStatusCode expect) where TResource : Model.Resource - { - return executeAsync(tx, new[] { expect }); - } - // Original - private TResource execute(Bundle tx, IEnumerable expect) where TResource : Resource - { - return executeAsync(tx, expect).WaitResult(); - } - - private async Task executeAsync(Bundle tx, IEnumerable expect) where TResource : Resource - { - verifyServerVersion(); - - var request = tx.Entry[0]; - var response = await _requester.ExecuteAsync(request).ConfigureAwait(false); - - if (!expect.Select(sc => ((int)sc).ToString()).Contains(response.Response.Status)) - { - Enum.TryParse(response.Response.Status, out HttpStatusCode code); - throw new FhirOperationException("Operation concluded successfully, but the return status {0} was unexpected".FormatWith(response.Response.Status), code); - } - - Resource result; - - // Special feature: if ReturnFullResource was requested (using the Prefer header), but the server did not return the resource - // (or it returned an OperationOutcome) - explicitly go out to the server to get the resource and return it. - // This behavior is only valid for PUT and POST requests, where the server may device whether or not to return the full body of the alterend resource. - var noRealBody = response.Resource == null || (response.Resource is OperationOutcome && string.IsNullOrEmpty(response.Resource.Id)); - if (noRealBody && isPostOrPut(request) - && PreferredReturn == Prefer.ReturnRepresentation && response.Response.Location != null - && new ResourceIdentity(response.Response.Location).IsRestResourceIdentity()) // Check that it isn't an operation too - { - result = await GetAsync(response.Response.Location).ConfigureAwait(false); - } - else - result = response.Resource; - - if (result == null) return null; - - // We have a success code (2xx), we have a body, but the body may not be of the type we expect. - if (!(result is TResource)) - { - // If this is an operationoutcome, that may still be allright. Keep the OperationOutcome in - // the LastResult, and return null as the result. Otherwise, throw. - if (result is OperationOutcome) - return null; - - var message = String.Format("Operation {0} on {1} expected a body of type {2} but a {3} was returned", response.Request.Method, - response.Request.Url, typeof(TResource).Name, result.GetType().Name); - throw new FhirOperationException(message, _requester.LastResponse.StatusCode); - } - else - return result as TResource; - } - private bool isPostOrPut(Bundle.EntryComponent interaction) - { - var method = interaction.Request.Method; - return method == Bundle.HTTPVerb.POST || method == Bundle.HTTPVerb.PUT; - } - - - private bool versionChecked = false; - - private void verifyServerVersion() - { - if (!VerifyFhirVersion) return; - - if (versionChecked) return; - versionChecked = true; // So we can now start calling Conformance() without getting into a loop - - CapabilityStatement conf = null; - try - { - conf = CapabilityStatement(SummaryType.True); // don't get the full version as its huge just to read the fhir version - } - catch (FormatException) - { - // Mmmm...cannot even read the body. Probably not so good. - throw Error.NotSupported("Cannot read the conformance statement of the server to verify FHIR version compatibility"); - } - - if (!conf.FhirVersion.StartsWith(ModelInfo.Version)) - { - throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); - } - } } - public class BeforeRequestEventArgs : EventArgs { public BeforeRequestEventArgs(HttpWebRequest rawRequest, byte[] body) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClientSearch.cs b/src/Hl7.Fhir.Core/Rest/FhirClientSearch.cs index ad253753dc..393e7572c9 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClientSearch.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClientSearch.cs @@ -14,7 +14,7 @@ namespace Hl7.Fhir.Rest { - public partial class FhirClient + public abstract partial class BaseFhirClient { #region Search Execution diff --git a/src/Hl7.Fhir.Core/Rest/FhirOperationException.cs b/src/Hl7.Fhir.Core/Rest/FhirOperationException.cs index bd8525e24e..16bc429956 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirOperationException.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirOperationException.cs @@ -32,14 +32,14 @@ public class FhirOperationException : Exception /// /// /// - public HttpStatusCode Status { get; set; } + public HttpStatusCode? Status { get; set; } /// /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. /// The http status code associated with the message - public FhirOperationException(string message, HttpStatusCode status) + public FhirOperationException(string message, HttpStatusCode? status) : base(message) { Status = status; @@ -52,7 +52,7 @@ public FhirOperationException(string message, HttpStatusCode status) /// The error message that explains the reason for the exception. /// The http status code associated with the message /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public FhirOperationException(string message, HttpStatusCode status, Exception inner) + public FhirOperationException(string message, HttpStatusCode? status, Exception inner) : base(message, inner) { Status = status; @@ -65,7 +65,7 @@ public FhirOperationException(string message, HttpStatusCode status, Exception i /// The http status code associated with the message /// The outcome of the operation . /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public FhirOperationException(string message, HttpStatusCode status, OperationOutcome outcome, Exception inner) + public FhirOperationException(string message, HttpStatusCode? status, OperationOutcome outcome, Exception inner) : base(message, inner) { Outcome = outcome; @@ -78,7 +78,7 @@ public FhirOperationException(string message, HttpStatusCode status, OperationOu /// The message that describes the error. /// The http status code associated with the message /// The outcome of the operation . - public FhirOperationException(string message, HttpStatusCode status, OperationOutcome outcome) + public FhirOperationException(string message, HttpStatusCode? status, OperationOutcome outcome) : base(message) { Outcome = outcome; diff --git a/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs index ec532047dc..d429734f4e 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.Rest.Http { internal static class EntryToHttpExtensions { - public static HttpRequestMessage ToHttpRequest(this Bundle.EntryComponent entry, + public static HttpRequestMessage ToHttpRequestMessage(this Bundle.EntryComponent entry, SearchParameterHandling? handlingPreference, Prefer? returnPreference, ResourceFormat format, bool useFormatParameter, bool CompressRequestBody) { System.Diagnostics.Debug.WriteLine("{0}: {1}", entry.Request.Method, entry.Request.Url); diff --git a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index 0986f610fb..8d1f6dda11 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -16,15 +16,14 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Hl7.Fhir.Rest.Http { - public partial class FhirClient : IFhirCompatibleClient + public partial class FhirClient : BaseFhirClient, IFhirCompatibleClient { - private Requester _requester; - /// /// Creates a new client using a default endpoint /// If the endpoint does not end with a slash (/), it will be added. @@ -33,28 +32,28 @@ public partial class FhirClient : IFhirCompatibleClient /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically /// + /// /// /// If parameter is set to true the first time a request is made to the server a /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirClient(Uri endpoint, bool verifyFhirVersion = false) + public FhirClient(Uri endpoint, HttpMessageHandler messageHandler = null, bool verifyFhirVersion = false) { - if (endpoint == null) throw new ArgumentNullException("endpoint"); - - if (!endpoint.OriginalString.EndsWith("/")) - endpoint = new Uri(endpoint.OriginalString + "/"); - - if (!endpoint.IsAbsoluteUri) throw new ArgumentException("endpoint", "Endpoint must be absolute"); - - Endpoint = endpoint; + Endpoint = GetValidatedEndpoint(endpoint); + VerifyFhirVersion = verifyFhirVersion; - _requester = new Requester(Endpoint) + // If user does not supply message handler, add decompression strategy in default handler. + var handler = messageHandler ?? new HttpClientHandler() { - + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; - VerifyFhirVersion = verifyFhirVersion; + var requester = new Requester(Endpoint, handler); + Requester = requester; + + // Expose default request headers to user. + RequestHeaders = requester.Client.DefaultRequestHeaders; } @@ -66,1419 +65,54 @@ public FhirClient(Uri endpoint, bool verifyFhirVersion = false) /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically /// + /// /// /// If parameter is set to true the first time a request is made to the server a /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirClient(string endpoint, bool verifyFhirVersion = false) - : this(new Uri(endpoint), verifyFhirVersion) - { - } - - #region << Client Communication Defaults (PreferredFormat, UseFormatParam, Timeout, ReturnFullResource) >> - public bool VerifyFhirVersion - { - get; - set; - } - - /// - /// The preferred format of the content to be used when communicating with the FHIR server (XML or JSON) - /// - public ResourceFormat PreferredFormat - { - get { return _requester.PreferredFormat; } - set { _requester.PreferredFormat = value; } - } - - /// - /// When passing the content preference, use the _format parameter instead of the request header - /// - public bool UseFormatParam - { - get { return _requester.UseFormatParameter; } - set { _requester.UseFormatParameter = value; } - } - - /// - /// The timeout (in milliseconds) to be used when making calls to the FHIR server - /// - public int Timeout - { - get { return _requester.Timeout; } - set { _requester.Timeout = value; } - } - - - //private bool _returnFullResource = false; - - /// - /// Should calls to Create, Update and transaction operations return the whole updated content? - /// - /// Refer to specification section 2.1.0.5 (Managing Return Content) - [Obsolete("In STU3 this is no longer a true/false option, use the PreferredReturn property instead")] - public bool ReturnFullResource - { - get => _requester.PreferredReturn == Prefer.ReturnRepresentation; - set => _requester.PreferredReturn = value ? Prefer.ReturnRepresentation : Prefer.ReturnMinimal; - } - - /// - /// Should calls to Create, Update and transaction operations return the whole updated content, - /// or an OperationOutcome? - /// - /// Refer to specification section 2.1.0.5 (Managing Return Content) - - public Prefer? PreferredReturn - { - get => _requester.PreferredReturn; - set => _requester.PreferredReturn = value; - } - - /// - /// Should server return which search parameters were supported after executing a search? - /// If true, the server should return an error for any unknown or unsupported parameter, otherwise - /// the server may ignore any unknown or unsupported parameter. - /// - public SearchParameterHandling? PreferredParameterHandling - { - get => _requester.PreferredParameterHandling; - set => _requester.PreferredParameterHandling = value; - } - - -#if NET_COMPRESSION - /// - /// This will do 2 things: - /// 1. Add the header Accept-Encoding: gzip, deflate - /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) - /// - public bool PreferCompressedResponses - { - get { return _requester.PreferCompressedResponses; } - set { _requester.PreferCompressedResponses = value; } - } - /// - /// Compress any Request bodies - /// (warning, if a server does not handle compressed requests you will get a 415 response) - /// - public bool CompressRequestBody + public FhirClient(string endpoint, HttpMessageHandler messageHandler,bool verifyFhirVersion = false) + : this(new Uri(endpoint), messageHandler, verifyFhirVersion) { - get { return _requester.CompressRequestBody; } - set { _requester.CompressRequestBody = value; } } -#endif - /// - /// The last transaction result that was executed on this connection to the FHIR server + /// Default request headers that can be modified to persist default headers to internal client. /// - public Bundle.ResponseComponent LastResult => _requester.LastResult?.Response; - - public ParserSettings ParserSettings - { - get { return _requester.ParserSettings; } - set { _requester.ParserSettings = value; } - } - + public HttpRequestHeaders RequestHeaders { get; protected set; } public byte[] LastBody => LastResult?.GetBody(); public string LastBodyAsText => LastResult?.GetBodyAsText(); - public Resource LastBodyAsResource => _requester.LastResult?.Resource; + public Resource LastBodyAsResource => Requester.LastResult?.Resource; /// - /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient + /// Returns the HttpRequestMessage as it was last constructed to execute a call on the FhirClient /// - public HttpRequestMessage LastRequest { get { return _requester.LastRequest; } } + public HttpRequestMessage LastRequest { get { return (Requester as Http.Requester)?.LastRequest; } } /// - /// Returns the HttpWebResponse as it was last received during a call on the FhirClient + /// Returns the HttpResponseMessage as it was last received during a call on the FhirClient /// - /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is + /// Note that the FhirClient will have read the body data from the HttpResponseMessage, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpResponseMessage LastResponse { get { return _requester.LastResponse; } } - - /// - /// The default endpoint for use with operations that use discrete id/version parameters - /// instead of explicit uri endpoints. This will always have a trailing "/" - /// - public Uri Endpoint - { - get; - private set; - } - - #endregion - - #region Read + public HttpResponseMessage LastResponse { get { return (Requester as Http.Requester)?.LastResponse; } } /// - /// Fetches a typed resource from a FHIR resource endpoint. + /// Override dispose in order to clean up request headers tied to disposed requester. /// - /// The url of the Resource to fetch. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// - /// The requested resource. This operation will throw an exception - /// if the resource has been deleted or does not exist. - /// The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - /// - /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. - /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome - public Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource + /// + protected override void Dispose(bool disposing) { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - Bundle tx; - - if (!id.HasVersion) - { - var ri = new TransactionBuilder(Endpoint).Read(id.ResourceType, id.Id, ifNoneMatch, ifModifiedSince); - tx = ri.ToBundle(); - } - else + if (!disposedValue) { - tx = new TransactionBuilder(Endpoint).VRead(id.ResourceType, id.Id, id.VersionId).ToBundle(); - } - - return executeAsync(tx, HttpStatusCode.OK); - } - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. (refer to spec 2.1.0.5) If this is used, the client will throw an exception you need - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// - /// The requested resource. This operation will throw an exception - /// if the resource has been deleted or does not exist. - /// The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - /// - /// Since ResourceLocation is a subclass of Uri, you may pass in ResourceLocations too. - /// This will occur if conditional request returns a status 304 and optionally an OperationOutcome - public TResource Read(Uri location, string ifNoneMatch = null, - DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); - } - - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// The requested resource - /// This operation will throw an exception - /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - public Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(new Uri(location, UriKind.RelativeOrAbsolute), ifNoneMatch, ifModifiedSince); - } - /// - /// Fetches a typed resource from a FHIR resource endpoint. - /// - /// The url of the Resource to fetch as a string. This can be a Resource id url or a version-specific - /// Resource url. - /// The (weak) ETag to use in a conditional read. Optional. - /// Last modified since date in a conditional read. Optional. - /// The type of resource to read. Resource or DomainResource is allowed if exact type is unknown - /// The requested resource - /// This operation will throw an exception - /// if the resource has been deleted or does not exist. The specified may be relative or absolute, if it is an absolute - /// url, it must reference an address within the endpoint. - public TResource Read(string location, string ifNoneMatch = null, - DateTimeOffset? ifModifiedSince = null) where TResource : Resource - { - return ReadAsync(location, ifNoneMatch, ifModifiedSince).WaitResult(); - } - - #endregion - - #region Refresh - - /// - /// Refreshes the data in the resource passed as an argument by re-reading it from the server - /// - /// - /// The resource for which you want to get the most recent version. - /// A new instance of the resource, containing the most up-to-date data - /// This function will not overwrite the argument with new data, rather it will return a new instance - /// which will have the newest data, leaving the argument intact. - public Task RefreshAsync(TResource current) where TResource : Resource - { - if (current == null) throw Error.ArgumentNull(nameof(current)); - - return ReadAsync(ResourceIdentity.Build(current.TypeName, current.Id)); - } - /// - /// Refreshes the data in the resource passed as an argument by re-reading it from the server - /// - /// - /// The resource for which you want to get the most recent version. - /// A new instance of the resource, containing the most up-to-date data - /// This function will not overwrite the argument with new data, rather it will return a new instance - /// which will have the newest data, leaving the argument intact. - public TResource Refresh(TResource current) where TResource : Resource - { - return RefreshAsync(current).WaitResult(); - } - - #endregion - - #region Update - - /// - /// Update (or create) a resource - /// - /// The resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be - /// created. - public Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (resource.Id == null) throw Error.Argument(nameof(resource), "Resource needs a non-null Id to send the update to"); - - var upd = new TransactionBuilder(Endpoint); - - if (versionAware && resource.HasVersionId) - upd.Update(resource.Id, resource, versionId: resource.VersionId); - else - upd.Update(resource.Id, resource); - - return internalUpdateAsync(resource, upd.ToBundle()); - } - /// - /// Update (or create) a resource - /// - /// The resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the resource does not yet exist - and the server allows client-assigned id's - a new resource with the given id will be - /// created. - public TResource Update(TResource resource, bool versionAware = false) where TResource : Resource - { - return UpdateAsync(resource, versionAware).WaitResult(); - } - - /// - /// Conditionally update (or create) a resource - /// - /// The resource to update - /// Criteria used to locate the resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. - public Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var upd = new TransactionBuilder(Endpoint); - - if (versionAware && resource.HasVersionId) - upd.Update(condition, resource, versionId: resource.VersionId); - else - upd.Update(condition, resource); - - return internalUpdateAsync(resource, upd.ToBundle()); - } - /// - /// Conditionally update (or create) a resource - /// - /// The resource to update - /// Criteria used to locate the resource to update - /// If true, asks the server to verify we are updating the latest version - /// The type of resource that is being updated - /// The body of the updated resource, unless ReturnFullResource is set to "false" - /// Throws an exception when the update failed, in particular when an update conflict is detected and the server returns a HTTP 409. - /// If the criteria passed in condition do not match a resource a new resource with a server assigned id will be created. - public TResource Update(TResource resource, SearchParams condition, bool versionAware = false) - where TResource : Resource - { - return UpdateAsync(resource, condition, versionAware).WaitResult(); - } - private Task internalUpdateAsync(TResource resource, Bundle tx) where TResource : Resource - { - resource.ResourceBase = Endpoint; - - // This might be an update of a resource that doesn't yet exist, so accept a status Created too - return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - private TResource internalUpdate(TResource resource, Bundle tx) where TResource : Resource - { - return internalUpdateAsync(resource, tx).WaitResult(); - } - #endregion - - #region Delete - - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public async System.Threading.Tasks.Task DeleteAsync(Uri location) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - var tx = new TransactionBuilder(Endpoint).Delete(id.ResourceType, id.Id).ToBundle(); - - await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public void Delete(Uri location) - { - DeleteAsync(location).WaitNoResult(); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public System.Threading.Tasks.Task DeleteAsync(string location) - { - return DeleteAsync(new Uri(location, UriKind.Relative)); - } - /// - /// Delete a resource at the given endpoint. - /// - /// endpoint of the resource to delete - /// Throws an exception when the delete failed, though this might - /// just mean the server returned 404 (the resource didn't exist before) or 410 (the resource was - /// already deleted). - public void Delete(string location) - { - DeleteAsync(location).WaitNoResult(); - } - - - /// - /// Delete a resource - /// - /// The resource to delete - public async System.Threading.Tasks.Task DeleteAsync(Resource resource) - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (resource.Id == null) throw Error.Argument(nameof(resource), "Entry must have an id"); - - await DeleteAsync(resource.ResourceIdentity(Endpoint).WithoutVersion()).ConfigureAwait(false); - } - /// - /// Delete a resource - /// - /// The resource to delete - public void Delete(Resource resource) - { - DeleteAsync(resource).WaitNoResult(); - } - - /// - /// Conditionally delete a resource - /// - /// The type of resource to delete - /// Criteria to use to match the resource to delete. - public async System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition) - { - if (resourceType == null) throw Error.ArgumentNull(nameof(resourceType)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var tx = new TransactionBuilder(Endpoint).Delete(resourceType, condition).ToBundle(); - await executeAsync(tx, new[] { HttpStatusCode.OK, HttpStatusCode.NoContent }).ConfigureAwait(false); - } - /// - /// Conditionally delete a resource - /// - /// The type of resource to delete - /// Criteria to use to match the resource to delete. - public void Delete(string resourceType, SearchParams condition) - { - DeleteAsync(resourceType, condition).WaitNoResult(); - } - - #endregion - - #region Create - - /// - /// Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public Task CreateAsync(TResource resource) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - - var tx = new TransactionBuilder(Endpoint).Create(resource).ToBundle(); - - return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - /// - /// Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public TResource Create(TResource resource) where TResource : Resource - { - return CreateAsync(resource).WaitResult(); - } - - /// - /// Conditionally Create a resource on a FHIR endpoint - /// - /// The resource instance to create - /// The criteria - /// The resource as created on the server, or an exception if the create failed. - /// The type of resource to create - public Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (condition == null) throw Error.ArgumentNull(nameof(condition)); - - var tx = new TransactionBuilder(Endpoint).Create(resource, condition).ToBundle(); - - return executeAsync(tx, new[] { HttpStatusCode.Created, HttpStatusCode.OK }); - } - public TResource Create(TResource resource, SearchParams condition) where TResource : Resource - { - return CreateAsync(resource, condition).WaitResult(); - } - - #endregion - - #region Conformance - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - [Obsolete("The Conformance operation has been replaced by the CapabilityStatement", false)] - public CapabilityStatement Conformance(SummaryType? summary = null) - { - return CapabilityStatement(summary); - } - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - public Task CapabilityStatementAsync(SummaryType? summary = null) - { - var tx = new TransactionBuilder(Endpoint).CapabilityStatement(summary).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - - /// - /// Get a conformance statement for the system - /// - /// A Conformance resource. Throws an exception if the operation failed. - public CapabilityStatement CapabilityStatement(SummaryType? summary = null) - { - return CapabilityStatementAsync(summary).WaitResult(); - } - #endregion - - #region History - - /// - /// Retrieve the version history for a specific resource type - /// - /// The type of Resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(resourceType, null, since, pageSize, summary); - } - /// - /// Retrieve the version history for a specific resource type - /// - /// The type of Resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle TypeHistory(string resourceType, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return TypeHistoryAsync(resourceType, since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a specific resource type - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// The type of Resource to get the history for - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task TypeHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() - { - string collection = typeof(TResource).GetCollectionName(); - return internalHistoryAsync(collection, null, since, pageSize, summary); - } - /// - /// Retrieve the version history for a specific resource type - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// The type of Resource to get the history for - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle TypeHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) where TResource : Resource, new() - { - return TypeHistoryAsync(since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task HistoryAsync(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - return internalHistoryAsync(id.ResourceType, id.Id, since, pageSize, summary); - } - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle History(Uri location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(location, since, pageSize, summary).WaitResult(); - } - - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task HistoryAsync(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(new Uri(location, UriKind.Relative), since, pageSize, summary); - } - /// - /// Retrieve the version history for a resource at a given location - /// - /// The address of the resource to get the history for - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Asks the server to only provide the fields defined for the summary - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle History(string location, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return HistoryAsync(location, since, pageSize, summary).WaitResult(); - } + if (disposing) + { + this.RequestHeaders = null; + base.Dispose(); + } - /// - /// Retrieve the full version history of the server - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Indicates whether the returned resources should just contain the minimal set of elements - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Task WholeSystemHistoryAsync(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(null, null, since, pageSize, summary); + disposedValue = true; + } } - /// - /// Retrieve the full version history of the server - /// - /// Optional. Returns only changes after the given date - /// Optional. Asks server to limit the number of entries per page returned - /// Indicates whether the returned resources should just contain the minimal set of elements - /// A bundle with the history for the indicated instance, may contain both - /// ResourceEntries and DeletedEntries. - public Bundle WholeSystemHistory(DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - return WholeSystemHistoryAsync(since, pageSize, summary).WaitResult(); - } - private Task internalHistoryAsync(string resourceType = null, string id = null, DateTimeOffset? since = null, int? pageSize = null, SummaryType summary = SummaryType.False) - { - TransactionBuilder history; - - if (resourceType == null) - history = new TransactionBuilder(Endpoint).ServerHistory(summary, pageSize, since); - else if (id == null) - history = new TransactionBuilder(Endpoint).CollectionHistory(resourceType, summary, pageSize, since); - else - history = new TransactionBuilder(Endpoint).ResourceHistory(resourceType, id, summary, pageSize, since); - - return executeAsync(history.ToBundle(), HttpStatusCode.OK); - } - private Bundle internalHistory(string resourceType = null, string id = null, DateTimeOffset? since = null, - int? pageSize = null, SummaryType summary = SummaryType.False) - { - return internalHistoryAsync(resourceType, id, since, pageSize, summary).WaitResult(); - } - - #endregion - - #region Transaction - - /// - /// Send a set of creates, updates and deletes to the server to be processed in one transaction - /// - /// The bundled creates, updates and deleted - /// A bundle as returned by the server after it has processed the transaction, or null - /// if an error occurred. - public Task TransactionAsync(Bundle bundle) - { - if (bundle == null) throw new ArgumentNullException(nameof(bundle)); - - var tx = new TransactionBuilder(Endpoint).Transaction(bundle).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - /// - /// Send a set of creates, updates and deletes to the server to be processed in one transaction - /// - /// The bundled creates, updates and deleted - /// A bundle as returned by the server after it has processed the transaction, or null - /// if an error occurred. - public Bundle Transaction(Bundle bundle) - { - return TransactionAsync(bundle).WaitResult(); - } - - #endregion - - #region Operation - - public Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - return internalOperationAsync(operationName, parameters: parameters, useGet: useGet); - } - - public Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false) - { - return WholeSystemOperationAsync(operationName, parameters, useGet).WaitResult(); - } - - - public Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) - where TResource : Resource - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - // [WMR 20160421] GetResourceNameForType is obsolete - // var typeName = ModelInfo.GetResourceNameForType(typeof(TResource)); - var typeName = ModelInfo.GetFhirTypeNameForType(typeof(TResource)); - - return TypeOperationAsync(operationName, typeName, parameters, useGet: useGet); - } - public Resource TypeOperation(string operationName, Parameters parameters = null, - bool useGet = false) where TResource : Resource - { - return TypeOperationAsync(operationName, parameters, useGet).WaitResult(); - } - - - - public Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false) - { - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - if (typeName == null) throw Error.ArgumentNull(nameof(typeName)); - - return internalOperationAsync(operationName, typeName, parameters: parameters, useGet: useGet); - } - public Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false) - { - return TypeOperationAsync(operationName, typeName, parameters, useGet).WaitResult(); - } - - - - public Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - var id = verifyResourceIdentity(location, needId: true, needVid: false); - - return internalOperationAsync(operationName, id.ResourceType, id.Id, id.VersionId, parameters, useGet); - } - public Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - return InstanceOperationAsync(location, operationName, parameters, useGet).WaitResult(); - } - - - - public Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - if (location == null) throw Error.ArgumentNull(nameof(location)); - if (operationName == null) throw Error.ArgumentNull(nameof(operationName)); - - var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(location), operationName, parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - public Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false) - { - return OperationAsync(location, operationName, parameters, useGet).WaitResult(); - } - - - public Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false) - { - if (operation == null) throw Error.ArgumentNull(nameof(operation)); - - var tx = new TransactionBuilder(Endpoint).EndpointOperation(new RestUrl(operation), parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - public Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false) - { - return OperationAsync(operation, parameters, useGet).WaitResult(); - } - - - - private Task internalOperationAsync(string operationName, string type = null, string id = null, string vid = null, - Parameters parameters = null, bool useGet = false) - { - // Brian: Not sure why we would create this parameters object as empty. - // I would imagine that a null parameters object is different to an empty one? - // EK: What else could we do? POST an empty body? We cannot use GET unless the caller indicates this is an - // idempotent call.... - // MV: (related to issue #419): we only provide an empty parameter when we are not performing a GET operation. In r4 it will be allowed - // to provide an empty body in POST operations. In that case the line of code can be deleted. - if (parameters == null && !useGet) parameters = new Parameters(); - - Bundle tx; - - if (type == null) - tx = new TransactionBuilder(Endpoint).ServerOperation(operationName, parameters, useGet).ToBundle(); - else if (id == null) - tx = new TransactionBuilder(Endpoint).TypeOperation(type, operationName, parameters, useGet).ToBundle(); - else - tx = new TransactionBuilder(Endpoint).ResourceOperation(type, id, vid, operationName, parameters, useGet).ToBundle(); - - return executeAsync(tx, HttpStatusCode.OK); - } - - private Resource internalOperation(string operationName, string type = null, string id = null, - string vid = null, Parameters parameters = null, bool useGet = false) - { - return internalOperationAsync(operationName, type, id, vid, parameters, useGet).WaitResult(); - } - - #endregion - - #region Get - - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Resource Get(Uri url) - { - return GetAsync(url).WaitResult(); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public async Task GetAsync(Uri url) - { - if (url == null) throw Error.ArgumentNull(nameof(url)); - - var tx = new TransactionBuilder(Endpoint).Get(url).ToBundle(); - return await executeAsync(tx, HttpStatusCode.OK).ConfigureAwait(false); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Resource Get(string url) - { - return GetAsync(url).WaitResult(); - } - /// - /// Invoke a general GET on the server. If the operation fails, then this method will throw an exception - /// - /// A relative or absolute url. If the url is absolute, it has to be located within the endpoint of the client. - /// A resource that is the outcome of the operation. The type depends on the definition of the operation at the given url - /// parameters to the method are simple, and are in the URL, and this is a GET operation - public Task GetAsync(string url) - { - if (url == null) throw Error.ArgumentNull(nameof(url)); - - return GetAsync(new Uri(url, UriKind.RelativeOrAbsolute)); - } - - #endregion - - - - - private ResourceIdentity verifyResourceIdentity(Uri location, bool needId, bool needVid) - { - var result = new ResourceIdentity(location); - - if (result.ResourceType == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the resource type in its path"); - if (needId && result.Id == null) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the logical id in its path"); - if (needVid && !result.HasVersion) throw Error.Argument(nameof(location), "Must be a FHIR REST url containing the version id in its path"); - - return result; - } - - - // TODO: Depending on type of response, update identity & always update lastupdated? - - private void updateIdentity(Resource resource, ResourceIdentity identity) - { - if (resource.Meta == null) resource.Meta = new Meta(); - - if (resource.Id == null) - { - resource.Id = identity.Id; - resource.VersionId = identity.VersionId; - } - } - - - private void setResourceBase(Resource resource, string baseUri) - { - resource.ResourceBase = new Uri(baseUri); - - if (resource is Bundle) - { - var bundle = resource as Bundle; - foreach (var entry in bundle.Entry.Where(e => e.Resource != null)) - entry.Resource.ResourceBase = new Uri(baseUri, UriKind.RelativeOrAbsolute); - } - } - - // Original - private TResource execute(Bundle tx, HttpStatusCode expect) where TResource : Model.Resource - { - return executeAsync(tx, new[] { expect }).WaitResult(); - } - public Task executeAsync(Model.Bundle tx, HttpStatusCode expect) where TResource : Model.Resource - { - return executeAsync(tx, new[] { expect }); - } - // Original - private TResource execute(Bundle tx, IEnumerable expect) where TResource : Resource - { - return executeAsync(tx, expect).WaitResult(); - } - - private async Task executeAsync(Bundle tx, IEnumerable expect) where TResource : Resource - { - verifyServerVersion(); - - var request = tx.Entry[0]; - var response = await _requester.ExecuteAsync(request).ConfigureAwait(false); - - if (!expect.Select(sc => ((int)sc).ToString()).Contains(response.Response.Status)) - { - Enum.TryParse(response.Response.Status, out HttpStatusCode code); - throw new FhirOperationException("Operation concluded successfully, but the return status {0} was unexpected".FormatWith(response.Response.Status), code); - } - - Resource result; - - // Special feature: if ReturnFullResource was requested (using the Prefer header), but the server did not return the resource - // (or it returned an OperationOutcome) - explicitly go out to the server to get the resource and return it. - // This behavior is only valid for PUT and POST requests, where the server may device whether or not to return the full body of the alterend resource. - var noRealBody = response.Resource == null || (response.Resource is OperationOutcome && string.IsNullOrEmpty(response.Resource.Id)); - if (noRealBody && isPostOrPut(request) - && PreferredReturn == Prefer.ReturnRepresentation && response.Response.Location != null - && new ResourceIdentity(response.Response.Location).IsRestResourceIdentity()) // Check that it isn't an operation too - { - result = await GetAsync(response.Response.Location).ConfigureAwait(false); - } - else - result = response.Resource; - - if (result == null) return null; - - // We have a success code (2xx), we have a body, but the body may not be of the type we expect. - if (!(result is TResource)) - { - // If this is an operationoutcome, that may still be allright. Keep the OperationOutcome in - // the LastResult, and return null as the result. Otherwise, throw. - if (result is OperationOutcome) - return null; - - var message = String.Format("Operation {0} on {1} expected a body of type {2} but a {3} was returned", response.Request.Method, - response.Request.Url, typeof(TResource).Name, result.GetType().Name); - throw new FhirOperationException(message, _requester.LastResponse.StatusCode); - } - else - return result as TResource; - } - private bool isPostOrPut(Bundle.EntryComponent interaction) - { - var method = interaction.Request.Method; - return method == Bundle.HTTPVerb.POST || method == Bundle.HTTPVerb.PUT; - } - - - private bool versionChecked = false; - - private void verifyServerVersion() - { - if (!VerifyFhirVersion) return; - - if (versionChecked) return; - versionChecked = true; // So we can now start calling Conformance() without getting into a loop - - CapabilityStatement conf = null; - try - { - conf = CapabilityStatement(SummaryType.True); // don't get the full version as its huge just to read the fhir version - } - catch (FormatException) - { - // Mmmm...cannot even read the body. Probably not so good. - throw Error.NotSupported("Cannot read the conformance statement of the server to verify FHIR version compatibility"); - } - - if (!conf.FhirVersion.StartsWith(ModelInfo.Version)) - { - throw Error.NotSupported("This client support FHIR version {0}, but the server uses version {1}".FormatWith(ModelInfo.Version, conf.FhirVersion)); - } - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - this._requester.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - } - #endregion - - #region Search Execution - - /// - /// Search for Resources based on criteria specified in a Query resource - /// - /// The Query resource containing the search parameters - /// The type of resource to filter on (optional). If not specified, will search on all resource types. - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - public Task SearchAsync(SearchParams q, string resourceType = null) - { - var tx = new TransactionBuilder(Endpoint).Search(q, resourceType).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - /// - /// Search for Resources based on criteria specified in a Query resource - /// - /// The Query resource containing the search parameters - /// The type of resource to filter on (optional). If not specified, will search on all resource types. - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - public Bundle Search(SearchParams q, string resourceType = null) - { - return SearchAsync(q, resourceType).WaitResult(); - } - - #endregion - - #region Search by Parameters - - /// - /// Search for Resources based on criteria specified in a Query resource - /// - /// The Query resource containing the search parameters - /// The type of resource to filter on - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - public Task SearchAsync(SearchParams q) - where TResource : Resource - { - // [WMR 20160421] GetResourceNameForType is obsolete - // return Search(q, ModelInfo.GetResourceNameForType(typeof(TResource))); - return SearchAsync(q, ModelInfo.GetFhirTypeNameForType(typeof(TResource))); - } - /// - /// Search for Resources based on criteria specified in a Query resource - /// - /// The Query resource containing the search parameters - /// The type of resource to filter on - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - public Bundle Search(SearchParams q) where TResource : Resource - { - return SearchAsync(q).WaitResult(); - } - - #endregion - - #region Generic Criteria Search - - /// - /// Search for Resources of a certain type that match the given criteria - /// - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// The type of resource to list - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - where TResource : Resource, new() - { - // [WMR 20160421] GetResourceNameForType is obsolete - // return Search(ModelInfo.GetResourceNameForType(typeof(TResource)), criteria, includes, pageSize, summary); - return SearchAsync(ModelInfo.GetFhirTypeNameForType(typeof(TResource)), criteria, includes, pageSize, summary, revIncludes); - } - /// - /// Search for Resources of a certain type that match the given criteria - /// - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// The type of resource to list - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - where TResource : Resource, new() - { - return SearchAsync(criteria, includes, pageSize, summary, revIncludes).WaitResult(); - } - - #endregion - - #region Non-Generic Criteria Search - - /// - /// Search for Resources of a certain type that match the given criteria - /// - /// The type of resource to search for - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - - return SearchAsync(toQuery(criteria, includes, pageSize, summary, revIncludes), resource); - } - /// - /// Search for Resources of a certain type that match the given criteria - /// - /// The type of resource to search for - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - { - return SearchAsync(resource, criteria, includes, pageSize, summary, revIncludes).WaitResult(); - } - - #endregion - - #region Whole system search - - /// - /// Search for Resources across the whole server that match the given criteria - /// - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - { - return SearchAsync(toQuery(criteria, includes, pageSize, summary, revIncludes)); - } - - /// - /// Search for Resources across the whole server that match the given criteria - /// - /// Optional. The search parameters to filter the resources on. Each - /// given string is a combined key/value pair (separated by '=') - /// Optional. A list of include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. Whether to include only return a summary of the resources in the Bundle - /// Optional. A list of reverse include paths - /// A Bundle with all resources found by the search, or an empty Bundle if none were found. - /// All parameters are optional, leaving all parameters empty will return an unfiltered list - /// of all resources of the given Resource type - public Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = null, - SummaryType? summary = null, string[] revIncludes = null) - { - return WholeSystemSearchAsync(criteria, includes, pageSize, summary, revIncludes).WaitResult(); - } - - #endregion - - #region Generic Search by ID - - /// - /// Search for resources based on a resource's id. - /// - /// The id of the resource to search for - /// Zero or more include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. A list of reverse include paths - /// The type of resource to search for - /// A Bundle with the BundleEntry as identified by the id parameter or an empty - /// Bundle if the resource wasn't found. - /// This operation is similar to Read, but additionally, - /// it is possible to specify include parameters to include resources in the bundle that the - /// returned resource refers to. - public Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = null, - string[] revIncludes = null) where TResource : Resource, new() - { - if (id == null) throw Error.ArgumentNull(nameof(id)); - - return SearchByIdAsync(typeof(TResource).GetCollectionName(), id, includes, pageSize, revIncludes); - } - - /// - /// Search for resources based on a resource's id. - /// - /// The id of the resource to search for - /// Zero or more include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. A list of reverse include paths - /// The type of resource to search for - /// A Bundle with the BundleEntry as identified by the id parameter or an empty - /// Bundle if the resource wasn't found. - /// This operation is similar to Read, but additionally, - /// it is possible to specify include parameters to include resources in the bundle that the - /// returned resource refers to. - public Bundle SearchById(string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) where TResource : Resource, new() - { - return SearchByIdAsync(id, includes, pageSize, revIncludes).WaitResult(); - } - - #endregion - - #region Non-Generic Search by Id - - /// - /// Search for resources based on a resource's id. - /// - /// The type of resource to search for - /// The id of the resource to search for - /// Zero or more include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. A list of reverse include paths - /// A Bundle with the BundleEntry as identified by the id parameter or an empty - /// Bundle if the resource wasn't found. - /// This operation is similar to Read, but additionally, - /// it is possible to specify include parameters to include resources in the bundle that the - /// returned resource refers to. - public Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) - { - if (resource == null) throw Error.ArgumentNull(nameof(resource)); - if (id == null) throw Error.ArgumentNull(nameof(id)); - - string criterium = "_id=" + id; - return SearchAsync(toQuery(new string[] { criterium }, includes, pageSize, summary: null, revIncludes: revIncludes), resource); - } - /// - /// Search for resources based on a resource's id. - /// - /// The type of resource to search for - /// The id of the resource to search for - /// Zero or more include paths - /// Optional. Asks server to limit the number of entries per page returned - /// Optional. A list of reverse include paths - /// A Bundle with the BundleEntry as identified by the id parameter or an empty - /// Bundle if the resource wasn't found. - /// This operation is similar to Read, but additionally, - /// it is possible to specify include parameters to include resources in the bundle that the - /// returned resource refers to. - public Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = null, string[] revIncludes = null) - { - return SearchByIdAsync(resource, id, includes, pageSize, revIncludes).WaitResult(); - } - - #endregion - - #region Continue - - /// - /// Uses the FHIR paging mechanism to go navigate around a series of paged result Bundles - /// - /// The bundle as received from the last response - /// Optional. Direction to browse to, default is the next page of results. - /// A bundle containing a new page of results based on the browse direction, or null if - /// the server did not have more results in that direction. - public Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next) - { - if (current == null) throw Error.ArgumentNull(nameof(current)); - if (current.Link == null) return null; - - Uri continueAt = null; - - switch (direction) - { - case PageDirection.First: - continueAt = current.FirstLink; break; - case PageDirection.Previous: - continueAt = current.PreviousLink; break; - case PageDirection.Next: - continueAt = current.NextLink; break; - case PageDirection.Last: - continueAt = current.LastLink; break; - } - - if (continueAt != null) - { - var tx = new TransactionBuilder(Endpoint).Get(continueAt).ToBundle(); - return executeAsync(tx, HttpStatusCode.OK); - } - else - { - // Return a null bundle, can not return simply null because this is a task - Bundle nullValue = null; - return System.Threading.Tasks.Task.FromResult(nullValue); - } - } - /// - /// Uses the FHIR paging mechanism to go navigate around a series of paged result Bundles - /// - /// The bundle as received from the last response - /// Optional. Direction to browse to, default is the next page of results. - /// A bundle containing a new page of results based on the browse direction, or null if - /// the server did not have more results in that direction. - public Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next) - { - return ContinueAsync(current, direction).WaitResult(); - } - - #endregion - - #region Private Methods - - private SearchParams toQuery(string[] criteria, string[] includes, int? pageSize, SummaryType? summary, string[] revIncludes) - { - var q = new SearchParams() - { - Count = pageSize - }; - - if (includes != null) - foreach (var inc in includes) q.Include.Add(inc); - - if (revIncludes != null) - foreach (var revInc in revIncludes) q.RevInclude.Add(revInc); - - if (criteria != null) - { - foreach (var crit in criteria) - { - var keyVal = crit.SplitLeft('='); - q.Add(keyVal.Item1, keyVal.Item2); - } - } - - if (summary != null) - q.Summary = summary.Value; - - return q; - } - - #endregion } } diff --git a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs index 92dac72348..2a955930bd 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs @@ -55,9 +55,9 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res { result.Response.SetBody(body); - if (IsBinaryResponse(result.Response.Location, contentType.ToString())) + if (Rest.HttpToEntryExtensions.IsBinaryResponse(result.Response.Location, contentType.ToString())) { - result.Resource = makeBinaryResource(body, contentType.ToString()); + result.Resource = Rest.HttpToEntryExtensions.MakeBinaryResource(body, contentType.ToString()); if (result.Response.Location != null) { var ri = new ResourceIdentity(result.Response.Location); @@ -69,8 +69,8 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res } else { - var bodyText = DecodeBody(body, charEncoding); - var resource = parseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); + var bodyText = Rest.HttpToEntryExtensions.DecodeBody(body, charEncoding); + var resource = Rest.HttpToEntryExtensions.ParseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); result.Resource = resource; if (result.Response.Location != null) @@ -81,111 +81,6 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res return result; } - internal static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) - { - Resource result= null; - - var fhirType = ContentType.GetResourceFormatFromContentType(contentType); - - if (fhirType == ResourceFormat.Unknown) - throw new UnsupportedBodyTypeException( - "Endpoint returned a body with contentType '{0}', while a valid FHIR xml/json body type was expected. Is this a FHIR endpoint?" - .FormatWith(contentType), contentType, bodyText); - - if (!SerializationUtil.ProbeIsJson(bodyText) && !SerializationUtil.ProbeIsXml(bodyText)) - throw new UnsupportedBodyTypeException( - "Endpoint said it returned '{0}', but the body is not recognized as either xml or json.".FormatWith(contentType), contentType, bodyText); - - try - { - if (fhirType == ResourceFormat.Json) - result = new FhirJsonParser(settings).Parse(bodyText); - else - result = new FhirXmlParser(settings).Parse(bodyText); - } - catch(FormatException fe) - { - if (throwOnFormatException) throw fe; - return null; - } - - return result; - } - - internal static bool IsBinaryResponse(string responseUri, string contentType) - { - if (!string.IsNullOrEmpty(contentType) - && (ContentType.XML_CONTENT_HEADERS.Contains(contentType.ToLower()) - || ContentType.JSON_CONTENT_HEADERS.Contains(contentType.ToLower()) - ) - ) - return false; - - if (ResourceIdentity.IsRestResourceIdentity(responseUri)) - { - var id = new ResourceIdentity(responseUri); - - if (id.ResourceType != ResourceType.Binary.ToString()) return false; - - if (id.Id != null && Id.IsValidValue(id.Id)) return true; - if (id.VersionId != null && Id.IsValidValue(id.VersionId)) return true; - } - - return false; - } - - internal static string DecodeBody(byte[] body, Encoding enc) - { - if (body == null) return null; - if (enc == null) enc = Encoding.UTF8; - - // [WMR 20160421] Explicit disposal - // return (new StreamReader(new MemoryStream(body), enc, true)).ReadToEnd(); - using (var stream = new MemoryStream(body)) - using (var reader = new StreamReader(stream, enc, true)) - { - return reader.ReadToEnd(); - } - } - - internal static Binary makeBinaryResource(byte[] data, string contentType) - { - var binary = new Binary(); - - binary.Content = data; - binary.ContentType = contentType; - - return binary; - } - - public static string GetBodyAsText(this Bundle.ResponseComponent interaction) - { - var body = interaction.GetBody(); - - if (body != null) - return DecodeBody(body, Encoding.UTF8); - else - return null; - } - - private class Body - { - public byte[] Data; - } - - - public static byte[] GetBody(this Bundle.ResponseComponent interaction) - { - var body = interaction.Annotation(); - return body != null ? body.Data : null; - } - - internal static void SetBody(this Bundle.ResponseComponent interaction, byte[] data) - { - interaction.RemoveAnnotations(); - interaction.AddAnnotation(new Body { Data = data }); - } - internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpResponseHeaders headers) { foreach (var header in headers) @@ -193,40 +88,5 @@ internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpR interaction.AddExtension(EXTENSION_RESPONSE_HEADER, new FhirString(header.Key + ":" + header.Value)); } } - - public static IEnumerable> GetHeaders(this Bundle.ResponseComponent interaction) - { - foreach (var headerExt in interaction.GetExtensions(EXTENSION_RESPONSE_HEADER)) - { - if(headerExt.Value != null && headerExt.Value is FhirString) - { - var header = ((FhirString)headerExt.Value).Value; - - if (header != null) - { - yield return header.SplitLeft(':'); - } - } - } - } - - - public static IEnumerable GetHeader(this Bundle.ResponseComponent interaction, string header) - { - return interaction.GetHeaders().Where(h => h.Item1 == header).Select(h => h.Item2); - } - } - - - public class UnsupportedBodyTypeException : Exception - { - public string BodyType { get; set; } - - public string Body { get; set; } - public UnsupportedBodyTypeException(string message, string mimeType, string body) : base(message) - { - BodyType = mimeType; - Body = body; - } } } diff --git a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs index ee09c931c5..7c844d9072 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.Rest.Http { - internal class Requester : IDisposable + internal class Requester : IRequester, IDisposable { public Uri BaseUrl { get; private set; } public HttpClient Client { get; private set; } @@ -43,15 +43,11 @@ internal class Requester : IDisposable public ParserSettings ParserSettings { get; set; } - public Requester(Uri baseUrl) + public Requester(Uri baseUrl, HttpMessageHandler messageHandler) { - var clientHandler = new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; - BaseUrl = baseUrl; - Client = new HttpClient(clientHandler); + Client = new HttpClient(messageHandler); + Client.DefaultRequestHeaders.Add("User-Agent", ".NET FhirClient for FHIR " + Model.ModelInfo.Version); UseFormatParameter = false; PreferredFormat = ResourceFormat.Xml; @@ -63,6 +59,7 @@ public Requester(Uri baseUrl) public Bundle.EntryComponent LastResult { get; private set; } + public HttpStatusCode? LastStatusCode => LastResponse?.StatusCode; public HttpResponseMessage LastResponse { get; private set; } public HttpRequestMessage LastRequest { get; private set; } public Action BeforeRequest { get; set; } @@ -80,7 +77,7 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) compressRequestBody = CompressRequestBody; // PCL doesn't support compression at the moment - using (var requestMessage = interaction.ToHttpRequest(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody)) + using (var requestMessage = interaction.ToHttpRequestMessage(this.PreferredParameterHandling, this.PreferredReturn, PreferredFormat, UseFormatParameter, compressRequestBody)) { if (PreferCompressedResponses) { diff --git a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs index 3b982a1312..4efbe4d3a2 100644 --- a/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/HttpToEntryExtensions.cs @@ -58,7 +58,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse respons if (IsBinaryResponse(response.ResponseUri.OriginalString, contentType)) { - result.Resource = makeBinaryResource(body, contentType); + result.Resource = MakeBinaryResource(body, contentType); if (result.Response.Location != null) { var ri = new ResourceIdentity(result.Response.Location); @@ -71,7 +71,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpWebResponse respons else { var bodyText = DecodeBody(body, charEncoding); - var resource = parseResource(bodyText, contentType, parserSettings, throwOnFormatException); + var resource = ParseResource(bodyText, contentType, parserSettings, throwOnFormatException); result.Resource = resource; if (result.Response.Location != null) @@ -120,7 +120,7 @@ private static Encoding getCharacterEncoding(HttpWebResponse response) return result; } - private static Resource parseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) + internal static Resource ParseResource(string bodyText, string contentType, ParserSettings settings, bool throwOnFormatException) { Resource result= null; @@ -189,7 +189,7 @@ internal static string DecodeBody(byte[] body, Encoding enc) } } - private static Binary makeBinaryResource(byte[] data, string contentType) + internal static Binary MakeBinaryResource(byte[] data, string contentType) { var binary = new Binary(); diff --git a/src/Hl7.Fhir.Core/Rest/IRequester.cs b/src/Hl7.Fhir.Core/Rest/IRequester.cs new file mode 100644 index 0000000000..57ce646ad4 --- /dev/null +++ b/src/Hl7.Fhir.Core/Rest/IRequester.cs @@ -0,0 +1,40 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Hl7.Fhir.Rest +{ + public interface IRequester + { + Bundle.EntryComponent Execute(Bundle.EntryComponent interaction); + Task ExecuteAsync(Bundle.EntryComponent interaction); + + Bundle.EntryComponent LastResult { get; } + bool UseFormatParameter { get; set; } + ResourceFormat PreferredFormat { get; set; } + int Timeout { get; set; } // In milliseconds + + Prefer? PreferredReturn { get; set; } + SearchParameterHandling? PreferredParameterHandling { get; set; } + + /// + /// This will do 2 things: + /// 1. Add the header Accept-Encoding: gzip, deflate + /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) + /// + bool PreferCompressedResponses { get; set; } + /// + /// Compress any Request bodies + /// (warning, if a server does not handle compressed requests you will get a 415 response) + /// + bool CompressRequestBody { get; set; } + + ParserSettings ParserSettings { get; set; } + HttpStatusCode? LastStatusCode { get; } + } +} diff --git a/src/Hl7.Fhir.Core/Rest/Requester.cs b/src/Hl7.Fhir.Core/Rest/Requester.cs index a5a4ab5772..2da1522bb5 100644 --- a/src/Hl7.Fhir.Core/Rest/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Requester.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.Rest { - internal class Requester + internal class Requester : IRequester { public Uri BaseUrl { get; private set; } @@ -55,6 +55,7 @@ public Requester(Uri baseUrl) public Bundle.EntryComponent LastResult { get; private set; } + public HttpStatusCode? LastStatusCode => LastResponse?.StatusCode; public HttpWebResponse LastResponse { get; private set; } public HttpWebRequest LastRequest { get; private set; } public Action BeforeRequest { get; set; } diff --git a/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs index d4f4fb1c2d..37cbcc2cd4 100644 --- a/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Source/ResourceResolverTests.cs @@ -18,7 +18,6 @@ using Hl7.Fhir.Serialization; using System.Linq; using Hl7.Fhir.Utility; -using System.Net.Http; namespace Hl7.Fhir.Specification.Tests { @@ -95,13 +94,13 @@ public int Status public TestFhirClient(Uri endpoint) : base(endpoint) { Status = 1; } - protected override void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) + protected override void BeforeRequest(HttpWebRequest rawRequest, byte[] body) { Status = 2; base.BeforeRequest(rawRequest, body); } - protected override void AfterResponse(HttpResponseMessage webResponse, byte[] body) + protected override void AfterResponse(HttpWebResponse webResponse, byte[] body) { Status = 3; base.AfterResponse(webResponse, body); From 4ff7dab7b15795962a7a72b290af65b5bf25016e Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 16:35:25 -0600 Subject: [PATCH 21/39] Copied modified tests to Http specific test directory. --- .../Rest/Http/FhirClientTests.cs | 1107 +++++++++++++++++ .../Rest/Http/OperationsTests.cs | 186 +++ .../Rest/Http/ReadAsyncTests.cs | 51 + .../Rest/Http/RequesterTests.cs | 74 ++ .../Rest/Http/SearchAsyncTests.cs | 182 +++ .../Rest/Http/TransactionBuilderTests.cs | 85 ++ .../Http/UpdateRefreshDeleteAsyncTests.cs | 64 + 7 files changed, 1749 insertions(+) create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs new file mode 100644 index 0000000000..349be126d8 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs @@ -0,0 +1,1107 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Net; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Serialization; +using Hl7.Fhir.Model; + +namespace Hl7.Fhir.Tests.Rest +{ + [TestClass] + public class FhirClientTests + { + //public static Uri testEndpoint = new Uri("http://spark-dstu3.furore.com/fhir"); + //public static Uri testEndpoint = new Uri("http://localhost.fiddler:1396/fhir"); + //public static Uri testEndpoint = new Uri("https://localhost:44346/fhir"); + //public static Uri testEndpoint = new Uri("http://localhost:1396/fhir"); + public static Uri testEndpoint = new Uri("http://test.fhir.org/r3"); + //public static Uri testEndpoint = new Uri("http://vonk.furore.com"); + //public static Uri testEndpoint = new Uri("https://api.fhir.me"); + //public static Uri testEndpoint = new Uri("http://fhirtest.uhn.ca/baseDstu3"); + //public static Uri testEndpoint = new Uri("http://localhost:49911/fhir"); + //public static Uri testEndpoint = new Uri("http://sqlonfhir-stu3.azurewebsites.net/fhir"); + + public static Uri TerminologyEndpoint = new Uri("http://ontoserver.csiro.au/stu3-latest"); + + [TestInitialize] + public void TestInitialize() + { + System.Diagnostics.Trace.WriteLine("Testing against fhir server: " + testEndpoint); + } + + public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) + { + System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); + + if (b.Entry != null) + { + foreach (var item in b.Entry) + { + if (item.Request != null) + System.Diagnostics.Trace.WriteLine(String.Format(" {0}: {1}", item.Request.Method.ToString(), item.Request.Url)); + if (item.Response != null && item.Response.Status != null) + System.Diagnostics.Trace.WriteLine(String.Format(" {0}", item.Response.Status)); + if (item.Resource != null && item.Resource is Hl7.Fhir.Model.DomainResource) + { + if (item.Resource.Meta != null && item.Resource.Meta.LastUpdated.HasValue) + System.Diagnostics.Trace.WriteLine(String.Format(" Last Updated:{0}, [{1}]", item.Resource.Meta.LastUpdated.Value, item.Resource.Meta.LastUpdated.Value.ToString("HH:mm:ss.FFFF"))); + Hl7.Fhir.Rest.ResourceIdentity ri = new Hl7.Fhir.Rest.ResourceIdentity(item.FullUrl); + System.Diagnostics.Trace.WriteLine(String.Format(" {0}", (item.Resource as Hl7.Fhir.Model.DomainResource).ResourceIdentity(ri.BaseUri).OriginalString)); + } + } + } + } + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void FetchConformance() + { + FhirClient client = new FhirClient(testEndpoint); + client.ParserSettings.AllowUnrecognizedEnums = true; + + var entry = client.CapabilityStatement(); + + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + entry = client.CapabilityStatement(SummaryType.True); + + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + } + + + [TestMethod, TestCategory("FhirClient")] + public void VerifyFormatParamProcessing() + { + // XML + Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("xml")); + Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("text/xml")); + Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/xml")); + Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/xml+fhir")); + Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/fhir+xml")); + + // JSON + Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("json")); + Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("text/json")); + Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/json")); + Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/json+fhir")); + Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/fhir+json")); + } + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ReadWithFormat() + { + FhirClient client = new FhirClient(testEndpoint); + + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; + + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); + } + + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void Read() + { + FhirClient client = new FhirClient(testEndpoint); + + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); + + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } + + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); + + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); + } + + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ReadRelative() + { + FhirClient client = new FhirClient(testEndpoint); + + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + } + +#if NO_ASYNC_ANYMORE + [TestMethod, TestCategory("FhirClient")] + public void ReadRelativeAsync() + { + FhirClient client = new FhirClient(testEndpoint); + + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } +#endif + + public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip"; + } + } + + public static void Compression_OnBeforeRequestDeflate(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "deflate"; + } + } + + public static void Compression_OnBeforeRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + } + } + + [TestMethod, Ignore] // Something does not work with the gzip + [TestCategory("FhirClient"), + TestCategory("IntegrationTest")] + public void Search() + { + FhirClient client = new FhirClient(testEndpoint); + Bundle result; + + client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestGZip; + client.OnAfterResponse += Client_OnAfterResponse; + + result = client.Search(); + client.OnAfterResponse -= Client_OnAfterResponse; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + + client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + + result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; + + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + + ResourceIdentity ri = withSubject.ResourceIdentity(); + + // TODO: The include on Grahame's server doesn't currently work + //result = client.SearchById(ri.Id, + // includes: new string[] { "DiagnosticReport:subject" }); + //Assert.IsNotNull(result); + + //Assert.AreEqual(2, result.Entry.Count); // should have subject too + + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(DiagnosticReport).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(Patient).GetCollectionName())); + + + client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; + + result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); + + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } + + private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) + { + // Test that the response was compressed + Assert.AreEqual("gzip", e.RawResponse.Headers[HttpResponseHeader.ContentEncoding]); + } + +#if NO_ASYNC_ANYMORE + [TestMethod, TestCategory("FhirClient")] + public void SearchAsync() + { + FhirClient client = new FhirClient(testEndpoint); + Bundle result; + + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); + + Assert.AreEqual(2, result.Entry.Count); // should have subject too + + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); + + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } +#endif + + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void Paging() + { + FhirClient client = new FhirClient(testEndpoint); + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void PagingInJson() + { + FhirClient client = new FhirClient(testEndpoint); + client.PreferredFormat = ResourceFormat.Json; + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } + + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CreateAndFullRepresentation() + { + FhirClient client = new FhirClient(testEndpoint); + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + + var pat = client.Read("Patient/glossy"); + ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); + pat.Id = null; + pat.Identifier.Clear(); + var patC = client.Create(pat); + Assert.IsNotNull(patC); + + client.PreferredReturn = Prefer.ReturnMinimal; + patC = client.Create(pat); + + Assert.IsNull(patC); + + if (client.LastBody != null) + { + var returned = client.LastBodyAsResource; + Assert.IsTrue(returned is OperationOutcome); + } + + // Now validate this resource + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + Parameters p = new Parameters(); + // p.Add("mode", new FhirString("create")); + p.Add("resource", pat); + OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); + Assert.IsNotNull(ooI); + } + + + + + private Uri createdTestPatientUrl = null; + + /// + /// This test is also used as a "setup" test for the History test. + /// If you change the number of operations in here, this will make the History test fail. + /// + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CreateEditDelete() + { + FhirClient client = new FhirClient(testEndpoint); + + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + // client.CompressRequestBody = true; + + var pat = client.Read("Patient/example"); + pat.Id = null; + pat.Identifier.Clear(); + pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); + + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); + + var fe = client.Create(pat); // Create as we are not providing the ID to be used. + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.Meta.VersionId); + createdTestPatientUrl = fe.ResourceIdentity(); + + fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.Update(fe); + + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); + Assert.AreEqual(2, fe2.Identifier.Count); + + fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.Update(fe); + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Identifier.Count); + + client.Delete(fe3); + + try + { + // Get most recent version + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.Fail(); + } + catch(FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); + Assert.AreEqual("410", client.LastResult.Status); + } + } + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 + public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() + { + FhirClient client = new FhirClient(testEndpoint); + var observation = new Observation(); + observation.Status = ObservationStatus.Preliminary; + observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); + observation.Value = new SimpleQuantity() + { + System = "http://unitsofmeasure.org", + Value = 23, + Code = "mg", + Unit = "miligram" + }; + observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); + var fe = client.Create(observation); + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + } + +#if NO_ASYNC_ANYMORE + /// + /// This test is also used as a "setup" test for the History test. + /// If you change the number of operations in here, this will make the History test fail. + /// + [TestMethod, TestCategory("FhirClient")] + public void CreateEditDeleteAsync() + { + var furore = new Organization + { + Name = "Furore", + Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, + Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } + }; + + FhirClient client = new FhirClient(testEndpoint); + var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; + + var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; + + Assert.IsNotNull(furore); + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.SelfLink); + Assert.AreNotEqual(fe.Id, fe.SelfLink); + Assert.IsNotNull(fe.Tags); + Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); + Assert.AreEqual(fe.Tags.First(), tags[0]); + createdTestOrganizationUrl = fe.Id; + + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.UpdateAsync(fe, refresh: true).Result; + + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); + Assert.AreEqual(2, fe2.Resource.Identifier.Count); + + Assert.IsNotNull(fe2.Tags); + Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); + Assert.AreEqual(fe2.Tags.First(), tags[0]); + + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Resource.Identifier.Count); + + client.DeleteAsync(fe3).Wait(); + + try + { + // Get most recent version + fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; + Assert.Fail(); + } + catch + { + Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); + } + } +#endif + + /// + /// This test will fail if the system records AuditEvents + /// and counts them in the WholeSystemHistory + /// + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + public void History() + { + System.Threading.Thread.Sleep(500); + DateTimeOffset timestampBeforeCreationAndDeletions = DateTimeOffset.Now; + + CreateEditDelete(); // this test does a create, update, update, delete (4 operations) + + FhirClient client = new FhirClient(testEndpoint); + + System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); + + Bundle history = client.History(createdTestPatientUrl); + Assert.IsNotNull(history); + DebugDumpBundle(history); + + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + //// Now, assume no one is quick enough to insert something between now and the next + //// tests.... + + + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); + history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); + history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) + { + System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); + history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); + // Check that the number of patients that have been created is what we expected + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + } + } + + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestWithParam() + { + var client = new FhirClient(testEndpoint); + var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); + Assert.IsNotNull(res); + } + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ManipulateMeta() + { + FhirClient client = new FhirClient(testEndpoint); + + var pat = new Patient(); + pat.Meta = new Meta(); + var key = new Random().Next(); + pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); + pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); + + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); + Assert.IsNotNull(wsm); + + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + + + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); + + // Meta should be present on created patient + verifyMeta(pat2.Meta, false, key); + + // Should be present when doing instance Meta() + var par = client.Meta(loc); + verifyMeta(par, false, key); + + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); + + // Should be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + + // Now add some additional meta to the patient + + var newMeta = new Meta(); + newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); + + + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); + + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); + + // New and old meta should be present on Meta() + par = client.Meta(loc); + verifyMeta(par, true, key); + + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, true, key); + + // New and old meta should be present when doing system Meta() + par = client.Meta(); + verifyMeta(par, true, key); + + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); + + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); + + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); + verifyMeta(par, false, key); + + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); + + // clear out the client that we created, no point keeping it around + client.Delete(pat4); + + // Should no longer be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + } + + + private void verifyMeta(Meta meta, bool hasNew, int key) + { + Assert.IsTrue(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsTrue(meta.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsTrue(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + + if (hasNew) + { + Assert.IsTrue(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsTrue(meta.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsTrue(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + } + + if (!hasNew) + { + Assert.IsFalse(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(meta.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + } + } + + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestSearchByPersonaCode() + { + var client = new FhirClient(testEndpoint); + + var pats = + client.Search( + new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); + var pat = (Patient)pats.Entry.First().Resource; + } + + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CreateDynamic() + { + Resource furore = new Organization + { + Name = "Furore", + Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, + Telecom = new List { + new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + } + }; + + FhirClient client = new FhirClient(testEndpoint); + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); + + var fe = client.Create(furore); + Assert.IsNotNull(fe); + } + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CallsCallbacks() + { + FhirClient client = new FhirClient(testEndpoint); + client.ParserSettings.AllowUnrecognizedEnums = true; + + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; + + client.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; + + client.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/glossy"); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); + + var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); + + Assert.IsTrue(bodyText.Contains(" e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; + + var result = client.Read("Patient/glossy"); + Assert.IsNotNull(result); + result.Id = null; + result.Meta = null; + + client.PreferredReturn = Prefer.ReturnRepresentation; + minimal = false; + var posted = client.Create(result); + Assert.IsNotNull(posted, "Patient example not found"); + + minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true + posted = client.Create(result); + Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); + + client.PreferredReturn = Prefer.ReturnMinimal; + minimal = true; + posted = client.Create(result); + Assert.IsNull(posted); + } + + void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) + { + throw new NotImplementedException(); + } + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. + public void TestReceiveHtmlIsHandled() + { + var client = new FhirClient("http://spark.furore.com/"); // an address that returns html + + try + { + var pat = client.Read("Patient/1"); + } + catch (FhirOperationException fe) + { + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + } + } + + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestRefresh() + { + var client = new FhirClient(testEndpoint); + var result = client.Read("Patient/example"); + + var orig = result.Name[0].FamilyElement.Value; + + result.Name[0].FamilyElement.Value = "overwritten name"; + + result = client.Refresh(result); + + Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + } + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestReceiveErrorStatusWithHtmlIsHandled() + { + var client = new FhirClient("http://spark.furore.com/"); // an address that returns Status 500 with HTML in its body + + try + { + var pat = client.Read("Patient/1"); + Assert.Fail("Failed to throw an Exception on status 500"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.InternalServerError) + Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); + + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); + + if (client.LastResult.Status != "500") + Assert.Fail("LastResult.Status is not 500."); + + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + + string message = operationOutcome.Issue[0].Diagnostics; + if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to carry error message over into OperationOutcome"); + } + catch (Exception) + { + Assert.Fail("Failed to throw FhirOperationException on status 500"); + } + } + + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() + { + var client = new FhirClient("http://test.fhir.org/r3"); // an address that returns Status 404 with an OperationOutcome + + try + { + var pat = client.Read("Patient/doesnotexist"); + Assert.Fail("Failed to throw an Exception on status 404"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.NotFound) + Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); + + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); + + Bundle.ResponseComponent entryComponent = client.LastResult; + + if (entryComponent.Status != "404") + Assert.Fail("LastResult.Status is not 404."); + + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + } + catch (Exception e) + { + Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + } + } + + + + [TestMethod,Ignore] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void FhirVersionIsChecked() + { + var testEndpointDSTU2 = new Uri("http://spark-dstu2.furore.com/fhir"); + var testEndpointDSTU1 = new Uri("http://spark.furore.com/fhir"); + var testEndpointDSTU12 = new Uri("http://fhirtest.uhn.ca/baseDstu1"); + var testEndpointDSTU22 = new Uri("http://fhirtest.uhn.ca/baseDstu2"); + var testEndpointDSTU23 = new Uri("http://test.fhir.org/r3"); + + var client = new FhirClient(testEndpointDSTU1); + client.ParserSettings.AllowUnrecognizedEnums = true; + + CapabilityStatement p; + + try + { + client = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true); + client.ParserSettings.AllowUnrecognizedEnums = true; + p = client.CapabilityStatement(); + } + catch (FhirOperationException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } + catch (NotSupportedException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } + + client = new FhirClient(testEndpointDSTU23); + client.ParserSettings.AllowUnrecognizedEnums = true; + p = client.CapabilityStatement(); + + //client = new FhirClient(testEndpointDSTU2); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); + + //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); + + + client = new FhirClient(testEndpointDSTU12); + client.ParserSettings.AllowUnrecognizedEnums = true; + + try + { + p = client.CapabilityStatement(); + Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); + } + catch (Exception) + { + // OK + } + + } + + [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] + public void TestAuthenticationOnBefore() + { + FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); + validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => + { + e.RawRequest.Headers["Authorization"] = "Bearer bad-bearer"; + }; + try + { + var output = validationFhirClient.ValidateResource(new Patient()); + + } + catch(FhirOperationException ex) + { + Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } + } + + [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] + public void TestOperationEverything() + { + FhirClient client = new FhirClient(testEndpoint) + { + UseFormatParam = true, + PreferredFormat = ResourceFormat.Json + }; + + // GET operation $everything without parameters + var loc = client.TypeOperation("everything", null, true); + Assert.IsNotNull(loc); + + // POST operation $everything without parameters + loc = client.TypeOperation("everything", null, false); + Assert.IsNotNull(loc); + + // GET operation $everything with 1 parameter + // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. + // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); + // Assert.IsNotNull(loc); + + // POST operation $everything with 1 parameter + loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); + Assert.IsNotNull(loc); + } + + } + +} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs new file mode 100644 index 0000000000..ee03c198e4 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Hl7.Fhir.Rest; + +namespace Hl7.Fhir.Tests.Rest +{ + [TestClass] + public class OperationsTests + + { + string testEndpoint = FhirClientTests.testEndpoint.OriginalString; + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeTestPatientGetEverything() + { + var client = new FhirClient(testEndpoint); + var start = new FhirDateTime(2014,11,1); + var end = new FhirDateTime(2015,1,1); + var par = new Parameters().Add("start", start).Add("end", end); + var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient","example"), start, end); + Assert.IsTrue(bundle2.Entry.Any()); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeExpandExistingValueSet() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet","administrative-gender")); + Assert.IsTrue(vs.Expansion.Contains.Any()); + } + + + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeExpandParameterValueSet() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + + var vs = client.Read("ValueSet/administrative-gender"); + var vsX = client.ExpandValueSet(vs); + + Assert.IsTrue(vsX.Expansion.Contains.Any()); + } + + // [WMR 20170927] Chris Munro + // https://chat.fhir.org/#narrow/stream/implementers/subject/How.20to.20expand.20ValueSets.20with.20the.20C.23.20FHIR.20API.3F + //[TestMethod] + //[TestCategory("IntegrationTest")] + //[Ignore] + //public void TestExpandValueSet() + //{ + // const string endpoint = @"https://stu3.simplifier.net/open/"; + // var location = new FhirUri("https://stu3.simplifier.net/open/ValueSet/043d233c-4ecf-4802-a4ac-75d82b4291c2"); + // var client = new FhirClient(endpoint); + // var expandedValueSet = client.ExpandValueSet(location, null); + //} + + /// + /// http://hl7.org/fhir/valueset-operations.html#lookup + /// + [TestMethod] // Server returns internal server error + [TestCategory("IntegrationTest")] + public void InvokeLookupCoding() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); + + var expansion = client.ConceptLookup(coding: coding); + + // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } + + [TestMethod] // Server returns internal server error + [TestCategory("IntegrationTest")] + public void InvokeLookupCode() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); + + //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeById() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeByCanonical() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), + coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeWithVS() + { + var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var vs = client.Read("ValueSet/c80-facilitycodes"); + Assert.IsNotNull(vs); + + var result = client.ValidateCode(valueSet: vs, coding: coding); + Assert.IsTrue(result.Result?.Value == true); + } + + + [TestMethod]//returns 500: validation of slices is not done yet. + [TestCategory("IntegrationTest"), Ignore] + public void InvokeResourceValidation() + { + var client = new FhirClient(testEndpoint); + + var pat = client.Read("Patient/patient-uslab-example1"); + + try + { + var vresult = client.ValidateResource(pat, null, + new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); + Assert.Fail("Should have resulted in 400"); + } + catch(FhirOperationException fe) + { + Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); + Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() + { + string _endpoint = "https://api.hspconsortium.org/rpineda/open"; + + var client = new FhirClient(_endpoint); + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2020, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); + + var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); + var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); + + await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); + + var bundle = (Bundle)bundleTask.Result; + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = (Bundle)bundle2Task.Result; + Assert.IsTrue(bundle2.Entry.Any()); + } + } +} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs new file mode 100644 index 0000000000..94fac8a41e --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hl7.Fhir.Core.AsyncTests +{ + [TestClass] + public class ReadAsyncTests + { + private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + Patient p = await client.ReadAsync("/Patient/SMART-1288992"); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs new file mode 100644 index 0000000000..f3e9def4e8 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs @@ -0,0 +1,74 @@ +/* + * Copyright(c) 2017, Furore(info @furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Hl7.Fhir.Support; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; + +namespace Hl7.Fhir.Test +{ + [TestClass] + public class RequesterTests + { + [TestMethod] + public void SetsInteractionType() + { + // just try a few + var tx = new TransactionBuilder("http://myserver.org/fhir").ServerHistory(); + Assert.AreEqual(TransactionBuilder.InteractionType.History, tx.ToBundle().Entry[0].Annotation()); + + tx = new TransactionBuilder("http://myserver.org/fhir").ServerOperation("$everything", null); + Assert.AreEqual(TransactionBuilder.InteractionType.Operation, tx.ToBundle().Entry[0].Annotation()); + + var p = new Patient(); + tx = new TransactionBuilder("http://myserver.org/fhir").Create(p); + Assert.AreEqual(TransactionBuilder.InteractionType.Create, tx.ToBundle().Entry[0].Annotation()); + + tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); + Assert.AreEqual(TransactionBuilder.InteractionType.Search, tx.ToBundle().Entry[0].Annotation()); + } + + [TestMethod] + public void TestPreferSetting() + { + var p = new Patient(); + var tx = new TransactionBuilder("http://myserver.org/fhir") + .Create(p); + var b = tx.ToBundle(); + byte[] dummy; + + var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=minimal", request.Headers["Prefer"]); + + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=representation", request.Headers["Prefer"]); + + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("return=OperationOutcome", request.Headers["Prefer"]); + + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false, out dummy); + Assert.IsNull(request.Headers["Prefer"]); + + tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); + b = tx.ToBundle(); + + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("handling=lenient", request.Headers["Prefer"]); + + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.AreEqual("handling=strict", request.Headers["Prefer"]); + + request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); + Assert.IsNull(request.Headers["Prefer"]); + } + } +} + \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs new file mode 100644 index 0000000000..c1f98dc942 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs @@ -0,0 +1,182 @@ +using System; +using System.Linq; +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hl7.Fhir.Core.AsyncTests +{ + [TestClass] + public class SearchAsyncTests + { + private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task Search_UsingSearchParams_SearchReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var result1 = await client.SearchAsync(srch); + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void SearchSync_UsingSearchParams_SearchReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var result1 = client.Search(srch); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchMultiple_UsingSearchParams_SearchReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var srchParams = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var task1 = client.SearchAsync(srchParams); + var task2 = client.SearchAsync(srchParams); + var task3 = client.SearchAsync(srchParams); + + await Task.WhenAll(task1, task2, task3); + var result1 = task1.Result; + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchWithCriteria_SyncContinue_SearchReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var result1 = await client.SearchAsync(new []{"family=clark"}); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchWithCriteria_AsyncContinue_SearchReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var result1 = await client.SearchAsync(new[] { "family=clark" },null,1); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + Console.WriteLine("Fetching more results..."); + result1 = await client.ContinueAsync(result1); + } + + Console.WriteLine("Test Completed"); + } + } +} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs new file mode 100644 index 0000000000..29c6f77265 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014, Furore (info@furore.com) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE + */ + +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Hl7.Fhir.Support; +using Hl7.Fhir.Rest; +using Hl7.Fhir.Model; + +namespace Hl7.Fhir.Test +{ + [TestClass] + public class TransactionBuilderTests + { + [TestMethod] + public void TestBuild() + { + var p = new Patient(); + var b = new TransactionBuilder("http://myserver.org/fhir") + .Create(p) + .ResourceHistory("Patient","7") + .Delete("Patient","8") + .Read("Patient","9", versionId: "bla") + .ToBundle(); + + Assert.AreEqual(4, b.Entry.Count); + + Assert.AreEqual(Bundle.HTTPVerb.POST, b.Entry[0].Request.Method); + Assert.AreEqual(p, b.Entry[0].Resource); + + Assert.AreEqual(Bundle.HTTPVerb.GET, b.Entry[1].Request.Method); + Assert.AreEqual("http://myserver.org/fhir/Patient/7/_history", b.Entry[1].Request.Url); + + Assert.AreEqual(Bundle.HTTPVerb.DELETE, b.Entry[2].Request.Method); + Assert.AreEqual("http://myserver.org/fhir/Patient/8", b.Entry[2].Request.Url); + + Assert.AreEqual(Bundle.HTTPVerb.GET, b.Entry[3].Request.Method); + Assert.AreEqual("http://myserver.org/fhir/Patient/9", b.Entry[3].Request.Url); + Assert.AreEqual("W/\"bla\"", b.Entry[3].Request.IfNoneMatch); + } + + [TestMethod] + public void TestUrlEncoding() + { + var tx = new TransactionBuilder("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f"); + tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); + var b = tx.ToBundle(); + + byte[] body; + + var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false, body: out body); + + Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); + } + + + [TestMethod] + public void TestConditionalCreate() + { + var p = new Patient(); + var tx = new TransactionBuilder("http://myserver.org/fhir") + .Create(p, new SearchParams().Where("name=foobar")); + var b = tx.ToBundle(); + + Assert.AreEqual("name=foobar",b.Entry[0].Request.IfNoneExist); + } + + + [TestMethod] + public void TestConditionalUpdate() + { + var p = new Patient(); + var tx = new TransactionBuilder("http://myserver.org/fhir") + .Update(new SearchParams().Where("name=foobar"), p, versionId: "314"); + var b = tx.ToBundle(); + + Assert.AreEqual("W/\"314\"", b.Entry[0].Request.IfMatch); + } + } +} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs new file mode 100644 index 0000000000..0b0d811413 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hl7.Fhir.Core.AsyncTests +{ + [TestClass] + public class UpdateRefreshDeleteAsyncTests + { + private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() + { + var client = new FhirClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }; + + var pat = new Patient() + { + Name = new List() + { + new HumanName() + { + Given = new List() {"test_given"}, + Family = "test_family", + } + }, + Id = "async-test-patient" + }; + // Create the patient + Console.WriteLine("Creating patient..."); + Patient p = await client.UpdateAsync(pat); + Assert.IsNotNull(p); + + // Refresh the patient + Console.WriteLine("Refreshing patient..."); + await client.RefreshAsync(p); + + // Delete the patient + Console.WriteLine("Deleting patient..."); + await client.DeleteAsync(p); + + Console.WriteLine("Reading patient..."); + Func act = async () => + { + await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); + }; + + // VERIFY // + Assert.ThrowsException(act, "the patient is no longer on the server"); + + + Console.WriteLine("Test Completed"); + } + + } +} \ No newline at end of file From 78dfba5a36fa0d89cc61b7fa3c4d1a0f4c10a421 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 16:38:15 -0600 Subject: [PATCH 22/39] Changed Http/ to actually be changed tests. --- .../Rest/Http/FhirClientTests.cs | 1252 +++++++++-------- .../Rest/Http/OperationsTests.cs | 150 +- .../Rest/Http/ReadAsyncTests.cs | 38 +- .../Rest/Http/RequesterTests.cs | 30 +- .../Rest/Http/SearchAsyncTests.cs | 195 +-- .../Rest/Http/TransactionBuilderTests.cs | 4 +- .../Http/UpdateRefreshDeleteAsyncTests.cs | 57 +- 7 files changed, 891 insertions(+), 835 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs index 349be126d8..068909363e 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs @@ -43,7 +43,7 @@ public void TestInitialize() public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) { System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); - + if (b.Entry != null) { foreach (var item in b.Entry) @@ -66,30 +66,32 @@ public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FetchConformance() { - FhirClient client = new FhirClient(testEndpoint); - client.ParserSettings.AllowUnrecognizedEnums = true; - - var entry = client.CapabilityStatement(); - - Assert.IsNotNull(entry.Text); - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - entry = client.CapabilityStatement(SummaryType.True); - - Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); - Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); - Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.ParserSettings.AllowUnrecognizedEnums = true; + + var entry = client.CapabilityStatement(); + + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + entry = client.CapabilityStatement(SummaryType.True); + + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + } } @@ -114,87 +116,90 @@ public void VerifyFormatParamProcessing() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadWithFormat() { - FhirClient client = new FhirClient(testEndpoint); - - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; - var loc = client.Read("Patient/example"); - Assert.IsNotNull(loc); + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Read() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.Read("Location/1"); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); - Assert.AreEqual("1", loc.Id); - Assert.IsNotNull(loc.Meta.VersionId); + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); - var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc2); - Assert.AreEqual(loc2.Id, loc.Id); - Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); - try - { - var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); - Assert.AreEqual("404", client.LastResult.Status); - } + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } - var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc3); - var jsonSer = new FhirJsonSerializer(); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc3)); + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); - var loc4 = client.Read(loc.ResourceIdentity()); - Assert.IsNotNull(loc4); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc4)); + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); + } } - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadRelative() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.Read(new Uri("Location/1", UriKind.Relative)); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.Read(ri); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + } } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] public void ReadRelativeAsync() { - FhirClient client = new FhirClient(testEndpoint); - - var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.ReadAsync(ri).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - } + using(FhirClient client = new FhirClient(testEndpoint)) + { + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } + } #endif public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) @@ -203,7 +208,7 @@ public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestE { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip"); } } @@ -213,7 +218,7 @@ public static void Compression_OnBeforeRequestDeflate(object sender, BeforeReque { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "deflate"); } } @@ -223,105 +228,102 @@ public static void Compression_OnBeforeRequestZipOrDeflate(object sender, Before { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); } } [TestMethod, Ignore] // Something does not work with the gzip - [TestCategory("FhirClient"), + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Search() { - FhirClient client = new FhirClient(testEndpoint); - Bundle result; + using (FhirClient client = new FhirClient(testEndpoint)) + { + Bundle result; - client.CompressRequestBody = true; - client.OnBeforeRequest += Compression_OnBeforeRequestGZip; - client.OnAfterResponse += Client_OnAfterResponse; + client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestGZip; - result = client.Search(); - client.OnAfterResponse -= Client_OnAfterResponse; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.Search(); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; + client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = withSubject.ResourceIdentity(); + ResourceIdentity ri = withSubject.ResourceIdentity(); - // TODO: The include on Grahame's server doesn't currently work - //result = client.SearchById(ri.Id, - // includes: new string[] { "DiagnosticReport:subject" }); - //Assert.IsNotNull(result); + // TODO: The include on Grahame's server doesn't currently work + //result = client.SearchById(ri.Id, + // includes: new string[] { "DiagnosticReport:subject" }); + //Assert.IsNotNull(result); - //Assert.AreEqual(2, result.Entry.Count); // should have subject too + //Assert.AreEqual(2, result.Entry.Count); // should have subject too - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(DiagnosticReport).GetCollectionName())); - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(Patient).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(DiagnosticReport).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(Patient).GetCollectionName())); - client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; + client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; - result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); + result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } } - private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) - { - // Test that the response was compressed - Assert.AreEqual("gzip", e.RawResponse.Headers[HttpResponseHeader.ContentEncoding]); - } #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] public void SearchAsync() { - FhirClient client = new FhirClient(testEndpoint); - Bundle result; + using(FhirClient client = new FhirClient(testEndpoint)) + { + Bundle result; - result = client.SearchAsync().Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - result = client.SearchAsync(pageSize: 10).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); - result = client.SearchByIdAsync(ri.Id, - includes: new string[] { "DiagnosticReport.subject" }).Result; - Assert.IsNotNull(result); + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); - Assert.AreEqual(2, result.Entry.Count); // should have subject too + Assert.AreEqual(2, result.Entry.Count); // should have subject too - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(DiagnosticReport).GetCollectionName())); - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(Patient).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); - result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } } #endif @@ -329,66 +331,69 @@ public void SearchAsync() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void Paging() { - FhirClient client = new FhirClient(testEndpoint); - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); + using (FhirClient client = new FhirClient(testEndpoint)) + { + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void PagingInJson() { - FhirClient client = new FhirClient(testEndpoint); - client.PreferredFormat = ResourceFormat.Json; - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.PreferredFormat = ResourceFormat.Json; + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } } @@ -396,39 +401,39 @@ public void PagingInJson() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateAndFullRepresentation() { - FhirClient client = new FhirClient(testEndpoint); - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - var pat = client.Read("Patient/glossy"); - ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); - pat.Id = null; - pat.Identifier.Clear(); - var patC = client.Create(pat); - Assert.IsNotNull(patC); + var pat = client.Read("Patient/glossy"); + ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); + pat.Id = null; + pat.Identifier.Clear(); + var patC = client.Create(pat); + Assert.IsNotNull(patC); - client.PreferredReturn = Prefer.ReturnMinimal; - patC = client.Create(pat); + client.PreferredReturn = Prefer.ReturnMinimal; + patC = client.Create(pat); - Assert.IsNull(patC); + Assert.IsNull(patC); - if (client.LastBody != null) - { - var returned = client.LastBodyAsResource; - Assert.IsTrue(returned is OperationOutcome); - } + if (client.LastBody != null) + { + var returned = client.LastBodyAsResource; + Assert.IsTrue(returned is OperationOutcome); + } - // Now validate this resource - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - Parameters p = new Parameters(); - // p.Add("mode", new FhirString("create")); - p.Add("resource", pat); - OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); - Assert.IsNotNull(ooI); + // Now validate this resource + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + Parameters p = new Parameters(); + // p.Add("mode", new FhirString("create")); + p.Add("resource", pat); + OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); + Assert.IsNotNull(ooI); + } } - - private Uri createdTestPatientUrl = null; /// @@ -439,49 +444,51 @@ public void CreateAndFullRepresentation() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateEditDelete() { - FhirClient client = new FhirClient(testEndpoint); + using (FhirClient client = new FhirClient(testEndpoint)) + { - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - // client.CompressRequestBody = true; + client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + // client.CompressRequestBody = true; - var pat = client.Read("Patient/example"); - pat.Id = null; - pat.Identifier.Clear(); - pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); + var pat = client.Read("Patient/example"); + pat.Id = null; + pat.Identifier.Clear(); + pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); - var fe = client.Create(pat); // Create as we are not providing the ID to be used. - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.Meta.VersionId); - createdTestPatientUrl = fe.ResourceIdentity(); + var fe = client.Create(pat); // Create as we are not providing the ID to be used. + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.Meta.VersionId); + createdTestPatientUrl = fe.ResourceIdentity(); - fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.Update(fe); + fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.Update(fe); - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); - Assert.AreEqual(2, fe2.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); + Assert.AreEqual(2, fe2.Identifier.Count); - fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.Update(fe); - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Identifier.Count); + fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.Update(fe); + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Identifier.Count); - client.Delete(fe3); + client.Delete(fe3); - try - { - // Get most recent version - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.Fail(); - } - catch(FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); - Assert.AreEqual("410", client.LastResult.Status); + try + { + // Get most recent version + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); + Assert.AreEqual("410", client.LastResult.Status); + } } } @@ -490,21 +497,23 @@ public void CreateEditDelete() //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() { - FhirClient client = new FhirClient(testEndpoint); - var observation = new Observation(); - observation.Status = ObservationStatus.Preliminary; - observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); - observation.Value = new SimpleQuantity() + using (FhirClient client = new FhirClient(testEndpoint)) { - System = "http://unitsofmeasure.org", - Value = 23, - Code = "mg", - Unit = "miligram" - }; - observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); - var fe = client.Create(observation); - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + var observation = new Observation(); + observation.Status = ObservationStatus.Preliminary; + observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); + observation.Value = new SimpleQuantity() + { + System = "http://unitsofmeasure.org", + Value = 23, + Code = "mg", + Unit = "miligram" + }; + observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); + var fe = client.Create(observation); + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + } } #if NO_ASYNC_ANYMORE @@ -522,50 +531,52 @@ public void CreateEditDeleteAsync() Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } }; - FhirClient client = new FhirClient(testEndpoint); - var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; + using(FhirClient client = new FhirClient(testEndpoint)) + { + var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; - var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; + var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; - Assert.IsNotNull(furore); - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.SelfLink); - Assert.AreNotEqual(fe.Id, fe.SelfLink); - Assert.IsNotNull(fe.Tags); - Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); - Assert.AreEqual(fe.Tags.First(), tags[0]); - createdTestOrganizationUrl = fe.Id; + Assert.IsNotNull(furore); + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.SelfLink); + Assert.AreNotEqual(fe.Id, fe.SelfLink); + Assert.IsNotNull(fe.Tags); + Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); + Assert.AreEqual(fe.Tags.First(), tags[0]); + createdTestOrganizationUrl = fe.Id; - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.UpdateAsync(fe, refresh: true).Result; + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.UpdateAsync(fe, refresh: true).Result; - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); - Assert.AreEqual(2, fe2.Resource.Identifier.Count); + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); + Assert.AreEqual(2, fe2.Resource.Identifier.Count); - Assert.IsNotNull(fe2.Tags); - Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); - Assert.AreEqual(fe2.Tags.First(), tags[0]); + Assert.IsNotNull(fe2.Tags); + Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); + Assert.AreEqual(fe2.Tags.First(), tags[0]); - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Resource.Identifier.Count); + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Resource.Identifier.Count); - client.DeleteAsync(fe3).Wait(); + client.DeleteAsync(fe3).Wait(); - try - { - // Get most recent version - fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; - Assert.Fail(); - } - catch - { - Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); - } + try + { + // Get most recent version + fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; + Assert.Fail(); + } + catch + { + Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); + } + } } #endif @@ -573,7 +584,7 @@ public void CreateEditDeleteAsync() /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? public void History() { System.Threading.Thread.Sleep(500); @@ -581,49 +592,51 @@ public void History() CreateEditDelete(); // this test does a create, update, update, delete (4 operations) - FhirClient client = new FhirClient(testEndpoint); - - System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); + using (FhirClient client = new FhirClient(testEndpoint)) + { - Bundle history = client.History(createdTestPatientUrl); - Assert.IsNotNull(history); - DebugDumpBundle(history); + System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + Bundle history = client.History(createdTestPatientUrl); + Assert.IsNotNull(history); + DebugDumpBundle(history); - //// Now, assume no one is quick enough to insert something between now and the next - //// tests.... + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + //// Now, assume no one is quick enough to insert something between now and the next + //// tests.... - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); - history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); + history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); - history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) - { - System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); - history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); + history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); Assert.IsNotNull(history); DebugDumpBundle(history); - Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); - // Check that the number of patients that have been created is what we expected - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) + { + System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); + history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); + // Check that the number of patients that have been created is what we expected + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + } } } @@ -632,102 +645,106 @@ public void History() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestWithParam() { - var client = new FhirClient(testEndpoint); - var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); - Assert.IsNotNull(res); + using (var client = new FhirClient(testEndpoint)) + { + var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); + Assert.IsNotNull(res); + } } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ManipulateMeta() { - FhirClient client = new FhirClient(testEndpoint); + using (FhirClient client = new FhirClient(testEndpoint)) + { + + var pat = new Patient(); + pat.Meta = new Meta(); + var key = new Random().Next(); + pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); + pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); - var pat = new Patient(); - pat.Meta = new Meta(); - var key = new Random().Next(); - pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); - pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); + Assert.IsNotNull(wsm); - //Before we begin, ensure that our new tags are not actually used when doing System Meta() - var wsm = client.Meta(); - Assert.IsNotNull(wsm); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); - // First, create a patient with the first set of meta - var pat2 = client.Create(pat); - var loc = pat2.ResourceIdentity(testEndpoint); + // Meta should be present on created patient + verifyMeta(pat2.Meta, false, key); - // Meta should be present on created patient - verifyMeta(pat2.Meta, false, key); + // Should be present when doing instance Meta() + var par = client.Meta(loc); + verifyMeta(par, false, key); - // Should be present when doing instance Meta() - var par = client.Meta(loc); - verifyMeta(par, false, key); + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // Should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); - // Should be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); + // Now add some additional meta to the patient - // Now add some additional meta to the patient + var newMeta = new Meta(); + newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - var newMeta = new Meta(); - newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); - newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - - client.AddMeta(loc, newMeta); - var pat3 = client.Read(loc); + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); - // New and old meta should be present on instance - verifyMeta(pat3.Meta, true, key); + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); - // New and old meta should be present on Meta() - par = client.Meta(loc); - verifyMeta(par, true, key); + // New and old meta should be present on Meta() + par = client.Meta(loc); + verifyMeta(par, true, key); - // New and old meta should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, true, key); + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, true, key); - // New and old meta should be present when doing system Meta() - par = client.Meta(); - verifyMeta(par, true, key); + // New and old meta should be present when doing system Meta() + par = client.Meta(); + verifyMeta(par, true, key); - // Now, remove those new meta tags - client.DeleteMeta(loc, newMeta); + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); - // Should no longer be present on instance - var pat4 = client.Read(loc); - verifyMeta(pat4.Meta, false, key); + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); - // Should no longer be present when doing instance Meta() - par = client.Meta(loc); - verifyMeta(par, false, key); + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); + verifyMeta(par, false, key); - // Should no longer be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); - // clear out the client that we created, no point keeping it around - client.Delete(pat4); + // clear out the client that we created, no point keeping it around + client.Delete(pat4); - // Should no longer be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); + // Should no longer be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + } } @@ -757,12 +774,13 @@ private void verifyMeta(Meta meta, bool hasNew, int key) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestSearchByPersonaCode() { - var client = new FhirClient(testEndpoint); - - var pats = - client.Search( - new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); - var pat = (Patient)pats.Entry.First().Resource; + using (var client = new FhirClient(testEndpoint)) + { + var pats = + client.Search( + new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); + var pat = (Patient)pats.Entry.First().Resource; + } } @@ -774,59 +792,63 @@ public void CreateDynamic() { Name = "Furore", Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { + Telecom = new List { new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, - new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } } }; - FhirClient client = new FhirClient(testEndpoint); - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); + using (FhirClient client = new FhirClient(testEndpoint)) + { + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); - var fe = client.Create(furore); - Assert.IsNotNull(fe); + var fe = client.Create(furore); + Assert.IsNotNull(fe); + } } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CallsCallbacks() { - FhirClient client = new FhirClient(testEndpoint); - client.ParserSettings.AllowUnrecognizedEnums = true; + using (FhirClient client = new FhirClient(testEndpoint)) + { + client.ParserSettings.AllowUnrecognizedEnums = true; - bool calledBefore = false; - HttpStatusCode? status = null; - byte[] body = null; - byte[] bodyOut = null; + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; - client.OnBeforeRequest += (sender, e) => - { - calledBefore = true; - bodyOut = e.Body; - }; + client.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; - client.OnAfterResponse += (sender, e) => - { - body = e.Body; - status = e.RawResponse.StatusCode; - }; + client.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; - var pat = client.Read("Patient/glossy"); - Assert.IsTrue(calledBefore); - Assert.IsNotNull(status); - Assert.IsNotNull(body); + var pat = client.Read("Patient/glossy"); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); - var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); + var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); - Assert.IsTrue(bodyText.Contains(" e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; - - var result = client.Read("Patient/glossy"); - Assert.IsNotNull(result); - result.Id = null; - result.Meta = null; - - client.PreferredReturn = Prefer.ReturnRepresentation; - minimal = false; - var posted = client.Create(result); - Assert.IsNotNull(posted, "Patient example not found"); - - minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true - posted = client.Create(result); - Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); - - client.PreferredReturn = Prefer.ReturnMinimal; - minimal = true; - posted = client.Create(result); - Assert.IsNull(posted); + using (var client = new FhirClient(testEndpoint)) + { + + var result = client.Read("Patient/glossy"); + Assert.IsNotNull(result); + result.Id = null; + result.Meta = null; + + client.PreferredReturn = Prefer.ReturnRepresentation; + var posted = client.Create(result); + Assert.IsNotNull(posted, "Patient example not found"); + + posted = client.Create(result); + Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); + + client.PreferredReturn = Prefer.ReturnMinimal; + posted = client.Create(result); + Assert.IsNull(posted); + } } void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) @@ -883,76 +902,80 @@ void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. public void TestReceiveHtmlIsHandled() { - var client = new FhirClient("http://spark.furore.com/"); // an address that returns html - - try + using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns html { - var pat = client.Read("Patient/1"); - } - catch (FhirOperationException fe) - { - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); - } + try + { + var pat = client.Read("Patient/1"); + } + catch (FhirOperationException fe) + { + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + } } + } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestRefresh() { - var client = new FhirClient(testEndpoint); - var result = client.Read("Patient/example"); + using (var client = new FhirClient(testEndpoint)) + { + var result = client.Read("Patient/example"); - var orig = result.Name[0].FamilyElement.Value; + var orig = result.Name[0].FamilyElement.Value; - result.Name[0].FamilyElement.Value = "overwritten name"; + result.Name[0].FamilyElement.Value = "overwritten name"; - result = client.Refresh(result); + result = client.Refresh(result); - Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + } } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithHtmlIsHandled() { - var client = new FhirClient("http://spark.furore.com/"); // an address that returns Status 500 with HTML in its body - - try + using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns Status 500 with HTML in its body { - var pat = client.Read("Patient/1"); - Assert.Fail("Failed to throw an Exception on status 500"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.InternalServerError) - Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); + try + { + var pat = client.Read("Patient/1"); + Assert.Fail("Failed to throw an Exception on status 500"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.InternalServerError) + Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - if (client.LastResult.Status != "500") - Assert.Fail("LastResult.Status is not 500."); + if (client.LastResult.Status != "500") + Assert.Fail("LastResult.Status is not 500."); - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - string message = operationOutcome.Issue[0].Diagnostics; - if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to carry error message over into OperationOutcome"); - } - catch (Exception) - { - Assert.Fail("Failed to throw FhirOperationException on status 500"); + string message = operationOutcome.Issue[0].Diagnostics; + if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to carry error message over into OperationOutcome"); + } + catch (Exception) + { + Assert.Fail("Failed to throw FhirOperationException on status 500"); + } } } @@ -961,44 +984,45 @@ public void TestReceiveErrorStatusWithHtmlIsHandled() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() { - var client = new FhirClient("http://test.fhir.org/r3"); // an address that returns Status 404 with an OperationOutcome - - try + using (var client = new FhirClient("http://test.fhir.org/r3")) // an address that returns Status 404 with an OperationOutcome { - var pat = client.Read("Patient/doesnotexist"); - Assert.Fail("Failed to throw an Exception on status 404"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.NotFound) - Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); + try + { + var pat = client.Read("Patient/doesnotexist"); + Assert.Fail("Failed to throw an Exception on status 404"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.NotFound) + Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); - Bundle.ResponseComponent entryComponent = client.LastResult; + Bundle.ResponseComponent entryComponent = client.LastResult; - if (entryComponent.Status != "404") - Assert.Fail("LastResult.Status is not 404."); + if (entryComponent.Status != "404") + Assert.Fail("LastResult.Status is not 404."); - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - } - catch (Exception e) - { - Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + } + catch (Exception e) + { + Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + } } } - [TestMethod,Ignore] + [TestMethod, Ignore] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FhirVersionIsChecked() { @@ -1008,100 +1032,100 @@ public void FhirVersionIsChecked() var testEndpointDSTU22 = new Uri("http://fhirtest.uhn.ca/baseDstu2"); var testEndpointDSTU23 = new Uri("http://test.fhir.org/r3"); - var client = new FhirClient(testEndpointDSTU1); - client.ParserSettings.AllowUnrecognizedEnums = true; + using(var clientDSTU1 = new FhirClient(testEndpointDSTU1)) + using(var clientDSTU12 = new FhirClient(testEndpointDSTU12)) + using(var clientVerifiedDSTU23 = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true)) + using(var clientDSTU23 = new FhirClient(testEndpointDSTU23)) + { + clientDSTU1.ParserSettings.AllowUnrecognizedEnums = true; - CapabilityStatement p; + CapabilityStatement p; - try - { - client = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true); - client.ParserSettings.AllowUnrecognizedEnums = true; - p = client.CapabilityStatement(); - } - catch (FhirOperationException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } - catch (NotSupportedException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } + try + { + clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; + p = clientDSTU23.CapabilityStatement(); + } + catch (FhirOperationException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } + catch (NotSupportedException) + { + //Client uses 1.0.1, server states 1.0.0-7104 + } - client = new FhirClient(testEndpointDSTU23); - client.ParserSettings.AllowUnrecognizedEnums = true; - p = client.CapabilityStatement(); + clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; + p = clientDSTU23.CapabilityStatement(); - //client = new FhirClient(testEndpointDSTU2); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU2); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); - //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); + //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); + //p = client.Read("Patient/example"); + //p = client.Read("Patient/example"); + clientDSTU12.ParserSettings.AllowUnrecognizedEnums = true; - client = new FhirClient(testEndpointDSTU12); - client.ParserSettings.AllowUnrecognizedEnums = true; - - try - { - p = client.CapabilityStatement(); - Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); - } - catch (Exception) - { - // OK + try + { + p = clientDSTU12.CapabilityStatement(); + Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); + } + catch (Exception) + { + // OK + } } - } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestAuthenticationOnBefore() { - FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); - validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => - { - e.RawRequest.Headers["Authorization"] = "Bearer bad-bearer"; - }; - try + using (FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir")) { - var output = validationFhirClient.ValidateResource(new Patient()); + validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => + { + e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); + }; + try + { + var output = validationFhirClient.ValidateResource(new Patient()); - } - catch(FhirOperationException ex) - { - Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } + catch (FhirOperationException ex) + { + Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } } } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] public void TestOperationEverything() { - FhirClient client = new FhirClient(testEndpoint) + using (FhirClient client = new FhirClient(testEndpoint)) { - UseFormatParam = true, - PreferredFormat = ResourceFormat.Json - }; + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; - // GET operation $everything without parameters - var loc = client.TypeOperation("everything", null, true); - Assert.IsNotNull(loc); + // GET operation $everything without parameters + var loc = client.TypeOperation("everything", null, true); + Assert.IsNotNull(loc); - // POST operation $everything without parameters - loc = client.TypeOperation("everything", null, false); - Assert.IsNotNull(loc); + // POST operation $everything without parameters + loc = client.TypeOperation("everything", null, false); + Assert.IsNotNull(loc); - // GET operation $everything with 1 parameter - // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. - // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); - // Assert.IsNotNull(loc); + // GET operation $everything with 1 parameter + // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. + // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); + // Assert.IsNotNull(loc); - // POST operation $everything with 1 parameter - loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); - Assert.IsNotNull(loc); + // POST operation $everything with 1 parameter + loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); + Assert.IsNotNull(loc); + } } - } - } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs index ee03c198e4..aa3763eb3b 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs @@ -23,28 +23,32 @@ public class OperationsTests { string testEndpoint = FhirClientTests.testEndpoint.OriginalString; - [TestMethod] + [TestMethod] [TestCategory("IntegrationTest")] public void InvokeTestPatientGetEverything() { - var client = new FhirClient(testEndpoint); - var start = new FhirDateTime(2014,11,1); - var end = new FhirDateTime(2015,1,1); - var par = new Parameters().Add("start", start).Add("end", end); - var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); - Assert.IsTrue(bundle.Entry.Any()); - - var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient","example"), start, end); - Assert.IsTrue(bundle2.Entry.Any()); + using (var client = new FhirClient(testEndpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2015, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); + var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient", "example"), start, end); + Assert.IsTrue(bundle2.Entry.Any()); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeExpandExistingValueSet() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet","administrative-gender")); - Assert.IsTrue(vs.Expansion.Contains.Any()); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet", "administrative-gender")); + Assert.IsTrue(vs.Expansion.Contains.Any()); + } } @@ -53,12 +57,14 @@ public void InvokeExpandExistingValueSet() [TestCategory("IntegrationTest")] public void InvokeExpandParameterValueSet() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { - var vs = client.Read("ValueSet/administrative-gender"); - var vsX = client.ExpandValueSet(vs); + var vs = client.Read("ValueSet/administrative-gender"); + var vsX = client.ExpandValueSet(vs); - Assert.IsTrue(vsX.Expansion.Contains.Any()); + Assert.IsTrue(vsX.Expansion.Contains.Any()); + } } // [WMR 20170927] Chris Munro @@ -81,61 +87,71 @@ public void InvokeExpandParameterValueSet() [TestCategory("IntegrationTest")] public void InvokeLookupCoding() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); - var expansion = client.ConceptLookup(coding: coding); + var expansion = client.ConceptLookup(coding: coding); - // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } } [TestMethod] // Server returns internal server error [TestCategory("IntegrationTest")] public void InvokeLookupCode() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); - //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeById() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeByCanonical() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), - coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), + coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } } [TestMethod] [TestCategory("IntegrationTest")] public void InvokeValidateCodeWithVS() { - var client = new FhirClient(FhirClientTests.TerminologyEndpoint); - var coding = new Coding("http://snomed.info/sct", "4322002"); + using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); - var vs = client.Read("ValueSet/c80-facilitycodes"); - Assert.IsNotNull(vs); + var vs = client.Read("ValueSet/c80-facilitycodes"); + Assert.IsNotNull(vs); - var result = client.ValidateCode(valueSet: vs, coding: coding); - Assert.IsTrue(result.Result?.Value == true); + var result = client.ValidateCode(valueSet: vs, coding: coding); + Assert.IsTrue(result.Result?.Value == true); + } } @@ -143,20 +159,22 @@ public void InvokeValidateCodeWithVS() [TestCategory("IntegrationTest"), Ignore] public void InvokeResourceValidation() { - var client = new FhirClient(testEndpoint); - - var pat = client.Read("Patient/patient-uslab-example1"); - - try - { - var vresult = client.ValidateResource(pat, null, - new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); - Assert.Fail("Should have resulted in 400"); - } - catch(FhirOperationException fe) + using (var client = new FhirClient(testEndpoint)) { - Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); - Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + + var pat = client.Read("Patient/patient-uslab-example1"); + + try + { + var vresult = client.ValidateResource(pat, null, + new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); + Assert.Fail("Should have resulted in 400"); + } + catch (FhirOperationException fe) + { + Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); + Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + } } } @@ -166,21 +184,23 @@ public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() { string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - var client = new FhirClient(_endpoint); - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2020, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); + using (var client = new FhirClient(_endpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2020, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); - var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); - var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); + var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); + var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); - await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); + await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); - var bundle = (Bundle)bundleTask.Result; - Assert.IsTrue(bundle.Entry.Any()); + var bundle = (Bundle)bundleTask.Result; + Assert.IsTrue(bundle.Entry.Any()); - var bundle2 = (Bundle)bundle2Task.Result; - Assert.IsTrue(bundle2.Entry.Any()); + var bundle2 = (Bundle)bundle2Task.Result; + Assert.IsTrue(bundle2.Entry.Any()); + } } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs index 94fac8a41e..ce2b6311d3 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs @@ -16,36 +16,40 @@ public class ReadAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); + }) + { + + Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - Patient p = await client.ReadAsync("/Patient/SMART-1288992"); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); + Patient p = await client.ReadAsync("/Patient/SMART-1288992"); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs index f3e9def4e8..6d29d8b941 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs @@ -12,6 +12,7 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; +using System.Linq; namespace Hl7.Fhir.Test { @@ -43,31 +44,30 @@ public void TestPreferSetting() var tx = new TransactionBuilder("http://myserver.org/fhir") .Create(p); var b = tx.ToBundle(); - byte[] dummy; - var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=minimal", request.Headers["Prefer"]); + var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("return=minimal", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=representation", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("return=representation", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("return=OperationOutcome", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false); + Assert.AreEqual("return=OperationOutcome", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false, out dummy); - Assert.IsNull(request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false); + Assert.IsFalse(request.Headers.TryGetValues("Prefer", out var headerValues)); tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); b = tx.ToBundle(); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("handling=lenient", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=lenient", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.AreEqual("handling=strict", request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=strict", request.Headers.GetValues("Prefer").FirstOrDefault()); - request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); - Assert.IsNull(request.Headers["Prefer"]); + request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.IsFalse(request.Headers.TryGetValues("Prefer", out headerValues)); } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs index c1f98dc942..c2412dbb3f 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs @@ -16,69 +16,73 @@ public class SearchAsyncTests [TestCategory("IntegrationTest")] public async Task Search_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = await client.SearchAsync(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + var result1 = await client.SearchAsync(srch); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); + + Console.WriteLine("Test Completed"); } - - Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] public void SearchSync_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); - var result1 = client.Search(srch); + var result1 = client.Search(srch); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } @@ -86,97 +90,102 @@ public void SearchSync_UsingSearchParams_SearchReturned() [TestCategory("IntegrationTest")] public async Task SearchMultiple_UsingSearchParams_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var srchParams = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var task1 = client.SearchAsync(srchParams); - var task2 = client.SearchAsync(srchParams); - var task3 = client.SearchAsync(srchParams); - - await Task.WhenAll(task1, task2, task3); - var result1 = task1.Result; - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) + }) { - foreach (var e in result1.Entry) + var srchParams = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var task1 = client.SearchAsync(srchParams); + var task2 = client.SearchAsync(srchParams); + var task3 = client.SearchAsync(srchParams); + + await Task.WhenAll(task1, task2, task3); + var result1 = task1.Result; + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_SyncContinue_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var result1 = await client.SearchAsync(new []{"family=clark"}); + }) + { - Assert.IsTrue(result1.Entry.Count >= 1); + var result1 = await client.SearchAsync(new[] { "family=clark" }); - while (result1 != null) - { - foreach (var e in result1.Entry) + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); } - result1 = client.Continue(result1, PageDirection.Next); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } [TestMethod] [TestCategory("IntegrationTest")] public async Task SearchWithCriteria_AsyncContinue_SearchReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; + }) + { - var result1 = await client.SearchAsync(new[] { "family=clark" },null,1); + var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); - Assert.IsTrue(result1.Entry.Count >= 1); + Assert.IsTrue(result1.Entry.Count >= 1); - while (result1 != null) - { - foreach (var e in result1.Entry) + while (result1 != null) { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + Console.WriteLine("Fetching more results..."); + result1 = await client.ContinueAsync(result1); } - Console.WriteLine("Fetching more results..."); - result1 = await client.ContinueAsync(result1); - } - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } } } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs index 29c6f77265..8bb2d9dda1 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs @@ -51,9 +51,7 @@ public void TestUrlEncoding() tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); var b = tx.ToBundle(); - byte[] body; - - var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false, body: out body); + var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs index 0b0d811413..da3339bd4b 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs @@ -16,15 +16,16 @@ public class UpdateRefreshDeleteAsyncTests [TestCategory("IntegrationTest")] public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() { - var client = new FhirClient(_endpoint) + using (var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation - }; - - var pat = new Patient() + }) { - Name = new List() + + var pat = new Patient() + { + Name = new List() { new HumanName() { @@ -32,33 +33,33 @@ public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_Resu Family = "test_family", } }, - Id = "async-test-patient" - }; - // Create the patient - Console.WriteLine("Creating patient..."); - Patient p = await client.UpdateAsync(pat); - Assert.IsNotNull(p); + Id = "async-test-patient" + }; + // Create the patient + Console.WriteLine("Creating patient..."); + Patient p = await client.UpdateAsync(pat); + Assert.IsNotNull(p); - // Refresh the patient - Console.WriteLine("Refreshing patient..."); - await client.RefreshAsync(p); + // Refresh the patient + Console.WriteLine("Refreshing patient..."); + await client.RefreshAsync(p); - // Delete the patient - Console.WriteLine("Deleting patient..."); - await client.DeleteAsync(p); + // Delete the patient + Console.WriteLine("Deleting patient..."); + await client.DeleteAsync(p); + + Console.WriteLine("Reading patient..."); + Func act = async () => + { + await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); + }; + + // VERIFY // + Assert.ThrowsException(act, "the patient is no longer on the server"); - Console.WriteLine("Reading patient..."); - Func act = async () => - { - await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); - }; - // VERIFY // - Assert.ThrowsException(act, "the patient is no longer on the server"); - - - Console.WriteLine("Test Completed"); + Console.WriteLine("Test Completed"); + } } - } } \ No newline at end of file From 674f169f1b6bf30d17b3b86bab2b255c3a28931c Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 16:51:00 -0600 Subject: [PATCH 23/39] Fixed constructor not having optional parameter. --- src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index 8d1f6dda11..b954059f3d 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -71,7 +71,7 @@ public FhirClient(Uri endpoint, HttpMessageHandler messageHandler = null, bool v /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirClient(string endpoint, HttpMessageHandler messageHandler,bool verifyFhirVersion = false) + public FhirClient(string endpoint, HttpMessageHandler messageHandler = null, bool verifyFhirVersion = false) : this(new Uri(endpoint), messageHandler, verifyFhirVersion) { } From 9b6cfa04cac929758032612414fb88f64afb3cf2 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Mon, 4 Dec 2017 17:36:38 -0600 Subject: [PATCH 24/39] Deleted extra test files and resolved to place new tests alongside old ones --- .../Rest/FhirClientTests.cs | 172 ++- .../Rest/Http/FhirClientTests.cs | 1131 ----------------- .../Rest/Http/OperationsTests.cs | 206 --- .../Rest/Http/ReadAsyncTests.cs | 55 - .../Rest/Http/RequesterTests.cs | 74 -- .../Rest/Http/SearchAsyncTests.cs | 191 --- .../Rest/Http/TransactionBuilderTests.cs | 83 -- .../Http/UpdateRefreshDeleteAsyncTests.cs | 65 - 8 files changed, 157 insertions(+), 1820 deletions(-) delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs delete mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 349be126d8..61a1f890d6 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -15,6 +15,7 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; using Hl7.Fhir.Model; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; namespace Hl7.Fhir.Tests.Rest { @@ -64,7 +65,7 @@ public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void FetchConformance() + public void FetchConformanceWebClient() { FhirClient client = new FhirClient(testEndpoint); client.ParserSettings.AllowUnrecognizedEnums = true; @@ -92,6 +93,35 @@ public void FetchConformance() Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void FetchConformanceHttpClient() + { + TestClient client = new TestClient(testEndpoint); + client.ParserSettings.AllowUnrecognizedEnums = true; + + var entry = client.CapabilityStatement(); + + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + entry = client.CapabilityStatement(SummaryType.True); + + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); + + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + } + [TestMethod, TestCategory("FhirClient")] public void VerifyFormatParamProcessing() @@ -112,7 +142,7 @@ public void VerifyFormatParamProcessing() } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ReadWithFormat() + public void ReadWithFormatWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -123,9 +153,21 @@ public void ReadWithFormat() Assert.IsNotNull(loc); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ReadWithFormatHttpClient() + { + TestClient client = new TestClient(testEndpoint); + + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; + + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); + } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void Read() + public void ReadWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -164,9 +206,49 @@ public void Read() jsonSer.SerializeToString(loc4)); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ReadHttpClient() + { + TestClient client = new TestClient(testEndpoint); + + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); + + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } + + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); + + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); + } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ReadRelative() + public void ReadRelativeWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -180,9 +262,24 @@ public void ReadRelative() Assert.AreEqual("Den Burg", loc.Address.City); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ReadRelativeHttpClient() + { + TestClient client = new TestClient(testEndpoint); + + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + } + #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] - public void ReadRelativeAsync() + public void ReadRelativeAsyncWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -195,9 +292,54 @@ public void ReadRelativeAsync() Assert.IsNotNull(loc); Assert.AreEqual("Den Burg", loc.Resource.Address.City); } + + [TestMethod, TestCategory("FhirClient")] + public void ReadRelativeAsyncHttpClient() + { + TestClient client = new TestClient(testEndpoint); + + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } #endif - public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeWebRequestGZip(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip"; + } + } + + public static void Compression_OnBeforeWebRequestDeflate(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "deflate"; + } + } + + public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) + { + if (e.RawRequest != null) + { + // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + e.RawRequest.Headers.Remove("Accept-Encoding"); + e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + } + } + + public static void Compression_OnBeforeHttpRequestGZip(object sender, BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -207,7 +349,7 @@ public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestE } } - public static void Compression_OnBeforeRequestDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestDeflate(object sender, BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -217,7 +359,7 @@ public static void Compression_OnBeforeRequestDeflate(object sender, BeforeReque } } - public static void Compression_OnBeforeRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -230,13 +372,13 @@ public static void Compression_OnBeforeRequestZipOrDeflate(object sender, Before [TestMethod, Ignore] // Something does not work with the gzip [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void Search() + public void SearchWebClient() { FhirClient client = new FhirClient(testEndpoint); Bundle result; client.CompressRequestBody = true; - client.OnBeforeRequest += Compression_OnBeforeRequestGZip; + client.OnBeforeRequest += Compression_OnBeforeWebRequestGZip; client.OnAfterResponse += Client_OnAfterResponse; result = client.Search(); @@ -244,14 +386,14 @@ public void Search() Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest -= Compression_OnBeforeWebRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeWebRequestZipOrDeflate; result = client.Search(pageSize: 10); Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count <= 10); - client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; + client.OnBeforeRequest -= Compression_OnBeforeWebRequestGZip; var withSubject = result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); @@ -272,7 +414,7 @@ public void Search() // typeof(Patient).GetCollectionName())); - client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; + client.OnBeforeRequest += Compression_OnBeforeWebRequestDeflate; result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); @@ -441,7 +583,7 @@ public void CreateEditDelete() { FhirClient client = new FhirClient(testEndpoint); - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; + client.OnBeforeRequest += Compression_OnBeforeWebRequestZipOrDeflate; // client.CompressRequestBody = true; var pat = client.Read("Patient/example"); diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs deleted file mode 100644 index 068909363e..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/FhirClientTests.cs +++ /dev/null @@ -1,1131 +0,0 @@ -/* - * Copyright (c) 2014, Furore (info@furore.com) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE - */ - -using System; -using System.Text; -using System.Collections.Generic; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Net; -using Hl7.Fhir.Rest; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Model; - -namespace Hl7.Fhir.Tests.Rest -{ - [TestClass] - public class FhirClientTests - { - //public static Uri testEndpoint = new Uri("http://spark-dstu3.furore.com/fhir"); - //public static Uri testEndpoint = new Uri("http://localhost.fiddler:1396/fhir"); - //public static Uri testEndpoint = new Uri("https://localhost:44346/fhir"); - //public static Uri testEndpoint = new Uri("http://localhost:1396/fhir"); - public static Uri testEndpoint = new Uri("http://test.fhir.org/r3"); - //public static Uri testEndpoint = new Uri("http://vonk.furore.com"); - //public static Uri testEndpoint = new Uri("https://api.fhir.me"); - //public static Uri testEndpoint = new Uri("http://fhirtest.uhn.ca/baseDstu3"); - //public static Uri testEndpoint = new Uri("http://localhost:49911/fhir"); - //public static Uri testEndpoint = new Uri("http://sqlonfhir-stu3.azurewebsites.net/fhir"); - - public static Uri TerminologyEndpoint = new Uri("http://ontoserver.csiro.au/stu3-latest"); - - [TestInitialize] - public void TestInitialize() - { - System.Diagnostics.Trace.WriteLine("Testing against fhir server: " + testEndpoint); - } - - public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) - { - System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); - - if (b.Entry != null) - { - foreach (var item in b.Entry) - { - if (item.Request != null) - System.Diagnostics.Trace.WriteLine(String.Format(" {0}: {1}", item.Request.Method.ToString(), item.Request.Url)); - if (item.Response != null && item.Response.Status != null) - System.Diagnostics.Trace.WriteLine(String.Format(" {0}", item.Response.Status)); - if (item.Resource != null && item.Resource is Hl7.Fhir.Model.DomainResource) - { - if (item.Resource.Meta != null && item.Resource.Meta.LastUpdated.HasValue) - System.Diagnostics.Trace.WriteLine(String.Format(" Last Updated:{0}, [{1}]", item.Resource.Meta.LastUpdated.Value, item.Resource.Meta.LastUpdated.Value.ToString("HH:mm:ss.FFFF"))); - Hl7.Fhir.Rest.ResourceIdentity ri = new Hl7.Fhir.Rest.ResourceIdentity(item.FullUrl); - System.Diagnostics.Trace.WriteLine(String.Format(" {0}", (item.Resource as Hl7.Fhir.Model.DomainResource).ResourceIdentity(ri.BaseUri).OriginalString)); - } - } - } - } - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void FetchConformance() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.ParserSettings.AllowUnrecognizedEnums = true; - - var entry = client.CapabilityStatement(); - - Assert.IsNotNull(entry.Text); - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - entry = client.CapabilityStatement(SummaryType.True); - - Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); - - Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); - Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); - Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary - } - } - - - [TestMethod, TestCategory("FhirClient")] - public void VerifyFormatParamProcessing() - { - // XML - Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("xml")); - Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("text/xml")); - Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/xml")); - Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/xml+fhir")); - Assert.AreEqual(ResourceFormat.Xml, ContentType.GetResourceFormatFromFormatParam("application/fhir+xml")); - - // JSON - Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("json")); - Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("text/json")); - Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/json")); - Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/json+fhir")); - Assert.AreEqual(ResourceFormat.Json, ContentType.GetResourceFormatFromFormatParam("application/fhir+json")); - } - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ReadWithFormat() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; - - var loc = client.Read("Patient/example"); - Assert.IsNotNull(loc); - } - } - - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void Read() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.Read("Location/1"); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - - Assert.AreEqual("1", loc.Id); - Assert.IsNotNull(loc.Meta.VersionId); - - var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc2); - Assert.AreEqual(loc2.Id, loc.Id); - Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); - - try - { - var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); - Assert.AreEqual("404", client.LastResult.Status); - } - - var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc3); - var jsonSer = new FhirJsonSerializer(); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc3)); - - var loc4 = client.Read(loc.ResourceIdentity()); - Assert.IsNotNull(loc4); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc4)); - } - } - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ReadRelative() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.Read(new Uri("Location/1", UriKind.Relative)); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.Read(ri); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); - } - } - -#if NO_ASYNC_ANYMORE - [TestMethod, TestCategory("FhirClient")] - public void ReadRelativeAsync() - { - using(FhirClient client = new FhirClient(testEndpoint)) - { - var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.ReadAsync(ri).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - } - } -#endif - - public static void Compression_OnBeforeRequestGZip(object sender, BeforeRequestEventArgs e) - { - if (e.RawRequest != null) - { - // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; - e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip"); - } - } - - public static void Compression_OnBeforeRequestDeflate(object sender, BeforeRequestEventArgs e) - { - if (e.RawRequest != null) - { - // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; - e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "deflate"); - } - } - - public static void Compression_OnBeforeRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) - { - if (e.RawRequest != null) - { - // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; - e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); - } - } - - [TestMethod, Ignore] // Something does not work with the gzip - [TestCategory("FhirClient"), - TestCategory("IntegrationTest")] - public void Search() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - Bundle result; - - client.CompressRequestBody = true; - client.OnBeforeRequest += Compression_OnBeforeRequestGZip; - - result = client.Search(); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - - client.OnBeforeRequest -= Compression_OnBeforeRequestZipOrDeflate; - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - - result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - client.OnBeforeRequest -= Compression_OnBeforeRequestGZip; - - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - - ResourceIdentity ri = withSubject.ResourceIdentity(); - - // TODO: The include on Grahame's server doesn't currently work - //result = client.SearchById(ri.Id, - // includes: new string[] { "DiagnosticReport:subject" }); - //Assert.IsNotNull(result); - - //Assert.AreEqual(2, result.Entry.Count); // should have subject too - - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(DiagnosticReport).GetCollectionName())); - //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == - // typeof(Patient).GetCollectionName())); - - - client.OnBeforeRequest += Compression_OnBeforeRequestDeflate; - - result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); - - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); - } - } - - -#if NO_ASYNC_ANYMORE - [TestMethod, TestCategory("FhirClient")] - public void SearchAsync() - { - using(FhirClient client = new FhirClient(testEndpoint)) - { - Bundle result; - - result = client.SearchAsync().Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); - - result = client.SearchAsync(pageSize: 10).Result; - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var withSubject = - result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); - Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); - - ResourceIdentity ri = new ResourceIdentity(withSubject.Id); - - result = client.SearchByIdAsync(ri.Id, - includes: new string[] { "DiagnosticReport.subject" }).Result; - Assert.IsNotNull(result); - - Assert.AreEqual(2, result.Entry.Count); // should have subject too - - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(DiagnosticReport).GetCollectionName())); - Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == - typeof(Patient).GetCollectionName())); - - result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; - - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count > 0); - } - } -#endif - - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void Paging() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - } - } - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void PagingInJson() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.PreferredFormat = ResourceFormat.Json; - - var result = client.Search(pageSize: 10); - Assert.IsNotNull(result); - Assert.IsTrue(result.Entry.Count <= 10); - - var firstId = result.Entry.First().Resource.Id; - - // Browse forward - result = client.Continue(result); - Assert.IsNotNull(result); - var nextId = result.Entry.First().Resource.Id; - Assert.AreNotEqual(firstId, nextId); - - // Browse to first - result = client.Continue(result, PageDirection.First); - Assert.IsNotNull(result); - var prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - - // Forward, then backwards - result = client.Continue(result, PageDirection.Next); - Assert.IsNotNull(result); - result = client.Continue(result, PageDirection.Previous); - Assert.IsNotNull(result); - prevId = result.Entry.First().Resource.Id; - Assert.AreEqual(firstId, prevId); - } - } - - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateAndFullRepresentation() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - - var pat = client.Read("Patient/glossy"); - ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); - pat.Id = null; - pat.Identifier.Clear(); - var patC = client.Create(pat); - Assert.IsNotNull(patC); - - client.PreferredReturn = Prefer.ReturnMinimal; - patC = client.Create(pat); - - Assert.IsNull(patC); - - if (client.LastBody != null) - { - var returned = client.LastBodyAsResource; - Assert.IsTrue(returned is OperationOutcome); - } - - // Now validate this resource - client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default - Parameters p = new Parameters(); - // p.Add("mode", new FhirString("create")); - p.Add("resource", pat); - OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); - Assert.IsNotNull(ooI); - } - } - - - private Uri createdTestPatientUrl = null; - - /// - /// This test is also used as a "setup" test for the History test. - /// If you change the number of operations in here, this will make the History test fail. - /// - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateEditDelete() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - - client.OnBeforeRequest += Compression_OnBeforeRequestZipOrDeflate; - // client.CompressRequestBody = true; - - var pat = client.Read("Patient/example"); - pat.Id = null; - pat.Identifier.Clear(); - pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); - - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); - - var fe = client.Create(pat); // Create as we are not providing the ID to be used. - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.Meta.VersionId); - createdTestPatientUrl = fe.ResourceIdentity(); - - fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.Update(fe); - - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); - Assert.AreEqual(2, fe2.Identifier.Count); - - fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.Update(fe); - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Identifier.Count); - - client.Delete(fe3); - - try - { - // Get most recent version - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); - Assert.AreEqual("410", client.LastResult.Status); - } - } - } - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 - public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - var observation = new Observation(); - observation.Status = ObservationStatus.Preliminary; - observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); - observation.Value = new SimpleQuantity() - { - System = "http://unitsofmeasure.org", - Value = 23, - Code = "mg", - Unit = "miligram" - }; - observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); - var fe = client.Create(observation); - fe = client.Read(fe.ResourceIdentity().WithoutVersion()); - Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); - } - } - -#if NO_ASYNC_ANYMORE - /// - /// This test is also used as a "setup" test for the History test. - /// If you change the number of operations in here, this will make the History test fail. - /// - [TestMethod, TestCategory("FhirClient")] - public void CreateEditDeleteAsync() - { - var furore = new Organization - { - Name = "Furore", - Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } - }; - - using(FhirClient client = new FhirClient(testEndpoint)) - { - var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; - - var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; - - Assert.IsNotNull(furore); - Assert.IsNotNull(fe); - Assert.IsNotNull(fe.Id); - Assert.IsNotNull(fe.SelfLink); - Assert.AreNotEqual(fe.Id, fe.SelfLink); - Assert.IsNotNull(fe.Tags); - Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); - Assert.AreEqual(fe.Tags.First(), tags[0]); - createdTestOrganizationUrl = fe.Id; - - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); - var fe2 = client.UpdateAsync(fe, refresh: true).Result; - - Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); - Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); - Assert.AreEqual(2, fe2.Resource.Identifier.Count); - - Assert.IsNotNull(fe2.Tags); - Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); - Assert.AreEqual(fe2.Tags.First(), tags[0]); - - fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); - var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; - Assert.IsNotNull(fe3); - Assert.AreEqual(3, fe3.Resource.Identifier.Count); - - client.DeleteAsync(fe3).Wait(); - - try - { - // Get most recent version - fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; - Assert.Fail(); - } - catch - { - Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); - } - } - } -#endif - - /// - /// This test will fail if the system records AuditEvents - /// and counts them in the WholeSystemHistory - /// - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? - public void History() - { - System.Threading.Thread.Sleep(500); - DateTimeOffset timestampBeforeCreationAndDeletions = DateTimeOffset.Now; - - CreateEditDelete(); // this test does a create, update, update, delete (4 operations) - - using (FhirClient client = new FhirClient(testEndpoint)) - { - - System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); - - Bundle history = client.History(createdTestPatientUrl); - Assert.IsNotNull(history); - DebugDumpBundle(history); - - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - - //// Now, assume no one is quick enough to insert something between now and the next - //// tests.... - - - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); - history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - - - System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); - history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); - - if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) - { - System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); - history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); - Assert.IsNotNull(history); - DebugDumpBundle(history); - Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); - // Check that the number of patients that have been created is what we expected - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); - Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); - } - } - } - - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestWithParam() - { - using (var client = new FhirClient(testEndpoint)) - { - var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); - Assert.IsNotNull(res); - } - } - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ManipulateMeta() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - - var pat = new Patient(); - pat.Meta = new Meta(); - var key = new Random().Next(); - pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); - pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); - - //Before we begin, ensure that our new tags are not actually used when doing System Meta() - var wsm = client.Meta(); - Assert.IsNotNull(wsm); - - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); - - Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - - - // First, create a patient with the first set of meta - var pat2 = client.Create(pat); - var loc = pat2.ResourceIdentity(testEndpoint); - - // Meta should be present on created patient - verifyMeta(pat2.Meta, false, key); - - // Should be present when doing instance Meta() - var par = client.Meta(loc); - verifyMeta(par, false, key); - - // Should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); - - // Should be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); - - // Now add some additional meta to the patient - - var newMeta = new Meta(); - newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); - newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); - - - client.AddMeta(loc, newMeta); - var pat3 = client.Read(loc); - - // New and old meta should be present on instance - verifyMeta(pat3.Meta, true, key); - - // New and old meta should be present on Meta() - par = client.Meta(loc); - verifyMeta(par, true, key); - - // New and old meta should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, true, key); - - // New and old meta should be present when doing system Meta() - par = client.Meta(); - verifyMeta(par, true, key); - - // Now, remove those new meta tags - client.DeleteMeta(loc, newMeta); - - // Should no longer be present on instance - var pat4 = client.Read(loc); - verifyMeta(pat4.Meta, false, key); - - // Should no longer be present when doing instance Meta() - par = client.Meta(loc); - verifyMeta(par, false, key); - - // Should no longer be present when doing type Meta() - par = client.Meta(ResourceType.Patient); - verifyMeta(par, false, key); - - // clear out the client that we created, no point keeping it around - client.Delete(pat4); - - // Should no longer be present when doing System Meta() - par = client.Meta(); - verifyMeta(par, false, key); - } - } - - - private void verifyMeta(Meta meta, bool hasNew, int key) - { - Assert.IsTrue(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); - Assert.IsTrue(meta.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); - Assert.IsTrue(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); - - if (hasNew) - { - Assert.IsTrue(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsTrue(meta.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsTrue(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - } - - if (!hasNew) - { - Assert.IsFalse(meta.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - Assert.IsFalse(meta.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); - Assert.IsFalse(meta.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - } - } - - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestSearchByPersonaCode() - { - using (var client = new FhirClient(testEndpoint)) - { - var pats = - client.Search( - new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); - var pat = (Patient)pats.Entry.First().Resource; - } - } - - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateDynamic() - { - Resource furore = new Organization - { - Name = "Furore", - Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { - new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, - new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } - } - }; - - using (FhirClient client = new FhirClient(testEndpoint)) - { - System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); - - var fe = client.Create(furore); - Assert.IsNotNull(fe); - } - } - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CallsCallbacks() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.ParserSettings.AllowUnrecognizedEnums = true; - - bool calledBefore = false; - HttpStatusCode? status = null; - byte[] body = null; - byte[] bodyOut = null; - - client.OnBeforeRequest += (sender, e) => - { - calledBefore = true; - bodyOut = e.Body; - }; - - client.OnAfterResponse += (sender, e) => - { - body = e.Body; - status = e.RawResponse.StatusCode; - }; - - var pat = client.Read("Patient/glossy"); - Assert.IsTrue(calledBefore); - Assert.IsNotNull(status); - Assert.IsNotNull(body); - - var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); - - Assert.IsTrue(bodyText.Contains("("Patient/glossy"); - Assert.IsNotNull(result); - result.Id = null; - result.Meta = null; - - client.PreferredReturn = Prefer.ReturnRepresentation; - var posted = client.Create(result); - Assert.IsNotNull(posted, "Patient example not found"); - - posted = client.Create(result); - Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); - - client.PreferredReturn = Prefer.ReturnMinimal; - posted = client.Create(result); - Assert.IsNull(posted); - } - } - - void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) - { - throw new NotImplementedException(); - } - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. - public void TestReceiveHtmlIsHandled() - { - using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns html - { - try - { - var pat = client.Read("Patient/1"); - } - catch (FhirOperationException fe) - { - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); - } - } - } - - - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestRefresh() - { - using (var client = new FhirClient(testEndpoint)) - { - var result = client.Read("Patient/example"); - - var orig = result.Name[0].FamilyElement.Value; - - result.Name[0].FamilyElement.Value = "overwritten name"; - - result = client.Refresh(result); - - Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); - } - } - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestReceiveErrorStatusWithHtmlIsHandled() - { - using (var client = new FhirClient("http://spark.furore.com/")) // an address that returns Status 500 with HTML in its body - { - try - { - var pat = client.Read("Patient/1"); - Assert.Fail("Failed to throw an Exception on status 500"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.InternalServerError) - Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); - - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); - - if (client.LastResult.Status != "500") - Assert.Fail("LastResult.Status is not 500."); - - if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to recognize invalid body contents"); - - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - - string message = operationOutcome.Issue[0].Diagnostics; - if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) - Assert.Fail("Failed to carry error message over into OperationOutcome"); - } - catch (Exception) - { - Assert.Fail("Failed to throw FhirOperationException on status 500"); - } - } - } - - - [TestMethod] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() - { - using (var client = new FhirClient("http://test.fhir.org/r3")) // an address that returns Status 404 with an OperationOutcome - { - try - { - var pat = client.Read("Patient/doesnotexist"); - Assert.Fail("Failed to throw an Exception on status 404"); - } - catch (FhirOperationException fe) - { - // Expected exception happened - if (fe.Status != HttpStatusCode.NotFound) - Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); - - if (client.LastResult == null) - Assert.Fail("LastResult not set in error case."); - - Bundle.ResponseComponent entryComponent = client.LastResult; - - if (entryComponent.Status != "404") - Assert.Fail("LastResult.Status is not 404."); - - // Check that LastResult is of type OperationOutcome and properly filled. - OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; - Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); - - Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); - - Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); - } - catch (Exception e) - { - Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); - } - } - } - - - - [TestMethod, Ignore] - [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void FhirVersionIsChecked() - { - var testEndpointDSTU2 = new Uri("http://spark-dstu2.furore.com/fhir"); - var testEndpointDSTU1 = new Uri("http://spark.furore.com/fhir"); - var testEndpointDSTU12 = new Uri("http://fhirtest.uhn.ca/baseDstu1"); - var testEndpointDSTU22 = new Uri("http://fhirtest.uhn.ca/baseDstu2"); - var testEndpointDSTU23 = new Uri("http://test.fhir.org/r3"); - - using(var clientDSTU1 = new FhirClient(testEndpointDSTU1)) - using(var clientDSTU12 = new FhirClient(testEndpointDSTU12)) - using(var clientVerifiedDSTU23 = new FhirClient(testEndpointDSTU23, verifyFhirVersion: true)) - using(var clientDSTU23 = new FhirClient(testEndpointDSTU23)) - { - clientDSTU1.ParserSettings.AllowUnrecognizedEnums = true; - - CapabilityStatement p; - - try - { - clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; - p = clientDSTU23.CapabilityStatement(); - } - catch (FhirOperationException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } - catch (NotSupportedException) - { - //Client uses 1.0.1, server states 1.0.0-7104 - } - - clientDSTU23.ParserSettings.AllowUnrecognizedEnums = true; - p = clientDSTU23.CapabilityStatement(); - - //client = new FhirClient(testEndpointDSTU2); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); - - //client = new FhirClient(testEndpointDSTU22, verifyFhirVersion:true); - //p = client.Read("Patient/example"); - //p = client.Read("Patient/example"); - - clientDSTU12.ParserSettings.AllowUnrecognizedEnums = true; - - try - { - p = clientDSTU12.CapabilityStatement(); - Assert.Fail("Getting DSTU1 data using DSTU2 parsers should have failed"); - } - catch (Exception) - { - // OK - } - } - } - - [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] - public void TestAuthenticationOnBefore() - { - using (FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir")) - { - validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => - { - e.RawRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); - }; - try - { - var output = validationFhirClient.ValidateResource(new Patient()); - - } - catch (FhirOperationException ex) - { - Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); - } - } - } - - [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] - public void TestOperationEverything() - { - using (FhirClient client = new FhirClient(testEndpoint)) - { - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; - - // GET operation $everything without parameters - var loc = client.TypeOperation("everything", null, true); - Assert.IsNotNull(loc); - - // POST operation $everything without parameters - loc = client.TypeOperation("everything", null, false); - Assert.IsNotNull(loc); - - // GET operation $everything with 1 parameter - // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. - // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); - // Assert.IsNotNull(loc); - - // POST operation $everything with 1 parameter - loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); - Assert.IsNotNull(loc); - } - } - } -} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs deleted file mode 100644 index aa3763eb3b..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/OperationsTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2014, Furore (info@furore.com) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE - */ - -using Hl7.Fhir.Model; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Hl7.Fhir.Rest; - -namespace Hl7.Fhir.Tests.Rest -{ - [TestClass] - public class OperationsTests - - { - string testEndpoint = FhirClientTests.testEndpoint.OriginalString; - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeTestPatientGetEverything() - { - using (var client = new FhirClient(testEndpoint)) - { - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2015, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); - var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); - Assert.IsTrue(bundle.Entry.Any()); - - var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient", "example"), start, end); - Assert.IsTrue(bundle2.Entry.Any()); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeExpandExistingValueSet() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet", "administrative-gender")); - Assert.IsTrue(vs.Expansion.Contains.Any()); - } - } - - - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeExpandParameterValueSet() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - - var vs = client.Read("ValueSet/administrative-gender"); - var vsX = client.ExpandValueSet(vs); - - Assert.IsTrue(vsX.Expansion.Contains.Any()); - } - } - - // [WMR 20170927] Chris Munro - // https://chat.fhir.org/#narrow/stream/implementers/subject/How.20to.20expand.20ValueSets.20with.20the.20C.23.20FHIR.20API.3F - //[TestMethod] - //[TestCategory("IntegrationTest")] - //[Ignore] - //public void TestExpandValueSet() - //{ - // const string endpoint = @"https://stu3.simplifier.net/open/"; - // var location = new FhirUri("https://stu3.simplifier.net/open/ValueSet/043d233c-4ecf-4802-a4ac-75d82b4291c2"); - // var client = new FhirClient(endpoint); - // var expandedValueSet = client.ExpandValueSet(location, null); - //} - - /// - /// http://hl7.org/fhir/valueset-operations.html#lookup - /// - [TestMethod] // Server returns internal server error - [TestCategory("IntegrationTest")] - public void InvokeLookupCoding() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); - - var expansion = client.ConceptLookup(coding: coding); - - // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); - } - } - - [TestMethod] // Server returns internal server error - [TestCategory("IntegrationTest")] - public void InvokeLookupCode() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); - - //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server - Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeValidateCodeById() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); - - var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeValidateCodeByCanonical() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); - - var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), - coding: coding, @abstract: new FhirBoolean(false)); - Assert.IsTrue(result.Result?.Value == true); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public void InvokeValidateCodeWithVS() - { - using (var client = new FhirClient(FhirClientTests.TerminologyEndpoint)) - { - var coding = new Coding("http://snomed.info/sct", "4322002"); - - var vs = client.Read("ValueSet/c80-facilitycodes"); - Assert.IsNotNull(vs); - - var result = client.ValidateCode(valueSet: vs, coding: coding); - Assert.IsTrue(result.Result?.Value == true); - } - } - - - [TestMethod]//returns 500: validation of slices is not done yet. - [TestCategory("IntegrationTest"), Ignore] - public void InvokeResourceValidation() - { - using (var client = new FhirClient(testEndpoint)) - { - - var pat = client.Read("Patient/patient-uslab-example1"); - - try - { - var vresult = client.ValidateResource(pat, null, - new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); - Assert.Fail("Should have resulted in 400"); - } - catch (FhirOperationException fe) - { - Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); - Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); - } - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() - { - string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - - using (var client = new FhirClient(_endpoint)) - { - var start = new FhirDateTime(2014, 11, 1); - var end = new FhirDateTime(2020, 1, 1); - var par = new Parameters().Add("start", start).Add("end", end); - - var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); - var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); - - await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); - - var bundle = (Bundle)bundleTask.Result; - Assert.IsTrue(bundle.Entry.Any()); - - var bundle2 = (Bundle)bundle2Task.Result; - Assert.IsTrue(bundle2.Entry.Any()); - } - } - } -} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs deleted file mode 100644 index ce2b6311d3..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/ReadAsyncTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Hl7.Fhir.Core.AsyncTests -{ - [TestClass] - public class ReadAsyncTests - { - private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - - [TestMethod] - [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - Patient p = await client.ReadAsync("/Patient/SMART-1288992"); - Assert.IsNotNull(p); - Assert.IsNotNull(p.Name[0].Given); - Assert.IsNotNull(p.Name[0].Family); - Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - Console.WriteLine("Test Completed"); - } - } - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs deleted file mode 100644 index 6d29d8b941..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/RequesterTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright(c) 2017, Furore(info @furore.com) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE - */ - -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Hl7.Fhir.Support; -using Hl7.Fhir.Rest; -using Hl7.Fhir.Model; -using Hl7.Fhir.Utility; -using System.Linq; - -namespace Hl7.Fhir.Test -{ - [TestClass] - public class RequesterTests - { - [TestMethod] - public void SetsInteractionType() - { - // just try a few - var tx = new TransactionBuilder("http://myserver.org/fhir").ServerHistory(); - Assert.AreEqual(TransactionBuilder.InteractionType.History, tx.ToBundle().Entry[0].Annotation()); - - tx = new TransactionBuilder("http://myserver.org/fhir").ServerOperation("$everything", null); - Assert.AreEqual(TransactionBuilder.InteractionType.Operation, tx.ToBundle().Entry[0].Annotation()); - - var p = new Patient(); - tx = new TransactionBuilder("http://myserver.org/fhir").Create(p); - Assert.AreEqual(TransactionBuilder.InteractionType.Create, tx.ToBundle().Entry[0].Annotation()); - - tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); - Assert.AreEqual(TransactionBuilder.InteractionType.Search, tx.ToBundle().Entry[0].Annotation()); - } - - [TestMethod] - public void TestPreferSetting() - { - var p = new Patient(); - var tx = new TransactionBuilder("http://myserver.org/fhir") - .Create(p); - var b = tx.ToBundle(); - - var request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); - Assert.AreEqual("return=minimal", request.Headers.GetValues("Prefer").FirstOrDefault()); - - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.AreEqual("return=representation", request.Headers.GetValues("Prefer").FirstOrDefault()); - - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false); - Assert.AreEqual("return=OperationOutcome", request.Headers.GetValues("Prefer").FirstOrDefault()); - - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false); - Assert.IsFalse(request.Headers.TryGetValues("Prefer", out var headerValues)); - - tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); - b = tx.ToBundle(); - - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); - Assert.AreEqual("handling=lenient", request.Headers.GetValues("Prefer").FirstOrDefault()); - - request = b.Entry[0].ToHttpRequest(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.AreEqual("handling=strict", request.Headers.GetValues("Prefer").FirstOrDefault()); - - request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); - Assert.IsFalse(request.Headers.TryGetValues("Prefer", out headerValues)); - } - } -} - \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs deleted file mode 100644 index c2412dbb3f..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/SearchAsyncTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Linq; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Task = System.Threading.Tasks.Task; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Hl7.Fhir.Core.AsyncTests -{ - [TestClass] - public class SearchAsyncTests - { - private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - - [TestMethod] - [TestCategory("IntegrationTest")] - public async Task Search_UsingSearchParams_SearchReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var result1 = await client.SearchAsync(srch); - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) - { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); - } - - Console.WriteLine("Test Completed"); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public void SearchSync_UsingSearchParams_SearchReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - var srch = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var result1 = client.Search(srch); - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) - { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); - } - - Console.WriteLine("Test Completed"); - } - } - - - [TestMethod] - [TestCategory("IntegrationTest")] - public async Task SearchMultiple_UsingSearchParams_SearchReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - var srchParams = new SearchParams() - .Where("name=Daniel") - .LimitTo(10) - .SummaryOnly() - .OrderBy("birthdate", - SortOrder.Descending); - - var task1 = client.SearchAsync(srchParams); - var task2 = client.SearchAsync(srchParams); - var task3 = client.SearchAsync(srchParams); - - await Task.WhenAll(task1, task2, task3); - var result1 = task1.Result; - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) - { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); - } - - Console.WriteLine("Test Completed"); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public async Task SearchWithCriteria_SyncContinue_SearchReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - var result1 = await client.SearchAsync(new[] { "family=clark" }); - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) - { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - result1 = client.Continue(result1, PageDirection.Next); - } - - Console.WriteLine("Test Completed"); - } - } - - [TestMethod] - [TestCategory("IntegrationTest")] - public async Task SearchWithCriteria_AsyncContinue_SearchReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); - - Assert.IsTrue(result1.Entry.Count >= 1); - - while (result1 != null) - { - foreach (var e in result1.Entry) - { - Patient p = (Patient)e.Resource; - Console.WriteLine( - $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); - } - Console.WriteLine("Fetching more results..."); - result1 = await client.ContinueAsync(result1); - } - - Console.WriteLine("Test Completed"); - } - } - } -} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs deleted file mode 100644 index 8bb2d9dda1..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/TransactionBuilderTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2014, Furore (info@furore.com) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/ewoutkramer/fhir-net-api/master/LICENSE - */ - -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Hl7.Fhir.Support; -using Hl7.Fhir.Rest; -using Hl7.Fhir.Model; - -namespace Hl7.Fhir.Test -{ - [TestClass] - public class TransactionBuilderTests - { - [TestMethod] - public void TestBuild() - { - var p = new Patient(); - var b = new TransactionBuilder("http://myserver.org/fhir") - .Create(p) - .ResourceHistory("Patient","7") - .Delete("Patient","8") - .Read("Patient","9", versionId: "bla") - .ToBundle(); - - Assert.AreEqual(4, b.Entry.Count); - - Assert.AreEqual(Bundle.HTTPVerb.POST, b.Entry[0].Request.Method); - Assert.AreEqual(p, b.Entry[0].Resource); - - Assert.AreEqual(Bundle.HTTPVerb.GET, b.Entry[1].Request.Method); - Assert.AreEqual("http://myserver.org/fhir/Patient/7/_history", b.Entry[1].Request.Url); - - Assert.AreEqual(Bundle.HTTPVerb.DELETE, b.Entry[2].Request.Method); - Assert.AreEqual("http://myserver.org/fhir/Patient/8", b.Entry[2].Request.Url); - - Assert.AreEqual(Bundle.HTTPVerb.GET, b.Entry[3].Request.Method); - Assert.AreEqual("http://myserver.org/fhir/Patient/9", b.Entry[3].Request.Url); - Assert.AreEqual("W/\"bla\"", b.Entry[3].Request.IfNoneMatch); - } - - [TestMethod] - public void TestUrlEncoding() - { - var tx = new TransactionBuilder("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f"); - tx.Get("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=<=2014-09-08T18:42:02.000Z&context=14187710&_format=json"); - var b = tx.ToBundle(); - - var req = b.Entry[0].ToHttpRequest(null, null, ResourceFormat.Json, useFormatParameter: true, CompressRequestBody: false); - - Assert.AreEqual("https://fhir.sandboxcernerpowerchart.com/may2015/open/d075cf8b-3261-481d-97e5-ba6c48d3b41f/MedicationPrescription?patient=1316024&status=completed%2Cstopped&_count=25&scheduledtiming-bounds-end=%3C%3D2014-09-08T18%3A42%3A02.000Z&context=14187710&_format=json&_format=json", req.RequestUri.AbsoluteUri); - } - - - [TestMethod] - public void TestConditionalCreate() - { - var p = new Patient(); - var tx = new TransactionBuilder("http://myserver.org/fhir") - .Create(p, new SearchParams().Where("name=foobar")); - var b = tx.ToBundle(); - - Assert.AreEqual("name=foobar",b.Entry[0].Request.IfNoneExist); - } - - - [TestMethod] - public void TestConditionalUpdate() - { - var p = new Patient(); - var tx = new TransactionBuilder("http://myserver.org/fhir") - .Update(new SearchParams().Where("name=foobar"), p, versionId: "314"); - var b = tx.ToBundle(); - - Assert.AreEqual("W/\"314\"", b.Entry[0].Request.IfMatch); - } - } -} diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs deleted file mode 100644 index da3339bd4b..0000000000 --- a/src/Hl7.Fhir.Core.Tests/Rest/Http/UpdateRefreshDeleteAsyncTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Hl7.Fhir.Core.AsyncTests -{ - [TestClass] - public class UpdateRefreshDeleteAsyncTests - { - private string _endpoint = "https://api.hspconsortium.org/rpineda/open"; - - [TestMethod] - [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() - { - using (var client = new FhirClient(_endpoint) - { - PreferredFormat = ResourceFormat.Json, - PreferredReturn = Prefer.ReturnRepresentation - }) - { - - var pat = new Patient() - { - Name = new List() - { - new HumanName() - { - Given = new List() {"test_given"}, - Family = "test_family", - } - }, - Id = "async-test-patient" - }; - // Create the patient - Console.WriteLine("Creating patient..."); - Patient p = await client.UpdateAsync(pat); - Assert.IsNotNull(p); - - // Refresh the patient - Console.WriteLine("Refreshing patient..."); - await client.RefreshAsync(p); - - // Delete the patient - Console.WriteLine("Deleting patient..."); - await client.DeleteAsync(p); - - Console.WriteLine("Reading patient..."); - Func act = async () => - { - await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); - }; - - // VERIFY // - Assert.ThrowsException(act, "the patient is no longer on the server"); - - - Console.WriteLine("Test Completed"); - } - } - } -} \ No newline at end of file From 14836e88cb93419b920bcddd1501d0a71ff84b1a Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 10:53:13 -0600 Subject: [PATCH 25/39] Added mock message handler and started adding HttpClient tests. --- .../Rest/FhirClientTests.cs | 553 +++++++++++++++++- .../Rest/Mocks/MockHttpMessageHandler.cs | 82 +++ 2 files changed, 609 insertions(+), 26 deletions(-) create mode 100644 src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 61a1f890d6..f046650ebd 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -16,6 +16,7 @@ using Hl7.Fhir.Serialization; using Hl7.Fhir.Model; using TestClient = Hl7.Fhir.Rest.Http.FhirClient; +using Hl7.Fhir.Core.Tests.Rest.Mocks; namespace Hl7.Fhir.Tests.Rest { @@ -309,7 +310,7 @@ public void ReadRelativeAsyncHttpClient() } #endif - public static void Compression_OnBeforeWebRequestGZip(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeWebRequestGZip(object sender, Fhir.Rest.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -319,7 +320,7 @@ public static void Compression_OnBeforeWebRequestGZip(object sender, BeforeReque } } - public static void Compression_OnBeforeWebRequestDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeWebRequestDeflate(object sender, Fhir.Rest.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -329,7 +330,7 @@ public static void Compression_OnBeforeWebRequestDeflate(object sender, BeforeRe } } - public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, Fhir.Rest.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -339,33 +340,33 @@ public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, Bef } } - public static void Compression_OnBeforeHttpRequestGZip(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestGZip(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) { if (e.RawRequest != null) { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip"); } } - public static void Compression_OnBeforeHttpRequestDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestDeflate(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) { if (e.RawRequest != null) { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "deflate"); } } - public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) { if (e.RawRequest != null) { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + e.RawRequest.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); } } @@ -379,10 +380,10 @@ public void SearchWebClient() client.CompressRequestBody = true; client.OnBeforeRequest += Compression_OnBeforeWebRequestGZip; - client.OnAfterResponse += Client_OnAfterResponse; + client.OnAfterResponse += Client_OnAfterWebResponse; result = client.Search(); - client.OnAfterResponse -= Client_OnAfterResponse; + client.OnAfterResponse -= Client_OnAfterWebResponse; Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); @@ -422,7 +423,61 @@ public void SearchWebClient() Assert.IsTrue(result.Entry.Count > 0); } - private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) + [TestMethod, Ignore] // Something does not work with the gzip + [TestCategory("FhirClient"), + TestCategory("IntegrationTest")] + public void SearchHttpClient() + { + using (var handler = new MockHttpMessageHandler()) + using (TestClient client = new TestClient(testEndpoint, handler)) + { + Bundle result; + + client.CompressRequestBody = true; + handler.OnBeforeRequest += Compression_OnBeforeHttpRequestGZip; + + result = client.Search(); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + + handler.OnBeforeRequest -= Compression_OnBeforeHttpRequestZipOrDeflate; + handler.OnBeforeRequest += Compression_OnBeforeHttpRequestZipOrDeflate; + + result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + handler.OnBeforeRequest -= Compression_OnBeforeHttpRequestGZip; + + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + + ResourceIdentity ri = withSubject.ResourceIdentity(); + + // TODO: The include on Grahame's server doesn't currently work + //result = client.SearchById(ri.Id, + // includes: new string[] { "DiagnosticReport:subject" }); + //Assert.IsNotNull(result); + + //Assert.AreEqual(2, result.Entry.Count); // should have subject too + + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(DiagnosticReport).GetCollectionName())); + //Assert.IsNotNull(result.Entry.Single(entry => entry.Resource.ResourceIdentity().ResourceType == + // typeof(Patient).GetCollectionName())); + + + handler.OnBeforeRequest += Compression_OnBeforeHttpRequestDeflate; + + result = client.Search(new string[] { "name=Chalmers", "name=Peter" }); + + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } + } + + private void Client_OnAfterWebResponse(object sender, Fhir.Rest.AfterResponseEventArgs e) { // Test that the response was compressed Assert.AreEqual("gzip", e.RawResponse.Headers[HttpResponseHeader.ContentEncoding]); @@ -430,7 +485,7 @@ private void Client_OnAfterResponse(object sender, AfterResponseEventArgs e) #if NO_ASYNC_ANYMORE [TestMethod, TestCategory("FhirClient")] - public void SearchAsync() + public void SearchAsyncWebClient() { FhirClient client = new FhirClient(testEndpoint); Bundle result; @@ -465,11 +520,49 @@ public void SearchAsync() Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count > 0); } + + public void SearchAsyncHttpClient() + { + using(TestClient client = new TestClient(testEndpoint)) + { + Bundle result; + + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); + + Assert.AreEqual(2, result.Entry.Count); // should have subject too + + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); + + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } + } #endif [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void Paging() + public void PagingWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -501,7 +594,40 @@ public void Paging() } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void PagingInJson() + public void PagingHttpClient() + { + using (TestClient client = new TestClient(testEndpoint)) + { + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } + } + + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void PagingInJsonWebClient() { FhirClient client = new FhirClient(testEndpoint); client.PreferredFormat = ResourceFormat.Json; @@ -533,10 +659,45 @@ public void PagingInJson() Assert.AreEqual(firstId, prevId); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void PagingInJsonHttpClient() + { + using (TestClient client = new TestClient(testEndpoint)) + { + client.PreferredFormat = ResourceFormat.Json; + + var result = client.Search(pageSize: 10); + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var firstId = result.Entry.First().Resource.Id; + + // Browse forward + result = client.Continue(result); + Assert.IsNotNull(result); + var nextId = result.Entry.First().Resource.Id; + Assert.AreNotEqual(firstId, nextId); + + // Browse to first + result = client.Continue(result, PageDirection.First); + Assert.IsNotNull(result); + var prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + + // Forward, then backwards + result = client.Continue(result, PageDirection.Next); + Assert.IsNotNull(result); + result = client.Continue(result, PageDirection.Previous); + Assert.IsNotNull(result); + prevId = result.Entry.First().Resource.Id; + Assert.AreEqual(firstId, prevId); + } + } + [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateAndFullRepresentation() + public void CreateAndFullRepresentationWebClient() { FhirClient client = new FhirClient(testEndpoint); client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default @@ -568,8 +729,41 @@ public void CreateAndFullRepresentation() Assert.IsNotNull(ooI); } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CreateAndFullRepresentationHttpClient() + { + using (TestClient client = new TestClient(testEndpoint)) + { + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + + var pat = client.Read("Patient/glossy"); + ResourceIdentity ri = pat.ResourceIdentity().WithBase(client.Endpoint); + pat.Id = null; + pat.Identifier.Clear(); + var patC = client.Create(pat); + Assert.IsNotNull(patC); + client.PreferredReturn = Prefer.ReturnMinimal; + patC = client.Create(pat); + Assert.IsNull(patC); + + if (client.LastBody != null) + { + var returned = client.LastBodyAsResource; + Assert.IsTrue(returned is OperationOutcome); + } + + // Now validate this resource + client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default + Parameters p = new Parameters(); + // p.Add("mode", new FhirString("create")); + p.Add("resource", pat); + OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); + Assert.IsNotNull(ooI); + } + } private Uri createdTestPatientUrl = null; @@ -579,7 +773,7 @@ public void CreateAndFullRepresentation() ///
[TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateEditDelete() + public void CreateEditDeleteWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -627,10 +821,67 @@ public void CreateEditDelete() } } + /// + /// This test is also used as a "setup" test for the History test. + /// If you change the number of operations in here, this will make the History test fail. + /// + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CreateEditDeleteHttpClient() + { + using (var handler = new MockHttpMessageHandler()) + using (TestClient client = new TestClient(testEndpoint)) + { + + handler.OnBeforeRequest += Compression_OnBeforeHttpRequestZipOrDeflate; + // client.CompressRequestBody = true; + + var pat = client.Read("Patient/example"); + pat.Id = null; + pat.Identifier.Clear(); + pat.Identifier.Add(new Identifier("http://hl7.org/test/2", "99999")); + + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(pat)); + + var fe = client.Create(pat); // Create as we are not providing the ID to be used. + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.Meta.VersionId); + createdTestPatientUrl = fe.ResourceIdentity(); + + fe.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.Update(fe); + + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.ResourceIdentity(), fe2.ResourceIdentity()); + Assert.AreEqual(2, fe2.Identifier.Count); + + fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.Update(fe); + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Identifier.Count); + + client.Delete(fe3); + + try + { + // Get most recent version + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); + Assert.AreEqual("410", client.LastResult.Status); + } + } + } + [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 - public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantity() + public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantityWebClient() { FhirClient client = new FhirClient(testEndpoint); var observation = new Observation(); @@ -649,13 +900,37 @@ public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuanti Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + //Test for github issue https://github.com/ewoutkramer/fhir-net-api/issues/145 + public void Create_ObservationWithValueAsSimpleQuantity_ReadReturnsValueAsQuantityHttpClient() + { + using (TestClient client = new TestClient(testEndpoint)) + { + var observation = new Observation(); + observation.Status = ObservationStatus.Preliminary; + observation.Code = new CodeableConcept("http://loinc.org", "2164-2"); + observation.Value = new SimpleQuantity() + { + System = "http://unitsofmeasure.org", + Value = 23, + Code = "mg", + Unit = "miligram" + }; + observation.BodySite = new CodeableConcept("http://snomed.info/sct", "182756003"); + var fe = client.Create(observation); + fe = client.Read(fe.ResourceIdentity().WithoutVersion()); + Assert.IsInstanceOfType(fe.Value, typeof(Quantity)); + } + } + #if NO_ASYNC_ANYMORE /// /// This test is also used as a "setup" test for the History test. /// If you change the number of operations in here, this will make the History test fail. /// [TestMethod, TestCategory("FhirClient")] - public void CreateEditDeleteAsync() + public void CreateEditDeleteAsyncWebClient() { var furore = new Organization { @@ -709,6 +984,68 @@ public void CreateEditDeleteAsync() Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); } } + + /// + /// This test is also used as a "setup" test for the History test. + /// If you change the number of operations in here, this will make the History test fail. + /// + [TestMethod, TestCategory("FhirClient")] + public void CreateEditDeleteAsyncHttpClient() + { + var furore = new Organization + { + Name = "Furore", + Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, + Telecom = new List { new Contact { System = Contact.ContactSystem.Phone, Value = "+31-20-3467171" } } + }; + + using (TestClient client = new TestClient(testEndpoint)) + { + var tags = new List { new Tag("http://nu.nl/testname", Tag.FHIRTAGSCHEME_GENERAL, "TestCreateEditDelete") }; + + var fe = client.CreateAsync(furore, tags: tags, refresh: true).Result; + + Assert.IsNotNull(furore); + Assert.IsNotNull(fe); + Assert.IsNotNull(fe.Id); + Assert.IsNotNull(fe.SelfLink); + Assert.AreNotEqual(fe.Id, fe.SelfLink); + Assert.IsNotNull(fe.Tags); + Assert.AreEqual(1, fe.Tags.Count(), "Tag count on new organization record don't match"); + Assert.AreEqual(fe.Tags.First(), tags[0]); + createdTestOrganizationUrl = fe.Id; + + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/2", "3141592")); + var fe2 = client.UpdateAsync(fe, refresh: true).Result; + + Assert.IsNotNull(fe2); + Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreNotEqual(fe.SelfLink, fe2.SelfLink); + Assert.AreEqual(2, fe2.Resource.Identifier.Count); + + Assert.IsNotNull(fe2.Tags); + Assert.AreEqual(1, fe2.Tags.Count(), "Tag count on updated organization record don't match"); + Assert.AreEqual(fe2.Tags.First(), tags[0]); + + fe.Resource.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); + var fe3 = client.UpdateAsync(fe2.Id, fe.Resource, refresh: true).Result; + Assert.IsNotNull(fe3); + Assert.AreEqual(3, fe3.Resource.Identifier.Count); + + client.DeleteAsync(fe3).Wait(); + + try + { + // Get most recent version + fe = client.ReadAsync(new ResourceIdentity(fe.Id)).Result; + Assert.Fail(); + } + catch + { + Assert.IsTrue(client.LastResponseDetails.Result == HttpStatusCode.Gone); + } + } + } #endif /// @@ -716,12 +1053,70 @@ public void CreateEditDeleteAsync() /// and counts them in the WholeSystemHistory /// [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? - public void History() + public void HistoryWebClient() + { + System.Threading.Thread.Sleep(500); + DateTimeOffset timestampBeforeCreationAndDeletions = DateTimeOffset.Now; + + CreateEditDeleteWebClient(); // this test does a create, update, update, delete (4 operations) + + FhirClient client = new FhirClient(testEndpoint); + + System.Diagnostics.Trace.WriteLine("History of this specific patient since just before the create, update, update, delete (4 operations)"); + + Bundle history = client.History(createdTestPatientUrl); + Assert.IsNotNull(history); + DebugDumpBundle(history); + + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + //// Now, assume no one is quick enough to insert something between now and the next + //// tests.... + + + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type"); + history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + + System.Diagnostics.Trace.WriteLine("\r\nHistory on the patient type (using the generic method in the client)"); + history = client.TypeHistory(timestampBeforeCreationAndDeletions.ToUniversalTime(), summary: SummaryType.True); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.AreEqual(4, history.Entry.Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); + + if (!testEndpoint.OriginalString.Contains("sqlonfhir-stu3")) + { + System.Diagnostics.Trace.WriteLine("\r\nWhole system history since the start of this test"); + history = client.WholeSystemHistory(timestampBeforeCreationAndDeletions.ToUniversalTime()); + Assert.IsNotNull(history); + DebugDumpBundle(history); + Assert.IsTrue(4 <= history.Entry.Count(), "Whole System history should have at least 4 new events"); + // Check that the number of patients that have been created is what we expected + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null && entry.Resource is Patient).Count()); + Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted() && entry.Request.Url.Contains("Patient")).Count()); + } + } + + /// + /// This test will fail if the system records AuditEvents + /// and counts them in the WholeSystemHistory + /// + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + public void HistoryHttpClient() { System.Threading.Thread.Sleep(500); DateTimeOffset timestampBeforeCreationAndDeletions = DateTimeOffset.Now; - CreateEditDelete(); // this test does a create, update, update, delete (4 operations) + CreateEditDeleteHttpClient(); // this test does a create, update, update, delete (4 operations) FhirClient client = new FhirClient(testEndpoint); @@ -772,15 +1167,26 @@ public void History() [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestWithParam() + public void TestWithParamWebClient() { var client = new FhirClient(testEndpoint); var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); Assert.IsNotNull(res); } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestWithParamHttpClient() + { + using (var client = new TestClient(testEndpoint)) + { + var res = client.Get("ValueSet/v2-0131/$validate-code?system=http://hl7.org/fhir/v2/0131&code=ep"); + Assert.IsNotNull(res); + } + } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void ManipulateMeta() + public void ManipulateMetaWebClient() { FhirClient client = new FhirClient(testEndpoint); @@ -872,6 +1278,101 @@ public void ManipulateMeta() verifyMeta(par, false, key); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void ManipulateMetaHttpClient() + { + using (TestClient client = new TestClient(testEndpoint)) + { + + var pat = new Patient(); + pat.Meta = new Meta(); + var key = new Random().Next(); + pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); + pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); + + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); + Assert.IsNotNull(wsm); + + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("1234-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag1-" + key + "@http://mysystem.com/tag")); + + Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + Assert.IsFalse(wsm.Security.Select(c => c.Code + "@" + c.System).Contains("5678-" + key + "@http://mysystem.com/sec")); + Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); + + + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); + + // Meta should be present on created patient + verifyMeta(pat2.Meta, false, key); + + // Should be present when doing instance Meta() + var par = client.Meta(loc); + verifyMeta(par, false, key); + + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); + + // Should be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + + // Now add some additional meta to the patient + + var newMeta = new Meta(); + newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); + + + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); + + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); + + // New and old meta should be present on Meta() + par = client.Meta(loc); + verifyMeta(par, true, key); + + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, true, key); + + // New and old meta should be present when doing system Meta() + par = client.Meta(); + verifyMeta(par, true, key); + + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); + + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); + + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); + verifyMeta(par, false, key); + + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); + verifyMeta(par, false, key); + + // clear out the client that we created, no point keeping it around + client.Delete(pat4); + + // Should no longer be present when doing System Meta() + par = client.Meta(); + verifyMeta(par, false, key); + } + } + private void verifyMeta(Meta meta, bool hasNew, int key) { @@ -994,7 +1495,7 @@ public void RequestFullResource() { var client = new FhirClient(testEndpoint); var minimal = false; - client.OnBeforeRequest += (object s, BeforeRequestEventArgs e) => e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; + client.OnBeforeRequest += (object s, Fhir.Rest.BeforeRequestEventArgs e) => e.RawRequest.Headers["Prefer"] = minimal ? "return=minimal" : "return=representation"; var result = client.Read("Patient/glossy"); Assert.IsNotNull(result); @@ -1016,7 +1517,7 @@ public void RequestFullResource() Assert.IsNull(posted); } - void client_OnBeforeRequest(object sender, BeforeRequestEventArgs e) + void client_OnBeforeRequest(object sender, Fhir.Rest.BeforeRequestEventArgs e) { throw new NotImplementedException(); } @@ -1202,7 +1703,7 @@ public void FhirVersionIsChecked() public void TestAuthenticationOnBefore() { FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); - validationFhirClient.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => + validationFhirClient.OnBeforeRequest += (object sender, Fhir.Rest.BeforeRequestEventArgs e) => { e.RawRequest.Headers["Authorization"] = "Bearer bad-bearer"; }; diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs b/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs new file mode 100644 index 0000000000..ce085582a3 --- /dev/null +++ b/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Hl7.Fhir.Core.Tests.Rest.Mocks +{ + public class MockHttpMessageHandler : HttpClientHandler + { + /// + /// Called just before the Http call is done + /// + public event EventHandler OnBeforeRequest; + + /// + /// Called just after the response was received + /// + public event EventHandler OnAfterResponse; + + /// + /// Inspect or modify the HttpRequestMessage just before the FhirClient issues a call to the server + /// + /// The request as it is about to be sent to the server + /// The data in the body of the request as it is about to be sent to the server + protected virtual void BeforeRequest(HttpRequestMessage rawRequest, byte[] body) + { + // Default implementation: call event + OnBeforeRequest?.Invoke(this, new BeforeRequestEventArgs(rawRequest, body)); + } + + /// + /// Inspect the HttpResponseMessage as it came back from the server + /// + /// You cannot read the body from the HttpResponseMessage, since it has + /// already been read by the framework. Use the body parameter instead. + protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] body) + { + // Default implementation: call event + OnAfterResponse?.Invoke(this, new AfterResponseEventArgs(webResponse, body)); + } + + + protected override async Task SendAsync(HttpRequestMessage message, CancellationToken cancellationToken) + { + BeforeRequest(message, (await message.Content.ReadAsByteArrayAsync())); + + var response = await base.SendAsync(message, cancellationToken); + + AfterResponse(response, (await response.Content.ReadAsByteArrayAsync())); + + return response; + } + } + + public class BeforeRequestEventArgs : EventArgs + { + public BeforeRequestEventArgs(HttpRequestMessage rawRequest, byte[] body) + { + this.RawRequest = rawRequest; + this.Body = body; + } + + public HttpRequestMessage RawRequest { get; internal set; } + public byte[] Body { get; internal set; } + } + + public class AfterResponseEventArgs : EventArgs + { + public AfterResponseEventArgs(HttpResponseMessage webResponse, byte[] body) + { + this.RawResponse = webResponse; + this.Body = body; + } + + public HttpResponseMessage RawResponse { get; internal set; } + public byte[] Body { get; internal set; } + } +} From 49c10300fce191cedccda74e78226c427b76e05b Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 10:54:30 -0600 Subject: [PATCH 26/39] Modified client operations to work on fhir client base class. --- .../Rest/FhirClientOperations.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs b/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs index 384e6a033a..34df570913 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs @@ -63,7 +63,7 @@ public static class FhirClientOperations { #region Validate (Create/Update/Delete/Resource) - public static async Task ValidateCreateAsync(this FhirClient client, DomainResource resource, FhirUri profile = null) + public static async Task ValidateCreateAsync(this BaseFhirClient client, DomainResource resource, FhirUri profile = null) { if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -72,13 +72,13 @@ public static async Task ValidateCreateAsync(this FhirClient c return OperationResult(await client.TypeOperationAsync(RestOperation.VALIDATE_RESOURCE, resource.TypeName, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateCreate(this FhirClient client, DomainResource resource, + public static OperationOutcome ValidateCreate(this BaseFhirClient client, DomainResource resource, FhirUri profile = null) { return ValidateCreateAsync(client, resource, profile).WaitResult(); } - public static async Task ValidateUpdateAsync(this FhirClient client, DomainResource resource, string id, FhirUri profile = null) + public static async Task ValidateUpdateAsync(this BaseFhirClient client, DomainResource resource, string id, FhirUri profile = null) { if (id == null) throw Error.ArgumentNull(nameof(id)); if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -89,14 +89,14 @@ public static async Task ValidateUpdateAsync(this FhirClient c var loc = ResourceIdentity.Build(resource.TypeName, id); return OperationResult(await client.InstanceOperationAsync(loc, RestOperation.VALIDATE_RESOURCE, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateUpdate(this FhirClient client, DomainResource resource, string id, + public static OperationOutcome ValidateUpdate(this BaseFhirClient client, DomainResource resource, string id, FhirUri profile = null) { return ValidateUpdateAsync(client, resource, id, profile).WaitResult(); } - public static async Task ValidateDeleteAsync(this FhirClient client, ResourceIdentity location) + public static async Task ValidateDeleteAsync(this BaseFhirClient client, ResourceIdentity location) { if (location == null) throw Error.ArgumentNull(nameof(location)); @@ -104,12 +104,12 @@ public static async Task ValidateDeleteAsync(this FhirClient c return OperationResult(await client.InstanceOperationAsync(location.WithoutVersion().MakeRelative(), RestOperation.VALIDATE_RESOURCE, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateDelete(this FhirClient client, ResourceIdentity location) + public static OperationOutcome ValidateDelete(this BaseFhirClient client, ResourceIdentity location) { return ValidateDeleteAsync(client,location).WaitResult(); } - public static async Task ValidateResourceAsync(this FhirClient client, DomainResource resource, string id = null, FhirUri profile = null) + public static async Task ValidateResourceAsync(this BaseFhirClient client, DomainResource resource, string id = null, FhirUri profile = null) { if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -127,7 +127,7 @@ public static async Task ValidateResourceAsync(this FhirClient } } - public static OperationOutcome ValidateResource(this FhirClient client, DomainResource resource, + public static OperationOutcome ValidateResource(this BaseFhirClient client, DomainResource resource, string id = null, FhirUri profile = null) { return ValidateResourceAsync(client, resource, id, profile).WaitResult(); @@ -137,7 +137,7 @@ public static OperationOutcome ValidateResource(this FhirClient client, DomainRe #region Fetch - public static async Task FetchPatientRecordAsync(this FhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) + public static async Task FetchPatientRecordAsync(this BaseFhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) { var par = new Parameters(); @@ -155,7 +155,7 @@ public static async Task FetchPatientRecordAsync(this FhirClient client, return OperationResult(result); } - public static Bundle FetchPatientRecord(this FhirClient client, Uri patient = null, FhirDateTime start = null, + public static Bundle FetchPatientRecord(this BaseFhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) { return FetchPatientRecordAsync(client, patient, start, end).WaitResult(); @@ -166,84 +166,84 @@ public static Bundle FetchPatientRecord(this FhirClient client, Uri patient = nu #region Meta //[base]/$meta - public static async Task MetaAsync(this FhirClient client) + public static async Task MetaAsync(this BaseFhirClient client) { return extractMeta(OperationResult(await client.WholeSystemOperationAsync(RestOperation.META, useGet:true).ConfigureAwait(false))); } - public static Meta Meta(this FhirClient client) + public static Meta Meta(this BaseFhirClient client) { return MetaAsync(client).WaitResult(); } //[base]/Resource/$meta - public static async Task MetaAsync(this FhirClient client, ResourceType type) + public static async Task MetaAsync(this BaseFhirClient client, ResourceType type) { return extractMeta(OperationResult(await client.TypeOperationAsync(RestOperation.META, type.ToString(), useGet: true).ConfigureAwait(false))); } - public static Meta Meta(this FhirClient client, ResourceType type) + public static Meta Meta(this BaseFhirClient client, ResourceType type) { return MetaAsync(client, type).WaitResult(); } //[base]/Resource/id/$meta/[_history/vid] - public static async Task MetaAsync(this FhirClient client, Uri location) + public static async Task MetaAsync(this BaseFhirClient client, Uri location) { Resource result; result = await client.InstanceOperationAsync(location, RestOperation.META, useGet: true).ConfigureAwait(false); return extractMeta(OperationResult(result)); } - public static Meta Meta(this FhirClient client, Uri location) + public static Meta Meta(this BaseFhirClient client, Uri location) { return MetaAsync(client, location).WaitResult(); } - public static Task MetaAsync(this FhirClient client, string location) + public static Task MetaAsync(this BaseFhirClient client, string location) { return MetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute)); } - public static Meta Meta(this FhirClient client, string location) + public static Meta Meta(this BaseFhirClient client, string location) { return MetaAsync(client, location).WaitResult(); } - public static async Task AddMetaAsync(this FhirClient client, Uri location, Meta meta) + public static async Task AddMetaAsync(this BaseFhirClient client, Uri location, Meta meta) { var par = new Parameters().Add("meta", meta); return extractMeta(OperationResult(await client.InstanceOperationAsync(location, RestOperation.META_ADD, par).ConfigureAwait(false))); } - public static Meta AddMeta(this FhirClient client, Uri location, Meta meta) + public static Meta AddMeta(this BaseFhirClient client, Uri location, Meta meta) { return AddMetaAsync(client, location, meta).WaitResult(); } - public static Task AddMetaAsync(this FhirClient client, string location, Meta meta) + public static Task AddMetaAsync(this BaseFhirClient client, string location, Meta meta) { return AddMetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute), meta); } - public static Meta AddMeta(this FhirClient client, string location, Meta meta) + public static Meta AddMeta(this BaseFhirClient client, string location, Meta meta) { return AddMetaAsync(client, location, meta).WaitResult(); } - public static async Task DeleteMetaAsync(this FhirClient client, Uri location, Meta meta) + public static async Task DeleteMetaAsync(this BaseFhirClient client, Uri location, Meta meta) { var par = new Parameters().Add("meta", meta); return extractMeta(OperationResult(await client.InstanceOperationAsync(location, RestOperation.META_DELETE, par).ConfigureAwait(false))); } - public static Meta DeleteMeta(this FhirClient client, Uri location, Meta meta) + public static Meta DeleteMeta(this BaseFhirClient client, Uri location, Meta meta) { return DeleteMetaAsync(client, location, meta).WaitResult(); } - public static Task DeleteMetaAsync(this FhirClient client, string location, Meta meta) + public static Task DeleteMetaAsync(this BaseFhirClient client, string location, Meta meta) { return DeleteMetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute), meta); } - public static Meta DeleteMeta(this FhirClient client, string location, Meta meta) + public static Meta DeleteMeta(this BaseFhirClient client, string location, Meta meta) { return DeleteMetaAsync(client, location, meta).WaitResult(); } @@ -260,14 +260,14 @@ public class TranslateConceptDependency } - public static async Task TranslateConceptAsync(this FhirClient client, string id, Code code, FhirUri system, FhirString version, + public static async Task TranslateConceptAsync(this BaseFhirClient client, string id, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) { Parameters par = createTranslateConceptParams(code, system, version, valueSet, coding, codeableConcept, target, dependencies); var loc = ResourceIdentity.Build("ConceptMap", id); return OperationResult(await client.InstanceOperationAsync(loc, RestOperation.TRANSLATE, par).ConfigureAwait(false)); } - public static Parameters TranslateConcept(this FhirClient client, string id, Code code, FhirUri system, + public static Parameters TranslateConcept(this BaseFhirClient client, string id, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) @@ -277,7 +277,7 @@ public static Parameters TranslateConcept(this FhirClient client, string id, Cod } - public static async Task TranslateConceptAsync(this FhirClient client, Code code, FhirUri system, FhirString version, + public static async Task TranslateConceptAsync(this BaseFhirClient client, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies ) { Parameters par = createTranslateConceptParams(code, system, version, valueSet, coding, codeableConcept, target, dependencies); @@ -285,7 +285,7 @@ public static async Task TranslateConceptAsync(this FhirClient clien return OperationResult(await client.TypeOperationAsync(RestOperation.TRANSLATE, par).ConfigureAwait(false)); } - public static Parameters TranslateConcept(this FhirClient client, Code code, FhirUri system, FhirString version, + public static Parameters TranslateConcept(this BaseFhirClient client, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) { From cb751a5627e969468ac3cd3c4b10b55ba40f77df Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 11:17:42 -0600 Subject: [PATCH 27/39] Modified more tests for HttpClient addition --- .../Rest/FhirClientTests.cs | 415 +++++++++++++++--- 1 file changed, 346 insertions(+), 69 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index f046650ebd..2736ecf9ca 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -45,7 +45,7 @@ public void TestInitialize() public static void DebugDumpBundle(Hl7.Fhir.Model.Bundle b) { System.Diagnostics.Trace.WriteLine(String.Format("--------------------------------------------\r\nBundle Type: {0} ({1} total items, {2} included)", b.Type.ToString(), b.Total, (b.Entry != null ? b.Entry.Count.ToString() : "-"))); - + if (b.Entry != null) { foreach (var item in b.Entry) @@ -89,7 +89,7 @@ public void FetchConformanceWebClient() Assert.AreEqual("200", client.LastResult.Status); Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary } @@ -118,7 +118,7 @@ public void FetchConformanceHttpClient() Assert.AreEqual("200", client.LastResult.Status); Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count , "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary } @@ -336,7 +336,7 @@ public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, Fhi { // e.RawRequest.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; e.RawRequest.Headers.Remove("Accept-Encoding"); - e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; + e.RawRequest.Headers["Accept-Encoding"] = "gzip, deflate"; } } @@ -371,7 +371,7 @@ public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, Co } [TestMethod, Ignore] // Something does not work with the gzip - [TestCategory("FhirClient"), + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void SearchWebClient() { @@ -723,7 +723,7 @@ public void CreateAndFullRepresentationWebClient() // Now validate this resource client.PreferredReturn = Prefer.ReturnRepresentation; // which is also the default Parameters p = new Parameters(); - // p.Add("mode", new FhirString("create")); + // p.Add("mode", new FhirString("create")); p.Add("resource", pat); OperationOutcome ooI = (OperationOutcome)client.InstanceOperation(ri.WithoutVersion(), "validate", p); Assert.IsNotNull(ooI); @@ -814,7 +814,7 @@ public void CreateEditDeleteWebClient() fe = client.Read(fe.ResourceIdentity().WithoutVersion()); Assert.Fail(); } - catch(FhirOperationException ex) + catch (FhirOperationException ex) { Assert.AreEqual(HttpStatusCode.Gone, ex.Status, "Expected the record to be gone"); Assert.AreEqual("410", client.LastResult.Status); @@ -1052,7 +1052,7 @@ public void CreateEditDeleteAsyncHttpClient() /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory ///
- [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? public void HistoryWebClient() { System.Threading.Thread.Sleep(500); @@ -1069,7 +1069,7 @@ public void HistoryWebClient() DebugDumpBundle(history); Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); //// Now, assume no one is quick enough to insert something between now and the next @@ -1110,7 +1110,7 @@ public void HistoryWebClient() /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// - [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"),Ignore] // Keeps on failing periodically. Grahames server? + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? public void HistoryHttpClient() { System.Threading.Thread.Sleep(500); @@ -1127,7 +1127,7 @@ public void HistoryHttpClient() DebugDumpBundle(history); Assert.AreEqual(4, history.Entry.Count()); - Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); + Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); //// Now, assume no one is quick enough to insert something between now and the next @@ -1188,17 +1188,17 @@ public void TestWithParamHttpClient() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ManipulateMetaWebClient() { - FhirClient client = new FhirClient(testEndpoint); + FhirClient client = new FhirClient(testEndpoint); var pat = new Patient(); pat.Meta = new Meta(); - var key = new Random().Next(); + var key = new Random().Next(); pat.Meta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); pat.Meta.Security.Add(new Coding("http://mysystem.com/sec", "1234-" + key)); pat.Meta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag1-" + key)); - //Before we begin, ensure that our new tags are not actually used when doing System Meta() - var wsm = client.Meta(); + //Before we begin, ensure that our new tags are not actually used when doing System Meta() + var wsm = client.Meta(); Assert.IsNotNull(wsm); Assert.IsFalse(wsm.Profile.Contains("http://someserver.org/fhir/StructureDefinition/XYZ1-" + key)); @@ -1210,71 +1210,71 @@ public void ManipulateMetaWebClient() Assert.IsFalse(wsm.Tag.Select(c => c.Code + "@" + c.System).Contains("sometag2-" + key + "@http://mysystem.com/tag")); - // First, create a patient with the first set of meta - var pat2 = client.Create(pat); - var loc = pat2.ResourceIdentity(testEndpoint); + // First, create a patient with the first set of meta + var pat2 = client.Create(pat); + var loc = pat2.ResourceIdentity(testEndpoint); - // Meta should be present on created patient + // Meta should be present on created patient verifyMeta(pat2.Meta, false, key); - // Should be present when doing instance Meta() - var par = client.Meta(loc); + // Should be present when doing instance Meta() + var par = client.Meta(loc); verifyMeta(par, false, key); - // Should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); + // Should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); verifyMeta(par, false, key); - // Should be present when doing System Meta() - par = client.Meta(); + // Should be present when doing System Meta() + par = client.Meta(); verifyMeta(par, false, key); - // Now add some additional meta to the patient + // Now add some additional meta to the patient - var newMeta = new Meta(); + var newMeta = new Meta(); newMeta.ProfileElement.Add(new FhirUri("http://someserver.org/fhir/StructureDefinition/XYZ2-" + key)); - newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); - newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); + newMeta.Security.Add(new Coding("http://mysystem.com/sec", "5678-" + key)); + newMeta.Tag.Add(new Coding("http://mysystem.com/tag", "sometag2-" + key)); + - - client.AddMeta(loc, newMeta); - var pat3 = client.Read(loc); + client.AddMeta(loc, newMeta); + var pat3 = client.Read(loc); - // New and old meta should be present on instance - verifyMeta(pat3.Meta, true, key); + // New and old meta should be present on instance + verifyMeta(pat3.Meta, true, key); - // New and old meta should be present on Meta() - par = client.Meta(loc); + // New and old meta should be present on Meta() + par = client.Meta(loc); verifyMeta(par, true, key); - // New and old meta should be present when doing type Meta() - par = client.Meta(ResourceType.Patient); + // New and old meta should be present when doing type Meta() + par = client.Meta(ResourceType.Patient); verifyMeta(par, true, key); - // New and old meta should be present when doing system Meta() - par = client.Meta(); + // New and old meta should be present when doing system Meta() + par = client.Meta(); verifyMeta(par, true, key); - // Now, remove those new meta tags - client.DeleteMeta(loc, newMeta); + // Now, remove those new meta tags + client.DeleteMeta(loc, newMeta); - // Should no longer be present on instance - var pat4 = client.Read(loc); - verifyMeta(pat4.Meta, false, key); + // Should no longer be present on instance + var pat4 = client.Read(loc); + verifyMeta(pat4.Meta, false, key); - // Should no longer be present when doing instance Meta() - par = client.Meta(loc); + // Should no longer be present when doing instance Meta() + par = client.Meta(loc); verifyMeta(par, false, key); - // Should no longer be present when doing type Meta() - par = client.Meta(ResourceType.Patient); + // Should no longer be present when doing type Meta() + par = client.Meta(ResourceType.Patient); verifyMeta(par, false, key); - // clear out the client that we created, no point keeping it around - client.Delete(pat4); + // clear out the client that we created, no point keeping it around + client.Delete(pat4); - // Should no longer be present when doing System Meta() - par = client.Meta(); + // Should no longer be present when doing System Meta() + par = client.Meta(); verifyMeta(par, false, key); } @@ -1398,7 +1398,7 @@ private void verifyMeta(Meta meta, bool hasNew, int key) [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestSearchByPersonaCode() + public void TestSearchByPersonaCodeWebClient() { var client = new FhirClient(testEndpoint); @@ -1408,18 +1408,31 @@ public void TestSearchByPersonaCode() var pat = (Patient)pats.Entry.First().Resource; } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestSearchByPersonaCodeHttpClient() + { + using (var client = new TestClient(testEndpoint)) + { + var pats = + client.Search( + new[] { string.Format("identifier={0}|{1}", "urn:oid:1.2.36.146.595.217.0.1", "12345") }); + var pat = (Patient)pats.Entry.First().Resource; + } + } + [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CreateDynamic() + public void CreateDynamicWebClient() { Resource furore = new Organization { Name = "Furore", Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, - Telecom = new List { + Telecom = new List { new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, - new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } } }; @@ -1432,7 +1445,30 @@ public void CreateDynamic() [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CallsCallbacks() + public void CreateDynamicHttpClient() + { + Resource furore = new Organization + { + Name = "Furore", + Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, + Telecom = new List { + new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171", Use = ContactPoint.ContactPointUse.Work }, + new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Value = "+31-20-3467172" } + } + }; + + using (TestClient client = new TestClient(testEndpoint)) + { + System.Diagnostics.Trace.WriteLine(new FhirXmlSerializer().SerializeToString(furore)); + + var fe = client.Create(furore); + Assert.IsNotNull(fe); + } + } + + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CallsCallbacksWebClient() { FhirClient client = new FhirClient(testEndpoint); client.ParserSettings.AllowUnrecognizedEnums = true; @@ -1472,6 +1508,51 @@ public void CallsCallbacks() Assert.IsTrue(bodyText.Contains(" + { + calledBefore = true; + bodyOut = e.Body; + }; + + handler.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/glossy"); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); + + var bodyText = HttpToEntryExtensions.DecodeBody(body, Encoding.UTF8); + + Assert.IsTrue(bodyText.Contains(" e.RawRequest.Headers.TryAddWithoutValidation("Prefer", minimal ? "return=minimal" : "return=representation"); + + var result = client.Read("Patient/glossy"); + Assert.IsNotNull(result); + result.Id = null; + result.Meta = null; + + client.PreferredReturn = Prefer.ReturnRepresentation; + minimal = false; + var posted = client.Create(result); + Assert.IsNotNull(posted, "Patient example not found"); + + minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true + posted = client.Create(result); + Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); + + client.PreferredReturn = Prefer.ReturnMinimal; + minimal = true; + posted = client.Create(result); + Assert.IsNull(posted); + } + } + void client_OnBeforeRequest(object sender, Fhir.Rest.BeforeRequestEventArgs e) { throw new NotImplementedException(); @@ -1524,7 +1636,7 @@ void client_OnBeforeRequest(object sender, Fhir.Rest.BeforeRequestEventArgs e) [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. - public void TestReceiveHtmlIsHandled() + public void TestReceiveHtmlIsHandledWebClient() { var client = new FhirClient("http://spark.furore.com/"); // an address that returns html @@ -1537,11 +1649,28 @@ public void TestReceiveHtmlIsHandled() if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) Assert.Fail("Failed to recognize invalid body contents"); } - } + } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] // Currently ignoring, as spark.furore.com returns Status 500. + public void TestReceiveHtmlIsHandledHttpClient() + { + using (var client = new TestClient("http://spark.furore.com/")) // an address that returns html + { + try + { + var pat = client.Read("Patient/1"); + } + catch (FhirOperationException fe) + { + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + } + } + } [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestRefresh() + public void TestRefreshWebClient() { var client = new FhirClient(testEndpoint); var result = client.Read("Patient/example"); @@ -1555,9 +1684,26 @@ public void TestRefresh() Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); } + [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestRefreshHttpClient() + { + using (var client = new TestClient(testEndpoint)) + { + var result = client.Read("Patient/example"); + + var orig = result.Name[0].FamilyElement.Value; + + result.Name[0].FamilyElement.Value = "overwritten name"; + + result = client.Refresh(result); + + Assert.AreEqual(orig, result.Name[0].FamilyElement.Value); + } + } + [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestReceiveErrorStatusWithHtmlIsHandled() + public void TestReceiveErrorStatusWithHtmlIsHandledWebClient() { var client = new FhirClient("http://spark.furore.com/"); // an address that returns Status 500 with HTML in its body @@ -1599,10 +1745,54 @@ public void TestReceiveErrorStatusWithHtmlIsHandled() } } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestReceiveErrorStatusWithHtmlIsHandledHttpClient() + { + using (var client = new TestClient("http://spark.furore.com/")) // an address that returns Status 500 with HTML in its body + { + try + { + var pat = client.Read("Patient/1"); + Assert.Fail("Failed to throw an Exception on status 500"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.InternalServerError) + Assert.Fail("Server response of 500 did not result in FhirOperationException with status 500."); + + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); + + if (client.LastResult.Status != "500") + Assert.Fail("LastResult.Status is not 500."); + + if (!fe.Message.Contains("a valid FHIR xml/json body type was expected") && !fe.Message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to recognize invalid body contents"); + + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + + string message = operationOutcome.Issue[0].Diagnostics; + if (!message.Contains("a valid FHIR xml/json body type was expected") && !message.Contains("not recognized as either xml or json")) + Assert.Fail("Failed to carry error message over into OperationOutcome"); + } + catch (Exception) + { + Assert.Fail("Failed to throw FhirOperationException on status 500"); + } + } + } [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() + public void TestReceiveErrorStatusWithOperationOutcomeIsHandledWebClient() { var client = new FhirClient("http://test.fhir.org/r3"); // an address that returns Status 404 with an OperationOutcome @@ -1639,9 +1829,49 @@ public void TestReceiveErrorStatusWithOperationOutcomeIsHandled() } } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void TestReceiveErrorStatusWithOperationOutcomeIsHandledHttpClient() + { + using (var client = new FhirClient("http://test.fhir.org/r3"))// an address that returns Status 404 with an OperationOutcome + { + try + { + var pat = client.Read("Patient/doesnotexist"); + Assert.Fail("Failed to throw an Exception on status 404"); + } + catch (FhirOperationException fe) + { + // Expected exception happened + if (fe.Status != HttpStatusCode.NotFound) + Assert.Fail("Server response of 404 did not result in FhirOperationException with status 404."); + + if (client.LastResult == null) + Assert.Fail("LastResult not set in error case."); + + Bundle.ResponseComponent entryComponent = client.LastResult; + + if (entryComponent.Status != "404") + Assert.Fail("LastResult.Status is not 404."); + + // Check that LastResult is of type OperationOutcome and properly filled. + OperationOutcome operationOutcome = client.LastBodyAsResource as OperationOutcome; + Assert.IsNotNull(operationOutcome, "Returned resource is not an OperationOutcome"); + + Assert.IsTrue(operationOutcome.Issue.Count > 0, "OperationOutcome does not contain an issue"); + + Assert.IsTrue(operationOutcome.Issue[0].Severity == OperationOutcome.IssueSeverity.Error, "OperationOutcome is not of severity 'error'"); + } + catch (Exception e) + { + Assert.Fail("Failed to throw FhirOperationException on status 404: " + e.Message); + } + } + } + - [TestMethod,Ignore] + [TestMethod, Ignore] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FhirVersionIsChecked() { @@ -1686,7 +1916,7 @@ public void FhirVersionIsChecked() client = new FhirClient(testEndpointDSTU12); client.ParserSettings.AllowUnrecognizedEnums = true; - + try { p = client.CapabilityStatement(); @@ -1700,7 +1930,7 @@ public void FhirVersionIsChecked() } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] - public void TestAuthenticationOnBefore() + public void TestAuthenticationOnBeforeWebClient() { FhirClient validationFhirClient = new FhirClient("https://sqlonfhir.azurewebsites.net/fhir"); validationFhirClient.OnBeforeRequest += (object sender, Fhir.Rest.BeforeRequestEventArgs e) => @@ -1712,14 +1942,33 @@ public void TestAuthenticationOnBefore() var output = validationFhirClient.ValidateResource(new Patient()); } - catch(FhirOperationException ex) + catch (FhirOperationException ex) { Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); } } [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] - public void TestOperationEverything() + public void TestAuthenticationOnBeforeHttpClient() + { + using (TestClient validationFhirClient = new TestClient("https://sqlonfhir.azurewebsites.net/fhir")) + { + validationFhirClient.RequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer bad-bearer"); + + try + { + var output = validationFhirClient.ValidateResource(new Patient()); + + } + catch (FhirOperationException ex) + { + Assert.IsTrue(ex.Status == HttpStatusCode.Forbidden || ex.Status == HttpStatusCode.Unauthorized, "Excpeted a security exception"); + } + } + } + + [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] + public void TestOperationEverythingWebClient() { FhirClient client = new FhirClient(testEndpoint) { @@ -1745,6 +1994,34 @@ public void TestOperationEverything() Assert.IsNotNull(loc); } + [TestMethod, TestCategory("IntegrationTest"), TestCategory("FhirClient")] + public void TestOperationEverythingHttpClient() + { + using (TestClient client = new TestClient(testEndpoint) + { + UseFormatParam = true, + PreferredFormat = ResourceFormat.Json + }) + { + // GET operation $everything without parameters + var loc = client.TypeOperation("everything", null, true); + Assert.IsNotNull(loc); + + // POST operation $everything without parameters + loc = client.TypeOperation("everything", null, false); + Assert.IsNotNull(loc); + + // GET operation $everything with 1 parameter + // This doesn't work yet. When an operation is used with primitive types then those parameters must be appended to the url as query parameters. + // loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), true); + // Assert.IsNotNull(loc); + + // POST operation $everything with 1 parameter + loc = client.TypeOperation("everything", new Parameters().Add("start", new Date(2017, 10)), false); + Assert.IsNotNull(loc); + } + } + } } From 4f5e304f331ab473c7ec6c5d521e5f63901f9497 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 11:25:34 -0600 Subject: [PATCH 28/39] Fixed some using statements in client tests. --- .../Rest/FhirClientTests.cs | 155 +++++++++--------- 1 file changed, 81 insertions(+), 74 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 30f5bbc90e..ff38f4e460 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -97,30 +97,32 @@ public void FetchConformanceWebClient() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void FetchConformanceHttpClient() { - TestClient client = new TestClient(testEndpoint); - client.ParserSettings.AllowUnrecognizedEnums = true; + using (TestClient client = new TestClient(testEndpoint)) + { + client.ParserSettings.AllowUnrecognizedEnums = true; - var entry = client.CapabilityStatement(); + var entry = client.CapabilityStatement(); - Assert.IsNotNull(entry.Text); - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); + Assert.IsNotNull(entry.Text); + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + // Assert.AreEqual("Spark.Service", c.Software.Name); // This is only for ewout's server + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); - entry = client.CapabilityStatement(SummaryType.True); + entry = client.CapabilityStatement(SummaryType.True); - Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) - Assert.IsNotNull(entry); - Assert.IsNotNull(entry.FhirVersion); - Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); - Assert.AreEqual("200", client.LastResult.Status); + Assert.IsNull(entry.Text); // DSTU2 has this property as not include as part of the summary (that would be with SummaryType.Text) + Assert.IsNotNull(entry); + Assert.IsNotNull(entry.FhirVersion); + Assert.AreEqual(CapabilityStatement.RestfulCapabilityMode.Server, entry.Rest[0].Mode.Value); + Assert.AreEqual("200", client.LastResult.Status); - Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); - Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); - Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); - Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + Assert.IsNotNull(entry.Rest[0].Resource, "The resource property should be in the summary"); + Assert.AreNotEqual(0, entry.Rest[0].Resource.Count, "There is expected to be at least 1 resource defined in the conformance statement"); + Assert.IsTrue(entry.Rest[0].Resource[0].Type.HasValue, "The resource type should be provided"); + Assert.AreNotEqual(0, entry.Rest[0].Operation.Count, "operations should be listed in the summary"); // actually operations are now a part of the summary + } } @@ -157,13 +159,14 @@ public void ReadWithFormatWebClient() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadWithFormatHttpClient() { - TestClient client = new TestClient(testEndpoint); - - client.UseFormatParam = true; - client.PreferredFormat = ResourceFormat.Json; + using (TestClient client = new TestClient(testEndpoint)) + { + client.UseFormatParam = true; + client.PreferredFormat = ResourceFormat.Json; - var loc = client.Read("Patient/example"); - Assert.IsNotNull(loc); + var loc = client.Read("Patient/example"); + Assert.IsNotNull(loc); + } } @@ -210,41 +213,43 @@ public void ReadWebClient() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadHttpClient() { - TestClient client = new TestClient(testEndpoint); + using (TestClient client = new TestClient(testEndpoint)) + { - var loc = client.Read("Location/1"); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + var loc = client.Read("Location/1"); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); - Assert.AreEqual("1", loc.Id); - Assert.IsNotNull(loc.Meta.VersionId); + Assert.AreEqual("1", loc.Id); + Assert.IsNotNull(loc.Meta.VersionId); - var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc2); - Assert.AreEqual(loc2.Id, loc.Id); - Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); + var loc2 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc2); + Assert.AreEqual(loc2.Id, loc.Id); + Assert.AreEqual(loc2.Meta.VersionId, loc.Meta.VersionId); - try - { - var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); - Assert.Fail(); - } - catch (FhirOperationException ex) - { - Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); - Assert.AreEqual("404", client.LastResult.Status); - } + try + { + var random = client.Read(new Uri("Location/45qq54", UriKind.Relative)); + Assert.Fail(); + } + catch (FhirOperationException ex) + { + Assert.AreEqual(HttpStatusCode.NotFound, ex.Status); + Assert.AreEqual("404", client.LastResult.Status); + } - var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); - Assert.IsNotNull(loc3); - var jsonSer = new FhirJsonSerializer(); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc3)); + var loc3 = client.Read(ResourceIdentity.Build("Location", "1", loc.Meta.VersionId)); + Assert.IsNotNull(loc3); + var jsonSer = new FhirJsonSerializer(); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc3)); - var loc4 = client.Read(loc.ResourceIdentity()); - Assert.IsNotNull(loc4); - Assert.AreEqual(jsonSer.SerializeToString(loc), - jsonSer.SerializeToString(loc4)); + var loc4 = client.Read(loc.ResourceIdentity()); + Assert.IsNotNull(loc4); + Assert.AreEqual(jsonSer.SerializeToString(loc), + jsonSer.SerializeToString(loc4)); + } } @@ -266,16 +271,17 @@ public void ReadRelativeWebClient() [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void ReadRelativeHttpClient() { - TestClient client = new TestClient(testEndpoint); - - var loc = client.Read(new Uri("Location/1", UriKind.Relative)); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + using (TestClient client = new TestClient(testEndpoint)) + { + var loc = client.Read(new Uri("Location/1", UriKind.Relative)); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.Read(ri); - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Address.City); + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.Read(ri); + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Address.City); + } } #if NO_ASYNC_ANYMORE @@ -297,17 +303,18 @@ public void ReadRelativeAsyncWebClient() [TestMethod, TestCategory("FhirClient")] public void ReadRelativeAsyncHttpClient() { - TestClient client = new TestClient(testEndpoint); - - var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); + using (TestClient client = new TestClient(testEndpoint)) + { + var loc = client.ReadAsync(new Uri("Location/1", UriKind.Relative)).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); - var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); - loc = client.ReadAsync(ri).Result; - Assert.IsNotNull(loc); - Assert.AreEqual("Den Burg", loc.Resource.Address.City); - } + var ri = ResourceIdentity.Build(testEndpoint, "Location", "1"); + loc = client.ReadAsync(ri).Result; + Assert.IsNotNull(loc); + Assert.AreEqual("Den Burg", loc.Resource.Address.City); + } + } #endif public static void Compression_OnBeforeWebRequestGZip(object sender, Fhir.Rest.BeforeRequestEventArgs e) @@ -830,7 +837,7 @@ public void CreateEditDeleteWebClient() public void CreateEditDeleteHttpClient() { using (var handler = new MockHttpMessageHandler()) - using (TestClient client = new TestClient(testEndpoint)) + using (TestClient client = new TestClient(testEndpoint, handler)) { handler.OnBeforeRequest += Compression_OnBeforeHttpRequestZipOrDeflate; From 5d0326fcbf8ecf71e7fb2a21e3eec8b507b21152 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 13:20:32 -0600 Subject: [PATCH 29/39] Removed unused delegates. --- src/Hl7.Fhir.Core/Rest/Http/Requester.cs | 5 ----- src/Hl7.Fhir.Core/Rest/IRequester.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs index 7c844d9072..58d24a60ad 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs @@ -62,8 +62,6 @@ public Requester(Uri baseUrl, HttpMessageHandler messageHandler) public HttpStatusCode? LastStatusCode => LastResponse?.StatusCode; public HttpResponseMessage LastResponse { get; private set; } public HttpRequestMessage LastRequest { get; private set; } - public Action BeforeRequest { get; set; } - public Action AfterResponse { get; set; } public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) { @@ -93,8 +91,6 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) outgoingBody = await requestMessage.Content.ReadAsByteArrayAsync(); } - BeforeRequest?.Invoke(requestMessage, outgoingBody); - using (var response = await Client.SendAsync(requestMessage).ConfigureAwait(false)) { try @@ -102,7 +98,6 @@ public Bundle.EntryComponent Execute(Bundle.EntryComponent interaction) var body = await response.Content.ReadAsByteArrayAsync(); LastResponse = response; - AfterResponse?.Invoke(response, body); // Do this call after AfterResponse, so AfterResponse will be called, even if exceptions are thrown by ToBundleEntry() try diff --git a/src/Hl7.Fhir.Core/Rest/IRequester.cs b/src/Hl7.Fhir.Core/Rest/IRequester.cs index 57ce646ad4..3f5bb4c3cc 100644 --- a/src/Hl7.Fhir.Core/Rest/IRequester.cs +++ b/src/Hl7.Fhir.Core/Rest/IRequester.cs @@ -9,7 +9,7 @@ namespace Hl7.Fhir.Rest { - public interface IRequester + internal interface IRequester { Bundle.EntryComponent Execute(Bundle.EntryComponent interaction); Task ExecuteAsync(Bundle.EntryComponent interaction); From ef39dbb25270ca2a9e7deadd531b7df82a43d9cb Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 14:00:28 -0600 Subject: [PATCH 30/39] Obsolete properties now included in common interface, will throw NotImplementedException or be hidden by default. --- src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs | 20 +++- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 15 +-- src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs | 12 +-- src/Hl7.Fhir.Core/Rest/IFhirClient.cs | 96 +++++++++++++++++- .../Rest/IFhirCompatibleClient.cs | 98 ------------------- src/Hl7.Fhir.Core/Rest/IRequester.cs | 2 +- 6 files changed, 129 insertions(+), 114 deletions(-) delete mode 100644 src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs diff --git a/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs index 0c5ef88280..61118aa626 100644 --- a/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs @@ -10,8 +10,14 @@ namespace Hl7.Fhir.Rest { - public abstract partial class BaseFhirClient : IDisposable + public abstract partial class BaseFhirClient : IDisposable, IFhirClient { + [Obsolete] + public event EventHandler OnAfterResponse; + + [Obsolete] + public event EventHandler OnBeforeRequest; + protected IRequester Requester { get; set; } /// @@ -131,6 +137,18 @@ public ParserSettings ParserSettings set { Requester.ParserSettings = value; } } + public abstract byte[] LastBody { get; } + + public abstract Resource LastBodyAsResource { get; } + + public abstract string LastBodyAsText { get; } + + [Obsolete] + public virtual HttpWebRequest LastRequest { get => throw new NotImplementedException(); } + + [Obsolete] + public virtual HttpWebResponse LastResponse { get => throw new NotImplementedException(); } + protected static Uri GetValidatedEndpoint(Uri endpoint) { if (endpoint == null) throw new ArgumentNullException("endpoint"); diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index ff0af76d38..13fb9b0d98 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -20,7 +20,8 @@ namespace Hl7.Fhir.Rest { - public partial class FhirClient : BaseFhirClient, IFhirClient + [Obsolete] + public partial class FhirClient : BaseFhirClient { /// /// Creates a new client using a default endpoint @@ -68,21 +69,23 @@ public FhirClient(string endpoint, bool verifyFhirVersion = false) } - public byte[] LastBody => LastResult?.GetBody(); - public string LastBodyAsText => LastResult?.GetBodyAsText(); - public Resource LastBodyAsResource => Requester.LastResult?.Resource; + public override byte[] LastBody => LastResult?.GetBody(); + public override string LastBodyAsText => LastResult?.GetBodyAsText(); + public override Resource LastBodyAsResource => Requester.LastResult?.Resource; /// /// Returns the HttpWebRequest as it was last constructed to execute a call on the FhirClient /// - public HttpWebRequest LastRequest { get { return (Requester as Requester)?.LastRequest; } } + [Obsolete] + public override HttpWebRequest LastRequest { get { return (Requester as Requester)?.LastRequest; } } /// /// Returns the HttpWebResponse as it was last received during a call on the FhirClient /// /// Note that the FhirClient will have read the body data from the HttpWebResponse, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpWebResponse LastResponse { get { return (Requester as Requester)?.LastResponse; } } + [Obsolete] + public override HttpWebResponse LastResponse { get { return (Requester as Requester)?.LastResponse; } } /// /// Called just before the Http call is done diff --git a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index b954059f3d..d0b31135e4 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -22,7 +22,7 @@ namespace Hl7.Fhir.Rest.Http { - public partial class FhirClient : BaseFhirClient, IFhirCompatibleClient + public partial class FhirClient : BaseFhirClient { /// /// Creates a new client using a default endpoint @@ -81,21 +81,21 @@ public FhirClient(string endpoint, HttpMessageHandler messageHandler = null, boo /// public HttpRequestHeaders RequestHeaders { get; protected set; } - public byte[] LastBody => LastResult?.GetBody(); - public string LastBodyAsText => LastResult?.GetBodyAsText(); - public Resource LastBodyAsResource => Requester.LastResult?.Resource; + public override byte[] LastBody => LastResult?.GetBody(); + public override string LastBodyAsText => LastResult?.GetBodyAsText(); + public override Resource LastBodyAsResource => Requester.LastResult?.Resource; /// /// Returns the HttpRequestMessage as it was last constructed to execute a call on the FhirClient /// - public HttpRequestMessage LastRequest { get { return (Requester as Http.Requester)?.LastRequest; } } + new public HttpRequestMessage LastRequest { get { return (Requester as Http.Requester)?.LastRequest; } } /// /// Returns the HttpResponseMessage as it was last received during a call on the FhirClient /// /// Note that the FhirClient will have read the body data from the HttpResponseMessage, so this is /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) - public HttpResponseMessage LastResponse { get { return (Requester as Http.Requester)?.LastResponse; } } + new public HttpResponseMessage LastResponse { get { return (Requester as Http.Requester)?.LastResponse; } } /// /// Override dispose in order to clean up request headers tied to disposed requester. diff --git a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs index 058123dc0a..bbeaf1b04e 100644 --- a/src/Hl7.Fhir.Core/Rest/IFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/IFhirClient.cs @@ -6,16 +6,108 @@ namespace Hl7.Fhir.Rest { - public interface IFhirClient : IFhirCompatibleClient + public interface IFhirClient { + +#if NET_COMPRESSION + bool PreferCompressedResponses { get; set; } + bool CompressRequestBody { get; set; } +#endif + Uri Endpoint { get; } + + ParserSettings ParserSettings { get; set; } + ResourceFormat PreferredFormat { get; set; } + bool ReturnFullResource { get; set; } + int Timeout { get; set; } + bool UseFormatParam { get; set; } + bool VerifyFhirVersion { get; set; } + byte[] LastBody { get; } Resource LastBodyAsResource { get; } string LastBodyAsText { get; } + + Bundle.ResponseComponent LastResult { get; } + + [Obsolete] HttpWebRequest LastRequest { get; } + + [Obsolete] HttpWebResponse LastResponse { get; } - Bundle.ResponseComponent LastResult { get; } + [Obsolete] event EventHandler OnAfterResponse; + + [Obsolete] event EventHandler OnBeforeRequest; + + CapabilityStatement CapabilityStatement(SummaryType? summary = default(SummaryType?)); + Task CapabilityStatementAsync(SummaryType? summary = default(SummaryType?)); + Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next); + Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next); + TResource Create(TResource resource) where TResource : Resource; + TResource Create(TResource resource, SearchParams condition) where TResource : Resource; + Task CreateAsync(TResource resource) where TResource : Resource; + Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource; + void Delete(Resource resource); + void Delete(string location); + void Delete(string resourceType, SearchParams condition); + void Delete(Uri location); + System.Threading.Tasks.Task DeleteAsync(Resource resource); + System.Threading.Tasks.Task DeleteAsync(string location); + System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition); + System.Threading.Tasks.Task DeleteAsync(Uri location); + Task executeAsync(Bundle tx, HttpStatusCode expect) where TResource : Resource; + Resource Get(string url); + Resource Get(Uri url); + Task GetAsync(string url); + Task GetAsync(Uri url); + Bundle History(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Bundle History(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task HistoryAsync(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task HistoryAsync(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false); + Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false); + Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); + TResource Read(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + TResource Read(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; + TResource Refresh(TResource current) where TResource : Resource; + Task RefreshAsync(TResource current) where TResource : Resource; + Bundle Search(SearchParams q, string resourceType = null); + Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); + Bundle Search(SearchParams q) where TResource : Resource; + Task SearchAsync(SearchParams q, string resourceType = null); + Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); + Task SearchAsync(SearchParams q) where TResource : Resource; + Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); + Bundle SearchById(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); + Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); + Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); + Bundle Transaction(Bundle bundle); + Task TransactionAsync(Bundle bundle); + Bundle TypeHistory(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Bundle TypeHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); + Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task TypeHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); + Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false); + Resource TypeOperation(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; + Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false); + Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; + TResource Update(TResource resource, bool versionAware = false) where TResource : Resource; + TResource Update(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; + Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource; + Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; + Bundle WholeSystemHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Task WholeSystemHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); + Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false); + Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false); + Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); + Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs b/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs deleted file mode 100644 index 8850fe39c2..0000000000 --- a/src/Hl7.Fhir.Core/Rest/IFhirCompatibleClient.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using System.Net; - -namespace Hl7.Fhir.Rest -{ - public interface IFhirCompatibleClient - { -#if NET_COMPRESSION - bool PreferCompressedResponses { get; set; } - bool CompressRequestBody { get; set; } -#endif - - Uri Endpoint { get; } - - ParserSettings ParserSettings { get; set; } - ResourceFormat PreferredFormat { get; set; } - bool ReturnFullResource { get; set; } - int Timeout { get; set; } - bool UseFormatParam { get; set; } - bool VerifyFhirVersion { get; set; } - - CapabilityStatement CapabilityStatement(SummaryType? summary = default(SummaryType?)); - Task CapabilityStatementAsync(SummaryType? summary = default(SummaryType?)); - Bundle Continue(Bundle current, PageDirection direction = PageDirection.Next); - Task ContinueAsync(Bundle current, PageDirection direction = PageDirection.Next); - TResource Create(TResource resource) where TResource : Resource; - TResource Create(TResource resource, SearchParams condition) where TResource : Resource; - Task CreateAsync(TResource resource) where TResource : Resource; - Task CreateAsync(TResource resource, SearchParams condition) where TResource : Resource; - void Delete(Resource resource); - void Delete(string location); - void Delete(string resourceType, SearchParams condition); - void Delete(Uri location); - System.Threading.Tasks.Task DeleteAsync(Resource resource); - System.Threading.Tasks.Task DeleteAsync(string location); - System.Threading.Tasks.Task DeleteAsync(string resourceType, SearchParams condition); - System.Threading.Tasks.Task DeleteAsync(Uri location); - Task executeAsync(Bundle tx, HttpStatusCode expect) where TResource : Resource; - Resource Get(string url); - Resource Get(Uri url); - Task GetAsync(string url); - Task GetAsync(Uri url); - Bundle History(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Bundle History(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task HistoryAsync(string location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task HistoryAsync(Uri location, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Resource InstanceOperation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Task InstanceOperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Resource Operation(Uri operation, Parameters parameters = null, bool useGet = false); - Resource Operation(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - Task OperationAsync(Uri operation, Parameters parameters = null, bool useGet = false); - Task OperationAsync(Uri location, string operationName, Parameters parameters = null, bool useGet = false); - TResource Read(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - TResource Read(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - Task ReadAsync(string location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - Task ReadAsync(Uri location, string ifNoneMatch = null, DateTimeOffset? ifModifiedSince = default(DateTimeOffset?)) where TResource : Resource; - TResource Refresh(TResource current) where TResource : Resource; - Task RefreshAsync(TResource current) where TResource : Resource; - Bundle Search(SearchParams q, string resourceType = null); - Bundle Search(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Bundle Search(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); - Bundle Search(SearchParams q) where TResource : Resource; - Task SearchAsync(SearchParams q, string resourceType = null); - Task SearchAsync(string resource, string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Task SearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null) where TResource : Resource, new(); - Task SearchAsync(SearchParams q) where TResource : Resource; - Bundle SearchById(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); - Bundle SearchById(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); - Task SearchByIdAsync(string resource, string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null); - Task SearchByIdAsync(string id, string[] includes = null, int? pageSize = default(int?), string[] revIncludes = null) where TResource : Resource, new(); - Bundle Transaction(Bundle bundle); - Task TransactionAsync(Bundle bundle); - Bundle TypeHistory(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Bundle TypeHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); - Task TypeHistoryAsync(string resourceType, DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task TypeHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False) where TResource : Resource, new(); - Resource TypeOperation(string operationName, string typeName, Parameters parameters = null, bool useGet = false); - Resource TypeOperation(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; - Task TypeOperationAsync(string operationName, string typeName, Parameters parameters = null, bool useGet = false); - Task TypeOperationAsync(string operationName, Parameters parameters = null, bool useGet = false) where TResource : Resource; - TResource Update(TResource resource, bool versionAware = false) where TResource : Resource; - TResource Update(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; - Task UpdateAsync(TResource resource, bool versionAware = false) where TResource : Resource; - Task UpdateAsync(TResource resource, SearchParams condition, bool versionAware = false) where TResource : Resource; - Bundle WholeSystemHistory(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Task WholeSystemHistoryAsync(DateTimeOffset? since = default(DateTimeOffset?), int? pageSize = default(int?), SummaryType summary = SummaryType.False); - Resource WholeSystemOperation(string operationName, Parameters parameters = null, bool useGet = false); - Task WholeSystemOperationAsync(string operationName, Parameters parameters = null, bool useGet = false); - Bundle WholeSystemSearch(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - Task WholeSystemSearchAsync(string[] criteria = null, string[] includes = null, int? pageSize = default(int?), SummaryType? summary = default(SummaryType?), string[] revIncludes = null); - } -} diff --git a/src/Hl7.Fhir.Core/Rest/IRequester.cs b/src/Hl7.Fhir.Core/Rest/IRequester.cs index 3f5bb4c3cc..57ce646ad4 100644 --- a/src/Hl7.Fhir.Core/Rest/IRequester.cs +++ b/src/Hl7.Fhir.Core/Rest/IRequester.cs @@ -9,7 +9,7 @@ namespace Hl7.Fhir.Rest { - internal interface IRequester + public interface IRequester { Bundle.EntryComponent Execute(Bundle.EntryComponent interaction); Task ExecuteAsync(Bundle.EntryComponent interaction); From d1b8f764e34ab1ddb84d722f3485ef0174c19262 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 15:24:44 -0600 Subject: [PATCH 31/39] Added operations tests for new client. --- .../Rest/FhirClientTests.cs | 8 +- .../Rest/OperationsTests.cs | 183 ++++++++++++++++-- src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs | 10 +- .../Rest/Http/HttpToEntryExtensions.cs | 12 +- src/Hl7.Fhir.Core/Rest/Http/Requester.cs | 1 + 5 files changed, 188 insertions(+), 26 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index ff38f4e460..3e81be8e99 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -436,7 +436,7 @@ public void SearchWebClient() public void SearchHttpClient() { using (var handler = new MockHttpMessageHandler()) - using (TestClient client = new TestClient(testEndpoint, handler)) + using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { Bundle result; @@ -837,7 +837,7 @@ public void CreateEditDeleteWebClient() public void CreateEditDeleteHttpClient() { using (var handler = new MockHttpMessageHandler()) - using (TestClient client = new TestClient(testEndpoint, handler)) + using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { handler.OnBeforeRequest += Compression_OnBeforeHttpRequestZipOrDeflate; @@ -1520,7 +1520,7 @@ public void CallsCallbacksWebClient() public void CallsCallbacksHttpClient() { using (var handler = new MockHttpMessageHandler()) - using (TestClient client = new TestClient(testEndpoint, handler)) + using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { client.ParserSettings.AllowUnrecognizedEnums = true; @@ -1610,7 +1610,7 @@ public void RequestFullResourceWebClient() public void RequestFullResourceHttpClient() { using (var handler = new MockHttpMessageHandler()) - using (var client = new TestClient(testEndpoint, handler)) + using (var client = new TestClient(testEndpoint, messageHandler: handler)) { var minimal = false; handler.OnBeforeRequest += (object s, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) => e.RawRequest.Headers.TryAddWithoutValidation("Prefer", minimal ? "return=minimal" : "return=representation"); diff --git a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs index ee03c198e4..959fe4eb75 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs @@ -14,6 +14,7 @@ using System.Text; using System.Threading.Tasks; using Hl7.Fhir.Rest; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; namespace Hl7.Fhir.Tests.Rest { @@ -25,7 +26,7 @@ public class OperationsTests [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeTestPatientGetEverything() + public void InvokeTestPatientGetEverythingWebClient() { var client = new FhirClient(testEndpoint); var start = new FhirDateTime(2014,11,1); @@ -40,18 +41,44 @@ public void InvokeTestPatientGetEverything() [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeExpandExistingValueSet() + public void InvokeTestPatientGetEverythingHttpClient() + { + using (var client = new TestClient(testEndpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2015, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); + var bundle = (Bundle)client.InstanceOperation(ResourceIdentity.Build("Patient", "example"), "everything", par); + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = client.FetchPatientRecord(ResourceIdentity.Build("Patient", "example"), start, end); + Assert.IsTrue(bundle2.Entry.Any()); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeExpandExistingValueSetWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet","administrative-gender")); Assert.IsTrue(vs.Expansion.Contains.Any()); } - + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeExpandExistingValueSetHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var vs = client.ExpandValueSet(ResourceIdentity.Build("ValueSet", "administrative-gender")); + Assert.IsTrue(vs.Expansion.Contains.Any()); + } + } [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeExpandParameterValueSet() + public void InvokeExpandParameterValueSetWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); @@ -61,6 +88,19 @@ public void InvokeExpandParameterValueSet() Assert.IsTrue(vsX.Expansion.Contains.Any()); } + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeExpandParameterValueSetWebClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var vs = client.Read("ValueSet/administrative-gender"); + var vsX = client.ExpandValueSet(vs); + + Assert.IsTrue(vsX.Expansion.Contains.Any()); + } + } + // [WMR 20170927] Chris Munro // https://chat.fhir.org/#narrow/stream/implementers/subject/How.20to.20expand.20ValueSets.20with.20the.20C.23.20FHIR.20API.3F //[TestMethod] @@ -79,7 +119,7 @@ public void InvokeExpandParameterValueSet() /// [TestMethod] // Server returns internal server error [TestCategory("IntegrationTest")] - public void InvokeLookupCoding() + public void InvokeLookupCodingWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); @@ -90,9 +130,27 @@ public void InvokeLookupCoding() Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); } + /// + /// http://hl7.org/fhir/valueset-operations.html#lookup + /// + [TestMethod] // Server returns internal server error + [TestCategory("IntegrationTest")] + public void InvokeLookupCodingHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://hl7.org/fhir/administrative-gender", "male"); + + var expansion = client.ConceptLookup(coding: coding); + + // Assert.AreEqual("AdministrativeGender", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } + } + [TestMethod] // Server returns internal server error [TestCategory("IntegrationTest")] - public void InvokeLookupCode() + public void InvokeLookupCodeWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); @@ -101,9 +159,22 @@ public void InvokeLookupCode() Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); } + [TestMethod] // Server returns internal server error + [TestCategory("IntegrationTest")] + public void InvokeLookupCodeHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var expansion = client.ConceptLookup(code: new Code("male"), system: new FhirUri("http://hl7.org/fhir/administrative-gender")); + + //Assert.AreEqual("male", expansion.GetSingleValue("name").Value); // Returns empty currently on Grahame's server + Assert.AreEqual("Male", expansion.GetSingleValue("display").Value); + } + } + [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeValidateCodeById() + public void InvokeValidateCodeByIdWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var coding = new Coding("http://snomed.info/sct", "4322002"); @@ -114,7 +185,20 @@ public void InvokeValidateCodeById() [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeValidateCodeByCanonical() + public void InvokeValidateCodeByIdHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var result = client.ValidateCode("c80-facilitycodes", coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeByCanonicalWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var coding = new Coding("http://snomed.info/sct", "4322002"); @@ -126,7 +210,21 @@ public void InvokeValidateCodeByCanonical() [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeValidateCodeWithVS() + public void InvokeValidateCodeByCanonicalHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var result = client.ValidateCode(url: new FhirUri("http://hl7.org/fhir/ValueSet/c80-facilitycodes"), + coding: coding, @abstract: new FhirBoolean(false)); + Assert.IsTrue(result.Result?.Value == true); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeWithVSWebClient() { var client = new FhirClient(FhirClientTests.TerminologyEndpoint); var coding = new Coding("http://snomed.info/sct", "4322002"); @@ -138,10 +236,26 @@ public void InvokeValidateCodeWithVS() Assert.IsTrue(result.Result?.Value == true); } + [TestMethod] + [TestCategory("IntegrationTest")] + public void InvokeValidateCodeWithVSHttpClient() + { + using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) + { + var coding = new Coding("http://snomed.info/sct", "4322002"); + + var vs = client.Read("ValueSet/c80-facilitycodes"); + Assert.IsNotNull(vs); + + var result = client.ValidateCode(valueSet: vs, coding: coding); + Assert.IsTrue(result.Result?.Value == true); + } + } + [TestMethod]//returns 500: validation of slices is not done yet. [TestCategory("IntegrationTest"), Ignore] - public void InvokeResourceValidation() + public void InvokeResourceValidationWebClient() { var client = new FhirClient(testEndpoint); @@ -160,9 +274,31 @@ public void InvokeResourceValidation() } } + [TestMethod]//returns 500: validation of slices is not done yet. + [TestCategory("IntegrationTest"), Ignore] + public void InvokeResourceValidationHttpClient() + { + using (var client = new TestClient(testEndpoint)) + { + var pat = client.Read("Patient/patient-uslab-example1"); + + try + { + var vresult = client.ValidateResource(pat, null, + new FhirUri("http://hl7.org/fhir/StructureDefinition/uslab-patient")); + Assert.Fail("Should have resulted in 400"); + } + catch (FhirOperationException fe) + { + Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, fe.Status); + Assert.IsTrue(fe.Outcome.Issue.Where(i => i.Severity == OperationOutcome.IssueSeverity.Error).Any()); + } + } + } + [TestMethod] [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() + public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsyncWebClient() { string _endpoint = "https://api.hspconsortium.org/rpineda/open"; @@ -182,5 +318,30 @@ public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsync() var bundle2 = (Bundle)bundle2Task.Result; Assert.IsTrue(bundle2.Entry.Any()); } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task InvokeTestPatientGetEverythingAsyncHttpClient() + { + string _endpoint = "https://api.hspconsortium.org/rpineda/open"; + + using (var client = new TestClient(_endpoint)) + { + var start = new FhirDateTime(2014, 11, 1); + var end = new FhirDateTime(2020, 1, 1); + var par = new Parameters().Add("start", start).Add("end", end); + + var bundleTask = client.InstanceOperationAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), "everything", par); + var bundle2Task = client.FetchPatientRecordAsync(ResourceIdentity.Build("Patient", "SMART-1288992"), start, end); + + await System.Threading.Tasks.Task.WhenAll(bundleTask, bundle2Task); + + var bundle = (Bundle)bundleTask.Result; + Assert.IsTrue(bundle.Entry.Any()); + + var bundle2 = (Bundle)bundle2Task.Result; + Assert.IsTrue(bundle2.Entry.Any()); + } + } } } diff --git a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index d0b31135e4..6cb3af640e 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -32,13 +32,13 @@ public partial class FhirClient : BaseFhirClient /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically /// - /// /// + /// /// If parameter is set to true the first time a request is made to the server a /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirClient(Uri endpoint, HttpMessageHandler messageHandler = null, bool verifyFhirVersion = false) + public FhirClient(Uri endpoint, bool verifyFhirVersion = false, HttpMessageHandler messageHandler = null) { Endpoint = GetValidatedEndpoint(endpoint); VerifyFhirVersion = verifyFhirVersion; @@ -65,14 +65,14 @@ public FhirClient(Uri endpoint, HttpMessageHandler messageHandler = null, bool v /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically /// - /// /// + /// /// If parameter is set to true the first time a request is made to the server a /// conformance check will be made to check that the FHIR versions are compatible. /// When they are not compatible, a FhirException will be thrown. /// - public FhirClient(string endpoint, HttpMessageHandler messageHandler = null, bool verifyFhirVersion = false) - : this(new Uri(endpoint), messageHandler, verifyFhirVersion) + public FhirClient(string endpoint, bool verifyFhirVersion = false, HttpMessageHandler messageHandler = null) + : this(new Uri(endpoint), verifyFhirVersion, messageHandler) { } diff --git a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs index 2a955930bd..01e8ff5a61 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs @@ -20,10 +20,10 @@ namespace Hl7.Fhir.Rest.Http { - public static class HttpToEntryExtensions + internal static class HttpToEntryExtensions { private const string USERDATA_BODY = "$body"; - private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; + private const string EXTENSION_RESPONSE_HEADER = "http://hl7.org/fhir/StructureDefinition/http-response-header"; internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage response, byte[] body, ParserSettings parserSettings, bool throwOnFormatException) { @@ -41,15 +41,15 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res { charEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); } - catch(ArgumentException e) + catch (ArgumentException e) { charEncoding = Encoding.UTF8; - } + } result.Response.Location = response.Headers.Location?.AbsoluteUri ?? response.Content.Headers.ContentLocation?.AbsoluteUri; result.Response.LastModified = response.Content.Headers.LastModified; - result.Response.Etag = response.Headers.ETag?.Tag; + result.Response.Etag = response.Headers.ETag?.Tag; if (body != null && body.Length != 0) { @@ -79,7 +79,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res } return result; - } + } internal static void SetHeaders(this Bundle.ResponseComponent interaction, HttpResponseHeaders headers) { diff --git a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs index 58d24a60ad..d415e67956 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/Requester.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/Requester.cs @@ -35,6 +35,7 @@ internal class Requester : IRequester, IDisposable /// 2. decompress any responses that have Content-Encoding: gzip (or deflate) ///
public bool PreferCompressedResponses { get; set; } + /// /// Compress any Request bodies /// (warning, if a server does not handle compressed requests you will get a 415 response) From 1da3f4bf860b5be79de94a5d88b13baeac563584 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 15:28:11 -0600 Subject: [PATCH 32/39] Added async read tests for new client. --- .../Rest/ReadAsyncTests.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs index 94fac8a41e..4ca7f78871 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/ReadAsyncTests.cs @@ -4,6 +4,7 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Rest; using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; namespace Hl7.Fhir.Core.AsyncTests { @@ -14,7 +15,7 @@ public class ReadAsyncTests [TestMethod] [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturned() + public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -32,7 +33,26 @@ public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturn [TestMethod] [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned() + public async System.Threading.Tasks.Task Read_UsingResourceIdentity_ResultReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + Patient p = await client.ReadAsync(new ResourceIdentity("/Patient/SMART-1288992")); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -47,5 +67,24 @@ public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturned Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); Console.WriteLine("Test Completed"); } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task Read_UsingLocationString_ResultReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + Patient p = await client.ReadAsync("/Patient/SMART-1288992"); + Assert.IsNotNull(p); + Assert.IsNotNull(p.Name[0].Given); + Assert.IsNotNull(p.Name[0].Family); + Console.WriteLine($"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + Console.WriteLine("Test Completed"); + } + } } } \ No newline at end of file From aaa06881503d76786714428bf542809ca855b733 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 15:43:43 -0600 Subject: [PATCH 33/39] Added async search tests for new client. --- .../Rest/RequesterTests.cs | 38 +++- .../Rest/SearchAsyncTests.cs | 197 ++++++++++++++++-- 2 files changed, 222 insertions(+), 13 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs index f3e9def4e8..d25ac09f66 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs @@ -37,7 +37,7 @@ public void SetsInteractionType() } [TestMethod] - public void TestPreferSetting() + public void TestPreferSettingWebRequester() { var p = new Patient(); var tx = new TransactionBuilder("http://myserver.org/fhir") @@ -69,6 +69,42 @@ public void TestPreferSetting() request = b.Entry[0].ToHttpRequest(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false, out dummy); Assert.IsNull(request.Headers["Prefer"]); } + + [TestMethod] + public void TestPreferSettingHttpRequester() + { + var p = new Patient(); + var tx = new TransactionBuilder("http://myserver.org/fhir") + .Create(p); + var b = tx.ToBundle(); + byte[] dummy; + + var request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("return=minimal", request.Headers.GetValues("Prefer").FirstOrDefault()); + + request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("return=representation", request.Headers.GetValues("Prefer").FirstOrDefault()); + + request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Strict, Prefer.OperationOutcome, ResourceFormat.Json, false, false); + Assert.AreEqual("return=OperationOutcome", request.Headers.GetValues("Prefer").FirstOrDefault()); + + request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Strict, null, ResourceFormat.Json, false, false); + request.Headers.TryGetValues("Prefer", out var preferHeader); + Assert.IsNull(preferHeader); + + tx = new TransactionBuilder("http://myserver.org/fhir").Search(new SearchParams().Where("name=ewout"), resourceType: "Patient"); + b = tx.ToBundle(); + + request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Lenient, Prefer.ReturnMinimal, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=lenient", request.Headers.GetValues("Prefer").FirstOrDefault()); + + request = b.Entry[0].ToHttpRequestMessage(SearchParameterHandling.Strict, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + Assert.AreEqual("handling=strict", request.Headers.GetValues("Prefer").FirstOrDefault()); + + request = b.Entry[0].ToHttpRequestMessage(null, Prefer.ReturnRepresentation, ResourceFormat.Json, false, false); + request.Headers.TryGetValues("Prefer", out preferHeader); + Assert.IsNull(preferHeader); + } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs index c1f98dc942..8a54a437a3 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/SearchAsyncTests.cs @@ -4,6 +4,7 @@ using Hl7.Fhir.Rest; using Task = System.Threading.Tasks.Task; using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; namespace Hl7.Fhir.Core.AsyncTests { @@ -14,7 +15,7 @@ public class SearchAsyncTests [TestMethod] [TestCategory("IntegrationTest")] - public async Task Search_UsingSearchParams_SearchReturned() + public async Task Search_UsingSearchParams_SearchReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -42,13 +43,50 @@ public async Task Search_UsingSearchParams_SearchReturned() } result1 = client.Continue(result1, PageDirection.Next); } - + Console.WriteLine("Test Completed"); } [TestMethod] [TestCategory("IntegrationTest")] - public void SearchSync_UsingSearchParams_SearchReturned() + public async Task Search_UsingSearchParams_SearchReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var result1 = await client.SearchAsync(srch); + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + } + + + [TestMethod] + [TestCategory("IntegrationTest")] + public void SearchSync_UsingSearchParams_SearchReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -81,10 +119,45 @@ public void SearchSync_UsingSearchParams_SearchReturned() Console.WriteLine("Test Completed"); } + [TestMethod] + [TestCategory("IntegrationTest")] + public void SearchSync_UsingSearchParams_SearchReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + var srch = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var result1 = client.Search(srch); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + } [TestMethod] [TestCategory("IntegrationTest")] - public async Task SearchMultiple_UsingSearchParams_SearchReturned() + public async Task SearchMultiple_UsingSearchParams_SearchReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -98,7 +171,7 @@ public async Task SearchMultiple_UsingSearchParams_SearchReturned() .SummaryOnly() .OrderBy("birthdate", SortOrder.Descending); - + var task1 = client.SearchAsync(srchParams); var task2 = client.SearchAsync(srchParams); var task3 = client.SearchAsync(srchParams); @@ -107,7 +180,7 @@ public async Task SearchMultiple_UsingSearchParams_SearchReturned() var result1 = task1.Result; Assert.IsTrue(result1.Entry.Count >= 1); - + while (result1 != null) { foreach (var e in result1.Entry) @@ -124,15 +197,56 @@ public async Task SearchMultiple_UsingSearchParams_SearchReturned() [TestMethod] [TestCategory("IntegrationTest")] - public async Task SearchWithCriteria_SyncContinue_SearchReturned() + public async Task SearchMultiple_UsingSearchParams_SearchReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + var srchParams = new SearchParams() + .Where("name=Daniel") + .LimitTo(10) + .SummaryOnly() + .OrderBy("birthdate", + SortOrder.Descending); + + var task1 = client.SearchAsync(srchParams); + var task2 = client.SearchAsync(srchParams); + var task3 = client.SearchAsync(srchParams); + + await Task.WhenAll(task1, task2, task3); + var result1 = task1.Result; + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchWithCriteria_SyncContinue_SearchReturnedWebClient() { var client = new FhirClient(_endpoint) { PreferredFormat = ResourceFormat.Json, PreferredReturn = Prefer.ReturnRepresentation }; - - var result1 = await client.SearchAsync(new []{"family=clark"}); + + var result1 = await client.SearchAsync(new[] { "family=clark" }); Assert.IsTrue(result1.Entry.Count >= 1); @@ -152,7 +266,36 @@ public async Task SearchWithCriteria_SyncContinue_SearchReturned() [TestMethod] [TestCategory("IntegrationTest")] - public async Task SearchWithCriteria_AsyncContinue_SearchReturned() + public async Task SearchWithCriteria_SyncContinue_SearchReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + var result1 = await client.SearchAsync(new[] { "family=clark" }); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + result1 = client.Continue(result1, PageDirection.Next); + } + + Console.WriteLine("Test Completed"); + } + } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchWithCriteria_AsyncContinue_SearchReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -160,7 +303,7 @@ public async Task SearchWithCriteria_AsyncContinue_SearchReturned() PreferredReturn = Prefer.ReturnRepresentation }; - var result1 = await client.SearchAsync(new[] { "family=clark" },null,1); + var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); Assert.IsTrue(result1.Entry.Count >= 1); @@ -178,5 +321,35 @@ public async Task SearchWithCriteria_AsyncContinue_SearchReturned() Console.WriteLine("Test Completed"); } + + [TestMethod] + [TestCategory("IntegrationTest")] + public async Task SearchWithCriteria_AsyncContinue_SearchReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + var result1 = await client.SearchAsync(new[] { "family=clark" }, null, 1); + + Assert.IsTrue(result1.Entry.Count >= 1); + + while (result1 != null) + { + foreach (var e in result1.Entry) + { + Patient p = (Patient)e.Resource; + Console.WriteLine( + $"NAME: {p.Name[0].Given.FirstOrDefault()} {p.Name[0].Family.FirstOrDefault()}"); + } + Console.WriteLine("Fetching more results..."); + result1 = await client.ContinueAsync(result1); + } + + Console.WriteLine("Test Completed"); + } + } } -} +} \ No newline at end of file From 4439d99d8d557aee7f0444caafe21aac512ec86d Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 15:45:58 -0600 Subject: [PATCH 34/39] Added update/refresh/delete tests for new client. --- .../Rest/UpdateRefreshDeleteAsyncTests.cs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs index 0b0d811413..80250a03ba 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs @@ -4,6 +4,7 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Rest; using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; namespace Hl7.Fhir.Core.AsyncTests { @@ -14,7 +15,7 @@ public class UpdateRefreshDeleteAsyncTests [TestMethod] [TestCategory("IntegrationTest")] - public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturned() + public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturnedWebClient() { var client = new FhirClient(_endpoint) { @@ -59,6 +60,54 @@ public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_Resu Console.WriteLine("Test Completed"); } - + + [TestMethod] + [TestCategory("IntegrationTest")] + public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_ResultReturnedHttpClient() + { + using (var client = new TestClient(_endpoint) + { + PreferredFormat = ResourceFormat.Json, + PreferredReturn = Prefer.ReturnRepresentation + }) + { + var pat = new Patient() + { + Name = new List() + { + new HumanName() + { + Given = new List() {"test_given"}, + Family = "test_family", + } + }, + Id = "async-test-patient" + }; + // Create the patient + Console.WriteLine("Creating patient..."); + Patient p = await client.UpdateAsync(pat); + Assert.IsNotNull(p); + + // Refresh the patient + Console.WriteLine("Refreshing patient..."); + await client.RefreshAsync(p); + + // Delete the patient + Console.WriteLine("Deleting patient..."); + await client.DeleteAsync(p); + + Console.WriteLine("Reading patient..."); + Func act = async () => + { + await client.ReadAsync(new ResourceIdentity("/Patient/async-test-patient")); + }; + + // VERIFY // + Assert.ThrowsException(act, "the patient is no longer on the server"); + + + Console.WriteLine("Test Completed"); + } + } } } \ No newline at end of file From e98ad7640a34dc37bb31259379b315bc6de9534a Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 16:52:18 -0600 Subject: [PATCH 35/39] Fixed faulty tests, bad logic in MockHandler, refactored exception logic in base classes. --- .../Rest/FhirClientTests.cs | 7 +-- .../Rest/Mocks/MockHttpMessageHandler.cs | 5 +- .../Rest/OperationsTests.cs | 2 +- .../Rest/RequesterTests.cs | 2 + .../Rest/UpdateRefreshDeleteAsyncTests.cs | 4 +- src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs | 4 +- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 6 +- .../Rest/FhirClientOperations.cs | 60 +++++++++---------- .../Rest/FhirClientTermSvcExtensions.cs | 12 ++-- src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs | 8 ++- 10 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 3e81be8e99..75529dd755 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -1612,8 +1612,6 @@ public void RequestFullResourceHttpClient() using (var handler = new MockHttpMessageHandler()) using (var client = new TestClient(testEndpoint, messageHandler: handler)) { - var minimal = false; - handler.OnBeforeRequest += (object s, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) => e.RawRequest.Headers.TryAddWithoutValidation("Prefer", minimal ? "return=minimal" : "return=representation"); var result = client.Read("Patient/glossy"); Assert.IsNotNull(result); @@ -1621,16 +1619,13 @@ public void RequestFullResourceHttpClient() result.Meta = null; client.PreferredReturn = Prefer.ReturnRepresentation; - minimal = false; var posted = client.Create(result); Assert.IsNotNull(posted, "Patient example not found"); - minimal = true; // simulate a server that does not return a body, even if ReturnFullResource = true posted = client.Create(result); Assert.IsNotNull(posted, "Did not return a resource, even when ReturnFullResource=true"); client.PreferredReturn = Prefer.ReturnMinimal; - minimal = true; posted = client.Create(result); Assert.IsNull(posted); } @@ -1960,7 +1955,7 @@ public void TestAuthenticationOnBeforeHttpClient() { using (TestClient validationFhirClient = new TestClient("https://sqlonfhir.azurewebsites.net/fhir")) { - validationFhirClient.RequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer bad-bearer"); + validationFhirClient.RequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "bad-bearer"); try { diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs b/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs index ce085582a3..eb2ffdba5d 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs @@ -46,11 +46,12 @@ protected virtual void AfterResponse(HttpResponseMessage webResponse, byte[] bod protected override async Task SendAsync(HttpRequestMessage message, CancellationToken cancellationToken) { - BeforeRequest(message, (await message.Content.ReadAsByteArrayAsync())); + var requestBody = message.Content != null ? await message.Content.ReadAsByteArrayAsync() : new byte[0]; + BeforeRequest(message, requestBody); var response = await base.SendAsync(message, cancellationToken); - AfterResponse(response, (await response.Content.ReadAsByteArrayAsync())); + AfterResponse(response, (await response.Content?.ReadAsByteArrayAsync() ?? new byte[0])); return response; } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs index 959fe4eb75..f292404239 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/OperationsTests.cs @@ -90,7 +90,7 @@ public void InvokeExpandParameterValueSetWebClient() [TestMethod] [TestCategory("IntegrationTest")] - public void InvokeExpandParameterValueSetWebClient() + public void InvokeExpandParameterValueSetHttpClient() { using (var client = new TestClient(FhirClientTests.TerminologyEndpoint)) { diff --git a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs index d25ac09f66..f72e1ff995 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/RequesterTests.cs @@ -7,9 +7,11 @@ */ using System; +using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Hl7.Fhir.Support; using Hl7.Fhir.Rest; +using Hl7.Fhir.Rest.Http; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; diff --git a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs index 80250a03ba..d397197145 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/UpdateRefreshDeleteAsyncTests.cs @@ -56,8 +56,8 @@ public async System.Threading.Tasks.Task UpdateDelete_UsingResourceIdentity_Resu // VERIFY // Assert.ThrowsException(act, "the patient is no longer on the server"); - - + + Console.WriteLine("Test Completed"); } diff --git a/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs index 61118aa626..079940312a 100644 --- a/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/BaseFhirClient.cs @@ -13,10 +13,10 @@ namespace Hl7.Fhir.Rest public abstract partial class BaseFhirClient : IDisposable, IFhirClient { [Obsolete] - public event EventHandler OnAfterResponse; + public abstract event EventHandler OnAfterResponse; [Obsolete] - public event EventHandler OnBeforeRequest; + public abstract event EventHandler OnBeforeRequest; protected IRequester Requester { get; set; } diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 13fb9b0d98..d6db4a3931 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -90,12 +90,14 @@ public FhirClient(string endpoint, bool verifyFhirVersion = false) /// /// Called just before the Http call is done /// - public event EventHandler OnBeforeRequest; + [Obsolete] + public override event EventHandler OnBeforeRequest; /// /// Called just after the response was received /// - public event EventHandler OnAfterResponse; + [Obsolete] + public override event EventHandler OnAfterResponse; /// /// Inspect or modify the HttpWebRequest just before the FhirClient issues a call to the server diff --git a/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs b/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs index 34df570913..bcf17ac1b5 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClientOperations.cs @@ -63,7 +63,7 @@ public static class FhirClientOperations { #region Validate (Create/Update/Delete/Resource) - public static async Task ValidateCreateAsync(this BaseFhirClient client, DomainResource resource, FhirUri profile = null) + public static async Task ValidateCreateAsync(this IFhirClient client, DomainResource resource, FhirUri profile = null) { if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -72,13 +72,13 @@ public static async Task ValidateCreateAsync(this BaseFhirClie return OperationResult(await client.TypeOperationAsync(RestOperation.VALIDATE_RESOURCE, resource.TypeName, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateCreate(this BaseFhirClient client, DomainResource resource, + public static OperationOutcome ValidateCreate(this IFhirClient client, DomainResource resource, FhirUri profile = null) { return ValidateCreateAsync(client, resource, profile).WaitResult(); } - public static async Task ValidateUpdateAsync(this BaseFhirClient client, DomainResource resource, string id, FhirUri profile = null) + public static async Task ValidateUpdateAsync(this IFhirClient client, DomainResource resource, string id, FhirUri profile = null) { if (id == null) throw Error.ArgumentNull(nameof(id)); if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -89,14 +89,14 @@ public static async Task ValidateUpdateAsync(this BaseFhirClie var loc = ResourceIdentity.Build(resource.TypeName, id); return OperationResult(await client.InstanceOperationAsync(loc, RestOperation.VALIDATE_RESOURCE, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateUpdate(this BaseFhirClient client, DomainResource resource, string id, + public static OperationOutcome ValidateUpdate(this IFhirClient client, DomainResource resource, string id, FhirUri profile = null) { return ValidateUpdateAsync(client, resource, id, profile).WaitResult(); } - public static async Task ValidateDeleteAsync(this BaseFhirClient client, ResourceIdentity location) + public static async Task ValidateDeleteAsync(this IFhirClient client, ResourceIdentity location) { if (location == null) throw Error.ArgumentNull(nameof(location)); @@ -104,12 +104,12 @@ public static async Task ValidateDeleteAsync(this BaseFhirClie return OperationResult(await client.InstanceOperationAsync(location.WithoutVersion().MakeRelative(), RestOperation.VALIDATE_RESOURCE, par).ConfigureAwait(false)); } - public static OperationOutcome ValidateDelete(this BaseFhirClient client, ResourceIdentity location) + public static OperationOutcome ValidateDelete(this IFhirClient client, ResourceIdentity location) { return ValidateDeleteAsync(client,location).WaitResult(); } - public static async Task ValidateResourceAsync(this BaseFhirClient client, DomainResource resource, string id = null, FhirUri profile = null) + public static async Task ValidateResourceAsync(this IFhirClient client, DomainResource resource, string id = null, FhirUri profile = null) { if (resource == null) throw Error.ArgumentNull(nameof(resource)); @@ -127,7 +127,7 @@ public static async Task ValidateResourceAsync(this BaseFhirCl } } - public static OperationOutcome ValidateResource(this BaseFhirClient client, DomainResource resource, + public static OperationOutcome ValidateResource(this IFhirClient client, DomainResource resource, string id = null, FhirUri profile = null) { return ValidateResourceAsync(client, resource, id, profile).WaitResult(); @@ -137,7 +137,7 @@ public static OperationOutcome ValidateResource(this BaseFhirClient client, Doma #region Fetch - public static async Task FetchPatientRecordAsync(this BaseFhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) + public static async Task FetchPatientRecordAsync(this IFhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) { var par = new Parameters(); @@ -155,7 +155,7 @@ public static async Task FetchPatientRecordAsync(this BaseFhirClient cli return OperationResult(result); } - public static Bundle FetchPatientRecord(this BaseFhirClient client, Uri patient = null, FhirDateTime start = null, + public static Bundle FetchPatientRecord(this IFhirClient client, Uri patient = null, FhirDateTime start = null, FhirDateTime end = null) { return FetchPatientRecordAsync(client, patient, start, end).WaitResult(); @@ -166,84 +166,84 @@ public static Bundle FetchPatientRecord(this BaseFhirClient client, Uri patient #region Meta //[base]/$meta - public static async Task MetaAsync(this BaseFhirClient client) + public static async Task MetaAsync(this IFhirClient client) { return extractMeta(OperationResult(await client.WholeSystemOperationAsync(RestOperation.META, useGet:true).ConfigureAwait(false))); } - public static Meta Meta(this BaseFhirClient client) + public static Meta Meta(this IFhirClient client) { return MetaAsync(client).WaitResult(); } //[base]/Resource/$meta - public static async Task MetaAsync(this BaseFhirClient client, ResourceType type) + public static async Task MetaAsync(this IFhirClient client, ResourceType type) { return extractMeta(OperationResult(await client.TypeOperationAsync(RestOperation.META, type.ToString(), useGet: true).ConfigureAwait(false))); } - public static Meta Meta(this BaseFhirClient client, ResourceType type) + public static Meta Meta(this IFhirClient client, ResourceType type) { return MetaAsync(client, type).WaitResult(); } //[base]/Resource/id/$meta/[_history/vid] - public static async Task MetaAsync(this BaseFhirClient client, Uri location) + public static async Task MetaAsync(this IFhirClient client, Uri location) { Resource result; result = await client.InstanceOperationAsync(location, RestOperation.META, useGet: true).ConfigureAwait(false); return extractMeta(OperationResult(result)); } - public static Meta Meta(this BaseFhirClient client, Uri location) + public static Meta Meta(this IFhirClient client, Uri location) { return MetaAsync(client, location).WaitResult(); } - public static Task MetaAsync(this BaseFhirClient client, string location) + public static Task MetaAsync(this IFhirClient client, string location) { return MetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute)); } - public static Meta Meta(this BaseFhirClient client, string location) + public static Meta Meta(this IFhirClient client, string location) { return MetaAsync(client, location).WaitResult(); } - public static async Task AddMetaAsync(this BaseFhirClient client, Uri location, Meta meta) + public static async Task AddMetaAsync(this IFhirClient client, Uri location, Meta meta) { var par = new Parameters().Add("meta", meta); return extractMeta(OperationResult(await client.InstanceOperationAsync(location, RestOperation.META_ADD, par).ConfigureAwait(false))); } - public static Meta AddMeta(this BaseFhirClient client, Uri location, Meta meta) + public static Meta AddMeta(this IFhirClient client, Uri location, Meta meta) { return AddMetaAsync(client, location, meta).WaitResult(); } - public static Task AddMetaAsync(this BaseFhirClient client, string location, Meta meta) + public static Task AddMetaAsync(this IFhirClient client, string location, Meta meta) { return AddMetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute), meta); } - public static Meta AddMeta(this BaseFhirClient client, string location, Meta meta) + public static Meta AddMeta(this IFhirClient client, string location, Meta meta) { return AddMetaAsync(client, location, meta).WaitResult(); } - public static async Task DeleteMetaAsync(this BaseFhirClient client, Uri location, Meta meta) + public static async Task DeleteMetaAsync(this IFhirClient client, Uri location, Meta meta) { var par = new Parameters().Add("meta", meta); return extractMeta(OperationResult(await client.InstanceOperationAsync(location, RestOperation.META_DELETE, par).ConfigureAwait(false))); } - public static Meta DeleteMeta(this BaseFhirClient client, Uri location, Meta meta) + public static Meta DeleteMeta(this IFhirClient client, Uri location, Meta meta) { return DeleteMetaAsync(client, location, meta).WaitResult(); } - public static Task DeleteMetaAsync(this BaseFhirClient client, string location, Meta meta) + public static Task DeleteMetaAsync(this IFhirClient client, string location, Meta meta) { return DeleteMetaAsync(client, new Uri(location, UriKind.RelativeOrAbsolute), meta); } - public static Meta DeleteMeta(this BaseFhirClient client, string location, Meta meta) + public static Meta DeleteMeta(this IFhirClient client, string location, Meta meta) { return DeleteMetaAsync(client, location, meta).WaitResult(); } @@ -260,14 +260,14 @@ public class TranslateConceptDependency } - public static async Task TranslateConceptAsync(this BaseFhirClient client, string id, Code code, FhirUri system, FhirString version, + public static async Task TranslateConceptAsync(this IFhirClient client, string id, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) { Parameters par = createTranslateConceptParams(code, system, version, valueSet, coding, codeableConcept, target, dependencies); var loc = ResourceIdentity.Build("ConceptMap", id); return OperationResult(await client.InstanceOperationAsync(loc, RestOperation.TRANSLATE, par).ConfigureAwait(false)); } - public static Parameters TranslateConcept(this BaseFhirClient client, string id, Code code, FhirUri system, + public static Parameters TranslateConcept(this IFhirClient client, string id, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) @@ -277,7 +277,7 @@ public static Parameters TranslateConcept(this BaseFhirClient client, string id, } - public static async Task TranslateConceptAsync(this BaseFhirClient client, Code code, FhirUri system, FhirString version, + public static async Task TranslateConceptAsync(this IFhirClient client, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies ) { Parameters par = createTranslateConceptParams(code, system, version, valueSet, coding, codeableConcept, target, dependencies); @@ -285,7 +285,7 @@ public static async Task TranslateConceptAsync(this BaseFhirClient c return OperationResult(await client.TypeOperationAsync(RestOperation.TRANSLATE, par).ConfigureAwait(false)); } - public static Parameters TranslateConcept(this BaseFhirClient client, Code code, FhirUri system, FhirString version, + public static Parameters TranslateConcept(this IFhirClient client, Code code, FhirUri system, FhirString version, FhirUri valueSet, Coding coding, CodeableConcept codeableConcept, FhirUri target, IEnumerable dependencies) { diff --git a/src/Hl7.Fhir.Core/Rest/FhirClientTermSvcExtensions.cs b/src/Hl7.Fhir.Core/Rest/FhirClientTermSvcExtensions.cs index a0de367593..728cbf8490 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClientTermSvcExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClientTermSvcExtensions.cs @@ -59,7 +59,7 @@ public static ValidateCodeResult FromParameters(Parameters p) public static class FhirClientTermSvcExtensions { #region Expand - public static async Task ExpandValueSetAsync(this FhirClient client, Uri valueset, FhirString filter = null, FhirDateTime date = null) + public static async Task ExpandValueSetAsync(this IFhirClient client, Uri valueset, FhirString filter = null, FhirDateTime date = null) { if (valueset == null) throw Error.ArgumentNull(nameof(valueset)); @@ -74,13 +74,13 @@ public static async Task ExpandValueSetAsync(this FhirClient client, U .OperationResult(); } - public static ValueSet ExpandValueSet(this FhirClient client, Uri valueset, FhirString filter = null, + public static ValueSet ExpandValueSet(this IFhirClient client, Uri valueset, FhirString filter = null, FhirDateTime date = null) { return ExpandValueSetAsync(client, valueset, filter, date).WaitResult(); } - public static async Task ExpandValueSetAsync(this FhirClient client, FhirUri identifier, FhirString filter = null, FhirDateTime date = null) + public static async Task ExpandValueSetAsync(this IFhirClient client, FhirUri identifier, FhirString filter = null, FhirDateTime date = null) { if (identifier == null) throw Error.ArgumentNull(nameof(identifier)); @@ -94,13 +94,13 @@ public static async Task ExpandValueSetAsync(this FhirClient client, F .OperationResult(); } - public static ValueSet ExpandValueSet(this FhirClient client, FhirUri identifier, FhirString filter = null, + public static ValueSet ExpandValueSet(this IFhirClient client, FhirUri identifier, FhirString filter = null, FhirDateTime date = null) { return ExpandValueSetAsync(client, identifier, filter, date).WaitResult(); } - public static async Task ExpandValueSetAsync(this FhirClient client, ValueSet vs, FhirString filter = null, FhirDateTime date = null) + public static async Task ExpandValueSetAsync(this IFhirClient client, ValueSet vs, FhirString filter = null, FhirDateTime date = null) { if (vs == null) throw Error.ArgumentNull(nameof(vs)); @@ -112,7 +112,7 @@ public static async Task ExpandValueSetAsync(this FhirClient client, V .OperationResult(); } - public static ValueSet ExpandValueSet(this FhirClient client, ValueSet vs, FhirString filter = null, + public static ValueSet ExpandValueSet(this IFhirClient client, ValueSet vs, FhirString filter = null, FhirDateTime date = null) { return ExpandValueSetAsync(client, vs, filter, date).WaitResult(); diff --git a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs index 6cb3af640e..b01b1c6ffc 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/FhirClient.cs @@ -97,6 +97,12 @@ public FhirClient(string endpoint, bool verifyFhirVersion = false, HttpMessageHa /// no longer available. Use LastBody, LastBodyAsText and LastBodyAsResource to get access to the received body (if any) new public HttpResponseMessage LastResponse { get { return (Requester as Http.Requester)?.LastResponse; } } + [Obsolete] + public override event EventHandler OnAfterResponse = (args, e) => throw new NotImplementedException(); + + [Obsolete] + public override event EventHandler OnBeforeRequest = (args, e) => throw new NotImplementedException(); + /// /// Override dispose in order to clean up request headers tied to disposed requester. /// @@ -108,7 +114,7 @@ protected override void Dispose(bool disposing) if (disposing) { this.RequestHeaders = null; - base.Dispose(); + base.Dispose(disposing); } disposedValue = true; From b0fd949572b7041b70623025e5a262f96f39de74 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 17:53:44 -0600 Subject: [PATCH 36/39] Added new client tests for TerminologyTests. --- .../Source/TerminologyTests.cs | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Source/TerminologyTests.cs b/src/Hl7.Fhir.Specification.Tests/Source/TerminologyTests.cs index c2dc1fa999..6f80d2a336 100644 --- a/src/Hl7.Fhir.Specification.Tests/Source/TerminologyTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Source/TerminologyTests.cs @@ -7,6 +7,8 @@ using System.Linq; using Xunit; +using TestClient = Hl7.Fhir.Rest.Http.FhirClient; + namespace Hl7.Fhir.Specification.Tests { public class TerminologyTests : IClassFixture @@ -167,7 +169,7 @@ public void LocalTermServiceValidateCodeTest() } [Fact, Trait("TestCategory", "IntegrationTest")] - public void ExternalServiceValidateCodeTest() + public void ExternalServiceValidateCodeTestWebClient() { var client = new FhirClient("http://ontoserver.csiro.au/stu3-latest"); var svc = new ExternalTerminologyService(client); @@ -181,7 +183,23 @@ public void ExternalServiceValidateCodeTest() } [Fact, Trait("TestCategory", "IntegrationTest")] - public void FallbackServiceValidateCodeTest() + public void ExternalServiceValidateCodeTestHttpClient() + { + using (var client = new TestClient("http://ontoserver.csiro.au/stu3-latest")) + { + var svc = new ExternalTerminologyService(client); + + // Do common tests for service + testService(svc); + + // Any good external service should be able to handle this one + var result = svc.ValidateCode("http://hl7.org/fhir/ValueSet/substance-code", code: "1166006", system: "http://snomed.info/sct"); + Assert.True(result.Success); + } + } + + [Fact, Trait("TestCategory", "IntegrationTest")] + public void FallbackServiceValidateCodeTestWebClient() { var client = new FhirClient("http://ontoserver.csiro.au/stu3-latest"); var external = new ExternalTerminologyService(client); @@ -196,7 +214,24 @@ public void FallbackServiceValidateCodeTest() } [Fact, Trait("TestCategory", "IntegrationTest")] - public void FallbackServiceValidateCodeTestWithVS() + public void FallbackServiceValidateCodeTestHttpClient() + { + using (var client = new TestClient("http://ontoserver.csiro.au/stu3-latest")) + { + var external = new ExternalTerminologyService(client); + var local = new LocalTerminologyService(_resolver); + var svc = new FallbackTerminologyService(local, external); + + testService(svc); + + // Now, this should fall back + var result = svc.ValidateCode("http://hl7.org/fhir/ValueSet/substance-code", code: "1166006", system: "http://snomed.info/sct"); + Assert.True(result.Success); + } + } + + [Fact, Trait("TestCategory", "IntegrationTest")] + public void FallbackServiceValidateCodeTestWithVSWebClient() { var client = new FhirClient("http://ontoserver.csiro.au/stu3-latest"); var service = new ExternalTerminologyService(client); @@ -213,6 +248,26 @@ public void FallbackServiceValidateCodeTestWithVS() Assert.True(result.Success); } + [Fact, Trait("TestCategory", "IntegrationTest")] + public void FallbackServiceValidateCodeTestWithVSHttpClient() + { + using (var client = new TestClient("http://ontoserver.csiro.au/stu3-latest")) + { + var service = new ExternalTerminologyService(client); + var vs = _resolver.FindValueSet("http://hl7.org/fhir/ValueSet/substance-code"); + Assert.NotNull(vs); + + // Override the canonical with something the remote server cannot know + vs.Url = "http://furore.com/fhir/ValueSet/testVS"; + var local = new LocalTerminologyService(new IKnowOnlyMyTestVSResolver(vs)); + var fallback = new FallbackTerminologyService(local, service); + + // Now, this should fall back to external + send our vs (that the server cannot know about) + var result = fallback.ValidateCode("http://furore.com/fhir/ValueSet/testVS", code: "1166006", system: "http://snomed.info/sct"); + Assert.True(result.Success); + } + } + private class IKnowOnlyMyTestVSResolver : IResourceResolver { public ValueSet _myOnlyVS; From 43bb2a4161b1e9a1bd9d1fb638d018b52d4802ec Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 5 Dec 2017 18:15:15 -0600 Subject: [PATCH 37/39] Reverted System.Net.Http version dependency --- src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj b/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj index 605c4580b9..41cc43b822 100644 --- a/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj +++ b/src/Hl7.Fhir.Core/Hl7.Fhir.Core.csproj @@ -30,7 +30,7 @@ - + @@ -39,7 +39,6 @@ - From 92e76022f511880d5ebd70874dba28e77834e887 Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 12 Dec 2017 16:35:20 -0600 Subject: [PATCH 38/39] Made changes per first set of review comments on base fhir-net-api repo. --- .../Hl7.Fhir.Core.Tests.csproj | 1 + src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs | 15 +++++++-------- .../Rest/Http/EntryToHttpExtensions.cs | 17 +++++++---------- .../Rest/Http/HttpClientEventHandler.cs} | 4 ++-- .../Rest/Http/HttpToEntryExtensions.cs | 8 ++++---- 5 files changed, 21 insertions(+), 24 deletions(-) rename src/{Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs => Hl7.Fhir.Core/Rest/Http/HttpClientEventHandler.cs} (96%) diff --git a/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj b/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj index dae7c5e54f..4e61cbec51 100644 --- a/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj +++ b/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj @@ -52,6 +52,7 @@ + diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 75529dd755..405bcec097 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -16,7 +16,6 @@ using Hl7.Fhir.Serialization; using Hl7.Fhir.Model; using TestClient = Hl7.Fhir.Rest.Http.FhirClient; -using Hl7.Fhir.Core.Tests.Rest.Mocks; namespace Hl7.Fhir.Tests.Rest { @@ -347,7 +346,7 @@ public static void Compression_OnBeforeWebRequestZipOrDeflate(object sender, Fhi } } - public static void Compression_OnBeforeHttpRequestGZip(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestGZip(object sender, Core.Rest.Http.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -357,7 +356,7 @@ public static void Compression_OnBeforeHttpRequestGZip(object sender, Core.Tests } } - public static void Compression_OnBeforeHttpRequestDeflate(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestDeflate(object sender, Core.Rest.Http.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -367,7 +366,7 @@ public static void Compression_OnBeforeHttpRequestDeflate(object sender, Core.Te } } - public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, Core.Tests.Rest.Mocks.BeforeRequestEventArgs e) + public static void Compression_OnBeforeHttpRequestZipOrDeflate(object sender, Core.Rest.Http.BeforeRequestEventArgs e) { if (e.RawRequest != null) { @@ -435,7 +434,7 @@ public void SearchWebClient() TestCategory("IntegrationTest")] public void SearchHttpClient() { - using (var handler = new MockHttpMessageHandler()) + using (var handler = new Core.Rest.Http.HttpClientEventHandler()) using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { Bundle result; @@ -836,7 +835,7 @@ public void CreateEditDeleteWebClient() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CreateEditDeleteHttpClient() { - using (var handler = new MockHttpMessageHandler()) + using (var handler = new Core.Rest.Http.HttpClientEventHandler()) using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { @@ -1519,7 +1518,7 @@ public void CallsCallbacksWebClient() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void CallsCallbacksHttpClient() { - using (var handler = new MockHttpMessageHandler()) + using (var handler = new Core.Rest.Http.HttpClientEventHandler()) using (TestClient client = new TestClient(testEndpoint, messageHandler: handler)) { client.ParserSettings.AllowUnrecognizedEnums = true; @@ -1609,7 +1608,7 @@ public void RequestFullResourceWebClient() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void RequestFullResourceHttpClient() { - using (var handler = new MockHttpMessageHandler()) + using (var handler = new Core.Rest.Http.HttpClientEventHandler()) using (var client = new TestClient(testEndpoint, messageHandler: handler)) { diff --git a/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs index d429734f4e..939e3a274f 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/EntryToHttpExtensions.cs @@ -35,19 +35,19 @@ public static HttpRequestMessage ToHttpRequestMessage(this Bundle.EntryComponent var request = new HttpRequestMessage(getMethod(interaction.Method), location.Uri); if (!useFormatParameter) - request.Headers.Add("Accept", Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false)); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(Hl7.Fhir.Rest.ContentType.BuildContentType(format, forBundle: false))); - if (interaction.IfMatch != null) request.Headers.TryAddWithoutValidation("If-Match", interaction.IfMatch); - if (interaction.IfNoneMatch != null) request.Headers.TryAddWithoutValidation("If-None-Match", interaction.IfNoneMatch); + if (interaction.IfMatch != null) request.Headers.Add("If-Match", interaction.IfMatch); + if (interaction.IfNoneMatch != null) request.Headers.Add("If-None-Match", interaction.IfNoneMatch); if (interaction.IfModifiedSince != null) request.Headers.IfModifiedSince = interaction.IfModifiedSince.Value.UtcDateTime; - if (interaction.IfNoneExist != null) request.Headers.TryAddWithoutValidation("If-None-Exist", interaction.IfNoneExist); + if (interaction.IfNoneExist != null) request.Headers.Add("If-None-Exist", interaction.IfNoneExist); var interactionType = entry.Annotation(); if (interactionType == TransactionBuilder.InteractionType.Create && returnPreference != null) - request.Headers.TryAddWithoutValidation("Prefer", "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference)); + request.Headers.Add("Prefer", "return=" + PrimitiveTypeConverter.ConvertTo(returnPreference)); else if (interactionType == TransactionBuilder.InteractionType.Search && handlingPreference != null) - request.Headers.TryAddWithoutValidation("Prefer", "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference)); + request.Headers.Add("Prefer", "handling=" + PrimitiveTypeConverter.ConvertTo(handlingPreference)); if (entry.Resource != null) setBodyAndContentType(request, entry.Resource, format, CompressRequestBody); @@ -105,10 +105,7 @@ private static void setBodyAndContentType(HttpRequestMessage request, Resource d request.Content = new ByteArrayContent(body); - // MediaTypeHeaderValue cannot accept a content type that contains charset at the end, so that value must be split out. - var contentTypeList = contentType.Split(';'); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentTypeList.FirstOrDefault()); - request.Content.Headers.ContentType.CharSet = System.Text.Encoding.UTF8.WebName; + request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); } diff --git a/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs b/src/Hl7.Fhir.Core/Rest/Http/HttpClientEventHandler.cs similarity index 96% rename from src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs rename to src/Hl7.Fhir.Core/Rest/Http/HttpClientEventHandler.cs index eb2ffdba5d..e9a24dc95c 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/Mocks/MockHttpMessageHandler.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/HttpClientEventHandler.cs @@ -7,9 +7,9 @@ using System.Threading; using System.Threading.Tasks; -namespace Hl7.Fhir.Core.Tests.Rest.Mocks +namespace Hl7.Fhir.Core.Rest.Http { - public class MockHttpMessageHandler : HttpClientHandler + public class HttpClientEventHandler : HttpClientHandler { /// /// Called just before the Http call is done diff --git a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs index 01e8ff5a61..57fcb00f70 100644 --- a/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs +++ b/src/Hl7.Fhir.Core/Rest/Http/HttpToEntryExtensions.cs @@ -41,7 +41,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res { charEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); } - catch (ArgumentException e) + catch (ArgumentException) { charEncoding = Encoding.UTF8; } @@ -49,13 +49,13 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res result.Response.Location = response.Headers.Location?.AbsoluteUri ?? response.Content.Headers.ContentLocation?.AbsoluteUri; result.Response.LastModified = response.Content.Headers.LastModified; - result.Response.Etag = response.Headers.ETag?.Tag; + result.Response.Etag = response.Headers.ETag?.Tag.Trim('\"'); if (body != null && body.Length != 0) { result.Response.SetBody(body); - if (Rest.HttpToEntryExtensions.IsBinaryResponse(result.Response.Location, contentType.ToString())) + if (Rest.HttpToEntryExtensions.IsBinaryResponse(result.Response.Location, contentType.MediaType.ToString())) { result.Resource = Rest.HttpToEntryExtensions.MakeBinaryResource(body, contentType.ToString()); if (result.Response.Location != null) @@ -70,7 +70,7 @@ internal static Bundle.EntryComponent ToBundleEntry(this HttpResponseMessage res else { var bodyText = Rest.HttpToEntryExtensions.DecodeBody(body, charEncoding); - var resource = Rest.HttpToEntryExtensions.ParseResource(bodyText, contentType.ToString(), parserSettings, throwOnFormatException); + var resource = Rest.HttpToEntryExtensions.ParseResource(bodyText, contentType.MediaType.ToString(), parserSettings, throwOnFormatException); result.Resource = resource; if (result.Response.Location != null) From 79ae5594573369acb018fdb7f703d663f79d6ccc Mon Sep 17 00:00:00 2001 From: "Henley_Contractor, Devereux" Date: Tue, 12 Dec 2017 16:46:08 -0600 Subject: [PATCH 39/39] Removed unnecessary include. --- src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj b/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj index 4e61cbec51..dae7c5e54f 100644 --- a/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj +++ b/src/Hl7.Fhir.Core.Tests/Hl7.Fhir.Core.Tests.csproj @@ -52,7 +52,6 @@ -