diff --git a/.gitignore b/.gitignore index 3e759b75..c01ddca4 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,12 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +## Mac OS generated +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs index 5a73caa9..27840f56 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/BundleExtensions.cs @@ -41,21 +41,28 @@ public static IEnumerable ReadFromBundle(this Bundle bundl } } - public static TResource ReadOneFromBundle(this Bundle bundle, bool throwOnMultipleFound = true) + public static async Task ReadOneFromBundleWithContinuationAsync(this Bundle bundle, IFhirClient fhirClient, bool throwOnMultipleFound = true) where TResource : Resource, new() { - var bundleCount = bundle?.Entry?.Count ?? 0; - if (bundleCount == 0) + if (bundle == null) + { + return null; + } + + var resources = await bundle?.ReadFromBundleWithContinuationAsync(fhirClient, 2); + + var resourceCount = resources.Count(); + if (resourceCount == 0) { return null; } - if (throwOnMultipleFound && bundleCount > 1) + if (throwOnMultipleFound && resourceCount > 1) { throw new MultipleResourceFoundException(); } - return bundle.Entry.ByResourceType().FirstOrDefault(); + return resources.FirstOrDefault(); } /// 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 e82e2522..9a8ba023 100644 --- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs +++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Service/ResourceManagementService.cs @@ -52,7 +52,7 @@ protected static async Task GetResourceByIdentityAsync(IFh EnsureArg.IsNotNull(identifier, nameof(identifier)); var searchParams = identifier.ToSearchParams(); var result = await client.SearchAsync(searchParams).ConfigureAwait(false); - return result.ReadOneFromBundle(); + return await result.ReadOneFromBundleWithContinuationAsync(client); } protected static async Task CreateResourceByIdentityAsync(IFhirClient client, Model.Identifier identifier, Action propertySetter) 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 1b101929..8063f285 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs @@ -93,8 +93,8 @@ public virtual async Task SaveObservationAsync(ILookupTemplate { var searchParams = identifier.ToSearchParams(); var result = await _client.SearchAsync(searchParams).ConfigureAwait(false); - return result.ReadOneFromBundle(); + return await result.ReadOneFromBundleWithContinuationAsync(_client); } } } \ No newline at end of file diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs new file mode 100644 index 00000000..c139c935 --- /dev/null +++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/BundleExtensionsTests.cs @@ -0,0 +1,119 @@ +// ------------------------------------------------------------------------------------------------- +// 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 Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using NSubstitute; +using Xunit; + +namespace Microsoft.Health.Extensions.Fhir.R4.UnitTests +{ + public class BundleExtensionsTests + { + [Fact] + public async void GivenNoEntriesAndNoContinuationToken_ReadOneFromBundleWithContinuationAsync_ThenNullIsReturned_Test() + { + var bundle = new Bundle(); + bundle.Entry = new List(); + bundle.Link = new List(); + + var client = Substitute.For(); + client.ContinueAsync(Arg.Any()).Returns(System.Threading.Tasks.Task.FromResult(null)); + + Assert.Null(await bundle.ReadOneFromBundleWithContinuationAsync(client)); + } + + [Fact] + public async void GivenOneEntryAndNoContinuationToken_ReadOneFromBundleWithContinuationAsync_ThenResourceIsReturned_Test() + { + var bundle = new Bundle(); + bundle.Entry = new List(); + bundle.Link = new List(); + + var observation = new Observation(); + var entry = new Bundle.EntryComponent(); + entry.Resource = observation; + bundle.Entry.Add(entry); + + var client = Substitute.For(); + client.ContinueAsync(Arg.Any()).Returns(System.Threading.Tasks.Task.FromResult(null)); + + Assert.Equal(observation, await bundle.ReadOneFromBundleWithContinuationAsync(client)); + } + + [Fact] + public async void GivenOneEntryAfterContinuationToken_ReadOneFromBundleWithContinuationAsync_ThenResourceIsReturned_Test() + { + var bundle = new Bundle(); + bundle.Entry = new List(); + bundle.Link = new List(); + + var continuationBundle = new Bundle(); + continuationBundle.Entry = new List(); + continuationBundle.Link = new List(); + + var observation = new Observation(); + var entry = new Bundle.EntryComponent(); + entry.Resource = observation; + continuationBundle.Entry.Add(entry); + + var client = Substitute.For(); + client.ContinueAsync(Arg.Any()).Returns(ret => System.Threading.Tasks.Task.FromResult(continuationBundle), ret => System.Threading.Tasks.Task.FromResult(null)); + + Assert.Equal(observation, await bundle.ReadOneFromBundleWithContinuationAsync(client)); + } + + [Fact] + public async void GivenTwoEntriesAndNoContinuationToken_ReadOneFromBundleWithContinuationAsync_ThenThrows_Test() + { + var bundle = new Bundle(); + bundle.Entry = new List(); + bundle.Link = new List(); + + var observation1 = new Observation(); + var entry1 = new Bundle.EntryComponent(); + entry1.Resource = observation1; + bundle.Entry.Add(entry1); + + var observation2 = new Observation(); + var entry2 = new Bundle.EntryComponent(); + entry2.Resource = observation2; + bundle.Entry.Add(entry2); + + var client = Substitute.For(); + client.ContinueAsync(Arg.Any()).Returns(System.Threading.Tasks.Task.FromResult(null)); + + await Assert.ThrowsAsync>(() => bundle.ReadOneFromBundleWithContinuationAsync(client)); + } + + [Fact] + public async void GivenOneEntryBeforeAndAfterContinuationToken_ReadOneFromBundleWithContinuationAsync_ThenThrows_Test() + { + var bundle = new Bundle(); + bundle.Entry = new List(); + bundle.Link = new List(); + + var observation1 = new Observation(); + var entry1 = new Bundle.EntryComponent(); + entry1.Resource = observation1; + bundle.Entry.Add(entry1); + + var continuationBundle = new Bundle(); + continuationBundle.Entry = new List(); + continuationBundle.Link = new List(); + + var observation2 = new Observation(); + var entry2 = new Bundle.EntryComponent(); + entry2.Resource = observation2; + continuationBundle.Entry.Add(entry2); + + var client = Substitute.For(); + client.ContinueAsync(Arg.Any()).Returns(ret => System.Threading.Tasks.Task.FromResult(continuationBundle), ret => System.Threading.Tasks.Task.FromResult(null)); + + await Assert.ThrowsAsync>(() => bundle.ReadOneFromBundleWithContinuationAsync(client)); + } + } +} 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 ecebd5dd..a5069212 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 @@ -23,8 +23,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Data/EventDataWithJsonBodyToJTokenConverterTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Data/EventDataWithJsonBodyToJTokenConverterTests.cs index e998781a..68a3a6d8 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Data/EventDataWithJsonBodyToJTokenConverterTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Data/EventDataWithJsonBodyToJTokenConverterTests.cs @@ -61,7 +61,7 @@ public void GivenPopulatedEvent_WhenConvert_ThenTokenWithNonSerializedBodyReturn } [Theory] - [FileData(@"TestInput\data_IotHubPayloadExample.json")] + [FileData(@"TestInput/data_IotHubPayloadExample.json")] public void GivenIoTCentralPopulatedEvent_WhenConvert_ThenTokenWithNonSerializedBodyAndPropertiesReturned_Test(string json) { var evt = EventDataTestHelper.BuildEventFromJson(json); diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs index a68ef5f4..007ee609 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Health.Fhir.Ingest.Template public class CodeValueFhirTemplateFactoryTests { [Theory] - [FileData(@"TestInput\data_CodeValueFhirTemplate_SampledData.json")] + [FileData(@"TestInput/data_CodeValueFhirTemplate_SampledData.json")] public void GivenValidTemplateJsonWithValueSampledDataType_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); @@ -46,7 +46,7 @@ public void GivenValidTemplateJsonWithValueSampledDataType_WhenFactoryCreate_The } [Theory] - [FileData(@"TestInput\data_CodeValueFhirTemplate_Components.json")] + [FileData(@"TestInput/data_CodeValueFhirTemplate_Components.json")] public void GivenValidTemplateJsonWithComponentValueSampledDataType_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); @@ -100,7 +100,7 @@ public void GivenValidTemplateJsonWithComponentValueSampledDataType_WhenFactoryC } [Theory] - [FileData(@"TestInput\data_CodeValueFhirTemplate_CodeableConceptData.json")] + [FileData(@"TestInput/data_CodeValueFhirTemplate_CodeableConceptData.json")] public void GivenValidTemplateJsonWithCodeableConceptDataType_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); @@ -141,7 +141,7 @@ public void GivenValidTemplateJsonWithCodeableConceptDataType_WhenFactoryCreate_ } [Theory] - [FileData(@"TestInput\data_CodeValueFhirTemplate_Quantity.json")] + [FileData(@"TestInput/data_CodeValueFhirTemplate_Quantity.json")] public void GivenValidTemplateJsonWithComponentValueQuantityType_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionContentTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionContentTemplateFactoryTests.cs index 28db6937..a01871de 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionContentTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionContentTemplateFactoryTests.cs @@ -12,14 +12,14 @@ namespace Microsoft.Health.Fhir.Ingest.Template public class CollectionContentTemplateFactoryTests { [Theory] - [FileData(@"TestInput\data_CollectionContentTemplateEmpty.json")] + [FileData(@"TestInput/data_CollectionContentTemplateEmpty.json")] public void GivenEmptyConfig_WhenCreate_ThenInvalidTemplateException_Test(string json) { Assert.Throws(() => CollectionContentTemplateFactory.Default.Create(json)); } [Theory] - [FileData(@"TestInput\data_CollectionContentTemplateEmptyWithType.json")] + [FileData(@"TestInput/data_CollectionContentTemplateEmptyWithType.json")] public void GivenEmptyTemplateCollection_WhenCreate_ThenTemplateReturned_Test(string json) { var template = CollectionContentTemplateFactory.Default.Create(json); @@ -27,7 +27,7 @@ public void GivenEmptyTemplateCollection_WhenCreate_ThenTemplateReturned_Test(st } [Theory] - [FileData(@"TestInput\data_CollectionContentTemplateMultipleMocks.json")] + [FileData(@"TestInput/data_CollectionContentTemplateMultipleMocks.json")] public void GivenInputWithMatchingFactories_WhenCreate_ThenTemplateReturned_Test(string json) { IContentTemplate nullReturn = null; @@ -48,7 +48,7 @@ public void GivenInputWithMatchingFactories_WhenCreate_ThenTemplateReturned_Test } [Theory] - [FileData(@"TestInput\data_CollectionContentTemplateMultipleMocks.json")] + [FileData(@"TestInput/data_CollectionContentTemplateMultipleMocks.json")] public void GivenInputWithNoMatchingFactories_WhenCreate_ThenException_Test(string json) { IContentTemplate nullReturn = null; @@ -67,7 +67,7 @@ public void GivenInputWithNoMatchingFactories_WhenCreate_ThenException_Test(stri } [Theory] - [FileData(@"TestInput\data_CollectionFhirTemplateMixed.json")] + [FileData(@"TestInput/data_CollectionFhirTemplateMixed.json")] public void GivenInputWithMultipleTemplates_WhenCreate_ThenTemplateReturn_Test(string json) { var template = CollectionContentTemplateFactory.Default.Create(json); diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionFhirTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionFhirTemplateFactoryTests.cs index a0c7ba47..c2aec989 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionFhirTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CollectionFhirTemplateFactoryTests.cs @@ -12,14 +12,14 @@ namespace Microsoft.Health.Fhir.Ingest.Template public class CollectionFhirTemplateFactoryTests { [Theory] - [FileData(@"TestInput\data_CollectionFhirTemplateEmpty.json")] + [FileData(@"TestInput/data_CollectionFhirTemplateEmpty.json")] public void GivenEmptyConfig_WhenCreate_ThenInvalidTemplateException_Test(string json) { Assert.Throws(() => CollectionFhirTemplateFactory.Default.Create(json)); } [Theory] - [FileData(@"TestInput\data_CollectionFhirTemplateEmptyWithType.json")] + [FileData(@"TestInput/data_CollectionFhirTemplateEmptyWithType.json")] public void GivenEmptyTemplateCollection_WhenCreate_ThenTemplateReturned_Test(string json) { var template = CollectionFhirTemplateFactory.Default.Create(json); @@ -27,7 +27,7 @@ public void GivenEmptyTemplateCollection_WhenCreate_ThenTemplateReturned_Test(st } [Theory] - [FileData(@"TestInput\data_CollectionFhirTemplateMultipleMocks.json")] + [FileData(@"TestInput/data_CollectionFhirTemplateMultipleMocks.json")] public void GivenInputWithRegisteredFactories_WhenCreate_ThenTemplateReturned_Test(string json) { IFhirTemplate nullReturn = null; @@ -56,7 +56,7 @@ public void GivenInputWithRegisteredFactories_WhenCreate_ThenTemplateReturned_Te } [Theory] - [FileData(@"TestInput\data_CollectionFhirTemplateMultipleMocks.json")] + [FileData(@"TestInput/data_CollectionFhirTemplateMultipleMocks.json")] public void GivenInputWithUnregisteredFactories_WhenCreate_ThenException_Test(string json) { IFhirTemplate nullReturn = null; diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateFactoryTests.cs index b6a8c1c8..552d195c 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateFactoryTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Health.Fhir.Ingest.Template public class IotJsonPathContentTemplateFactoryTests { [Theory] - [FileData(@"TestInput\data_IotJsonPathContentTemplateValid.json")] + [FileData(@"TestInput/data_IotJsonPathContentTemplateValid.json")] public void GivenValidTemplateJson_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); @@ -39,7 +39,7 @@ public void GivenValidTemplateJson_WhenFactoryCreate_ThenTemplateCreated_Test(st } [Theory] - [FileData(@"TestInput\data_IotJsonPathContentTemplateValidWithOptional.json")] + [FileData(@"TestInput/data_IotJsonPathContentTemplateValidWithOptional.json")] public void GivenValidTemplateJsonWithOptionalExpressions_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateTests.cs index 1821104b..eafe14ab 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/IotJsonPathContentTemplateTests.cs @@ -35,7 +35,7 @@ public class IotJsonPathContentTemplateTests }; [Theory] - [FileData(@"TestInput\data_IotHubPayloadExample.json")] + [FileData(@"TestInput/data_IotHubPayloadExample.json")] public void GivenTemplateAndSingleValidToken_WhenGetMeasurements_ThenSingleMeasurementReturned_Test(string eventJson) { var evt = EventDataTestHelper.BuildEventFromJson(eventJson); @@ -58,7 +58,7 @@ public void GivenTemplateAndSingleValidToken_WhenGetMeasurements_ThenSingleMeasu } [Theory] - [FileData(@"TestInput\data_IotHubPayloadMultiValueExample.json")] + [FileData(@"TestInput/data_IotHubPayloadMultiValueExample.json")] public void GivenTemplateAndSingleMultiValueValidToken_WhenGetMeasurements_ThenSingleMeasurementReturned_Test(string eventJson) { var evt = EventDataTestHelper.BuildEventFromJson(eventJson); @@ -88,7 +88,7 @@ public void GivenTemplateAndSingleMultiValueValidToken_WhenGetMeasurements_ThenS } [Theory] - [FileData(@"TestInput\data_IoTHubPayloadExampleMissingCreateTime.json")] + [FileData(@"TestInput/data_IoTHubPayloadExampleMissingCreateTime.json")] public void GivenTemplateAndSingleValidTokenWithoutCreationTime_WhenGetMeasurements_ThenSingleMeasurementReturned_Test(string eventJson) { var evt = EventDataTestHelper.BuildEventFromJson(eventJson); diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/JsonContentTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/JsonContentTemplateFactoryTests.cs index c9714abf..36bae75e 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/JsonContentTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/JsonContentTemplateFactoryTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Health.Fhir.Ingest.Template public class JsonContentTemplateFactoryTests { [Theory] - [FileData(@"TestInput\data_JsonPathContentTemplateValid.json")] + [FileData(@"TestInput/data_JsonPathContentTemplateValid.json")] public void GivenValidTemplateJson_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json); @@ -39,7 +39,7 @@ public void GivenValidTemplateJson_WhenFactoryCreate_ThenTemplateCreated_Test(st } [Theory] - [FileData(@"TestInput\data_JsonPathContentTemplateValidWithOptional.json")] + [FileData(@"TestInput/data_JsonPathContentTemplateValidWithOptional.json")] public void GivenValidTemplateJsonWithOptionalExpressions_WhenFactoryCreate_ThenTemplateCreated_Test(string json) { var templateContainer = JsonConvert.DeserializeObject(json);