public async Task <ReindexJobWrapper> GetReindexJobByIdAsync(string jobId, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNullOrWhiteSpace(jobId, nameof(jobId));

            try
            {
                DocumentResponse <CosmosReindexJobRecordWrapper> cosmosReindexJobRecord = await _documentClientScope.Value.ReadDocumentAsync <CosmosReindexJobRecordWrapper>(
                    UriFactory.CreateDocumentUri(DatabaseId, CollectionId, jobId),
                    new RequestOptions { PartitionKey = new PartitionKey(CosmosDbReindexConstants.ReindexJobPartitionKey) },
                    cancellationToken);

                var outcome = new ReindexJobWrapper(cosmosReindexJobRecord.Document.JobRecord, WeakETag.FromVersionId(cosmosReindexJobRecord.Document.ETag));

                return(outcome);
            }
            catch (DocumentClientException dce)
            {
                if (dce.StatusCode == HttpStatusCode.TooManyRequests)
                {
                    throw new RequestRateExceededException(dce.RetryAfter);
                }
                else if (dce.StatusCode == HttpStatusCode.NotFound)
                {
                    throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobId));
                }

                _logger.LogError(dce, $"Failed to get reindex job by id: {jobId}.");
                throw;
            }
        }
        public void GivenAVersion_WhenAddingETagDecoration_AWeakEtagShouldBeReturned()
        {
            var weakETag = WeakETag.FromVersionId("version1");

            Assert.Equal("W/\"version1\"", weakETag.ToString());
        }
Example #3
0
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            EnsureArg.IsNotNull(context, nameof(context));

            if (context?.Exception is FhirException fhirException)
            {
                FhirResult fhirResult = FhirResult.Create(
                    new OperationOutcome
                {
                    Id    = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                    Issue = fhirException.Issues.ToList(),
                }, HttpStatusCode.BadRequest);

                switch (fhirException)
                {
                case ResourceGoneException resourceGoneException:
                    fhirResult.StatusCode = HttpStatusCode.Gone;
                    if (!string.IsNullOrEmpty(resourceGoneException.DeletedResource?.VersionId))
                    {
                        fhirResult.SetETagHeader(WeakETag.FromVersionId(resourceGoneException.DeletedResource.VersionId));
                    }

                    break;

                case ResourceNotFoundException _:
                    fhirResult.StatusCode = HttpStatusCode.NotFound;
                    break;

                case MethodNotAllowedException _:
                    fhirResult.StatusCode = HttpStatusCode.MethodNotAllowed;
                    break;

                case ServiceUnavailableException _:
                case OpenIdConfigurationException _:
                    fhirResult.StatusCode = HttpStatusCode.ServiceUnavailable;
                    break;

                case ResourceNotValidException _:
                case BadRequestException _:
                    fhirResult.StatusCode = HttpStatusCode.BadRequest;
                    break;

                case ResourceConflictException _:
                    fhirResult.StatusCode = HttpStatusCode.Conflict;
                    break;

                case UnsupportedMediaTypeException _:
                    fhirResult.StatusCode = HttpStatusCode.UnsupportedMediaType;
                    break;

                case PreconditionFailedException _:
                    fhirResult.StatusCode = HttpStatusCode.PreconditionFailed;
                    break;

                case InvalidSearchOperationException _:
                case SearchOperationNotSupportedException _:
                    fhirResult.StatusCode = HttpStatusCode.Forbidden;
                    break;

                case UnsupportedConfigurationException _:
                    fhirResult.StatusCode = HttpStatusCode.InternalServerError;
                    break;

                case RequestRateExceededException ex:
                    fhirResult.StatusCode = HttpStatusCode.TooManyRequests;

                    if (ex.RetryAfter != null)
                    {
                        fhirResult.Headers.Add(
                            RetryAfterHeaderName,
                            ex.RetryAfter.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
                    }

                    break;
                }

                context.Result           = fhirResult;
                context.ExceptionHandled = true;
            }
        }
        public async Task GivenANonexistentResourceAndCosmosDb_WhenUpsertingWithCreateEnabledAndInvalidETagHeader_ThenResourceNotFoundIsThrown()
        {
            SetAllowCreate(true);

            await Assert.ThrowsAsync <ResourceNotFoundException>(() => Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight"), WeakETag.FromVersionId("invalidVersion")));
        }
        public void GivenANonWeakETag_WhenRemovingETagDecoration_ThenOriginalShouldRemain()
        {
            var weakETag = WeakETag.FromVersionId("\"version1\"");

            Assert.Equal("\"version1\"", weakETag.VersionId);
        }
        public async Task GivenAResourceTypeWithVersionedUpdateVersioningPolicy_WhenSearchingHistory_ThenAllVersionsAreReturned()
        {
            // The FHIR storage fixture configures medication resources to have the "versioned-update" versioning policy
            RawResourceElement medicationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultMedication());

            ResourceElement newResourceValues = Samples.GetDefaultMedication().UpdateId(medicationResource.Id);

            SaveOutcome updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(medicationResource.VersionId));

            ResourceElement historyResults = await Mediator.SearchResourceHistoryAsync(KnownResourceTypes.Medication, updateResult.RawResourceElement.Id);

            // The history bundle has both versions because history is kept
            Bundle bundle = historyResults.ToPoco <Bundle>();

            Assert.Equal(2, bundle.Entry.Count);

            Assert.Equal(WeakETag.FromVersionId(updateResult.RawResourceElement.VersionId).ToString(), bundle.Entry.Max(entry => entry.Response.Etag));
            Assert.Equal(WeakETag.FromVersionId(medicationResource.VersionId).ToString(), bundle.Entry.Min(entry => entry.Response.Etag));
        }
        public async Task GivenASavedResource_WhenUpserting_ThenTheExistingResourceIsUpdated()
        {
            var saveResult = await Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight"));

            var newResourceValues = Samples.GetJsonSample("WeightInGrams").ToPoco();

            newResourceValues.Id = saveResult.Resource.Id;

            var updateResult = await Mediator.UpsertResourceAsync(newResourceValues.ToResourceElement(), WeakETag.FromVersionId(saveResult.Resource.VersionId));

            Assert.NotNull(updateResult);
            Assert.Equal(SaveOutcomeType.Updated, updateResult.Outcome);

            Assert.NotNull(updateResult.Resource);
            Assert.Equal(saveResult.Resource.Id, updateResult.Resource.Id);
        }
Example #8
0
        public async Task GivenAnUpdatedResourceWithWrongResourceId_WhenUpdatingSearchParameterIndexAsync_ThenExceptionIsThrown()
        {
            ResourceElement patientResource = CreatePatientResourceElement("Patient", Guid.NewGuid().ToString());
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            SearchParameter searchParam     = null;
            const string    searchParamName = "newSearchParam";

            try
            {
                searchParam = await CreatePatientSearchParam(searchParamName, SearchParamType.String, "Patient.name");

                ISearchValue searchValue = new StringSearchValue(searchParamName);

                // Update the resource wrapper, adding the new search parameter and a different ID
                (ResourceWrapper original, ResourceWrapper updated) = await CreateUpdatedWrapperFromExistingPatient(upsertResult, searchParam, searchValue, null, Guid.NewGuid().ToString());

                await Assert.ThrowsAsync <ResourceNotFoundException>(() => _dataStore.UpdateSearchParameterIndicesAsync(updated, WeakETag.FromVersionId(original.Version), CancellationToken.None));
            }
            finally
            {
                if (searchParam != null)
                {
                    _searchParameterDefinitionManager.DeleteSearchParameter(searchParam.ToTypedElement());
                    await _fixture.TestHelper.DeleteSearchParameterStatusAsync(searchParam.Url, CancellationToken.None);
                }
            }
        }
        public async Task GivenAResourceTypeWithVersionedUpdateVersioningPolicy_WhenUpsertingWithNonMatchingVersion_ThenAPreconditionFailedExceptionIsThrown()
        {
            // The FHIR storage fixture configures medication resources to have the "versioned-update" versioning policy
            RawResourceElement medicationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultMedication());

            ResourceElement newResourceValues = Samples.GetDefaultMedication().UpdateId(medicationResource.Id);

            // Pass in a version that does not match the most recent version of the resource being updated
            // This simulates a request where a non-matching version is specified in the if-match header
            const string incorrectVersion = "2";
            var          exception        = await Assert.ThrowsAsync <PreconditionFailedException>(async() => await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(incorrectVersion)));

            Assert.Equal(string.Format(Core.Resources.ResourceVersionConflict, incorrectVersion), exception.Message);
        }
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            EnsureArg.IsNotNull(context, nameof(context));

            if (context?.Exception == null)
            {
                return;
            }

            if (context.Exception is FhirException fhirException)
            {
                var operationOutcomeResult = new OperationOutcomeResult(
                    new OperationOutcome
                {
                    Id    = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                    Issue = fhirException.Issues.Select(x => x.ToPoco()).ToList(),
                }, HttpStatusCode.BadRequest);

                switch (fhirException)
                {
                case ResourceGoneException resourceGoneException:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Gone;
                    if (!string.IsNullOrEmpty(resourceGoneException.DeletedResource?.VersionId))
                    {
                        operationOutcomeResult.Headers.Add(HeaderNames.ETag, WeakETag.FromVersionId(resourceGoneException.DeletedResource.VersionId).ToString());
                    }

                    break;

                case ResourceNotFoundException _:
                case JobNotFoundException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.NotFound;
                    break;

                case MethodNotAllowedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.MethodNotAllowed;
                    break;

                case ServiceUnavailableException _:
                case OpenIdConfigurationException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.ServiceUnavailable;
                    break;

                case ResourceNotValidException _:
                    if (context.ActionDescriptor is ControllerActionDescriptor controllerDescriptor)
                    {
                        if (controllerDescriptor.ControllerName.Equals(ValidateController, StringComparison.OrdinalIgnoreCase))
                        {
                            operationOutcomeResult.StatusCode = HttpStatusCode.OK;
                            break;
                        }
                    }

                    operationOutcomeResult.StatusCode = HttpStatusCode.BadRequest;
                    break;

                case BadRequestException _:
                case RequestNotValidException _:
                case BundleEntryLimitExceededException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.BadRequest;
                    break;

                case ResourceConflictException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Conflict;
                    break;

                case UnsupportedMediaTypeException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.UnsupportedMediaType;
                    break;

                case PreconditionFailedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.PreconditionFailed;
                    break;

                case InvalidSearchOperationException _:
                case SearchOperationNotSupportedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Forbidden;
                    break;

                case UnsupportedConfigurationException _:
                case AuditException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.InternalServerError;
                    break;

                case AuditHeaderException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.RequestHeaderFieldsTooLarge;
                    break;

                case OperationFailedException ofe:
                    operationOutcomeResult.StatusCode = ofe.ResponseStatusCode;
                    break;

                case OperationNotImplementedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.NotImplemented;
                    break;

                case NotAcceptableException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.NotAcceptable;
                    break;

                case TransactionFailedException tfe:
                    operationOutcomeResult.StatusCode = tfe.ResponseStatusCode;
                    break;

                case RequestEntityTooLargeException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.RequestEntityTooLarge;
                    break;
                }

                context.Result           = operationOutcomeResult;
                context.ExceptionHandled = true;
            }
            else if (context.Exception is MicrosoftHealthException microsoftHealthException)
            {
                OperationOutcomeResult healthExceptionResult;

                switch (microsoftHealthException)
                {
                case RequestRateExceededException ex:
                    healthExceptionResult = new OperationOutcomeResult(
                        new OperationOutcome
                    {
                        Id    = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                        Issue = new List <OperationOutcome.IssueComponent>
                        {
                            new OperationOutcome.IssueComponent
                            {
                                Severity    = OperationOutcome.IssueSeverity.Error,
                                Code        = OperationOutcome.IssueType.Throttled,
                                Diagnostics = ex.Message,
                            },
                        },
                    }, HttpStatusCode.BadRequest);
                    healthExceptionResult.StatusCode = HttpStatusCode.TooManyRequests;

                    if (ex.RetryAfter != null)
                    {
                        healthExceptionResult.Headers.Add(
                            RetryAfterHeaderName,
                            ex.RetryAfter.Value.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
                    }

                    break;

                default:
                    healthExceptionResult = new OperationOutcomeResult(
                        new OperationOutcome
                    {
                        Id = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                    }, HttpStatusCode.InternalServerError);
                    healthExceptionResult.StatusCode = HttpStatusCode.InternalServerError;
                    break;
                }

                context.Result           = healthExceptionResult;
                context.ExceptionHandled = true;
            }
        }
Example #11
0
        public async Task GivenAnUpdatedResourceWithWrongWeakETag_WhenUpdatingSearchParameterIndexAsync_ThenExceptionIsThrown()
        {
            ResourceElement patientResource = CreatePatientResourceElement("Patient", Guid.NewGuid().ToString());
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            SearchParameter searchParam1     = null;
            const string    searchParamName1 = "newSearchParam1";

            SearchParameter searchParam2     = null;
            const string    searchParamName2 = "newSearchParam2";

            try
            {
                searchParam1 = await CreatePatientSearchParam(searchParamName1, SearchParamType.String, "Patient.name");

                ISearchValue searchValue1 = new StringSearchValue(searchParamName1);

                (ResourceWrapper original, ResourceWrapper updatedWithSearchParam1) = await CreateUpdatedWrapperFromExistingPatient(upsertResult, searchParam1, searchValue1);

                await _dataStore.UpsertAsync(updatedWithSearchParam1, WeakETag.FromVersionId(original.Version), allowCreate : false, keepHistory : false, CancellationToken.None);

                // Let's update the resource again with new information
                searchParam2 = await CreatePatientSearchParam(searchParamName2, SearchParamType.Token, "Patient.gender");

                ISearchValue searchValue2 = new TokenSearchValue("system", "code", "text");

                // Create the updated wrapper from the original resource that has the outdated version
                (_, ResourceWrapper updatedWithSearchParam2) = await CreateUpdatedWrapperFromExistingPatient(upsertResult, searchParam2, searchValue2, original);

                // Attempt to reindex the resource
                await Assert.ThrowsAsync <PreconditionFailedException>(() => _dataStore.UpdateSearchParameterIndicesAsync(updatedWithSearchParam2, WeakETag.FromVersionId(original.Version), CancellationToken.None));
            }
            finally
            {
                if (searchParam1 != null)
                {
                    _searchParameterDefinitionManager.DeleteSearchParameter(searchParam1.ToTypedElement());
                    await _fixture.TestHelper.DeleteSearchParameterStatusAsync(searchParam1.Url, CancellationToken.None);
                }

                if (searchParam2 != null)
                {
                    _searchParameterDefinitionManager.DeleteSearchParameter(searchParam2.ToTypedElement());
                    await _fixture.TestHelper.DeleteSearchParameterStatusAsync(searchParam2.Url, CancellationToken.None);
                }
            }
        }
        public async Task <DeleteResourceResponse> Handle(DeleteResourceRequest message, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(message, nameof(message));

            var key = message.ResourceKey;

            if (!string.IsNullOrEmpty(key.VersionId))
            {
                throw new MethodNotAllowedException(Core.Resources.DeleteVersionNotAllowed);
            }

            string version = null;

            if (message.HardDelete)
            {
                await FhirDataStore.HardDeleteAsync(key, cancellationToken);
            }
            else
            {
                ResourceWrapper existing = await FhirDataStore.GetAsync(key, cancellationToken);

                version = existing?.Version;

                if (existing?.IsDeleted == false)
                {
                    var emptyInstance = (Resource)Activator.CreateInstance(ModelInfo.GetTypeForFhirType(existing.ResourceTypeName));
                    emptyInstance.Id = existing.ResourceId;

                    ResourceWrapper deletedWrapper = CreateResourceWrapper(emptyInstance, deleted: true);

                    bool keepHistory = await ConformanceProvider.Value.CanKeepHistory(key.ResourceType, cancellationToken);

                    UpsertOutcome result = await FhirDataStore.UpsertAsync(
                        deletedWrapper,
                        WeakETag.FromVersionId(existing.Version),
                        allowCreate : true,
                        keepHistory : keepHistory,
                        cancellationToken : cancellationToken);

                    version = result.Wrapper.Version;
                }
            }

            if (string.IsNullOrWhiteSpace(version))
            {
                return(new DeleteResourceResponse(new ResourceKey(key.ResourceType, key.Id)));
            }

            return(new DeleteResourceResponse(new ResourceKey(key.ResourceType, key.Id, version), WeakETag.FromVersionId(version)));
        }
Example #13
0
        public void WhenAddingStringEtag_ThenStringETagIsReturned()
        {
            var fhirResult = FhirResult.Create(_mockResource).SetETagHeader(WeakETag.FromVersionId("etag"));

            Assert.Equal("W/\"etag\"", fhirResult.Headers[HeaderNames.ETag]);
        }
Example #14
0
        private ExportJobOutcome CreateExportJobOutcome()
        {
            var exportRequest = new CreateExportRequest(new Uri($"http://localhost/ExportJob/"));

            return(new ExportJobOutcome(new ExportJobRecord(exportRequest.RequestUri, "Patient", "hash"), WeakETag.FromVersionId("0")));
        }
        public async Task GivenAResourceTypeWithVersionedUpdateVersioningPolicy_WhenPutCreatingWithAVersion_ThenAResourceNotFoundExceptionIsThrown()
        {
            // The FHIR storage fixture configures medication resources to have the "versioned-update" versioning policy
            var randomId = Guid.NewGuid().ToString();

            // Any version id on a PUT create is invalid, as we can't specify the version of a resource that does not exist
            const string invalidVersionId = "1";

            // Upserting a resource that does not already exist in the database simulates a PUT create
            // Pass in an eTag to mock a request where an invalid if-match header is provided
            var exception = await Assert.ThrowsAsync <ResourceNotFoundException>(async() => await Mediator.UpsertResourceAsync(Samples.GetDefaultMedication().UpdateId(randomId), WeakETag.FromVersionId(invalidVersionId)));

            Assert.Equal(string.Format(Core.Resources.ResourceNotFoundByIdAndVersion, KnownResourceTypes.Medication, randomId, invalidVersionId), exception.Message);
        }
Example #16
0
        public async Task <ExportJobOutcome> GetExportJobByIdAsync(string id, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNullOrWhiteSpace(id, nameof(id));

            try
            {
                ItemResponse <CosmosExportJobRecordWrapper> cosmosExportJobRecord = await _containerScope.Value.ReadItemAsync <CosmosExportJobRecordWrapper>(
                    id,
                    new PartitionKey(CosmosDbExportConstants.ExportJobPartitionKey),
                    cancellationToken : cancellationToken);

                var outcome = new ExportJobOutcome(cosmosExportJobRecord.Resource.JobRecord, WeakETag.FromVersionId(cosmosExportJobRecord.Resource.ETag));

                return(outcome);
            }
            catch (CosmosException dce)
            {
                if (dce.StatusCode == HttpStatusCode.TooManyRequests)
                {
                    throw new RequestRateExceededException(dce.RetryAfter);
                }
                else if (dce.StatusCode == HttpStatusCode.NotFound)
                {
                    throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, id));
                }

                _logger.LogError(dce, "Failed to get an export job by id.");
                throw;
            }
        }
        public async Task GivenAResourceTypeWithNoVersionVersioningPolicy_WhenSearchingHistory_ThenOnlyLatestVersionIsReturned()
        {
            // The FHIR storage fixture configures organization resources to have the "no-version" versioning policy
            RawResourceElement organizationResource = await Mediator.CreateResourceAsync(Samples.GetDefaultOrganization());

            ResourceElement newResourceValues = Samples.GetDefaultOrganization().UpdateId(organizationResource.Id);

            SaveOutcome updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(organizationResource.VersionId));

            ResourceElement historyResults = await Mediator.SearchResourceHistoryAsync(KnownResourceTypes.Organization, updateResult.RawResourceElement.Id);

            // The history bundle only has one entry because resource history is not kept
            Bundle bundle = historyResults.ToPoco <Bundle>();

            Assert.Single(bundle.Entry);

            Assert.Equal(WeakETag.FromVersionId(updateResult.RawResourceElement.VersionId).ToString(), bundle.Entry[0].Response.Etag);
        }
Example #18
0
        public async Task <IReadOnlyCollection <ReindexJobWrapper> > AcquireReindexJobsAsync(ushort maximumNumberOfConcurrentJobsAllowed, TimeSpan jobHeartbeatTimeoutThreshold, CancellationToken cancellationToken)
        {
            try
            {
                StoredProcedureExecuteResponse <IReadOnlyCollection <CosmosReindexJobRecordWrapper> > response = await _retryExceptionPolicyFactory.CreateRetryPolicy().ExecuteAsync(
                    async ct => await _acquireReindexJobs.ExecuteAsync(
                        _containerScope.Value.Scripts,
                        maximumNumberOfConcurrentJobsAllowed,
                        (ushort)jobHeartbeatTimeoutThreshold.TotalSeconds,
                        ct),
                    cancellationToken);

                return(response.Resource.Select(cosmosReindexWrapper => new ReindexJobWrapper(cosmosReindexWrapper.JobRecord, WeakETag.FromVersionId(cosmosReindexWrapper.ETag))).ToList());
            }
            catch (CosmosException dce)
            {
                if (dce.GetSubStatusCode() == HttpStatusCode.RequestEntityTooLarge)
                {
                    throw new RequestRateExceededException(null);
                }

                _logger.LogError(dce, "Failed to acquire reindex jobs.");
                throw;
            }
        }
Example #19
0
        private ExportJobOutcome CreateExportJobOutcome()
        {
            var exportRequest = new CreateExportRequest(new Uri($"http://localhost/ExportJob/"), ExportJobType.All);

            return(new ExportJobOutcome(new ExportJobRecord(exportRequest.RequestUri, exportRequest.RequestType, ExportFormatTags.ResourceName, null, null, "hash", rollingFileSizeInMB: 64), WeakETag.FromVersionId("0")));
        }
        public async Task GivenASavedResource_WhenUpsertIsAnUpdate_ThenTheExistingResourceIsUpdated()
        {
            var saveResult = await Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight"));

            var newResourceValues = Samples.GetJsonSample("WeightInGrams").ToPoco();

            newResourceValues.Id = saveResult.Resource.Id;

            var updateResult = await Mediator.UpsertResourceAsync(newResourceValues.ToResourceElement(), WeakETag.FromVersionId(saveResult.Resource.VersionId));

            Assert.NotNull(updateResult);
            Assert.Equal(SaveOutcomeType.Updated, updateResult.Outcome);

            var wrapper = await _fixture.DataStore.GetAsync(new ResourceKey("Observation", updateResult.Resource.Id), CancellationToken.None);

            Assert.NotNull(wrapper);
            Assert.False(wrapper.RawResource.IsMetaSet);
        }
        public async Task GivenANonexistentResource_WhenUpsertingWithCreateDisabledAndIntegerETagHeader_TheServerShouldReturnResourceNotFoundResponse(string versionId)
        {
            SetAllowCreate(false);

            await Assert.ThrowsAsync <ResourceNotFoundException>(async() =>
                                                                 await Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight"), WeakETag.FromVersionId(versionId)));
        }
        public async Task GivenASavedResource_WhenUpserting_ThenMetaSetIsSetToFalse()
        {
            var versionId  = Guid.NewGuid().ToString();
            var resource   = Samples.GetJsonSample("Weight").UpdateVersion(versionId);
            var saveResult = await Mediator.UpsertResourceAsync(resource);

            var newResourceValues = Samples.GetJsonSample("WeightInGrams").ToPoco();

            newResourceValues.Id = saveResult.Resource.Id;

            var updateResult = await Mediator.UpsertResourceAsync(newResourceValues.ToResourceElement(), WeakETag.FromVersionId(saveResult.Resource.VersionId));

            Assert.NotNull(updateResult);
            Assert.Equal(SaveOutcomeType.Updated, updateResult.Outcome);

            Assert.NotNull(updateResult.Resource);
            Assert.Equal(saveResult.Resource.Id, updateResult.Resource.Id);

            var wrapper = await _fixture.DataStore.GetAsync(new ResourceKey("Observation", saveResult.Resource.Id), CancellationToken.None);

            Assert.NotNull(wrapper);
            Assert.False(wrapper.RawResource.IsMetaSet);
            Assert.NotEqual(wrapper.Version, versionId);

            var deserialized = _fhirJsonParser.Parse <Observation>(wrapper.RawResource.Data);

            Assert.Equal("1", deserialized.VersionId);
        }
Example #23
0
        public async Task WhenUpsertingASavedResourceWithInvalidETagHeader_GivenR4Server_ThenPreconditionFailedIsThrown()
        {
            var saveResult = await Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight"));

            var newResourceValues = Samples.GetJsonSample("WeightInGrams").ToPoco();

            newResourceValues.Id = saveResult.Resource.Id;

            await Assert.ThrowsAsync <PreconditionFailedException>(async() =>
                                                                   await Mediator.UpsertResourceAsync(newResourceValues.ToResourceElement(), WeakETag.FromVersionId("invalidVersion")));
        }
        public async Task GivenAnUpdatedResource_WhenUpdateSearchIndexForResourceAsync_ThenResourceGetsUpdated()
        {
            ResourceElement patientResource = Samples.GetJsonSample("Patient");
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            (ResourceWrapper original, ResourceWrapper updated) = await CreateUpdatedWrapperFromExistingResource(upsertResult);

            ResourceWrapper replaceResult = await _dataStore.UpdateSearchIndexForResourceAsync(updated, WeakETag.FromVersionId(original.Version), CancellationToken.None);

            Assert.Equal(original.ResourceId, replaceResult.ResourceId);
            Assert.Equal(original.Version, replaceResult.Version);
            Assert.Equal(original.ResourceTypeName, replaceResult.ResourceTypeName);
            Assert.Equal(original.LastModified, replaceResult.LastModified);
            Assert.NotEqual((original as FhirCosmosResourceWrapper).ETag, (replaceResult as FhirCosmosResourceWrapper).ETag);
        }
Example #25
0
 public void GivenAWeakETag_WhenUsingTheWrongMethodToCreate_ThenThrow()
 {
     Assert.Throws <ArgumentException>(() => WeakETag.FromVersionId("W/\"version1\""));
 }
        public async Task GivenAnUpdatedResourceWithWrongWeakETag_WhenUpdateSearchIndexForResourceAsync_ThenExceptionIsThrown()
        {
            ResourceElement patientResource = Samples.GetJsonSample("Patient");
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            (ResourceWrapper originalWrapper, ResourceWrapper updatedWrapper) = await CreateUpdatedWrapperFromExistingResource(upsertResult);

            UpsertOutcome upsertOutcome = await _dataStore.UpsertAsync(updatedWrapper, WeakETag.FromVersionId(originalWrapper.Version), allowCreate : false, keepHistory : false, CancellationToken.None);

            // Let's update the resource again with new information.
            var searchParamInfo = new SearchParameterInfo("newSearchParam2");
            var searchIndex     = new SearchIndexEntry(searchParamInfo, new TokenSearchValue("system", "code", "text"));
            var searchIndices   = new List <SearchIndexEntry>()
            {
                searchIndex
            };

            updatedWrapper = new ResourceWrapper(
                originalWrapper.ResourceId,
                originalWrapper.Version,
                originalWrapper.ResourceTypeName,
                originalWrapper.RawResource,
                originalWrapper.Request,
                originalWrapper.LastModified,
                deleted: false,
                searchIndices,
                originalWrapper.CompartmentIndices,
                originalWrapper.LastModifiedClaims);

            // Attempt to replace resource with the old weaketag
            await Assert.ThrowsAsync <PreconditionFailedException>(() => _dataStore.UpdateSearchIndexForResourceAsync(updatedWrapper, WeakETag.FromVersionId(originalWrapper.Version), CancellationToken.None));
        }
 private ExportJobOutcome CreateExportJobOutcome(ExportJobRecord exportJobRecord, WeakETag weakETag = null)
 {
     return(new ExportJobOutcome(
                exportJobRecord,
                weakETag ?? WeakETag.FromVersionId("123")));
 }
        public async Task GivenAnUpdatedResourceWithWrongResourceId_WhenUpdateSearchIndexForResourceAsync_ThenExceptionIsThrown()
        {
            ResourceElement patientResource = Samples.GetJsonSample("Patient");
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            (ResourceWrapper original, ResourceWrapper updated) = await CreateUpdatedWrapperFromExistingResource(upsertResult, Guid.NewGuid().ToString());

            await Assert.ThrowsAsync <ResourceNotFoundException>(() => _dataStore.UpdateSearchIndexForResourceAsync(updated, WeakETag.FromVersionId(original.Version), CancellationToken.None));
        }
Example #29
0
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            EnsureArg.IsNotNull(context, nameof(context));

            if (context?.Exception == null)
            {
                return;
            }

            if (context.Exception is FhirException fhirException)
            {
                var operationOutcomeResult = new OperationOutcomeResult(
                    new OperationOutcome
                {
                    Id    = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                    Issue = fhirException.Issues.Select(x => x.ToPoco()).ToList(),
                },
                    HttpStatusCode.BadRequest);

                switch (fhirException)
                {
                case UnauthorizedFhirActionException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Forbidden;
                    break;

                case ResourceGoneException resourceGoneException:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Gone;
                    if (!string.IsNullOrEmpty(resourceGoneException.DeletedResource?.VersionId))
                    {
                        operationOutcomeResult.Headers.Add(HeaderNames.ETag, WeakETag.FromVersionId(resourceGoneException.DeletedResource.VersionId).ToString());
                    }

                    break;

                case ResourceNotFoundException _:
                case JobNotFoundException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.NotFound;
                    break;

                case JobConflictException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Conflict;
                    break;

                case MethodNotAllowedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.MethodNotAllowed;
                    break;

                case OpenIdConfigurationException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.ServiceUnavailable;
                    break;

                case ResourceNotValidException _:
                    if (context.ActionDescriptor is ControllerActionDescriptor controllerDescriptor)
                    {
                        if (controllerDescriptor.ControllerName.Equals(ValidateController, StringComparison.OrdinalIgnoreCase))
                        {
                            operationOutcomeResult.StatusCode = HttpStatusCode.OK;
                            break;
                        }
                    }

                    operationOutcomeResult.StatusCode = HttpStatusCode.BadRequest;
                    break;

                case BadRequestException _:
                case RequestNotValidException _:
                case BundleEntryLimitExceededException _:
                case ProvenanceHeaderException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.BadRequest;
                    break;

                case ResourceConflictException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Conflict;
                    break;

                case PreconditionFailedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.PreconditionFailed;
                    break;

                case InvalidSearchOperationException _:
                case SearchOperationNotSupportedException _:
                case CustomerManagedKeyException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.Forbidden;
                    break;

                case UnsupportedConfigurationException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.InternalServerError;
                    break;

                case OperationFailedException ofe:
                    operationOutcomeResult.StatusCode = ofe.ResponseStatusCode;
                    break;

                case OperationNotImplementedException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.MethodNotAllowed;
                    break;

                case NotAcceptableException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.NotAcceptable;
                    break;

                case RequestEntityTooLargeException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.RequestEntityTooLarge;
                    break;

                case FhirTransactionFailedException fhirTransactionFailedException:
                    operationOutcomeResult.StatusCode = fhirTransactionFailedException.ResponseStatusCode;
                    break;

                case AzureContainerRegistryTokenException azureContainerRegistryTokenException:
                    operationOutcomeResult.StatusCode = azureContainerRegistryTokenException.StatusCode;
                    break;

                case FetchTemplateCollectionFailedException _:
                case ConvertDataUnhandledException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.InternalServerError;
                    break;

                case ConvertDataTimeoutException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.GatewayTimeout;
                    break;

                case ConfigureCustomSearchException _:
                    operationOutcomeResult.StatusCode = HttpStatusCode.FailedDependency;
                    break;
                }

                context.Result           = operationOutcomeResult;
                context.ExceptionHandled = true;
            }
            else if (context.Exception is MicrosoftHealthException microsoftHealthException)
            {
                OperationOutcomeResult healthExceptionResult;

                switch (microsoftHealthException)
                {
                case RequestRateExceededException ex:
                    healthExceptionResult = CreateOperationOutcomeResult(ex.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.Throttled, HttpStatusCode.TooManyRequests);

                    if (ex.RetryAfter != null)
                    {
                        healthExceptionResult.Headers.AddRetryAfterHeaders(ex.RetryAfter.Value);
                    }

                    break;

                case UnsupportedMediaTypeException unsupportedMediaTypeException:
                    healthExceptionResult = CreateOperationOutcomeResult(unsupportedMediaTypeException.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.NotSupported, HttpStatusCode.UnsupportedMediaType);
                    break;

                case ServiceUnavailableException serviceUnavailableException:
                    healthExceptionResult = CreateOperationOutcomeResult(serviceUnavailableException.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.Processing, HttpStatusCode.ServiceUnavailable);
                    break;

                case TransactionFailedException transactionFailedException:
                    healthExceptionResult = CreateOperationOutcomeResult(transactionFailedException.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.Processing, HttpStatusCode.InternalServerError);
                    break;

                case AuditException _:
                    healthExceptionResult = CreateOperationOutcomeResult(microsoftHealthException.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.Invalid, HttpStatusCode.BadRequest);
                    break;

                case AuditHeaderCountExceededException _:
                case AuditHeaderTooLargeException _:
                    healthExceptionResult = CreateOperationOutcomeResult(microsoftHealthException.Message, OperationOutcome.IssueSeverity.Error, OperationOutcome.IssueType.Invalid, HttpStatusCode.RequestHeaderFieldsTooLarge);
                    break;

                default:
                    healthExceptionResult = new OperationOutcomeResult(
                        new OperationOutcome
                    {
                        Id = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId,
                    },
                        HttpStatusCode.InternalServerError);
                    break;
                }

                context.Result           = healthExceptionResult;
                context.ExceptionHandled = true;
            }
            else if (context.Exception.InnerException != null)
            {
                Exception outerException = context.Exception;
                context.Exception = outerException.InnerException;

                try
                {
                    OnActionExecuted(context);
                }
                finally
                {
                    if (!context.ExceptionHandled)
                    {
                        context.Exception = outerException;
                    }
                }
            }
        }
Example #30
0
        public async Task GivenADeletedResource_WhenUpdatingSearchParameterIndexAsync_ThenExceptionIsThrown()
        {
            ResourceElement patientResource = CreatePatientResourceElement("Patient", Guid.NewGuid().ToString());
            SaveOutcome     upsertResult    = await Mediator.UpsertResourceAsync(patientResource);

            SearchParameter searchParam     = null;
            const string    searchParamName = "newSearchParam";

            try
            {
                searchParam = await CreatePatientSearchParam(searchParamName, SearchParamType.String, "Patient.name");

                ISearchValue searchValue = new StringSearchValue(searchParamName);

                // Update the resource wrapper, adding the new search parameter
                (ResourceWrapper original, ResourceWrapper updated) = await CreateUpdatedWrapperFromExistingPatient(upsertResult, searchParam, searchValue);

                ResourceWrapper deletedWrapper = CreateDeletedWrapper(original);
                await _dataStore.UpsertAsync(deletedWrapper, WeakETag.FromVersionId(deletedWrapper.Version), allowCreate : true, keepHistory : false, CancellationToken.None);

                // Attempt to reindex the version of the resource that hasn't been deleted
                await Assert.ThrowsAsync <PreconditionFailedException>(() => _dataStore.UpdateSearchParameterIndicesAsync(updated, WeakETag.FromVersionId(updated.Version), CancellationToken.None));
            }
            finally
            {
                if (searchParam != null)
                {
                    _searchParameterDefinitionManager.DeleteSearchParameter(searchParam.ToTypedElement());
                    await _fixture.TestHelper.DeleteSearchParameterStatusAsync(searchParam.Url, CancellationToken.None);
                }
            }
        }