diff --git a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/BulkDelete/CreateBulkDeleteHandlerTests.cs b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/BulkDelete/CreateBulkDeleteHandlerTests.cs index 9f832e9763..40e2be957e 100644 --- a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/BulkDelete/CreateBulkDeleteHandlerTests.cs +++ b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/BulkDelete/CreateBulkDeleteHandlerTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Core.Features.Security.Authorization; @@ -56,7 +57,12 @@ public CreateBulkDeleteHandlerTests() requestHeaders: new Dictionary(), responseHeaders: new Dictionary()); - _handler = new CreateBulkDeleteHandler(_authorizationService, _queueClient, _contextAccessor, _searchService); + _handler = new CreateBulkDeleteHandler( + _authorizationService, + _queueClient, + _contextAccessor, + _searchService, + Substitute.For>()); } [Fact] diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/BulkDelete/Handlers/CreateBulkDeleteHandler.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/BulkDelete/Handlers/CreateBulkDeleteHandler.cs index 7cbaf74619..4ef3636076 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/BulkDelete/Handlers/CreateBulkDeleteHandler.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/BulkDelete/Handlers/CreateBulkDeleteHandler.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using EnsureThat; using MediatR; +using Microsoft.Extensions.Logging; using Microsoft.Health.Core; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Core.Features.Security.Authorization; @@ -32,17 +33,20 @@ public class CreateBulkDeleteHandler : IRequestHandler _contextAccessor; private readonly ISearchService _searchService; + private readonly ILogger _logger; public CreateBulkDeleteHandler( IAuthorizationService authorizationService, IQueueClient queueClient, RequestContextAccessor contextAccessor, - ISearchService searchService) + ISearchService searchService, + ILogger logger) { _authorizationService = EnsureArg.IsNotNull(authorizationService, nameof(authorizationService)); _queueClient = EnsureArg.IsNotNull(queueClient, nameof(queueClient)); _contextAccessor = EnsureArg.IsNotNull(contextAccessor, nameof(contextAccessor)); _searchService = EnsureArg.IsNotNull(searchService, nameof(searchService)); + _logger = EnsureArg.IsNotNull(logger, nameof(logger)); } public async Task Handle(CreateBulkDeleteRequest request, CancellationToken cancellationToken) @@ -65,7 +69,7 @@ public async Task Handle(CreateBulkDeleteRequest reque searchParameters.Add(Tuple.Create("_lastUpdated", $"lt{dateCurrent}")); // Should not run bulk delete if any of the search parameters are invalid as it can lead to unpredicatable results - await _searchService.ConditionalSearchAsync(request.ResourceType, searchParameters, cancellationToken, count: 1); + await _searchService.ConditionalSearchAsync(request.ResourceType, searchParameters, cancellationToken, count: 1, logger: _logger); if (_contextAccessor.RequestContext?.BundleIssues?.Count > 0) { throw new BadRequestException(_contextAccessor.RequestContext.BundleIssues.Select(issue => issue.Diagnostics).ToList()); diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerEdgeCaseTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerEdgeCaseTests.cs index 6b9179d2c6..14f8062ec4 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerEdgeCaseTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerEdgeCaseTests.cs @@ -116,7 +116,7 @@ private IFhirRequestContext CreateRequestContextForBundleHandlerProcessing(Bundl var fhirJsonParser = new FhirJsonParser(); ISearchService searchService = Substitute.For(); - var resourceReferenceResolver = new ResourceReferenceResolver(searchService, new QueryStringParser()); + var resourceReferenceResolver = new ResourceReferenceResolver(searchService, new QueryStringParser(), Substitute.For>()); var transactionBundleValidatorLogger = Substitute.For>(); var transactionBundleValidator = new TransactionBundleValidator(resourceReferenceResolver, transactionBundleValidatorLogger); diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerTests.cs index 64bed653c7..7d18eeb006 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/BundleHandlerTests.cs @@ -79,8 +79,10 @@ public BundleHandlerTests() var fhirJsonSerializer = new FhirJsonSerializer(); var fhirJsonParser = new FhirJsonParser(); + var loggerResourceReferenceResolver = Substitute.For>(); + ISearchService searchService = Substitute.For(); - var resourceReferenceResolver = new ResourceReferenceResolver(searchService, new QueryStringParser()); + var resourceReferenceResolver = new ResourceReferenceResolver(searchService, new QueryStringParser(), loggerResourceReferenceResolver); var transactionBundleValidatorLogger = Substitute.For>(); var transactionBundleValidator = new TransactionBundleValidator(resourceReferenceResolver, transactionBundleValidatorLogger); diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/TransactionBundleValidatorTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/TransactionBundleValidatorTests.cs index 3661eb1910..42b14d0688 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/TransactionBundleValidatorTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/TransactionBundleValidatorTests.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using Castle.Core.Logging; using Microsoft.Extensions.Logging; using Microsoft.Health.Fhir.Api.Features.Resources.Bundle; using Microsoft.Health.Fhir.Api.Features.Routing; @@ -31,12 +30,13 @@ public class TransactionBundleValidatorTests { private readonly ISearchService _searchService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); + private readonly ILogger _loggerResourceReferenceResolver = Substitute.For>(); private readonly TransactionBundleValidator _transactionBundleValidator; private readonly Dictionary _idDictionary; public TransactionBundleValidatorTests() { - _transactionBundleValidator = new TransactionBundleValidator(new ResourceReferenceResolver(_searchService, new QueryStringParser()), _logger); + _transactionBundleValidator = new TransactionBundleValidator(new ResourceReferenceResolver(_searchService, new QueryStringParser(), _loggerResourceReferenceResolver), _logger); _idDictionary = new Dictionary(); } diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs index e3f273e63d..af614314be 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Transactions; @@ -22,10 +23,7 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.AspNetCore.Http.Headers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -54,7 +52,6 @@ using Microsoft.Health.Fhir.Core.Messages.Bundle; using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.Fhir.ValueSets; -using SharpCompress.Common; using static Hl7.Fhir.Model.Bundle; using Task = System.Threading.Tasks.Task; @@ -101,6 +98,16 @@ public partial class BundleHandler : IRequestHandler private static readonly string[] HeadersToAccumulate = new[] { KnownHeaders.RetryAfter, KnownHeaders.RetryAfterMilliseconds, "x-ms-session-token", "x-ms-request-charge" }; + /// + /// Status codes that do not require additional logging for troubleshooting. + /// + private static readonly string[] SuccessfullStatusCodeToAvoidAdditionalLogging = new[] + { + ((int)HttpStatusCode.OK).ToString(), + ((int)HttpStatusCode.Created).ToString(), + ((int)HttpStatusCode.Accepted).ToString(), + }; + /// /// Properties to propagate from the outer HTTP requests to the inner actions. /// @@ -676,6 +683,8 @@ private async Task ExecuteRequestsWithSingleHttpVerbInSequenceAs statistics.RegisterNewEntry(httpVerb, resourceContext.Index, entryComponent.Response.Status, watch.Elapsed); + LogFinalOperationOutcomeForFailedRecords(resourceContext.Index, entryComponent); + if (_bundleType.Equals(BundleType.Transaction) && entryComponent.Response.Outcome != null) { var errorMessage = string.Format(Api.Resources.TransactionFailed, resourceContext.Context.HttpContext.Request.Method, resourceContext.Context.HttpContext.Request.Path); @@ -845,6 +854,46 @@ private static void SetupContexts( } } + private void LogFinalOperationOutcomeForFailedRecords(int index, EntryComponent entryComponent) + { + if (entryComponent?.Response?.Outcome == null) + { + return; + } + + // If the result is a successful, no need to log the outcome for potential troubleshooting. + if (SuccessfullStatusCodeToAvoidAdditionalLogging.Contains(entryComponent.Response.Status)) + { + return; + } + + try + { + StringBuilder reason = new StringBuilder(); + if (entryComponent.Response.Outcome is OperationOutcome operationOutcome && operationOutcome.Issue.Any()) + { + foreach (OperationOutcome.IssueComponent issue in operationOutcome.Issue) + { + reason.AppendLine($"{issue.Severity} / {issue.Code} / {SanitizeString(issue.Diagnostics)}"); + } + } + else + { + reason.Append("Reason is not defined."); + } + + _logger.LogInformation( + "Throubleshoot Outcome {Index}: {HttpStatus}. Reason: {Reason}", + index, + entryComponent.Response.Status, + reason.ToString()); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Throubleshoot Outcome {index}: Error while logging the final operation outcome for failed records. This error will not block the bundle processing."); + } + } + private void PopulateReferenceIdDictionary(IReadOnlyCollection bundleEntries, IDictionary idDictionary) { foreach (EntryComponent entry in bundleEntries) @@ -888,6 +937,14 @@ private static OperationOutcome CreateOperationOutcome(OperationOutcome.IssueSev }; } + private static string SanitizeString(string input) + { + return input + .Replace(Environment.NewLine, string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace("\r", " ", StringComparison.OrdinalIgnoreCase) + .Replace("\n", " ", StringComparison.OrdinalIgnoreCase); + } + private BundleHandlerStatistics CreateNewBundleHandlerStatistics(BundleProcessingLogic processingLogic) { BundleHandlerStatistics statistics = new BundleHandlerStatistics(_bundleType, processingLogic, _optimizedQuerySet, _requestCount); diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs index 4b42c164bb..5b634af176 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net.Http; using System.Threading; -using Castle.Core.Logging; using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; @@ -16,7 +15,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; -using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Core.Features.Security.Authorization; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Exceptions; @@ -114,7 +112,7 @@ public ResourceHandlerTests() var contextAccessor = Substitute.For(); contextAccessor.RequestContext = new FhirRequestContext("method", "http://localhost", "http://localhost", "id", new Dictionary(), new Dictionary()); - var referenceResolver = new ResourceReferenceResolver(_searchService, new TestQueryStringParser()); + var referenceResolver = new ResourceReferenceResolver(_searchService, new TestQueryStringParser(), Substitute.For>()); _resourceIdProvider = new ResourceIdProvider(); var auditLogger = Substitute.For(); diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceReferenceResolverTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceReferenceResolverTests.cs index 431fefa7ac..6081ce23ef 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceReferenceResolverTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceReferenceResolverTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using Hl7.Fhir.Model; +using Microsoft.Extensions.Logging; using Microsoft.Health.Fhir.Core.Exceptions; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Persistence; @@ -32,7 +33,7 @@ public class ResourceReferenceResolverTests public ResourceReferenceResolverTests() { - _referenceResolver = new ResourceReferenceResolver(_searchService, new TestQueryStringParser()); + _referenceResolver = new ResourceReferenceResolver(_searchService, new TestQueryStringParser(), Substitute.For>()); } [Fact] diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ConditionalResourceHandler.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ConditionalResourceHandler.cs index 66ed82751d..a8cba0c33d 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ConditionalResourceHandler.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ConditionalResourceHandler.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; @@ -75,7 +74,7 @@ public async Task Handle(TRequest request, CancellationToken cancella else { // Multiple matches: The server returns a 412 Precondition Failed error indicating the client's criteria were not selective enough - _logger.LogInformation("PreconditionFailed - Conditional handler: Multiple Matches Found. ResourceType={ResourceType}, NumberOfMatches={NumberOfMatches}", request.ResourceType, count); + _logger.LogInformation("PreconditionFailed: Conditional handler: Multiple Matches Found. ResourceType={ResourceType}, NumberOfMatches={NumberOfMatches}", request.ResourceType, count); throw new PreconditionFailedException(string.Format(CultureInfo.InvariantCulture, Core.Resources.ConditionalOperationNotSelectiveEnough, request.ResourceType)); } } diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Create/ConditionalCreateResourceValidator.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Create/ConditionalCreateResourceValidator.cs index 9f3dc2d10e..bd942ea6a8 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Create/ConditionalCreateResourceValidator.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Create/ConditionalCreateResourceValidator.cs @@ -5,6 +5,7 @@ using System.Globalization; using FluentValidation; +using Microsoft.Extensions.Logging; using Microsoft.Health.Fhir.Core.Messages.Create; namespace Microsoft.Health.Fhir.Core.Features.Resources.Create @@ -12,13 +13,14 @@ namespace Microsoft.Health.Fhir.Core.Features.Resources.Create [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "Follows validator naming convention.")] public class ConditionalCreateResourceValidator : AbstractValidator { - public ConditionalCreateResourceValidator() + public ConditionalCreateResourceValidator(ILogger logger) { RuleFor(x => x.ConditionalParameters) .Custom((conditionalParameters, context) => { if (conditionalParameters.Count == 0) { + logger?.LogInformation("PreconditionFailed: ConditionalOperationNotSelectiveEnough"); context.AddFailure(string.Format(CultureInfo.InvariantCulture, Core.Resources.ConditionalOperationNotSelectiveEnough, context.InstanceToValidate.ResourceType)); } }); diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/ConditionalDeleteResourceValidator.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/ConditionalDeleteResourceValidator.cs index af6cebc66d..be69e175b3 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/ConditionalDeleteResourceValidator.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/ConditionalDeleteResourceValidator.cs @@ -6,6 +6,7 @@ using System.Globalization; using EnsureThat; using FluentValidation; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Health.Fhir.Core.Configs; using Microsoft.Health.Fhir.Core.Messages.Delete; @@ -16,7 +17,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Resources.Create [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "Follows validator naming convention.")] public class ConditionalDeleteResourceValidator : AbstractValidator { - public ConditionalDeleteResourceValidator(IOptions configuration, IModelInfoProvider modelInfoProvider) + public ConditionalDeleteResourceValidator(IOptions configuration, IModelInfoProvider modelInfoProvider, ILogger logger) { EnsureArg.IsNotNull(configuration?.Value, nameof(configuration)); @@ -29,6 +30,7 @@ public ConditionalDeleteResourceValidator(IOptions con { if (conditionalParameters.Count == 0) { + logger?.LogInformation("PreconditionFailed: ConditionalOperationNotSelectiveEnough"); context.AddFailure(string.Format(CultureInfo.InvariantCulture, Core.Resources.ConditionalOperationNotSelectiveEnough, context.InstanceToValidate.ResourceType)); } }); diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/DeletionService.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/DeletionService.cs index 6f27e45144..88f43c7320 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/DeletionService.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Delete/DeletionService.cs @@ -127,7 +127,8 @@ public async Task DeleteMultipleAsync(ConditionalDeleteResourceRequest req cancellationToken, request.DeleteAll ? searchCount : request.MaxDeleteCount, versionType: request.VersionType, - onlyIds: true); + onlyIds: true, + logger: _logger); } long numDeleted = 0; @@ -179,7 +180,8 @@ public async Task DeleteMultipleAsync(ConditionalDeleteResourceRequest req request.DeleteAll ? searchCount : (int)(request.MaxDeleteCount - numQueuedForDeletion), ct, request.VersionType, - onlyIds: true); + onlyIds: true, + logger: _logger); } } else diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ResourceReferenceResolver.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ResourceReferenceResolver.cs index d362ae0666..1f639735ca 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ResourceReferenceResolver.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/ResourceReferenceResolver.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using EnsureThat; using Hl7.Fhir.Model; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Health.Core.Extensions; using Microsoft.Health.Fhir.Core.Exceptions; @@ -26,14 +27,16 @@ public class ResourceReferenceResolver { private readonly ISearchService _searchService; private readonly IQueryStringParser _queryStringParser; + private readonly ILogger _logger; - public ResourceReferenceResolver(ISearchService searchService, IQueryStringParser queryStringParser) + public ResourceReferenceResolver( + ISearchService searchService, + IQueryStringParser queryStringParser, + ILogger logger) { - EnsureArg.IsNotNull(searchService, nameof(searchService)); - EnsureArg.IsNotNull(queryStringParser, nameof(queryStringParser)); - - _searchService = searchService; - _queryStringParser = queryStringParser; + _searchService = EnsureArg.IsNotNull(searchService, nameof(searchService)); + _queryStringParser = EnsureArg.IsNotNull(queryStringParser, nameof(queryStringParser)); + _logger = EnsureArg.IsNotNull(logger, nameof(logger)); } public async Task ResolveReferencesAsync(Resource resource, IDictionary referenceIdDictionary, string requestUrl, CancellationToken cancellationToken) @@ -93,7 +96,7 @@ public async Task> GetExistingResourceId( var searchResourceRequest = new SearchResourceRequest(resourceType, conditionalParameters); - return (await _searchService.ConditionalSearchAsync(searchResourceRequest.ResourceType, searchResourceRequest.Queries, cancellationToken)).Results; + return (await _searchService.ConditionalSearchAsync(searchResourceRequest.ResourceType, searchResourceRequest.Queries, cancellationToken, logger: _logger)).Results; } } } diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Upsert/ConditionalUpsertResourceValidator.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Upsert/ConditionalUpsertResourceValidator.cs index 1b052b3692..26a4f846a2 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Upsert/ConditionalUpsertResourceValidator.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Resources/Upsert/ConditionalUpsertResourceValidator.cs @@ -5,6 +5,7 @@ using System.Globalization; using FluentValidation; +using Microsoft.Extensions.Logging; using Microsoft.Health.Fhir.Core.Messages.Upsert; namespace Microsoft.Health.Fhir.Core.Features.Resources.Upsert @@ -12,13 +13,14 @@ namespace Microsoft.Health.Fhir.Core.Features.Resources.Upsert [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "Follows validator naming convention.")] public class ConditionalUpsertResourceValidator : AbstractValidator { - public ConditionalUpsertResourceValidator() + public ConditionalUpsertResourceValidator(ILogger logger) { RuleFor(x => x.ConditionalParameters) .Custom((conditionalParameters, context) => { if (conditionalParameters.Count == 0) { + logger?.LogInformation("PreconditionFailed: ConditionalOperationNotSelectiveEnough"); context.AddFailure(string.Format(CultureInfo.InvariantCulture, Core.Resources.ConditionalOperationNotSelectiveEnough, context.InstanceToValidate.ResourceType)); } }); diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs index edb3758be1..bbef6e8fe3 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTestsFixture.cs @@ -195,8 +195,8 @@ public async Task InitializeAsync() var collection = new ServiceCollection(); - collection.AddSingleton(typeof(IRequestHandler), new CreateResourceHandler(DataStore, new Lazy(() => ConformanceProvider), resourceWrapperFactory, _resourceIdProvider, new ResourceReferenceResolver(SearchService, new TestQueryStringParser()), DisabledFhirAuthorizationService.Instance)); - collection.AddSingleton(typeof(IRequestHandler), new UpsertResourceHandler(DataStore, new Lazy(() => ConformanceProvider), resourceWrapperFactory, _resourceIdProvider, new ResourceReferenceResolver(SearchService, new TestQueryStringParser()), DisabledFhirAuthorizationService.Instance, ModelInfoProvider.Instance)); + collection.AddSingleton(typeof(IRequestHandler), new CreateResourceHandler(DataStore, new Lazy(() => ConformanceProvider), resourceWrapperFactory, _resourceIdProvider, new ResourceReferenceResolver(SearchService, new TestQueryStringParser(), Substitute.For>()), DisabledFhirAuthorizationService.Instance)); + collection.AddSingleton(typeof(IRequestHandler), new UpsertResourceHandler(DataStore, new Lazy(() => ConformanceProvider), resourceWrapperFactory, _resourceIdProvider, new ResourceReferenceResolver(SearchService, new TestQueryStringParser(), Substitute.For>()), DisabledFhirAuthorizationService.Instance, ModelInfoProvider.Instance)); collection.AddSingleton(typeof(IRequestHandler), GetResourceHandler); collection.AddSingleton(typeof(IRequestHandler), new DeleteResourceHandler(DataStore, new Lazy(() => ConformanceProvider), resourceWrapperFactory, _resourceIdProvider, DisabledFhirAuthorizationService.Instance, deleter)); collection.AddSingleton(typeof(IRequestHandler), new SearchResourceHistoryHandler(SearchService, bundleFactory, DisabledFhirAuthorizationService.Instance, new DataResourceFilter(MissingDataFilterCriteria.Default)));