public async Task GivenResourceDoesNotExist_WhenHandle_ThenResourceNotFoundExceptionIsThrown()
        {
            _fhirDataStore.GetAsync(Arg.Any <ResourceKey>(), _cancellationToken).Returns(Task.FromResult <ResourceWrapper>(null));

            var request = GetReindexRequest(HttpGetName);
            await Assert.ThrowsAsync <ResourceNotFoundException>(() => _reindexHandler.Handle(request, _cancellationToken));
        }
예제 #2
0
        public async Task GivenAnUpdatedResource_WhenUpdatingSearchParameterIndexAsync_ThenResourceMetadataIsUnchanged()
        {
            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);

                (ResourceWrapper original, ResourceWrapper updated) = await CreateUpdatedWrapperFromExistingPatient(upsertResult, searchParam, searchValue);

                await _dataStore.UpdateSearchParameterIndicesAsync(updated, WeakETag.FromVersionId(original.Version), CancellationToken.None);

                // Get the reindexed resource from the database
                var             resourceKey1 = new ResourceKey(upsertResult.RawResourceElement.InstanceType, upsertResult.RawResourceElement.Id, upsertResult.RawResourceElement.VersionId);
                ResourceWrapper reindexed    = await _dataStore.GetAsync(resourceKey1, CancellationToken.None);

                VerifyReindexedResource(original, reindexed);
            }
            finally
            {
                if (searchParam != null)
                {
                    _searchParameterDefinitionManager.DeleteSearchParameter(searchParam.ToTypedElement());
                    await _fixture.TestHelper.DeleteSearchParameterStatusAsync(searchParam.Url, CancellationToken.None);
                }
            }
        }
예제 #3
0
        public async Task GivenAFhirMediator_GettingAnResourceThatIsDeleted_ThenAGoneExceptionIsThrown()
        {
            var observation = Samples.GetDefaultObservation()
                              .UpdateId("id1");

            _fhirDataStore.GetAsync(Arg.Is <ResourceKey>(x => x.Id == "id1"), Arg.Any <CancellationToken>()).Returns(CreateResourceWrapper(observation, true));

            await Assert.ThrowsAsync <ResourceGoneException>(async() => await _mediator.GetResourceAsync(new ResourceKey <Observation>("id1")));

            await _fhirDataStore.Received().GetAsync(Arg.Any <ResourceKey>(), Arg.Any <CancellationToken>());
        }
예제 #4
0
        public async Task <UpsertResourceResponse> Handle(UpsertResourceRequest request, CancellationToken cancellationToken, RequestHandlerDelegate <UpsertResourceResponse> next)
        {
            // if the resource type being updated is a SearchParameter, then we want to query the previous version before it is changed
            // because we will need to the Url property to update the definition in the SearchParameterDefinitionManager
            // and the user could be changing the Url as part of this update
            if (request.Resource.InstanceType.Equals(KnownResourceTypes.SearchParameter, StringComparison.Ordinal))
            {
                var             resourceKey             = new ResourceKey(request.Resource.InstanceType, request.Resource.Id, request.Resource.VersionId);
                ResourceWrapper prevSearchParamResource = await _fhirDataStore.GetAsync(resourceKey, cancellationToken);

                if (prevSearchParamResource != null)
                {
                    // Update the SearchParameterDefinitionManager with the new SearchParameter in order to validate any changes
                    // to the fhirpath or the datatype
                    await _searchParameterOperations.UpdateSearchParameterAsync(request.Resource.Instance, prevSearchParamResource.RawResource, cancellationToken);
                }
                else
                {
                    await _searchParameterOperations.AddSearchParameterAsync(request.Resource.Instance, cancellationToken);
                }
            }

            // Now allow the resource to updated per the normal behavior
            return(await next());
        }
        public ListSearchBehaviorTests()
        {
            _cancellationToken = _cancellationTokenSource.Token;

            var so = new SearchOptions();

            so.UnsupportedSearchParams = new Tuple <string, string> [0];

            _searchOptionsFactory = Substitute.For <ISearchOptionsFactory>();
            _searchOptionsFactory.Create(Arg.Any <string>(), Arg.Any <IReadOnlyList <Tuple <string, string> > >()).Returns(so);

            _fhirDataStore = Substitute.For <IFhirDataStore>();

            // for an 'existing list' return a list with Patients
            _fhirDataStore.GetAsync(Arg.Is <ResourceKey>(x => x.Id == "existing-list"), Arg.Any <CancellationToken>()).Returns(
                x =>
            {
                var longList           = Samples.GetDefaultList();
                var rawResourceFactory = new RawResourceFactory(new FhirJsonSerializer());
                return(new ResourceWrapper(
                           longList,
                           rawResourceFactory.Create(longList, keepMeta: true),
                           new ResourceRequest(HttpMethod.Post, "http://fhir"),
                           false,
                           null,
                           null,
                           null));
            });

            _scopedDataStore = Substitute.For <IScoped <IFhirDataStore> >();
            _scopedDataStore.Value.Returns(_fhirDataStore);

            _nonEmptyBundle = new Bundle
            {
                Type  = Bundle.BundleType.Batch,
                Entry = new List <Bundle.EntryComponent>
                {
                    new Bundle.EntryComponent
                    {
                        Resource = Samples.GetDefaultObservation().ToPoco(),
                        Request  = new Bundle.RequestComponent
                        {
                            Method = Bundle.HTTPVerb.POST,
                            Url    = "Observation",
                        },
                    },
                    new Bundle.EntryComponent
                    {
                        Request = new Bundle.RequestComponent
                        {
                            Method = Bundle.HTTPVerb.GET,
                            Url    = "Patient?name=peter",
                        },
                    },
                },
            }.ToResourceElement();

            _bundleFactory = Substitute.For <IBundleFactory>();
            _bundleFactory.CreateSearchBundle(Arg.Any <SearchResult>()).Returns(_nonEmptyBundle);
        }
        public async Task <UpsertResourceResponse> Handle(UpsertResourceRequest request, CancellationToken cancellationToken, RequestHandlerDelegate <UpsertResourceResponse> next)
        {
            ResourceWrapper prevSearchParamResource = null;
            bool            isSearchParameter       = false;

            // if the resource type being updated is a SearchParaemter, then we want to query the previous version before it is changed
            // because we will need to the Url property to update the definition in the SearchParameterDefinitionManager
            // and the user could be changing the Url as part of this update
            if (request.Resource.InstanceType.Equals(KnownResourceTypes.SearchParameter, StringComparison.Ordinal))
            {
                isSearchParameter = true;
                var resourceKey = new ResourceKey(request.Resource.InstanceType, request.Resource.Id, request.Resource.VersionId);
                prevSearchParamResource = await _fhirDataStore.GetAsync(resourceKey, cancellationToken);
            }

            // Now allow the resource to updated per the normal behavior
            var response = await next();

            if (isSearchParameter)
            {
                // Once the SearchParameter resource is update in the data store, we will update
                // the metadata in the SearchParameterDefinitionManager
                await _searchParameterOperations.UpdateSearchParameterAsync(request.Resource.Instance, prevSearchParamResource.RawResource);
            }

            return(response);
        }
예제 #7
0
        public async Task GivenADeleteResourceRequest_WhenDeletingAResourceOtherThanSearchParameter_ThenNoCallToDeleteParameterMade()
        {
            var resource = Samples.GetDefaultObservation().UpdateId("id1");

            var key     = new ResourceKey("Observation", "id1");
            var request = new DeleteResourceRequest(key, DeleteOperation.SoftDelete);
            var wrapper = CreateResourceWrapper(resource, false);

            _fhirDataStore.GetAsync(key, Arg.Any <CancellationToken>()).Returns(wrapper);

            var response = new DeleteResourceResponse(key);

            var behavior = new DeleteSearchParameterBehavior <DeleteResourceRequest, DeleteResourceResponse>(_searchParameterOperations, _fhirDataStore);
            await behavior.Handle(request, CancellationToken.None, async() => await Task.Run(() => response));

            // Ensure for non-SearchParameter, that we do not call Add SearchParameter
            await _searchParameterOperations.DidNotReceive().DeleteSearchParameterAsync(Arg.Any <RawResource>());
        }
예제 #8
0
        public async Task <ReindexSingleResourceResponse> Handle(ReindexSingleResourceRequest request, CancellationToken cancellationToken)
        {
            EnsureArg.IsNotNull(request, nameof(request));

            if (await _authorizationService.CheckAccess(DataActions.Reindex) != DataActions.Reindex)
            {
                throw new UnauthorizedFhirActionException();
            }

            var             key            = new ResourceKey(request.ResourceType, request.ResourceId);
            ResourceWrapper storedResource = await _fhirDataStore.GetAsync(key, cancellationToken);

            if (storedResource == null)
            {
                throw new ResourceNotFoundException(string.Format(Core.Resources.ResourceNotFoundById, request.ResourceType, request.ResourceId));
            }

            // We need to extract the "new" search indices since the assumption is that
            // a new search parameter has been added to the fhir server.
            ResourceElement resourceElement = _resourceDeserializer.Deserialize(storedResource);
            IReadOnlyCollection <SearchIndexEntry> newIndices = _searchIndexer.Extract(resourceElement);

            // Create a new parameter resource and include the new search indices and the corresponding values.
            var parametersResource = new Parameters
            {
                Id        = Guid.NewGuid().ToString(),
                VersionId = "1",
                Parameter = new List <Parameters.ParameterComponent>(),
            };

            parametersResource.Parameter.Add(new Parameters.ParameterComponent()
            {
                Name = "originalResourceId", Value = new FhirString(request.ResourceId)
            });
            parametersResource.Parameter.Add(new Parameters.ParameterComponent()
            {
                Name = "originalResourceType", Value = new FhirString(request.ResourceType)
            });

            foreach (SearchIndexEntry searchIndex in newIndices)
            {
                parametersResource.Parameter.Add(new Parameters.ParameterComponent()
                {
                    Name = searchIndex.SearchParameter.Name.ToString(), Value = new FhirString(searchIndex.Value.ToString())
                });
            }

            return(new ReindexSingleResourceResponse(parametersResource.ToResourceElement()));
        }
        public async Task <TDeleteResourceResponse> Handle(TDeleteResourceRequest request, CancellationToken cancellationToken, RequestHandlerDelegate <TDeleteResourceResponse> next)
        {
            var             deleteRequest       = request as DeleteResourceRequest;
            ResourceWrapper searchParamResource = null;

            if (deleteRequest.ResourceKey.ResourceType.Equals(KnownResourceTypes.SearchParameter, StringComparison.Ordinal))
            {
                searchParamResource = await _fhirDataStore.GetAsync(deleteRequest.ResourceKey, cancellationToken);
            }

            var response = await next();

            if (searchParamResource != null && searchParamResource.IsDeleted == false)
            {
                // Once the SearchParameter resource is removed from the data store, we can update the in
                // memory SearchParameterDefinitionManager, and remove the status metadata from the data store
                await _searchParameterOperations.DeleteSearchParameterAsync(searchParamResource.RawResource);
            }

            return(response);
        }
예제 #10
0
        public async Task <ResourceElement> SearchHistoryAsync(
            string resourceType,
            string resourceId,
            PartialDateTime at,
            PartialDateTime since,
            PartialDateTime before,
            int?count,
            string continuationToken,
            CancellationToken cancellationToken)
        {
            var queryParameters = new List <Tuple <string, string> >();

            if (at != null)
            {
                if (since != null)
                {
                    // _at and _since cannot be both specified.
                    throw new InvalidSearchOperationException(
                              string.Format(
                                  CultureInfo.InvariantCulture,
                                  Core.Resources.AtCannotBeSpecifiedWithBeforeOrSince,
                                  KnownQueryParameterNames.At,
                                  KnownQueryParameterNames.Since));
                }

                if (before != null)
                {
                    // _at and _since cannot be both specified.
                    throw new InvalidSearchOperationException(
                              string.Format(
                                  CultureInfo.InvariantCulture,
                                  Core.Resources.AtCannotBeSpecifiedWithBeforeOrSince,
                                  KnownQueryParameterNames.At,
                                  KnownQueryParameterNames.Before));
                }
            }

            if (before != null)
            {
                var beforeOffset = before.ToDateTimeOffset(
                    defaultMonth: 1,
                    defaultDaySelector: (year, month) => 1,
                    defaultHour: 0,
                    defaultMinute: 0,
                    defaultSecond: 0,
                    defaultFraction: 0.0000000m,
                    defaultUtcOffset: TimeSpan.Zero).ToUniversalTime();

                if (beforeOffset.CompareTo(Clock.UtcNow) > 0)
                {
                    // you cannot specify a value for _before in the future
                    throw new InvalidSearchOperationException(
                              string.Format(
                                  CultureInfo.InvariantCulture,
                                  Core.Resources.HistoryParameterBeforeCannotBeFuture,
                                  KnownQueryParameterNames.Before));
                }
            }

            bool searchByResourceId = !string.IsNullOrEmpty(resourceId);

            if (searchByResourceId)
            {
                queryParameters.Add(Tuple.Create(SearchParameterNames.Id, resourceId));
            }

            if (!string.IsNullOrEmpty(continuationToken))
            {
                queryParameters.Add(Tuple.Create(KnownQueryParameterNames.ContinuationToken, continuationToken));
            }

            if (at != null)
            {
                queryParameters.Add(Tuple.Create(SearchParameterNames.LastUpdated, at.ToString()));
            }
            else
            {
                if (since != null)
                {
                    queryParameters.Add(Tuple.Create(SearchParameterNames.LastUpdated, $"ge{since}"));
                }

                if (before != null)
                {
                    queryParameters.Add(Tuple.Create(SearchParameterNames.LastUpdated, $"lt{before}"));
                }
            }

            if (count.HasValue && count > 0)
            {
                queryParameters.Add(Tuple.Create(KnownQueryParameterNames.Count, count.ToString()));
            }

            SearchOptions searchOptions =
                !string.IsNullOrEmpty(resourceType)
                    ? _searchOptionsFactory.Create(resourceType, queryParameters)
                    : _searchOptionsFactory.Create(queryParameters);

            SearchResult searchResult = await SearchHistoryInternalAsync(searchOptions, cancellationToken);

            // If no results are returned from the _history search
            // determine if the resource actually exists or if the results were just filtered out.
            // The 'deleted' state has no effect because history will return deleted resources
            if (searchByResourceId && searchResult.Results.Any() == false)
            {
                var resource = await _fhirDataStore.GetAsync(new ResourceKey(resourceType, resourceId), cancellationToken);

                if (resource == null)
                {
                    throw new ResourceNotFoundException(string.Format(Core.Resources.ResourceNotFoundById, resourceType, resourceId));
                }
            }

            return(_bundleFactory.CreateHistoryBundle(
                       unsupportedSearchParams: null,
                       result: searchResult));
        }
예제 #11
0
        private async Task <(ResourceWrapper original, ResourceWrapper updated)> CreateUpdatedWrapperFromExistingResource(
            SaveOutcome upsertResult,
            string updatedId = null)
        {
            // Get wrapper from data store directly
            ResourceKey resourceKey = new ResourceKey(upsertResult.RawResourceElement.InstanceType, upsertResult.RawResourceElement.Id, upsertResult.RawResourceElement.VersionId);
            FhirCosmosResourceWrapper originalWrapper = (FhirCosmosResourceWrapper)await _dataStore.GetAsync(resourceKey, CancellationToken.None);

            // Add new search index entry to existing wrapper.
            SearchParameterInfo     searchParamInfo = new SearchParameterInfo("newSearchParam");
            SearchIndexEntry        searchIndex     = new SearchIndexEntry(searchParamInfo, new NumberSearchValue(12));
            List <SearchIndexEntry> searchIndices   = new List <SearchIndexEntry>()
            {
                searchIndex
            };

            var updatedWrapper = new ResourceWrapper(
                updatedId == null ? originalWrapper.Id : updatedId,
                originalWrapper.Version,
                originalWrapper.ResourceTypeName,
                originalWrapper.RawResource,
                originalWrapper.Request,
                originalWrapper.LastModified,
                deleted: false,
                searchIndices,
                originalWrapper.CompartmentIndices,
                originalWrapper.LastModifiedClaims);

            return(originalWrapper, updatedWrapper);
        }
예제 #12
0
        public async Task <Bundle> SearchHistoryAsync(
            string resourceType,
            string resourceId,
            PartialDateTime at,
            PartialDateTime since,
            int?count,
            string continuationToken,
            CancellationToken cancellationToken)
        {
            var queryParameters = new List <Tuple <string, string> >();

            if (at != null && since != null)
            {
                // _at and _since cannot be both specified.
                throw new InvalidSearchOperationException(
                          string.Format(
                              CultureInfo.InvariantCulture,
                              Core.Resources.AtAndSinceCannotBeBothSpecified,
                              KnownQueryParameterNames.At,
                              KnownQueryParameterNames.Since));
            }

            bool searchByResourceId = !string.IsNullOrEmpty(resourceId);

            if (searchByResourceId)
            {
                queryParameters.Add(Tuple.Create(SearchParameterNames.Id, resourceId));
            }

            if (!string.IsNullOrEmpty(continuationToken))
            {
                queryParameters.Add(Tuple.Create(KnownQueryParameterNames.ContinuationToken, continuationToken));
            }

            if (at != null)
            {
                queryParameters.Add(Tuple.Create(SearchParameterNames.LastUpdated, at.ToString()));
            }
            else if (since != null)
            {
                queryParameters.Add(Tuple.Create(SearchParameterNames.LastUpdated, $"ge{since}"));
            }

            if (count.HasValue && count > 0)
            {
                queryParameters.Add(Tuple.Create(KnownQueryParameterNames.Count, count.ToString()));
            }

            SearchOptions searchOptions =
                !string.IsNullOrEmpty(resourceType)
                    ? _searchOptionsFactory.Create(resourceType, queryParameters)
                    : _searchOptionsFactory.Create(queryParameters);

            SearchResult searchResult = await SearchHistoryInternalAsync(searchOptions, cancellationToken);

            // If no results are returned from the _history search
            // determine if the resource actually exists or if the results were just filtered out.
            // The 'deleted' state has no effect because history will return deleted resources
            if (searchByResourceId && searchResult.Results.Any() == false)
            {
                var resource = await _fhirDataStore.GetAsync(new ResourceKey(resourceType, resourceId), cancellationToken);

                if (resource == null)
                {
                    throw new ResourceNotFoundException(string.Format(Core.Resources.ResourceNotFoundById, resourceType, resourceId));
                }
            }

            return(_bundleFactory.CreateHistoryBundle(
                       unsupportedSearchParams: null,
                       result: searchResult));
        }