diff --git a/build/build.yml b/build/build.yml index 928652d8..6b9bdf2c 100644 --- a/build/build.yml +++ b/build/build.yml @@ -7,6 +7,8 @@ steps: displayName: 'Use .NET Core sdk 6.0.x' inputs: version: 6.0.x + selectOrConfig: configs + nugetConfigPath: nuget.config - script: dotnet build --configuration $(buildConfiguration) --version-suffix $(build.buildNumber) /warnaserror displayName: 'dotnet build $(buildConfiguration)' diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..47ede985 --- /dev/null +++ b/nuget.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/console/MeasurementCollectionToFhir/ProcessorStartup.cs b/src/console/MeasurementCollectionToFhir/ProcessorStartup.cs index e1350b08..21ba1a6f 100644 --- a/src/console/MeasurementCollectionToFhir/ProcessorStartup.cs +++ b/src/console/MeasurementCollectionToFhir/ProcessorStartup.cs @@ -1,6 +1,5 @@ using EnsureThat; using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -10,6 +9,7 @@ using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Config; +using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Host; using Microsoft.Health.Fhir.Ingest.Service; @@ -38,13 +38,15 @@ public void ConfigureServices(IServiceCollection services) services.Configure(Configuration.GetSection("ResourceIdentity")); services.Configure(Configuration.GetSection("FhirClient")); - services.TryAddSingleton, FhirClientFactory>(); - services.TryAddSingleton(sp => sp.GetRequiredService>().Create()); - services.TryAddSingleton, Observation>, R4FhirLookupTemplateProcessor>(); + services.AddFhirClient(Configuration); services.TryAddSingleton(ResolveResourceIdentityService); + services.TryAddSingleton(); + + services.TryAddSingleton, Observation>, R4FhirLookupTemplateProcessor>(); services.TryAddSingleton(sp => new MemoryCache(Options.Create(new MemoryCacheOptions { SizeLimit = 5000 }))); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(ResolveMeasurementImportProvider); @@ -65,9 +67,9 @@ private static IResourceIdentityService ResolveResourceIdentityService(IServiceP { EnsureArg.IsNotNull(serviceProvider, nameof(serviceProvider)); - var fhirClient = serviceProvider.GetRequiredService(); + var fhirService = serviceProvider.GetRequiredService(); var resourceIdentityOptions = serviceProvider.GetRequiredService>(); - return ResourceIdentityServiceFactory.Instance.Create(resourceIdentityOptions.Value, fhirClient); + return ResourceIdentityServiceFactory.Instance.Create(resourceIdentityOptions.Value, fhirService); } } } diff --git a/src/console/Microsoft.Health.Fhir.Ingest.Console.csproj b/src/console/Microsoft.Health.Fhir.Ingest.Console.csproj index 07d3917a..82dc6079 100644 --- a/src/console/Microsoft.Health.Fhir.Ingest.Console.csproj +++ b/src/console/Microsoft.Health.Fhir.Ingest.Console.csproj @@ -20,6 +20,7 @@ + diff --git a/src/func/Microsoft.Health.Fhir.Ingest.Host/Microsoft.Health.Fhir.Ingest.Host.csproj b/src/func/Microsoft.Health.Fhir.Ingest.Host/Microsoft.Health.Fhir.Ingest.Host.csproj index e93f9985..c7231c63 100644 --- a/src/func/Microsoft.Health.Fhir.Ingest.Host/Microsoft.Health.Fhir.Ingest.Host.csproj +++ b/src/func/Microsoft.Health.Fhir.Ingest.Host/Microsoft.Health.Fhir.Ingest.Host.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/BearerTokenAuthorizationMessageHandler.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BearerTokenAuthorizationMessageHandler.cs new file mode 100644 index 00000000..573b19d4 --- /dev/null +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BearerTokenAuthorizationMessageHandler.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using EnsureThat; +using Microsoft.Health.Common.Telemetry; +using Microsoft.Health.Extensions.Fhir.Telemetry.Metrics; +using Microsoft.Health.Logging.Telemetry; + +namespace Microsoft.Health.Extensions.Fhir +{ + public class BearerTokenAuthorizationMessageHandler : DelegatingHandler + { + public BearerTokenAuthorizationMessageHandler(Uri uri, TokenCredential tokenCredentialProvider, ITelemetryLogger logger) + { + TokenCredential = EnsureArg.IsNotNull(tokenCredentialProvider, nameof(tokenCredentialProvider)); + Uri = EnsureArg.IsNotNull(uri, nameof(uri)); + Logger = EnsureArg.IsNotNull(logger, nameof(logger)); + Scopes = new string[] { Uri.ToString().EndsWith(@"/") ? Uri + ".default" : Uri + "/.default" }; + } + + private ITelemetryLogger Logger { get; } + + private TokenCredential TokenCredential { get; } + + private Uri Uri { get; } + + private string[] Scopes { get; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var requestContext = new TokenRequestContext(Scopes); + var accessToken = await TokenCredential.GetTokenAsync(requestContext, cancellationToken); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token); + var response = await base.SendAsync(request, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var statusDescription = response.ReasonPhrase.Replace(" ", string.Empty); + var severity = response.StatusCode == System.Net.HttpStatusCode.TooManyRequests ? ErrorSeverity.Informational : ErrorSeverity.Critical; + Logger.LogMetric(FhirClientMetrics.HandledException($"{ErrorType.FHIRServiceError}{statusDescription}", severity), 1); + } + + return response; + } + } +} diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs index d41778bf..143f05d4 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using EnsureThat; using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; +using Microsoft.Health.Extensions.Fhir.Service; namespace Microsoft.Health.Extensions.Fhir { @@ -41,7 +41,10 @@ public static IEnumerable ReadFromBundle(this Bundle bundl } } - public static async Task ReadOneFromBundleWithContinuationAsync(this Bundle bundle, FhirClient fhirClient, bool throwOnMultipleFound = true) + public static async Task ReadOneFromBundleWithContinuationAsync( + this Bundle bundle, + IFhirService fhirService, + bool throwOnMultipleFound = true) where TResource : Resource, new() { if (bundle == null) @@ -49,7 +52,7 @@ public static async Task ReadOneFromBundleWithContinuationAsync(fhirClient, 2); + var resources = await bundle?.ReadFromBundleWithContinuationAsync(fhirService, 2); var resourceCount = resources.Count(); if (resourceCount == 0) @@ -75,13 +78,17 @@ public static int EntryCount(this Bundle bundle) return bundle?.Entry?.Count ?? 0; } - public static async Task> ReadFromBundleWithContinuationAsync(this Bundle bundle, FhirClient fhirClient, int? count = null) + private static async Task> ReadFromBundleWithContinuationAsync( + this Bundle bundle, + IFhirService fhirService, + int? count = null) where TResource : Resource { - EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); + EnsureArg.IsNotNull(fhirService, nameof(fhirService)); var resources = new List(); - while (bundle != null) + + Action storeResources = (bundle) => { foreach (var r in bundle.ReadFromBundle(count)) { @@ -96,30 +103,16 @@ public static async Task> ReadFromBundleWithContinuationA count--; } } + }; + + storeResources.Invoke(bundle); - bundle = await fhirClient.ContinueAsync(bundle).ConfigureAwait(false); + await foreach (var currentBundle in fhirService.IterateOverAdditionalBundlesAsync(bundle)) + { + storeResources.Invoke(currentBundle); } return resources; } - - public static async Task> SearchWithContinuationAsync(this FhirClient fhirClient, SearchParams searchParams) - where TResource : Resource - { - EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); - EnsureArg.IsNotNull(searchParams, nameof(searchParams)); - - var result = await fhirClient.SearchAsync(searchParams).ConfigureAwait(false); - return await result.ReadFromBundleWithContinuationAsync(fhirClient, searchParams.Count).ConfigureAwait(false); - } - - public static async Task> GetWithContinuationAsync(this FhirClient fhirClient, Uri resourceUri, int? count = null) - where TResource : Resource - { - EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); - - var result = await fhirClient.GetAsync(resourceUri).ConfigureAwait(false) as Bundle; - return await result.ReadFromBundleWithContinuationAsync(fhirClient, count).ConfigureAwait(false); - } } } diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs deleted file mode 100644 index f5c4772a..00000000 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs +++ /dev/null @@ -1,139 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------------------------------------------- - -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core; -using EnsureThat; -using Hl7.Fhir.Rest; -using Microsoft.Extensions.Options; -using Microsoft.Health.Common; -using Microsoft.Health.Common.Auth; -using Microsoft.Health.Common.Telemetry; -using Microsoft.Health.Extensions.Fhir.Config; -using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions; -using Microsoft.Health.Extensions.Fhir.Telemetry.Metrics; -using Microsoft.Health.Extensions.Host.Auth; -using Microsoft.Health.Logging.Telemetry; - -namespace Microsoft.Health.Extensions.Fhir -{ - public class FhirClientFactory : IFactory - { - private readonly bool _useManagedIdentity = false; - private readonly IAzureCredentialProvider _tokenCredentialProvider; - private readonly ITelemetryLogger _logger; - - public FhirClientFactory(IOptions options, ITelemetryLogger logger) - : this(EnsureArg.IsNotNull(options, nameof(options)).Value.UseManagedIdentity, logger) - { - } - - private FhirClientFactory() - : this(useManagedIdentity: false, logger: null) - { - } - - private FhirClientFactory(bool useManagedIdentity, ITelemetryLogger logger) - { - _useManagedIdentity = useManagedIdentity; - _logger = logger; - } - - public FhirClientFactory(IAzureCredentialProvider provider, ITelemetryLogger logger) - { - _tokenCredentialProvider = provider; - _logger = logger; - } - - public static IFactory Instance { get; } = new FhirClientFactory(); - - public FhirClient Create() - { - if (_tokenCredentialProvider != null) - { - return CreateClient(_tokenCredentialProvider.GetCredential(), _logger); - } - - return _useManagedIdentity ? CreateManagedIdentityClient(_logger) : CreateConfidentialApplicationClient(_logger); - } - - private static FhirClient CreateClient(TokenCredential tokenCredential, ITelemetryLogger logger) - { - EnsureArg.IsNotNull(tokenCredential, nameof(tokenCredential)); - - var url = Environment.GetEnvironmentVariable("FhirService:Url"); - EnsureArg.IsNotNullOrEmpty(url, nameof(url)); - var uri = new Uri(url); - - var fhirClientSettings = new FhirClientSettings - { - PreferredFormat = ResourceFormat.Json, - }; - - FhirClient client = null; - try - { - client = new FhirClient(url, fhirClientSettings, new BearerTokenAuthorizationMessageHandler(uri, tokenCredential, logger)); - FhirServiceValidator.ValidateFhirService(client, logger); - } - catch (Exception ex) - { - FhirServiceExceptionProcessor.ProcessException(ex, logger); - } - - return client; - } - - private static FhirClient CreateManagedIdentityClient(ITelemetryLogger logger) - { - return CreateClient(new ManagedIdentityAuthService(), logger); - } - - private static FhirClient CreateConfidentialApplicationClient(ITelemetryLogger logger) - { - return CreateClient(new OAuthConfidentialClientAuthService(), logger); - } - - private class BearerTokenAuthorizationMessageHandler : HttpClientHandler - { - public BearerTokenAuthorizationMessageHandler(Uri uri, TokenCredential tokenCredentialProvider, ITelemetryLogger logger) - { - TokenCredential = EnsureArg.IsNotNull(tokenCredentialProvider, nameof(tokenCredentialProvider)); - Uri = EnsureArg.IsNotNull(uri); - Scopes = new string[] { Uri + ".default" }; - Logger = logger; - } - - private ITelemetryLogger Logger { get; } - - private TokenCredential TokenCredential { get; } - - private Uri Uri { get; } - - private string[] Scopes { get; } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var requestContext = new TokenRequestContext(Scopes); - var accessToken = await TokenCredential.GetTokenAsync(requestContext, CancellationToken.None); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token); - var response = await base.SendAsync(request, cancellationToken); - - if (Logger != null && !response.IsSuccessStatusCode) - { - var statusDescription = response.ReasonPhrase.Replace(" ", string.Empty); - var severity = response.StatusCode == System.Net.HttpStatusCode.TooManyRequests ? ErrorSeverity.Informational : ErrorSeverity.Critical; - Logger.LogMetric(FhirClientMetrics.HandledException($"{ErrorType.FHIRServiceError}{statusDescription}", severity), 1); - } - - return response; - } - } - } -} diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientValidator.cs similarity index 67% rename from src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs rename to src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientValidator.cs index 09acfa7b..3461d967 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientValidator.cs @@ -4,22 +4,26 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Logging.Telemetry; namespace Microsoft.Health.Extensions.Fhir { - public static class FhirServiceValidator + public static class FhirClientValidator { - public static bool ValidateFhirService(FhirClient client, ITelemetryLogger logger) + public static async Task ValidateFhirClientAsync( + this IFhirClient client, + ITelemetryLogger logger) { EnsureArg.IsNotNull(client, nameof(client)); + EnsureArg.IsNotNull(logger, nameof(logger)); try { - client.CapabilityStatement(SummaryType.True); + await client.ReadAsync("metadata?_summary=true").ConfigureAwait(false); return true; } catch (Exception exception) diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/HttpClientBuilderRegistrationExtensions.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/HttpClientBuilderRegistrationExtensions.cs new file mode 100644 index 00000000..a334f275 --- /dev/null +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/HttpClientBuilderRegistrationExtensions.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using EnsureThat; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Health.Extensions.Host.Auth; +using Microsoft.Health.Logging.Telemetry; + +namespace Microsoft.Health.Extensions.Fhir +{ + public static class HttpClientBuilderRegistrationExtensions + { + public static void AddAuthenticationHandler( + this IHttpClientBuilder httpClientBuilder, + IServiceCollection services, + ITelemetryLogger logger, + Uri uri, + bool useManagedIdentity) + { + EnsureArg.IsNotNull(httpClientBuilder, nameof(httpClientBuilder)); + EnsureArg.IsNotNull(services, nameof(services)); + EnsureArg.IsNotNull(logger, nameof(logger)); + EnsureArg.IsNotNull(uri, nameof(uri)); + + if (useManagedIdentity) + { + services.AddNamedManagedIdentityCredentialProvider(); + httpClientBuilder.AddHttpMessageHandler(x => + new BearerTokenAuthorizationMessageHandler(uri, new ManagedIdentityAuthService(), logger)); + } + else + { + services.AddNamedOAuth2ClientCredentialProvider(); + httpClientBuilder.AddHttpMessageHandler(x => + new BearerTokenAuthorizationMessageHandler(uri, new OAuthConfidentialClientAuthService(), logger)); + } + } + } +} diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Microsoft.Health.Extensions.Fhir.R4.csproj b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Microsoft.Health.Extensions.Fhir.R4.csproj index 0b0a51dd..1196f8ee 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Microsoft.Health.Extensions.Fhir.R4.csproj +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Microsoft.Health.Extensions.Fhir.R4.csproj @@ -1,39 +1,44 @@  - - net6.0 - ..\..\..\CustomAnalysisRules.ruleset - true - 7.3 - - - true - - - true - - Microsoft.Health.Extensions.Fhir - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + net6.0 + ..\..\..\CustomAnalysisRules.ruleset + true + 10.0 + + + true + + + true + + Microsoft.Health.Extensions.Fhir + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Search/SearchExtensions.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Search/SearchExtensions.cs index a4e9a742..14ba7701 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Search/SearchExtensions.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Search/SearchExtensions.cs @@ -190,5 +190,12 @@ public static string ToSearchToken(this Hl7.Fhir.Model.Identifier identifier) token += identifier.Value; return token; } + + public static string ToSearchQueryParameter(this Hl7.Fhir.Model.Identifier identifier) + { + EnsureArg.IsNotNull(identifier, nameof(identifier)); + + return $"identifier={identifier.ToSearchToken()}"; + } } } diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/FhirService.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/FhirService.cs new file mode 100644 index 00000000..41d9e110 --- /dev/null +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/FhirService.cs @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using EnsureThat; +using Hl7.Fhir.Model; +using IFhirClient = Microsoft.Health.Fhir.Client.IFhirClient; + +namespace Microsoft.Health.Extensions.Fhir.Service +{ + public class FhirService : IFhirService + { + private readonly IFhirClient _fhirClient; + + public FhirService(IFhirClient fhirClient) + { + _fhirClient = EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); + } + + public async Task CreateResourceAsync( + T resource, + string conditionalCreateCriteria = null, + string provenanceHeader = null, + CancellationToken cancellationToken = default) + where T : Resource + { + EnsureArg.IsNotNull(resource, nameof(resource)); + + return await _fhirClient.CreateAsync(resource, conditionalCreateCriteria, provenanceHeader, cancellationToken).ConfigureAwait(false); + } + + public async Task SearchForResourceAsync( + ResourceType resourceType, + string query = null, + int? count = null, + CancellationToken cancellationToken = default) + { + EnsureArg.IsNotNull(resourceType, nameof(resourceType)); + + return await _fhirClient.SearchAsync(resourceType, query, count, cancellationToken).ConfigureAwait(false); + } + + public async Task ReadResourceAsync( + ResourceType resourceType, + string resourceId, + CancellationToken cancellationToken = default) + where T : Resource + { + EnsureArg.IsNotNull(resourceType, nameof(resourceType)); + EnsureArg.IsNotNullOrWhiteSpace(resourceId, nameof(resourceId)); + + return await _fhirClient.ReadAsync(resourceType, resourceId, cancellationToken).ConfigureAwait(false); + } + + public async Task ReadResourceAsync( + string uri, + CancellationToken cancellationToken = default) + where T : Resource + { + EnsureArg.IsNotNullOrWhiteSpace(uri, nameof(uri)); + + return await _fhirClient.ReadAsync(uri, cancellationToken).ConfigureAwait(false); + } + + public async Task UpdateResourceAsync( + T resource, + string ifMatchVersion = null, + string provenanceHeader = null, + CancellationToken cancellationToken = default) + where T : Resource + { + EnsureArg.IsNotNull(resource, nameof(resource)); + + return await _fhirClient.UpdateAsync(resource, ifMatchVersion, provenanceHeader, cancellationToken).ConfigureAwait(false); + } + + public async IAsyncEnumerable IterateOverAdditionalBundlesAsync( + Bundle bundle, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + Bundle nextBundle = bundle; + while (nextBundle?.NextLink != null) + { + nextBundle = await _fhirClient.SearchAsync(bundle.NextLink.ToString(), cancellationToken).ConfigureAwait(false); + if (nextBundle != null) + { + yield return nextBundle; + } + } + } + } +} diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/IFhirService.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/IFhirService.cs new file mode 100644 index 00000000..70a78c76 --- /dev/null +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/IFhirService.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Hl7.Fhir.Model; + +namespace Microsoft.Health.Extensions.Fhir.Service +{ + public interface IFhirService + { + Task CreateResourceAsync(T resource, string conditionalCreateCriteria = null, string provenanceHeader = null, CancellationToken cancellationToken = default(CancellationToken)) + where T : Resource; + + Task SearchForResourceAsync(ResourceType resourceType, string query = null, int? count = null, CancellationToken cancellationToken = default(CancellationToken)); + + Task ReadResourceAsync(ResourceType resourceType, string resourceId, CancellationToken cancellationToken = default(CancellationToken)) + where T : Resource; + + Task ReadResourceAsync(string uri, CancellationToken cancellationToken = default(CancellationToken)) + where T : Resource; + + Task UpdateResourceAsync(T resource, string ifMatchVersion = null, string provenanceHeader = null, CancellationToken cancellationToken = default) + where T : Resource; + + /// + /// Produces an iterator over additional Bundles associated with the passed Bundle. The original Bundle is not returned. The + /// iterator completes when there are no more pages in the Bundle + /// + /// The Bundle to begin iterating over + /// The cancellation token + /// A collection of Bundles associated with the supplied Bundle + IAsyncEnumerable IterateOverAdditionalBundlesAsync(Bundle bundle, CancellationToken cancellationToken = default(CancellationToken)); + } +} \ No newline at end of file diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs index 33089a6f..0c0420c6 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs @@ -6,7 +6,7 @@ using System; using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; +using Hl7.Fhir.Model; using Microsoft.Health.Extensions.Fhir.Search; using Model = Hl7.Fhir.Model; @@ -14,56 +14,62 @@ namespace Microsoft.Health.Extensions.Fhir.Service { public class ResourceManagementService { + public ResourceManagementService(IFhirService fhirService) + { + FhirService = EnsureArg.IsNotNull(fhirService, nameof(fhirService)); + } + + public IFhirService FhirService { get; private set; } + /// /// Gets or creates the FHIR Resource with the provided identifier. /// /// The type of FHIR resource to ensure exists. - /// Client to use for FHIR rest calls. /// The identifier value to search for or create. /// The system the identifier belongs to. /// Optional setter to provide property values if the resource needs to be created. /// Reource that was found or created. - public virtual async Task EnsureResourceByIdentityAsync(FhirClient client, string value, string system, Action propertySetter = null) + public virtual async Task EnsureResourceByIdentityAsync(string value, string system, Action propertySetter = null) where TResource : Model.Resource, new() { - EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNullOrWhiteSpace(value, nameof(value)); var identifier = BuildIdentifier(value, system); - return await GetResourceByIdentityAsync(client, identifier).ConfigureAwait(false) - ?? await CreateResourceByIdentityAsync(client, identifier, propertySetter).ConfigureAwait(false); + return await GetResourceByIdentityAsync(identifier).ConfigureAwait(false) + ?? await CreateResourceByIdentityAsync(identifier, propertySetter).ConfigureAwait(false); } - public virtual async Task GetResourceByIdentityAsync(FhirClient client, string value, string system) + public virtual async Task GetResourceByIdentityAsync(string value, string system) where TResource : Model.Resource, new() { - EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNullOrWhiteSpace(value, nameof(value)); var identifier = BuildIdentifier(value, system); - return await GetResourceByIdentityAsync(client, identifier).ConfigureAwait(false); + return await GetResourceByIdentityAsync(identifier).ConfigureAwait(false); } - protected static async Task GetResourceByIdentityAsync(FhirClient client, Model.Identifier identifier) + protected async Task GetResourceByIdentityAsync(Model.Identifier identifier) where TResource : Model.Resource, new() { - EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNull(identifier, nameof(identifier)); - var searchParams = identifier.ToSearchParams(); - var result = await client.SearchAsync(searchParams).ConfigureAwait(false); - return await result.ReadOneFromBundleWithContinuationAsync(client); + + string fhirTypeName = ModelInfo.GetFhirTypeNameForType(typeof(TResource)); + + _ = Enum.TryParse(fhirTypeName, out ResourceType resourceType); + + Model.Bundle result = await FhirService.SearchForResourceAsync(resourceType, identifier.ToSearchQueryParameter()).ConfigureAwait(false); + return await result.ReadOneFromBundleWithContinuationAsync(FhirService); } - protected static async Task CreateResourceByIdentityAsync(FhirClient client, Model.Identifier identifier, Action propertySetter) + protected async Task CreateResourceByIdentityAsync(Model.Identifier identifier, Action propertySetter) where TResource : Model.Resource, new() { - EnsureArg.IsNotNull(client, nameof(client)); EnsureArg.IsNotNull(identifier, nameof(identifier)); var resource = new TResource(); propertySetter?.Invoke(resource, identifier); - return await client.CreateAsync(resource).ConfigureAwait(false); + return await FhirService.CreateResourceAsync(resource).ConfigureAwait(false); } private static Model.Identifier BuildIdentifier(string value, string system) diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/ServiceCollectionExtensions.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..0fdc7860 --- /dev/null +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/ServiceCollectionExtensions.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using EnsureThat; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Health.Extensions.Host.Auth; +using Microsoft.Health.Logging.Telemetry; +using FhirClient = Microsoft.Health.Fhir.Client.FhirClient; +using IFhirClient = Microsoft.Health.Fhir.Client.IFhirClient; + +namespace Microsoft.Health.Extensions.Fhir +{ + public static class ServiceCollectionExtensions + { + public static void AddFhirClient(this IServiceCollection serviceCollection, IConfiguration configuration) + { + EnsureArg.IsNotNull(serviceCollection, nameof(serviceCollection)); + EnsureArg.IsNotNull(configuration, nameof(configuration)); + + var url = new Uri(configuration.GetValue("FhirService:Url")); + bool useManagedIdentity = configuration.GetValue("FhirClient:UseManagedIdentity"); + + serviceCollection.AddSingleton(typeof(ITelemetryLogger), typeof(IomtTelemetryLogger)); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var logger = serviceProvider.GetRequiredService(); + + serviceCollection.AddHttpClient(client => + { + client.BaseAddress = url; + client.Timeout = TimeSpan.FromSeconds(60); + + // Using discard because we don't need result + var fhirClient = new FhirClient(client); + _ = fhirClient.ValidateFhirClientAsync(logger); + return fhirClient; + }) + .AddAuthenticationHandler(serviceCollection, logger, url, useManagedIdentity); + } + + public static void AddNamedManagedIdentityCredentialProvider(this IServiceCollection serviceCollection) + { + EnsureArg.IsNotNull(serviceCollection, nameof(serviceCollection)); + + serviceCollection.TryAddSingleton(); + } + + public static void AddNamedOAuth2ClientCredentialProvider(this IServiceCollection serviceCollection) + { + EnsureArg.IsNotNull(serviceCollection, nameof(serviceCollection)); + + serviceCollection.TryAddSingleton(); + } + } +} diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs index 020b577a..d0e0df0d 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs @@ -7,10 +7,10 @@ using System.Net; using System.Net.Http; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Health.Common.Telemetry; using Microsoft.Health.Extensions.Fhir.Resources; using Microsoft.Health.Extensions.Fhir.Telemetry.Metrics; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Logging.Telemetry; using Microsoft.Identity.Client; @@ -41,8 +41,8 @@ public static (Exception customException, string errorName) CustomizeException(E switch (exception) { - case FhirOperationException _: - var status = ((FhirOperationException)exception).Status; + case FhirException _: + var status = ((FhirException)exception).StatusCode; switch (status) { case HttpStatusCode.Forbidden: @@ -75,7 +75,7 @@ public static (Exception customException, string errorName) CustomizeException(E return (new InvalidFhirServiceException(message, exception, errorName), errorName); case HttpRequestException _: - // TODO: In .NET 5 and later, check HttpRequestException's StatusCode property instead of the Message property + if (exception.Message.Contains(FhirResources.HttpRequestErrorNotKnown, StringComparison.CurrentCultureIgnoreCase)) { message = FhirResources.FhirServiceHttpRequestError; @@ -83,7 +83,8 @@ public static (Exception customException, string errorName) CustomizeException(E return (new InvalidFhirServiceException(message, exception, errorName), errorName); } - return (exception, nameof(FhirServiceErrorCode.HttpRequestError)); + var statusCode = ((HttpRequestException)exception).StatusCode; + return (exception, $"{FhirServiceErrorCode.HttpRequestError}{statusCode}"); case MsalServiceException _: var errorCode = ((MsalServiceException)exception).ErrorCode; diff --git a/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj b/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj index b490fa34..bc0fa4d5 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj +++ b/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj @@ -16,7 +16,9 @@ - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -29,6 +31,7 @@ + diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Validation/Microsoft.Health.Fhir.Ingest.Validation.csproj b/src/lib/Microsoft.Health.Fhir.Ingest.Validation/Microsoft.Health.Fhir.Ingest.Validation.csproj index 37815313..932efa93 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Validation/Microsoft.Health.Fhir.Ingest.Validation.csproj +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Validation/Microsoft.Health.Fhir.Ingest.Validation.csproj @@ -19,8 +19,8 @@ true - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +32,7 @@ - + diff --git a/src/lib/Microsoft.Health.Fhir.Ingest/Microsoft.Health.Fhir.Ingest.csproj b/src/lib/Microsoft.Health.Fhir.Ingest/Microsoft.Health.Fhir.Ingest.csproj index 6a1b4699..4d3be2bc 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest/Microsoft.Health.Fhir.Ingest.csproj +++ b/src/lib/Microsoft.Health.Fhir.Ingest/Microsoft.Health.Fhir.Ingest.csproj @@ -5,7 +5,7 @@ true Microsoft.Health.Fhir.Ingest Microsoft.Health.Fhir.Ingest - 7.3 + 10.0 true @@ -18,14 +18,14 @@ true - + - - + + all diff --git a/src/lib/Microsoft.Health.Fhir.Ingest/Service/ResourceIdentityServiceFactory.cs b/src/lib/Microsoft.Health.Fhir.Ingest/Service/ResourceIdentityServiceFactory.cs index 7464355d..51e4e05b 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest/Service/ResourceIdentityServiceFactory.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest/Service/ResourceIdentityServiceFactory.cs @@ -88,4 +88,4 @@ private static IDictionary GetResourceIdentityServiceRegistry() return serviceTypeRegistry; } } -} +} \ No newline at end of file diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/FhirHealthCheckExtensions.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/FhirHealthCheckExtensions.cs index 1a84fe56..5e54ed4b 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/FhirHealthCheckExtensions.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/FhirHealthCheckExtensions.cs @@ -4,14 +4,13 @@ // ------------------------------------------------------------------------------------------------- using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Config; +using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Service; @@ -33,8 +32,12 @@ internal static IWebJobsBuilder AddFhirHealthCheck(this IWebJobsBuilder builder) builder.Services.Configure(config.GetSection("FhirClient")); // Register services - builder.Services.TryAddSingleton, FhirClientFactory>(); - builder.Services.TryAddSingleton(sp => sp.GetRequiredService>().Create()); + builder.Services.AddFhirClient(config); + + builder.Services.TryAddSingleton(); + + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.AddExtension(); diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/MeasurementFhirImportExtensions.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/MeasurementFhirImportExtensions.cs index 5a8b33c2..ddd76f62 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/MeasurementFhirImportExtensions.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Host/MeasurementFhirImportExtensions.cs @@ -6,16 +6,15 @@ using System; using EnsureThat; using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Config; +using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Service; using Microsoft.Health.Fhir.Ingest.Template; @@ -37,10 +36,13 @@ public static IWebJobsBuilder AddMeasurementFhirImport(this IWebJobsBuilder buil builder.Services.Configure(config.GetSection("ResourceIdentity")); builder.Services.Configure(config.GetSection("FhirClient")); - builder.Services.TryAddSingleton, FhirClientFactory>(); - builder.Services.TryAddSingleton(sp => sp.GetRequiredService>().Create()); - builder.Services.TryAddSingleton, Observation>, R4FhirLookupTemplateProcessor>(); + builder.Services.AddFhirClient(config); + + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(ResolveResourceIdentityService); + builder.Services.TryAddSingleton(); + + builder.Services.TryAddSingleton, Observation>, R4FhirLookupTemplateProcessor>(); builder.Services.TryAddSingleton(sp => new MemoryCache(Options.Create(new MemoryCacheOptions { SizeLimit = 5000 }))); builder.Services.TryAddSingleton(); @@ -55,9 +57,9 @@ private static IResourceIdentityService ResolveResourceIdentityService(IServiceP { EnsureArg.IsNotNull(serviceProvider, nameof(serviceProvider)); - var fhirClient = serviceProvider.GetRequiredService(); + var fhirService = serviceProvider.GetRequiredService(); var resourceIdentityOptions = serviceProvider.GetRequiredService>(); - return ResourceIdentityServiceFactory.Instance.Create(resourceIdentityOptions.Value, fhirClient); + return ResourceIdentityServiceFactory.Instance.Create(resourceIdentityOptions.Value, fhirService); } } } diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Microsoft.Health.Fhir.R4.Ingest.csproj b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Microsoft.Health.Fhir.R4.Ingest.csproj index 82dfbf08..b871c4b5 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Microsoft.Health.Fhir.R4.Ingest.csproj +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Microsoft.Health.Fhir.R4.Ingest.csproj @@ -26,15 +26,15 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientCreateIdentityService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientCreateIdentityService.cs index 2abf6eb3..e7c8c8cc 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientCreateIdentityService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientCreateIdentityService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; @@ -20,13 +19,13 @@ namespace Microsoft.Health.Fhir.Ingest.Service [ResourceIdentityService(nameof(R4DeviceAndPatientCreateIdentityService))] public class R4DeviceAndPatientCreateIdentityService : R4DeviceAndPatientLookupIdentityService { - public R4DeviceAndPatientCreateIdentityService(FhirClient fhirClient) - : base(fhirClient) + public R4DeviceAndPatientCreateIdentityService(IFhirService fhirService) + : base(fhirService) { } - public R4DeviceAndPatientCreateIdentityService(FhirClient fhirClient, ResourceManagementService resourceIdService) - : base(fhirClient, resourceIdService) + public R4DeviceAndPatientCreateIdentityService(IFhirService fhirService, ResourceManagementService resourceIdService) + : base(fhirService, resourceIdService) { } @@ -70,14 +69,12 @@ protected async override Task> ResolveResource // Begin critical section var patient = await ResourceManagementService.EnsureResourceByIdentityAsync( - FhirClient, input.PatientId, null, (p, id) => p.Identifier = new List { id }) .ConfigureAwait(false); var device = await ResourceManagementService.EnsureResourceByIdentityAsync( - FhirClient, GetDeviceIdentity(input), ResourceIdentityOptions?.DefaultDeviceIdentifierSystem, (d, id) => @@ -92,7 +89,7 @@ protected async override Task> ResolveResource if (device.Patient == null) { device.Patient = patient.ToReference(); - device = await FhirClient.UpdateAsync(device, true).ConfigureAwait(false); + device = await FhirService.UpdateResourceAsync(device).ConfigureAwait(false); } else if (device.Patient.GetId() != patient.Id) { @@ -105,4 +102,4 @@ protected async override Task> ResolveResource return (device.Id, patient.Id); } } -} +} \ No newline at end of file diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientLookupIdentityService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientLookupIdentityService.cs index b15a7b13..8e63fc91 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientLookupIdentityService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientLookupIdentityService.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; @@ -19,21 +18,21 @@ namespace Microsoft.Health.Fhir.Ingest.Service [ResourceIdentityService(nameof(R4DeviceAndPatientLookupIdentityService))] public class R4DeviceAndPatientLookupIdentityService : DeviceAndPatientLookupIdentityService { - private readonly FhirClient _fhirClient; + private readonly IFhirService _fhirService; private readonly ResourceManagementService _resourceManagementService; - public R4DeviceAndPatientLookupIdentityService(FhirClient fhirClient) - : this(fhirClient, new ResourceManagementService()) + public R4DeviceAndPatientLookupIdentityService(IFhirService fhirService) + : this(fhirService, new ResourceManagementService(fhirService)) { } - public R4DeviceAndPatientLookupIdentityService(FhirClient fhirClient, ResourceManagementService resourceManagementService) + public R4DeviceAndPatientLookupIdentityService(IFhirService fhirService, ResourceManagementService resourceManagementService) { - _fhirClient = EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); + _fhirService = EnsureArg.IsNotNull(fhirService, nameof(fhirService)); _resourceManagementService = EnsureArg.IsNotNull(resourceManagementService, nameof(resourceManagementService)); } - protected FhirClient FhirClient => _fhirClient; + protected IFhirService FhirService => _fhirService; protected ResourceManagementService ResourceManagementService => _resourceManagementService; @@ -46,8 +45,8 @@ protected static string GetPatientIdFromDevice(Model.Device device) protected async override Task<(string DeviceId, string PatientId)> LookUpDeviceAndPatientIdAsync(string value, string system = null) { - var device = await ResourceManagementService.GetResourceByIdentityAsync(FhirClient, value, system).ConfigureAwait(false) ?? throw new FhirResourceNotFoundException(ResourceType.Device); + var device = await ResourceManagementService.GetResourceByIdentityAsync(value, system).ConfigureAwait(false) ?? throw new FhirResourceNotFoundException(ResourceType.Device); return (device.Id, GetPatientIdFromDevice(device)); } } -} +} \ No newline at end of file diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientWithEncounterLookupIdentityService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientWithEncounterLookupIdentityService.cs index 463df5c4..6af601c5 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientWithEncounterLookupIdentityService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4DeviceAndPatientWithEncounterLookupIdentityService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Data; @@ -23,13 +22,13 @@ namespace Microsoft.Health.Fhir.Ingest.Service [ResourceIdentityService(nameof(R4DeviceAndPatientWithEncounterLookupIdentityService))] public class R4DeviceAndPatientWithEncounterLookupIdentityService : R4DeviceAndPatientLookupIdentityService { - public R4DeviceAndPatientWithEncounterLookupIdentityService(FhirClient fhirClient) - : base(fhirClient) + public R4DeviceAndPatientWithEncounterLookupIdentityService(IFhirService fhirService) + : base(fhirService) { } - public R4DeviceAndPatientWithEncounterLookupIdentityService(FhirClient fhirClient, ResourceManagementService resourceIdService) - : base(fhirClient, resourceIdService) + public R4DeviceAndPatientWithEncounterLookupIdentityService(IFhirService fhirService, ResourceManagementService resourceIdService) + : base(fhirService, resourceIdService) { } @@ -44,7 +43,7 @@ protected async override Task> ResolveResource throw new ResourceIdentityNotDefinedException(ResourceType.Encounter); } - var encounter = await ResourceManagementService.GetResourceByIdentityAsync(FhirClient, input.EncounterId, null).ConfigureAwait(false) ?? throw new FhirResourceNotFoundException(ResourceType.Encounter); + var encounter = await ResourceManagementService.GetResourceByIdentityAsync(input.EncounterId, null).ConfigureAwait(false) ?? throw new FhirResourceNotFoundException(ResourceType.Encounter); identities[ResourceType.Encounter] = encounter?.Id; return identities; diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirHealthService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirHealthService.cs index 3af94a11..86554810 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirHealthService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirHealthService.cs @@ -9,6 +9,8 @@ using EnsureThat; using Hl7.Fhir.Rest; using Microsoft.Health.Extensions.Fhir.Search; +using Microsoft.Health.Extensions.Fhir.Service; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Fhir.Ingest.Data; namespace Microsoft.Health.Fhir.Ingest.Service @@ -16,11 +18,11 @@ namespace Microsoft.Health.Fhir.Ingest.Service public class R4FhirHealthService : FhirHealthService { - private readonly FhirClient _client; + private readonly IFhirService _fhirService; - public R4FhirHealthService(FhirClient fhirClient) + public R4FhirHealthService(IFhirService fhirService) { - _client = EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); + _fhirService = EnsureArg.IsNotNull(fhirService, nameof(fhirService)); } public override async Task CheckHealth(CancellationToken token = default) @@ -30,16 +32,16 @@ public override async Task CheckHealth(CancellationToken while (!token.IsCancellationRequested) { SearchParams search = new SearchParams().SetCount(1); - Hl7.Fhir.Model.Bundle result = await _client.SearchAsync(search); + Hl7.Fhir.Model.Bundle result = await _fhirService.SearchForResourceAsync(Hl7.Fhir.Model.ResourceType.StructureDefinition, query: null, search.Count, token).ConfigureAwait(false); return await Task.FromResult(new FhirHealthCheckStatus(string.Empty, 200)); } token.ThrowIfCancellationRequested(); return await Task.FromResult(new FhirHealthCheckStatus(token.ToString(), 500)); } - catch (FhirOperationException ex) + catch (FhirException ex) { - return await Task.FromResult(new FhirHealthCheckStatus(ex.Message, (int)ex.Status)); + return await Task.FromResult(new FhirHealthCheckStatus(ex.Message, (int)ex.StatusCode)); } catch (IdentityModel.Clients.ActiveDirectory.AdalServiceException ex) { diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs index f3347259..2f972c61 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs @@ -7,11 +7,12 @@ using System.Collections.Generic; using System.Threading.Tasks; using EnsureThat; -using Hl7.Fhir.Rest; using Microsoft.Extensions.Caching.Memory; using Microsoft.Health.Extensions.Fhir; using Microsoft.Health.Extensions.Fhir.Search; +using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Fhir.Ingest.Data; using Microsoft.Health.Fhir.Ingest.Telemetry; using Microsoft.Health.Fhir.Ingest.Template; @@ -24,15 +25,20 @@ namespace Microsoft.Health.Fhir.Ingest.Service public class R4FhirImportService : FhirImportService { - private readonly FhirClient _client; + private readonly IFhirService _fhirService; private readonly IFhirTemplateProcessor, Model.Observation> _fhirTemplateProcessor; private readonly IMemoryCache _observationCache; private readonly ITelemetryLogger _logger; - public R4FhirImportService(IResourceIdentityService resourceIdentityService, FhirClient fhirClient, IFhirTemplateProcessor, Model.Observation> fhirTemplateProcessor, IMemoryCache observationCache, ITelemetryLogger logger) + public R4FhirImportService( + IResourceIdentityService resourceIdentityService, + IFhirService fhirService, + IFhirTemplateProcessor, Model.Observation> fhirTemplateProcessor, + IMemoryCache observationCache, + ITelemetryLogger logger) { _fhirTemplateProcessor = EnsureArg.IsNotNull(fhirTemplateProcessor, nameof(fhirTemplateProcessor)); - _client = EnsureArg.IsNotNull(fhirClient, nameof(fhirClient)); + _fhirService = EnsureArg.IsNotNull(fhirService, nameof(fhirService)); _observationCache = EnsureArg.IsNotNull(observationCache, nameof(observationCache)); _logger = EnsureArg.IsNotNull(logger, nameof(logger)); @@ -73,7 +79,7 @@ public virtual async Task SaveObservationAsync(ILookupTemplate - .Handle(ex => ex.Status == System.Net.HttpStatusCode.Conflict || ex.Status == System.Net.HttpStatusCode.PreconditionFailed) + .Handle(ex => ex.StatusCode == System.Net.HttpStatusCode.Conflict || ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed) .RetryAsync(2, async (polyRes, attempt) => { // 409 Conflict or 412 Precondition Failed can occur if the Observation.meta.versionId does not match the update request. @@ -101,7 +107,7 @@ public virtual async Task SaveObservationAsync(ILookupTemplate SaveObservationAsync(ILookupTemplate GetObservationFromServerAsync(Model.Identifier identifier) { - var searchParams = identifier.ToSearchParams(); - var result = await _client.SearchAsync(searchParams).ConfigureAwait(false); - return await result.ReadOneFromBundleWithContinuationAsync(_client); + var result = await _fhirService.SearchForResourceAsync(Model.ResourceType.Observation, identifier.ToSearchQueryParameter()).ConfigureAwait(false); + return await result.ReadOneFromBundleWithContinuationAsync(_fhirService); } } } \ No newline at end of file diff --git a/test/Microsoft.Health.Common.UnitTests/Microsoft.Health.Common.UnitTests.csproj b/test/Microsoft.Health.Common.UnitTests/Microsoft.Health.Common.UnitTests.csproj index 5650e8e5..c8dab4c3 100644 --- a/test/Microsoft.Health.Common.UnitTests/Microsoft.Health.Common.UnitTests.csproj +++ b/test/Microsoft.Health.Common.UnitTests/Microsoft.Health.Common.UnitTests.csproj @@ -1,40 +1,42 @@  - - net6.0 - ..\..\CustomAnalysisRules.Test.ruleset - true - Off - Microsoft.Health.Common.UnitTests - 7.3 - - - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + net6.0 + ..\..\CustomAnalysisRules.Test.ruleset + true + Off + Microsoft.Health.Common.UnitTests + 7.3 + + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.Health.Common.UnitTests/Utilities.cs b/test/Microsoft.Health.Common.UnitTests/Utilities.cs new file mode 100644 index 00000000..f598c3ad --- /dev/null +++ b/test/Microsoft.Health.Common.UnitTests/Utilities.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using Microsoft.Health.Extensions.Fhir.Service; +using NSubstitute; + +namespace Microsoft.Health.Common +{ + public static class Utilities + { + public static IFhirService CreateMockFhirService() + { + return Substitute.For(); + } + } +} diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs index 76769d4a..1c79f11e 100644 --- a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs +++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs @@ -7,9 +7,10 @@ using System.Collections.Generic; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using Microsoft.Health.Tests.Common; +using Microsoft.Health.Common; using NSubstitute; using Xunit; +using Task = System.Threading.Tasks.Task; namespace Microsoft.Health.Extensions.Fhir.R4.UnitTests { @@ -26,7 +27,7 @@ public async void GivenNoEntriesAndNoContinuationToken_ReadOneFromBundleWithCont Link = new List(), }; - var client = Utilities.CreateMockFhirClient(); + var client = Utilities.CreateMockFhirService(); Assert.Null(await bundle.ReadOneFromBundleWithContinuationAsync(client)); } @@ -50,10 +51,7 @@ public async void GivenOneEntryAndNoContinuationToken_ReadOneFromBundleWithConti }; bundle.Entry.Add(entry); - MockFhirResourceHttpMessageHandler messageHandler = Utilities.CreateMockMessageHandler(); - messageHandler.GetReturnContent(default).ReturnsForAnyArgs((Bundle)null); - - var client = Utilities.CreateMockFhirClient(messageHandler); + var client = Utilities.CreateMockFhirService(); var result = await bundle.ReadOneFromBundleWithContinuationAsync(client); @@ -88,9 +86,10 @@ public async void GivenOneEntryAfterContinuationToken_ReadOneFromBundleWithConti }; continuationBundle.Entry.Add(entry); - MockFhirResourceHttpMessageHandler messageHandler = Utilities.CreateMockMessageHandler(); - messageHandler.GetReturnContent(default).ReturnsForAnyArgs(continuationBundle, null); - var client = Utilities.CreateMockFhirClient(messageHandler); + var client = Utilities.CreateMockFhirService(); + client.IterateOverAdditionalBundlesAsync(Arg.Any()).Returns( + x => GetTestValues(continuationBundle), + x => null); var result = await bundle.ReadOneFromBundleWithContinuationAsync(client); @@ -121,7 +120,7 @@ public async void GivenTwoEntriesAndNoContinuationToken_ReadOneFromBundleWithCon }; bundle.Entry.Add(entry2); - var client = Utilities.CreateMockFhirClient(); + var client = Utilities.CreateMockFhirService(); await Assert.ThrowsAsync>(() => bundle.ReadOneFromBundleWithContinuationAsync(client)); } @@ -162,11 +161,19 @@ public async void GivenOneEntryBeforeAndAfterContinuationToken_ReadOneFromBundle }; continuationBundle.Entry.Add(entry2); - MockFhirResourceHttpMessageHandler messageHandler = Utilities.CreateMockMessageHandler(); - messageHandler.GetReturnContent(default).ReturnsForAnyArgs(continuationBundle, null); - var client = Utilities.CreateMockFhirClient(messageHandler); + var client = Utilities.CreateMockFhirService(); + client.IterateOverAdditionalBundlesAsync(Arg.Any()).Returns( + x => GetTestValues(continuationBundle), + x => null); await Assert.ThrowsAsync>(() => bundle.ReadOneFromBundleWithContinuationAsync(client)); } + + private static async IAsyncEnumerable GetTestValues(Bundle bundle) + { + yield return bundle; + + await Task.CompletedTask; + } } } diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceValidatorTests.cs b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirClientValidatorTests.cs similarity index 55% rename from test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceValidatorTests.cs rename to test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirClientValidatorTests.cs index a46e1261..16ee3aef 100644 --- a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceValidatorTests.cs +++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirClientValidatorTests.cs @@ -3,38 +3,40 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; +using System.Threading.Tasks; using Hl7.Fhir.Rest; using Microsoft.Health.Logging.Telemetry; using NSubstitute; using Xunit; +using FhirClient = Microsoft.Health.Fhir.Client.FhirClient; namespace Microsoft.Health.Extensions.Fhir.R4.UnitTests { - public class FhirServiceValidatorTests + public class FhirClientValidatorTests { [Theory] [InlineData("https://testfoobar.azurehealthcareapis.com")] [InlineData("https://microsoft.com")] - public void GivenInvalidFhirServiceUrl_WhenValidateFhirService_ThenNotValidReturned_Test(string url) + public async Task GivenInvalidFhirServiceUrl_WhenValidateFhirService_ThenNotValidReturned_Test(string url) { - ValidateFhirServiceUrl(url, false); + await ValidateFhirClientUrl(url, false); } - private void ValidateFhirServiceUrl(string url, bool expectedIsValid) + private async Task ValidateFhirClientUrl(string url, bool expectedIsValid) { var fhirClientSettings = new FhirClientSettings { PreferredFormat = ResourceFormat.Json, }; - using (var client = new FhirClient(url, fhirClientSettings)) - { - var logger = Substitute.For(); + var fhirClient = new FhirClient(new Uri(url), fhirClientSettings.PreferredFormat); + + var logger = Substitute.For(); - bool actualIsValid = FhirServiceValidator.ValidateFhirService(client, logger); + bool actualIsValid = await fhirClient.ValidateFhirClientAsync(logger); - Assert.Equal(expectedIsValid, actualIsValid); - } + Assert.Equal(expectedIsValid, actualIsValid); } } } diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs index 4906604f..00ba4efc 100644 --- a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs +++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs @@ -7,9 +7,10 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; -using Hl7.Fhir.Rest; +using Hl7.Fhir.Model; using Microsoft.Health.Common.Telemetry; using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Logging.Telemetry; using Microsoft.Identity.Client; using NSubstitute; @@ -19,14 +20,15 @@ namespace Microsoft.Health.Extensions.Fhir.R4.UnitTests { public class FhirServiceExceptionProcessorTests { - private static readonly Exception _fhirForbiddenEx = new FhirOperationException("test", HttpStatusCode.Forbidden); - private static readonly Exception _fhirNotFoundEx = new FhirOperationException("test", HttpStatusCode.NotFound); - private static readonly Exception _fhirBadRequestEx = new FhirOperationException("test", HttpStatusCode.BadRequest); + private static readonly Exception _fhirForbiddenEx = new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.Forbidden), new OperationOutcome())); + private static readonly Exception _fhirNotFoundEx = new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.NotFound), new OperationOutcome())); + private static readonly Exception _fhirBadRequestEx = new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.BadRequest), new OperationOutcome())); private static readonly Exception _argEndpointNullEx = new ArgumentNullException("endpoint"); private static readonly Exception _argEndpointEx = new ArgumentException("endpoint", "Endpoint must be absolute"); private static readonly Exception _argEx = new ArgumentException("test_message", "test_param"); private static readonly Exception _uriEx = new UriFormatException(); private static readonly Exception _httpNotKnownEx = new HttpRequestException("Name or service not known"); + private static readonly Exception _httpNotFoundEx = new HttpRequestException("test_message", new Exception(), HttpStatusCode.NotFound); private static readonly Exception _httpEx = new HttpRequestException(); private static readonly Exception _msalInvalidResourceEx = new MsalServiceException("invalid_resource", "test_message"); private static readonly Exception _msalInvalidScopeEx = new MsalServiceException("invalid_scope", "test_message"); @@ -43,6 +45,7 @@ public class FhirServiceExceptionProcessorTests new object[] { _argEndpointEx, "FHIRServiceErrorConfigurationError", nameof(ErrorSource.User) }, new object[] { _argEx, "FHIRServiceErrorArgumentErrortest_param" }, new object[] { _uriEx, "FHIRServiceErrorConfigurationError", nameof(ErrorSource.User) }, + new object[] { _httpNotFoundEx, "FHIRServiceErrorHttpRequestErrorNotFound" }, new object[] { _httpNotKnownEx, "FHIRServiceErrorConfigurationError", nameof(ErrorSource.User) }, new object[] { _httpEx, "FHIRServiceErrorHttpRequestError" }, new object[] { _msalInvalidResourceEx, "FHIRServiceErrorConfigurationError", nameof(ErrorSource.User) }, @@ -56,12 +59,13 @@ public class FhirServiceExceptionProcessorTests { new object[] { _fhirForbiddenEx, typeof(UnauthorizedAccessFhirServiceException) }, new object[] { _fhirNotFoundEx, typeof(InvalidFhirServiceException) }, - new object[] { _fhirBadRequestEx, typeof(FhirOperationException) }, + new object[] { _fhirBadRequestEx, typeof(FhirException) }, new object[] { _argEndpointNullEx, typeof(InvalidFhirServiceException) }, new object[] { _argEndpointEx, typeof(InvalidFhirServiceException) }, new object[] { _argEx, typeof(ArgumentException) }, new object[] { _uriEx, typeof(InvalidFhirServiceException) }, new object[] { _httpNotKnownEx, typeof(InvalidFhirServiceException) }, + new object[] { _httpNotFoundEx, typeof(HttpRequestException) }, new object[] { _httpEx, typeof(HttpRequestException) }, new object[] { _msalInvalidResourceEx, typeof(InvalidFhirServiceException) }, new object[] { _msalInvalidScopeEx, typeof(InvalidFhirServiceException) }, diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/Microsoft.Health.Extensions.Fhir.R4.UnitTests.csproj b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/Microsoft.Health.Extensions.Fhir.R4.UnitTests.csproj index 44591b7d..da62e9be 100644 --- a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/Microsoft.Health.Extensions.Fhir.R4.UnitTests.csproj +++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/Microsoft.Health.Extensions.Fhir.R4.UnitTests.csproj @@ -4,7 +4,7 @@ ..\..\CustomAnalysisRules.Test.ruleset true false - 7.3 + 10.0 true @@ -27,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -35,6 +35,6 @@ - + diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Service/EventProcessingMeterTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Service/EventProcessingMeterTests.cs index 78b72f8c..e26eacd7 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Service/EventProcessingMeterTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Service/EventProcessingMeterTests.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.EventHubs; diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Microsoft.Health.Fhir.R4.Ingest.UnitTests.csproj b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Microsoft.Health.Fhir.R4.Ingest.UnitTests.csproj index 275373d8..adc6e025 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Microsoft.Health.Fhir.R4.Ingest.UnitTests.csproj +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Microsoft.Health.Fhir.R4.Ingest.UnitTests.csproj @@ -38,7 +38,7 @@ - + diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientCreateIdentityServiceTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientCreateIdentityServiceTests.cs index 03c1c43b..19b496db 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientCreateIdentityServiceTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientCreateIdentityServiceTests.cs @@ -5,11 +5,10 @@ using System; using System.Threading.Tasks; -using Hl7.Fhir.Rest; +using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Data; -using Microsoft.Health.Tests.Common; using NSubstitute; using Xunit; using Model = Hl7.Fhir.Model; @@ -21,8 +20,8 @@ public class R4DeviceAndPatientCreateIdentityServiceTests [Fact] public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientIdReturnedAndCreateNotInvoked_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -32,7 +31,7 @@ public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -43,16 +42,16 @@ public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); } [Fact] public async void GivenValidDeviceIdentifierWithSystem_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientIdReturnedAndCreateNotInvokedWithSystemUsed_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -62,7 +61,7 @@ public async void GivenValidDeviceIdentifierWithSystem_WhenResolveResourceIdenti var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -74,16 +73,16 @@ public async void GivenValidDeviceIdentifierWithSystem_WhenResolveResourceIdenti Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", "mySystem"); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", "mySystem"); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); } [Fact] public async void GivenPatientNotFoundException_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientCreateInvokedAndIdsReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { @@ -96,14 +95,14 @@ public async void GivenPatientNotFoundException_WhenResolveResourceIdentitiesAsy Id = "123", }; - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); mg.PatientId.Returns("patientId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromException(new FhirResourceNotFoundException(ResourceType.Patient))); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -114,16 +113,16 @@ public async void GivenPatientNotFoundException_WhenResolveResourceIdentitiesAsy Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "deviceId", null, Arg.Any>()); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "patientId", null, Arg.Any>()); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.Received(1).EnsureResourceByIdentityAsync("deviceId", null, Arg.Any>()); + await resourceService.Received(1).EnsureResourceByIdentityAsync("patientId", null, Arg.Any>()); } [Fact] public async void GivenDeviceNotFoundException_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientCreateInvokedAndIdsReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { @@ -136,14 +135,14 @@ public async void GivenDeviceNotFoundException_WhenResolveResourceIdentitiesAsyn Id = "123", }; - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); mg.PatientId.Returns("patientId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromException(new FhirResourceNotFoundException(ResourceType.Device))); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -154,16 +153,16 @@ public async void GivenDeviceNotFoundException_WhenResolveResourceIdentitiesAsyn Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "deviceId", null, Arg.Any>()); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "patientId", null, Arg.Any>()); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.Received(1).EnsureResourceByIdentityAsync("deviceId", null, Arg.Any>()); + await resourceService.Received(1).EnsureResourceByIdentityAsync("patientId", null, Arg.Any>()); } [Fact] public async void GivenDeviceNotFoundExceptionWithDeviceSystemSet_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientCreateInvokedWithDeviceSystemAndIdsReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { @@ -176,14 +175,14 @@ public async void GivenDeviceNotFoundExceptionWithDeviceSystemSet_WhenResolveRes Id = "123", }; - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); mg.PatientId.Returns("patientId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromException(new FhirResourceNotFoundException(ResourceType.Device))); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -195,9 +194,9 @@ public async void GivenDeviceNotFoundExceptionWithDeviceSystemSet_WhenResolveRes Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", "mySystem"); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "deviceId", "mySystem", Arg.Any>()); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "patientId", null, Arg.Any>()); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", "mySystem"); + await resourceService.Received(1).EnsureResourceByIdentityAsync("deviceId", "mySystem", Arg.Any>()); + await resourceService.Received(1).EnsureResourceByIdentityAsync("patientId", null, Arg.Any>()); } [Theory] @@ -206,8 +205,8 @@ public async void GivenDeviceNotFoundExceptionWithDeviceSystemSet_WhenResolveRes [InlineData(" ")] public async void GivenIdNotFoundExceptionWithNoPatientId_WhenResolveResourceIdentitiesAsync_ThenResourceIdentityNotDefinedExceptionThrown_Test(string value) { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { @@ -220,15 +219,15 @@ public async void GivenIdNotFoundExceptionWithNoPatientId_WhenResolveResourceIde Id = "123", }; - var createService = Substitute.For(); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); + var createService = Substitute.For(fhirClient); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); mg.PatientId.Returns(value); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromException(new FhirResourceNotFoundException(ResourceType.Patient))); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -237,16 +236,16 @@ public async void GivenIdNotFoundExceptionWithNoPatientId_WhenResolveResourceIde Assert.Equal(ResourceType.Patient, ex.FhirResourceType); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); - await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null, null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); + await resourceService.DidNotReceiveWithAnyArgs().EnsureResourceByIdentityAsync(null, null, null); } [Fact] public async void GivenMismatchedDeviceAndPatientIdReference_WhenResolveResourceIdentitiesAsync_ThenPatientDeviceMismatchExceptionThrown_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { @@ -259,14 +258,14 @@ public async void GivenMismatchedDeviceAndPatientIdReference_WhenResolveResource Id = "123", }; - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); - resourceService.EnsureResourceByIdentityAsync(null, null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(device)); + resourceService.EnsureResourceByIdentityAsync(null, null, null).ReturnsForAnyArgs(Task.FromResult(patient)); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); mg.PatientId.Returns("patientId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromException(new FhirResourceNotFoundException(ResourceType.Patient))); using (var idSrv = new R4DeviceAndPatientCreateIdentityService(fhirClient, resourceService)) @@ -274,9 +273,9 @@ public async void GivenMismatchedDeviceAndPatientIdReference_WhenResolveResource await Assert.ThrowsAsync(() => idSrv.ResolveResourceIdentitiesAsync(mg)); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "deviceId", null, Arg.Any>()); - await resourceService.Received(1).EnsureResourceByIdentityAsync(fhirClient, "patientId", null, Arg.Any>()); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.Received(1).EnsureResourceByIdentityAsync("deviceId", null, Arg.Any>()); + await resourceService.Received(1).EnsureResourceByIdentityAsync("patientId", null, Arg.Any>()); } } } diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientLookupIdentityServiceTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientLookupIdentityServiceTests.cs index 05a48e5b..2b35bf04 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientLookupIdentityServiceTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientLookupIdentityServiceTests.cs @@ -4,11 +4,10 @@ // ------------------------------------------------------------------------------------------------- using System.Threading.Tasks; -using Hl7.Fhir.Rest; +using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Config; using Microsoft.Health.Fhir.Ingest.Data; -using Microsoft.Health.Tests.Common; using NSubstitute; using Xunit; using Model = Hl7.Fhir.Model; @@ -20,8 +19,8 @@ public class R4DeviceAndPatientLookupIdentityServiceTests [Fact] public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientIdReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -31,7 +30,7 @@ public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientLookupIdentityService(fhirClient, resourceService)) @@ -42,14 +41,14 @@ public async void GivenValidDeviceIdentifier_WhenResolveResourceIdentitiesAsync_ Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); } [Fact] public async void GivenValidDeviceIdentifierWhenDefaultSystemSet_WhenResolveResourceIdentitiesAsync_ThenDeviceAndPatientIdReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -64,7 +63,7 @@ public async void GivenValidDeviceIdentifierWhenDefaultSystemSet_WhenResolveReso DefaultDeviceIdentifierSystem = "mySystem", }; - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientLookupIdentityService(fhirClient, resourceService)) @@ -76,20 +75,20 @@ public async void GivenValidDeviceIdentifierWhenDefaultSystemSet_WhenResolveReso Assert.Equal("123", ids[ResourceType.Patient]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", "mySystem"); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", "mySystem"); } [Fact] public async void GivenDeviceWithNotPatientReference_WhenResolveResourceIdentitiesAsync_ThenFhirResourceNotFoundExceptionThrown_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); Model.Device device = new Model.Device(); var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientLookupIdentityService(fhirClient, resourceService)) @@ -98,14 +97,14 @@ public async void GivenDeviceWithNotPatientReference_WhenResolveResourceIdentiti Assert.Equal(ResourceType.Patient, ex.FhirResourceType); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); } [Fact] public async void GivenDeviceWithPatientReferenceUnsupportedCharacters_WhenResolveResourceIdentitiesAsync_ThenNotSupportedExceptionThrown_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -115,7 +114,7 @@ public async void GivenDeviceWithPatientReferenceUnsupportedCharacters_WhenResol var mg = Substitute.For(); mg.DeviceId.Returns("deviceId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientLookupIdentityService(fhirClient, resourceService)) @@ -124,7 +123,7 @@ public async void GivenDeviceWithPatientReferenceUnsupportedCharacters_WhenResol Assert.Equal(ResourceType.Patient, ex.FhirResourceType); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); } } } diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientWithEncounterLookupIdentityServiceTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientWithEncounterLookupIdentityServiceTests.cs index 6f1dc879..46b8e00e 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientWithEncounterLookupIdentityServiceTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4DeviceAndPatientWithEncounterLookupIdentityServiceTests.cs @@ -4,10 +4,9 @@ // ------------------------------------------------------------------------------------------------- using System.Threading.Tasks; -using Hl7.Fhir.Rest; +using Microsoft.Health.Common; using Microsoft.Health.Extensions.Fhir.Service; using Microsoft.Health.Fhir.Ingest.Data; -using Microsoft.Health.Tests.Common; using NSubstitute; using Xunit; using Model = Hl7.Fhir.Model; @@ -19,8 +18,8 @@ public class R4DeviceAndPatientWithEncounterLookupIdentityServiceTests [Fact] public async void GivenValidEncounterIdentifier_WhenResolveResourceIdentitiesAsync_ThenEncounterIdReturned_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -36,10 +35,10 @@ public async void GivenValidEncounterIdentifier_WhenResolveResourceIdentitiesAsy mg.DeviceId.Returns("deviceId"); mg.EncounterId.Returns("eId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(encounter)); using (var idSrv = new R4DeviceAndPatientWithEncounterLookupIdentityService(fhirClient, resourceService)) @@ -51,15 +50,15 @@ public async void GivenValidEncounterIdentifier_WhenResolveResourceIdentitiesAsy Assert.Equal("abc", ids[ResourceType.Encounter]); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "eId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("eId", null); } [Fact] public async void GivenInValidEncounterIdentifier_WhenResolveResourceIdentitiesAsync_ThenFhirResourceNotFoundExceptionThrown_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -70,10 +69,10 @@ public async void GivenInValidEncounterIdentifier_WhenResolveResourceIdentitiesA mg.DeviceId.Returns("deviceId"); mg.EncounterId.Returns("eId"); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult((Model.Encounter)null)); using (var idSrv = new R4DeviceAndPatientWithEncounterLookupIdentityService(fhirClient, resourceService)) @@ -82,15 +81,15 @@ public async void GivenInValidEncounterIdentifier_WhenResolveResourceIdentitiesA Assert.Equal(ResourceType.Encounter, ex.FhirResourceType); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "eId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.Received(1).GetResourceByIdentityAsync("eId", null); } [Fact] public async void GivenNoEncounterIdentifier_WhenResolveResourceIdentitiesAsync_ThenResourceIdentityNotDefinedExceptionThrown_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); - var resourceService = Substitute.For(); + var fhirClient = Utilities.CreateMockFhirService(); + var resourceService = Substitute.For(fhirClient); var device = new Model.Device { Id = "1", @@ -101,7 +100,7 @@ public async void GivenNoEncounterIdentifier_WhenResolveResourceIdentitiesAsync_ mg.DeviceId.Returns("deviceId"); mg.EncounterId.Returns((string)null); - resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any(), Arg.Any()) + resourceService.GetResourceByIdentityAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(device)); using (var idSrv = new R4DeviceAndPatientWithEncounterLookupIdentityService(fhirClient, resourceService)) @@ -110,8 +109,8 @@ public async void GivenNoEncounterIdentifier_WhenResolveResourceIdentitiesAsync_ Assert.Equal(ResourceType.Encounter, ex.FhirResourceType); } - await resourceService.Received(1).GetResourceByIdentityAsync(fhirClient, "deviceId", null); - await resourceService.DidNotReceiveWithAnyArgs().GetResourceByIdentityAsync(null, null, null); + await resourceService.Received(1).GetResourceByIdentityAsync("deviceId", null); + await resourceService.DidNotReceiveWithAnyArgs().GetResourceByIdentityAsync(null, null); } } } diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirHealthServiceTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirHealthServiceTests.cs index 07d46e40..2d36c2cd 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirHealthServiceTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirHealthServiceTests.cs @@ -4,14 +4,15 @@ // ------------------------------------------------------------------------------------------------- using System; -using System.Net; -using Hl7.Fhir.Rest; -using Microsoft.Health.Tests.Common; +using System.Net; +using System.Net.Http; +using Hl7.Fhir.Model; +using Microsoft.Health.Common; +using Microsoft.Health.Fhir.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; -using Xunit; -using Model = Hl7.Fhir.Model; - +using Xunit; + namespace Microsoft.Health.Fhir.Ingest.Service { public class R4FhirHealthServiceTests @@ -19,9 +20,8 @@ public class R4FhirHealthServiceTests [Fact] public async void GivenValidFhirClientConfig_WhenCheckHealthAsync_ThenRespondWithSuccess_Test() { - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(default).ReturnsForAnyArgs(new Model.Bundle())); - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any(), Arg.Any(), default).ReturnsForAnyArgs(new FhirResponse(new HttpResponseMessage(HttpStatusCode.OK), null)); var service = new R4FhirHealthService(fhirClient); var response = await service.CheckHealth(); @@ -33,23 +33,21 @@ public async void GivenValidFhirClientConfig_WhenCheckHealthAsync_ThenRespondWit [Fact] public async void GivenInvalidOAuthToken_WhenCheckHealthAsync_ThenRespondWithFhirOperationException_Test() { - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(default).ThrowsForAnyArgs(new FhirOperationException("Unauthorized", HttpStatusCode.Unauthorized))); - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any(), Arg.Any(), default).ThrowsForAnyArgs(new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.Unauthorized), new OperationOutcome()))); var service = new R4FhirHealthService(fhirClient); var response = await service.CheckHealth(); Assert.Equal(401, response.StatusCode); - Assert.Equal("Unauthorized", response.Message); + Assert.Equal("Unauthorized: ", response.Message); } [Fact] public async void GivenInvalidClientSecret_WhenCheckHealthAsync_ThenRespondWithAADException_Test() { - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(default).ThrowsForAnyArgs(new IdentityModel.Clients.ActiveDirectory.AdalServiceException("AADSTS123", "Unauthorized") { StatusCode = 401 })); - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any(), Arg.Any(), default).ThrowsForAnyArgs(new IdentityModel.Clients.ActiveDirectory.AdalServiceException("AADSTS123", "Unauthorized") { StatusCode = 401 }); var service = new R4FhirHealthService(fhirClient); var response = await service.CheckHealth(); @@ -61,9 +59,8 @@ public async void GivenInvalidClientSecret_WhenCheckHealthAsync_ThenRespondWithA [Fact] public async void GivenInvalidUrl_WhenCheckHealthAsync_ThenRespondWithGenericException_Test() { - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(default).ThrowsForAnyArgs(new Exception("No such host is known"))); - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any(), Arg.Any(), default).ThrowsForAnyArgs(new Exception("No such host is known")); var service = new R4FhirHealthService(fhirClient); var response = await service.CheckHealth(); diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirImportServiceTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirImportServiceTests.cs index 19b17ec9..3486080a 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirImportServiceTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Service/R4FhirImportServiceTests.cs @@ -6,18 +6,22 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Threading.Tasks; -using Hl7.Fhir.Rest; +using Hl7.Fhir.Model; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Health.Common; using Microsoft.Health.Common.Telemetry; +using Microsoft.Health.Fhir.Client; using Microsoft.Health.Fhir.Ingest.Data; using Microsoft.Health.Fhir.Ingest.Telemetry; using Microsoft.Health.Fhir.Ingest.Template; using Microsoft.Health.Logging.Telemetry; using Microsoft.Health.Tests.Common; using NSubstitute; +using NSubstitute.ExceptionExtensions; using Xunit; using Model = Hl7.Fhir.Model; +using ResourceType = Microsoft.Health.Fhir.Ingest.Data.ResourceType; +using Task = System.Threading.Tasks.Task; namespace Microsoft.Health.Fhir.Ingest.Service { @@ -26,7 +30,7 @@ public class R4FhirImportServiceTests [Fact] public async void GivenValidData_WhenProcessAsync_ThenSaveObservationInvokedForEachObservationGroup_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); + var fhirClient = Utilities.CreateMockFhirService(); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -63,12 +67,8 @@ public async void GivenValidData_WhenProcessAsync_ThenSaveObservationInvokedForE [Fact] public async void GivenNotFoundObservation_WhenSaveObservationAsync_ThenCreateInvoked_Test() { - // Mock search and update request - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)).Returns(new Model.Bundle())) - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Post)).Returns(new Model.Observation())); - - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.CreateResourceAsync(Arg.Any()).ReturnsForAnyArgs(Task.FromResult(new Model.Observation())); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -87,8 +87,6 @@ public async void GivenNotFoundObservation_WhenSaveObservationAsync_ThenCreateIn var result = await service.SaveObservationAsync(config, observationGroup, ids); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Post)); logger.Received(1).LogMetric(Arg.Is(x => Equals("ObservationCreated", x.Dimensions[DimensionNames.Name])), 1); } @@ -109,12 +107,10 @@ public async void GivenFoundObservation_WhenSaveObservationAsync_ThenUpdateInvok var savedObservation = new Model.Observation(); - // Mock search and update request - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)).Returns(foundBundle)) - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)).Returns(savedObservation)); - - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.CreateResourceAsync(Arg.Any()).ReturnsForAnyArgs(Task.FromResult(new Model.Observation())); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(Task.FromResult(foundBundle)); + fhirClient.UpdateResourceAsync(Arg.Any()).ReturnsForAnyArgs(Task.FromResult(savedObservation)); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -136,8 +132,6 @@ public async void GivenFoundObservation_WhenSaveObservationAsync_ThenUpdateInvok var result = await service.SaveObservationAsync(config, observationGroup, ids); templateProcessor.ReceivedWithAnyArgs(1).MergeObservation(default, default, default); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)); logger.Received(1).LogMetric(Arg.Is(x => Equals("ObservationUpdated", x.Dimensions[DimensionNames.Name])), 1); } @@ -170,12 +164,12 @@ public async void GivenFoundObservationAndConflictOnSave_WhenSaveObservationAsyn var savedObservation = new Model.Observation(); - // Mock search and update request - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)).Returns(foundBundle1, foundBundle1)) - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)).Returns(x => ThrowConflictException(), x => savedObservation)); - - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(Task.FromResult(foundBundle1)); + fhirClient.UpdateResourceAsync(Arg.Any()) + .Returns( + x => { throw new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.Conflict), new OperationOutcome())); }, + x => Task.FromResult(savedObservation)); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -197,15 +191,13 @@ public async void GivenFoundObservationAndConflictOnSave_WhenSaveObservationAsyn var result = await service.SaveObservationAsync(config, observationGroup, ids); templateProcessor.ReceivedWithAnyArgs(2).MergeObservation(default, default, default); - handler.Received(2).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)); - handler.Received(2).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)); logger.Received(1).LogMetric(Arg.Is(x => Equals("ObservationUpdated", x.Dimensions[DimensionNames.Name])), 1); } [Fact] public void GivenValidTemplate_WhenGenerateObservation_ExpectedReferencesSet_Test() { - var fhirClient = Utilities.CreateMockFhirClient(); + var fhirClient = Utilities.CreateMockFhirService(); var ids = BuildIdCollection(); ids[ResourceType.Encounter] = "encounterId"; @@ -252,13 +244,10 @@ public async Task GivenCachedObservationDeleted_WhenGenerateObservation_ThenCach return true; }); - // Mock update request - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)).Returns(x => ThrowConflictException())) - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)).Returns(new Model.Bundle())) - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Post)).Returns(savedObservation)); - - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.UpdateResourceAsync(Arg.Any()).ThrowsForAnyArgs(new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.Conflict), new OperationOutcome()))); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(Task.FromResult(new Model.Bundle())); + fhirClient.CreateResourceAsync(Arg.Any()).ReturnsForAnyArgs(Task.FromResult(savedObservation)); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -282,9 +271,6 @@ public async Task GivenCachedObservationDeleted_WhenGenerateObservation_ThenCach templateProcessor.ReceivedWithAnyArgs(1).MergeObservation(default, default, default); cache.Received(1).Remove(Arg.Any()); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Put)); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Post)); logger.Received(1).LogMetric(Arg.Is(x => Equals("ObservationCreated", x.Dimensions[DimensionNames.Name])), 1); cache.Received(1).Set(Arg.Any(), savedObservation); } @@ -303,7 +289,7 @@ public async Task GivenCachedObservationUnchanged_WhenGenerateObservation_ThenCa return true; }); - var fhirClient = Utilities.CreateMockFhirClient(); + var fhirClient = Utilities.CreateMockFhirService(); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -346,11 +332,9 @@ public async void GivenFoundObservationUnchanged_WhenSaveObservationAsync_ThenUp var savedObservation = new Model.Observation(); - // Mock search and update request - var handler = Utilities.CreateMockMessageHandler() - .Mock(m => m.GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)).Returns(foundBundle)); - - var fhirClient = Utilities.CreateMockFhirClient(handler); + var fhirClient = Utilities.CreateMockFhirService(); + fhirClient.SearchForResourceAsync(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(Task.FromResult(foundBundle)); + fhirClient.UpdateResourceAsync(Arg.Any()).ReturnsForAnyArgs(Task.FromResult(savedObservation)); var ids = BuildIdCollection(); var identityService = Substitute.For() @@ -371,7 +355,6 @@ public async void GivenFoundObservationUnchanged_WhenSaveObservationAsync_ThenUp var result = await service.SaveObservationAsync(config, observationGroup, ids); templateProcessor.ReceivedWithAnyArgs(1).MergeObservation(default, default, default); - handler.Received(1).GetReturnContent(Arg.Is(msg => msg.Method == HttpMethod.Get)); logger.Received(1).LogMetric(Arg.Is(x => Equals("ObservationNoOperation", x.Dimensions[DimensionNames.Name])), 1); } @@ -385,7 +368,7 @@ public async void GivenFoundObservationUnchanged_WhenSaveObservationAsync_ThenUp private static Model.Observation ThrowConflictException() { - throw new FhirOperationException("error", HttpStatusCode.Conflict); + throw new FhirException(new FhirResponse(new HttpResponseMessage(HttpStatusCode.Conflict), new OperationOutcome())); } } } diff --git a/test/Microsoft.Health.Tests.Common.R4/Utilities.cs b/test/Microsoft.Health.Tests.Common.R4/Utilities.cs deleted file mode 100644 index 011bcdad..00000000 --- a/test/Microsoft.Health.Tests.Common.R4/Utilities.cs +++ /dev/null @@ -1,24 +0,0 @@ -// ------------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------------------------------------------- - -using System.Net.Http; -using Hl7.Fhir.Rest; -using NSubstitute; - -namespace Microsoft.Health.Tests.Common -{ - public static class Utilities - { - public static MockFhirResourceHttpMessageHandler CreateMockMessageHandler() - { - return Substitute.ForPartsOf(); - } - - public static FhirClient CreateMockFhirClient(HttpMessageHandler messageHandler = null) - { - return Substitute.For("https://localhost", FhirClientSettings.CreateDefault(), messageHandler ?? CreateMockMessageHandler(), null); - } - } -}