public async Task UpdatingAResourceWithNoHistory_ThenWeCannotAccessHistoricValues() { var saveResult = await Mediator.UpsertResourceAsync(Samples.GetDefaultOrganization()); var newResourceValues = Samples.GetDefaultOrganization() .UpdateId(saveResult.RawResourceElement.Id); var updateResult = await Mediator.UpsertResourceAsync(newResourceValues, WeakETag.FromVersionId(saveResult.RawResourceElement.VersionId)); await Assert.ThrowsAsync <ResourceNotFoundException>( () => Mediator.GetResourceAsync(new ResourceKey <Organization>(saveResult.RawResourceElement.Id, saveResult.RawResourceElement.VersionId))); }
public ConditionalPatchResourceRequest( string resourceType, JsonPatchDocument patchDocument, IReadOnlyList <Tuple <string, string> > conditionalParameters, WeakETag weakETag = null) : base(resourceType, conditionalParameters) { EnsureArg.IsNotNull(patchDocument, nameof(patchDocument)); PatchDocument = patchDocument; WeakETag = weakETag; }
public ConditionalPatchResourceRequest( string resourceType, PatchPayload payload, IReadOnlyList <Tuple <string, string> > conditionalParameters, WeakETag weakETag = null) : base(resourceType, conditionalParameters) { EnsureArg.IsNotNull(payload, nameof(payload)); Payload = payload; WeakETag = weakETag; }
public Bundle CreateHistoryBundle(IEnumerable <Tuple <string, string> > unsupportedSearchParameters, SearchResult result) { // Create the bundle from the result. var bundle = new Bundle(); if (result != null) { IEnumerable <Bundle.EntryComponent> entries = result.Results.Select(r => { Resource resource = _deserializer.Deserialize(r); var hasVerb = Enum.TryParse(r.Request?.Method, true, out Bundle.HTTPVerb httpVerb); return(new Bundle.EntryComponent { FullUrlElement = new FhirUri(_urlResolver.ResolveResourceUrl(resource, true)), Resource = resource, Request = new Bundle.RequestComponent { Method = hasVerb ? (Bundle.HTTPVerb?)httpVerb: null, Url = r.Request?.Url?.ToString(), }, Response = new Bundle.ResponseComponent { LastModified = r.LastModified, Etag = WeakETag.FromVersionId(r.Version).ToString(), }, }); }); bundle.Entry.AddRange(entries); if (result.ContinuationToken != null) { bundle.NextLink = _urlResolver.ResolveRouteUrl( unsupportedSearchParameters, result.ContinuationToken); } } // Add the self link to indicate which search parameters were used. bundle.SelfLink = _urlResolver.ResolveRouteUrl(unsupportedSearchParameters); bundle.Id = _fhirRequestContextAccessor.FhirRequestContext.CorrelationId; bundle.Type = Bundle.BundleType.History; bundle.Total = result?.TotalCount; bundle.Meta = new Meta { LastUpdated = Clock.UtcNow, }; return(bundle); }
public async Task <ReindexJobWrapper> UpdateReindexJobAsync(ReindexJobRecord jobRecord, WeakETag eTag, CancellationToken cancellationToken) { EnsureArg.IsNotNull(jobRecord, nameof(jobRecord)); var cosmosReindexJob = new CosmosReindexJobRecordWrapper(jobRecord); var requestOptions = new RequestOptions() { PartitionKey = new PartitionKey(CosmosDbReindexConstants.ReindexJobPartitionKey), }; // Create access condition so that record is replaced only if eTag matches. if (eTag != null) { requestOptions.AccessCondition = new AccessCondition() { Type = AccessConditionType.IfMatch, Condition = eTag.VersionId, }; } try { ResourceResponse <Document> replaceResult = await _retryExceptionPolicyFactory.CreateRetryPolicy().ExecuteAsync( () => _documentClientScope.Value.ReplaceDocumentAsync( UriFactory.CreateDocumentUri(DatabaseId, CollectionId, jobRecord.Id), cosmosReindexJob, requestOptions, cancellationToken)); var latestETag = replaceResult.Resource.ETag; return(new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId(latestETag))); } catch (DocumentClientException dce) { if (dce.StatusCode == HttpStatusCode.TooManyRequests) { throw new RequestRateExceededException(dce.RetryAfter); } else if (dce.StatusCode == HttpStatusCode.PreconditionFailed) { throw new JobConflictException(); } else if (dce.StatusCode == HttpStatusCode.NotFound) { throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobRecord.Id)); } _logger.LogError(dce, "Failed to update a reindex job."); throw; } }
public async Task UpdatingAResourceWithNoHistory_ThenWeCannotAccessHistoricValues() { var saveResult = await FhirRepository.UpsertAsync(Samples.GetDefaultOrganization()); var newResourceValues = Samples.GetDefaultOrganization(); newResourceValues.Id = saveResult.Resource.Id; var updateResult = await FhirRepository.UpsertAsync(newResourceValues, WeakETag.FromVersionId(saveResult.Resource.VersionId)); await Assert.ThrowsAsync <ResourceNotFoundException>( () => FhirRepository.GetAsync(new ResourceKey <Organization>(saveResult.Resource.Id, saveResult.Resource.VersionId))); }
/// <summary> /// For each result in a batch of resources this will extract new search params /// Then compare those to the old values to determine if a change is needed /// Needed updates will be processed in a batch /// </summary> /// <param name="results">The resource batch to process</param> /// <param name="searchParamHash">the current hash value of the search parameters</param> /// <param name="cancellationToken">Cancellation token</param> /// <returns>A Task</returns> public async Task ProcessSearchResultsAsync(SearchResult results, string searchParamHash, CancellationToken cancellationToken) { // TODO: placeholder, will be updated to extract new indices, compare with old values and update indices // It should update only the indices and search parameter hash as a bulk operation, and not affect lastUpdated timestamp using (IScoped <IFhirDataStore> store = _fhirDataStoreFactory()) { foreach (var result in results.Results) { result.Resource.SearchParameterHash = searchParamHash; await store.Value.UpsertAsync(result.Resource, WeakETag.FromVersionId(result.Resource.Version), false, true, cancellationToken); } } }
public async Task GivenAGetRequest_WhenIdNotFound_ThenJobNotFoundExceptionThrown() { var request = new GetReindexRequest("id"); var jobRecord = new ReindexJobRecord("hash", 1, null); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", Arg.Any <CancellationToken>()).Throws(new JobNotFoundException("not found")); var handler = new GetReindexRequestHandler(_fhirOperationDataStore, DisabledFhirAuthorizationService.Instance); await Assert.ThrowsAsync <JobNotFoundException>(() => handler.Handle(request, CancellationToken.None)); }
public async Task <DeleteResourceResponse> Handle(DeleteResourceRequest message, CancellationToken cancellationToken) { EnsureArg.IsNotNull(message, nameof(message)); var deletedResourceKey = await _repository.DeleteAsync(message.ResourceKey, message.HardDelete, cancellationToken); if (string.IsNullOrWhiteSpace(deletedResourceKey.VersionId)) { return(new DeleteResourceResponse()); } return(new DeleteResourceResponse(WeakETag.FromVersionId(deletedResourceKey.VersionId))); }
public async Task GivenAGetRequest_WhenTooManyRequestsThrown_ThenTooManyRequestsThrown() { var request = new GetReindexRequest("id"); var jobRecord = new ReindexJobRecord("hash", 1, null); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", CancellationToken.None).Throws(new RequestRateExceededException(TimeSpan.FromMilliseconds(100))); var handler = new GetReindexRequestHandler(_fhirOperationDataStore, DisabledFhirAuthorizationService.Instance); await Assert.ThrowsAsync <RequestRateExceededException>(() => handler.Handle(request, CancellationToken.None)); }
public async Task GivenADeletedIdAndVersionId_WhenGettingAResource_TheServerShouldReturnAGoneStatus() { Observation createdResource = await _client.CreateAsync(Samples.GetDefaultObservation().ToPoco <Observation>()); using FhirResponse deleteResponse = await _client.DeleteAsync(createdResource); var deletedVersion = WeakETag.FromWeakETag(deleteResponse.Headers.ETag.ToString()).VersionId; using FhirException ex = await Assert.ThrowsAsync <FhirException>( () => _client.VReadAsync <Observation>(ResourceType.Observation, createdResource.Id, deletedVersion)); Assert.Equal(System.Net.HttpStatusCode.Gone, ex.StatusCode); }
public async Task <UpsertResourceResponse> Handle(ConditionalUpsertResourceRequest message, CancellationToken cancellationToken = default) { EnsureArg.IsNotNull(message, nameof(message)); if (await AuthorizationService.CheckAccess(DataActions.Read | DataActions.Write) != (DataActions.Read | DataActions.Write)) { throw new UnauthorizedFhirActionException(); } SearchResultEntry[] matchedResults = await Search(message.Resource.InstanceType, message.ConditionalParameters, cancellationToken); int count = matchedResults.Length; if (count == 0) { if (string.IsNullOrEmpty(message.Resource.Id)) { // No matches, no id provided: The server creates the resource // TODO: There is a potential contention issue here in that this could create another new resource with a different id return(await _mediator.Send <UpsertResourceResponse>(new CreateResourceRequest(message.Resource), cancellationToken)); } else { // No matches, id provided: The server treats the interaction as an Update as Create interaction (or rejects it, if it does not support Update as Create) // TODO: There is a potential contention issue here that this could replace an existing resource return(await _mediator.Send <UpsertResourceResponse>(new UpsertResourceRequest(message.Resource), cancellationToken)); } } else if (count == 1) { ResourceWrapper resourceWrapper = matchedResults.First().Resource; Resource resource = message.Resource.ToPoco(); WeakETag version = WeakETag.FromVersionId(resourceWrapper.Version); // One Match, no resource id provided OR (resource id provided and it matches the found resource): The server performs the update against the matching resource if (string.IsNullOrEmpty(resource.Id) || string.Equals(resource.Id, resourceWrapper.ResourceId, StringComparison.Ordinal)) { resource.Id = resourceWrapper.ResourceId; return(await _mediator.Send <UpsertResourceResponse>(new UpsertResourceRequest(resource.ToResourceElement(), version), cancellationToken)); } else { throw new BadRequestException(string.Format(Core.Resources.ConditionalUpdateMismatchedIds, resourceWrapper.ResourceId, resource.Id)); } } else { // Multiple matches: The server returns a 412 Precondition Failed error indicating the client's criteria were not selective enough throw new PreconditionFailedException(Core.Resources.ConditionalOperationNotSelectiveEnough); } }
public async void GivenAFhirMediator_WhenGettingAnExistingExportJobWithNotCompletedStatus_ThenHttpResponseCodeShouldBeAccepted() { var jobRecord = new ExportJobRecord(new Uri(CreateRequestUrl)); jobRecord.Status = OperationStatus.Running; var jobOutcome = new ExportJobOutcome(jobRecord, WeakETag.FromVersionId("eTag")); _fhirDataStore.GetExportJobAsync(jobRecord.Id, Arg.Any <CancellationToken>()).Returns(jobOutcome); var result = await _mediator.GetExportStatusAsync(new Uri(CreateRequestUrl), jobRecord.Id); Assert.Equal(HttpStatusCode.Accepted, result.StatusCode); Assert.Null(result.JobResult); }
private static byte[] GetRowVersionAsBytes(WeakETag eTag) { // The SQL rowversion data type is 8 bytes in length. var versionAsBytes = new byte[8]; BitConverter.TryWriteBytes(versionAsBytes, int.Parse(eTag.VersionId)); if (BitConverter.IsLittleEndian) { Array.Reverse(versionAsBytes); } return(versionAsBytes); }
public async Task GivenAnOldVersionOfAJob_WhenUpdatingTheJob_ThenJobConflictExceptionShouldBeThrown() { ExportJobOutcome jobOutcome = await CreateRunningJob(); ExportJobRecord job = jobOutcome.JobRecord; // Update the job for a first time. This should not fail. job.Status = OperationStatus.Completed; WeakETag jobVersion = jobOutcome.ETag; await _operationDataStore.UpdateExportJobAsync(job, jobVersion, CancellationToken.None); // Attempt to update the job a second time with the old version. await Assert.ThrowsAsync <JobConflictException>(() => _operationDataStore.UpdateExportJobAsync(job, jobVersion, 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))); }
public async Task GivenAGetRequest_WhenGettingAnExistingJob_ThenHttpResponseCodeShouldBeOk() { var request = new GetReindexRequest("id"); var jobRecord = new ReindexJobRecord("hash", 1, null); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", Arg.Any <CancellationToken>()).Returns(jobWrapper); var handler = new GetReindexRequestHandler(_fhirOperationDataStore, DisabledFhirAuthorizationService.Instance); var result = await handler.Handle(request, CancellationToken.None); Assert.Equal(HttpStatusCode.OK, result.StatusCode); }
public async Task GivenAGetRequest_WhenTooManyRequestsThrown_ThenTooManyRequestsThrown() { var request = new GetReindexRequest("id"); var jobRecord = new ReindexJobRecord(_resourceTypeSearchParameterHashMap, 1); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", CancellationToken.None).Throws(new Exception(null, new RequestRateExceededException(TimeSpan.FromMilliseconds(100)))); var handler = new GetReindexRequestHandler(_fhirOperationDataStore, DisabledFhirAuthorizationService.Instance); Exception thrownException = await Assert.ThrowsAsync <Exception>(() => handler.Handle(request, CancellationToken.None)); Assert.IsType <RequestRateExceededException>(thrownException.InnerException); }
private async System.Threading.Tasks.Task ProcessPostReindexSingleResourceRequest(ResourceWrapper originalResource, IReadOnlyCollection <SearchIndexEntry> searchIndices) { ResourceWrapper updatedResource = new ResourceWrapper( originalResource.ResourceId, originalResource.Version, originalResource.ResourceTypeName, originalResource.RawResource, originalResource.Request, originalResource.LastModified, deleted: false, searchIndices, originalResource.CompartmentIndices, originalResource.LastModifiedClaims); await _fhirDataStore.UpdateSearchIndexForResourceAsync(updatedResource, WeakETag.FromVersionId(originalResource.Version), CancellationToken.None); }
public async Task GivenASavedResource_WhenUpserting_ThenTheExistingResourceIsUpdated() { var saveResult = await Mediator.UpsertResourceAsync(Samples.GetJsonSample("Weight")); var newResourceValues = Samples.GetJsonSample("WeightInGrams"); newResourceValues.Id = saveResult.Resource.Id; var updateResult = await Mediator.UpsertResourceAsync(newResourceValues, 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); }
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 GivenACancelRequest_WhenUserUnauthorized_ThenUnauthorizedFhirExceptionThrown() { var request = new CancelReindexRequest("id"); var jobRecord = new ReindexJobRecord("hash", 1, null); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", Arg.Any <CancellationToken>()).Returns(jobWrapper); var authorizationService = Substitute.For <IFhirAuthorizationService>(); authorizationService.CheckAccess(DataActions.Reindex).Returns(DataActions.None); var handler = new CancelReindexRequestHandler(_fhirOperationDataStore, authorizationService); await Assert.ThrowsAsync <UnauthorizedFhirActionException>(() => handler.Handle(request, CancellationToken.None)); }
private async Task UpdateJobRecordAsync(CancellationToken cancellationToken) { foreach (OperationOutcomeIssue issue in _contextAccessor.FhirRequestContext.BundleIssues) { _exportJobRecord.Issues.Add(issue); } using (IScoped <IFhirOperationDataStore> fhirOperationDataStore = _fhirOperationDataStoreFactory()) { ExportJobOutcome updatedExportJobOutcome = await fhirOperationDataStore.Value.UpdateExportJobAsync(_exportJobRecord, _weakETag, cancellationToken); _exportJobRecord = updatedExportJobOutcome.JobRecord; _weakETag = updatedExportJobOutcome.ETag; _contextAccessor.FhirRequestContext.BundleIssues.Clear(); } }
public async Task GivenACancelRequest_WhenJobCompleted_ThenRequestNotValidExceptionThrown() { var request = new CancelReindexRequest("id"); var jobRecord = new ReindexJobRecord("hash", 1, null) { Status = OperationStatus.Completed, }; var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", Arg.Any <CancellationToken>()).Returns(jobWrapper); var handler = new CancelReindexRequestHandler(_fhirOperationDataStore, DisabledFhirAuthorizationService.Instance); await Assert.ThrowsAsync <RequestNotValidException>(() => handler.Handle(request, CancellationToken.None)); }
public override async Task <UpsertResourceResponse> HandleSingleMatch(ConditionalUpsertResourceRequest request, SearchResultEntry match, CancellationToken cancellationToken) { ResourceWrapper resourceWrapper = match.Resource; Resource resource = request.Resource.ToPoco(); var version = WeakETag.FromVersionId(resourceWrapper.Version); // One Match, no resource id provided OR (resource id provided and it matches the found resource): The server performs the update against the matching resource if (string.IsNullOrEmpty(resource.Id) || string.Equals(resource.Id, resourceWrapper.ResourceId, StringComparison.Ordinal)) { resource.Id = resourceWrapper.ResourceId; return(await _mediator.Send <UpsertResourceResponse>(new UpsertResourceRequest(resource.ToResourceElement(), version), cancellationToken)); } else { throw new BadRequestException(string.Format(Core.Resources.ConditionalUpdateMismatchedIds, resourceWrapper.ResourceId, resource.Id)); } }
public async Task GivenAGetRequest_WhenUserUnauthorized_ThenUnauthorizedFhirExceptionThrown() { var request = new GetReindexRequest("id"); var jobRecord = new ReindexJobRecord(_resourceTypeSearchParameterHashMap, new List <string>(), 1); var jobWrapper = new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId("id")); _fhirOperationDataStore.GetReindexJobByIdAsync("id", Arg.Any <CancellationToken>()).Returns(jobWrapper); var authorizationService = Substitute.For <IAuthorizationService <DataActions> >(); authorizationService.CheckAccess(DataActions.Reindex, Arg.Any <CancellationToken>()).Returns(DataActions.None); var handler = new GetReindexRequestHandler(_fhirOperationDataStore, authorizationService); await Assert.ThrowsAsync <UnauthorizedFhirActionException>(() => handler.Handle(request, CancellationToken.None)); }
public async Task <ReindexJobWrapper> UpdateReindexJobAsync(ReindexJobRecord jobRecord, WeakETag eTag, CancellationToken cancellationToken) { EnsureArg.IsNotNull(jobRecord, nameof(jobRecord)); var cosmosReindexJob = new CosmosReindexJobRecordWrapper(jobRecord); var requestOptions = new ItemRequestOptions(); // Create access condition so that record is replaced only if eTag matches. if (eTag != null) { requestOptions.IfMatchEtag = eTag.VersionId; } try { var replaceResult = await _retryExceptionPolicyFactory.CreateRetryPolicy().ExecuteAsync( () => _containerScope.Value.ReplaceItemAsync( cosmosReindexJob, jobRecord.Id, new PartitionKey(CosmosDbReindexConstants.ReindexJobPartitionKey), requestOptions, cancellationToken)); var latestETag = replaceResult.Resource.ETag; return(new ReindexJobWrapper(jobRecord, WeakETag.FromVersionId(latestETag))); } catch (CosmosException dce) { if (dce.IsRequestRateExceeded()) { throw; } else if (dce.StatusCode == HttpStatusCode.PreconditionFailed) { throw new JobConflictException(); } else if (dce.StatusCode == HttpStatusCode.NotFound) { throw new JobNotFoundException(string.Format(Core.Resources.JobNotFound, jobRecord.Id)); } _logger.LogError(dce, "Failed to update a reindex job."); 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); }
public Task BindModelAsync(ModelBindingContext bindingContext) { EnsureArg.IsNotNull(bindingContext, nameof(bindingContext)); var suppliedWeakETag = bindingContext.HttpContext.Request.Headers[HeaderNames.IfMatch]; WeakETag model = null; if (!string.IsNullOrWhiteSpace(suppliedWeakETag)) { model = WeakETag.FromWeakETag(suppliedWeakETag); } bindingContext.Model = model; bindingContext.Result = ModelBindingResult.Success(model); return(Task.CompletedTask); }
public ResourceElement CreateHistoryBundle(SearchResult result) { return(CreateBundle(result, Bundle.BundleType.History, r => { var resource = new RawBundleEntryComponent(r.Resource); var hasVerb = Enum.TryParse(r.Resource.Request?.Method, true, out Bundle.HTTPVerb httpVerb); resource.FullUrlElement = new FhirUri(_urlResolver.ResolveResourceWrapperUrl(r.Resource, true)); resource.Request = new Bundle.RequestComponent { Method = hasVerb ? httpVerb : null, Url = hasVerb ? $"{r.Resource.ResourceTypeName}/{(httpVerb == Bundle.HTTPVerb.POST ? null : r.Resource.ResourceId)}" : null, }; string statusString; switch (httpVerb) { case Bundle.HTTPVerb.POST: statusString = ((int)HttpStatusCode.Created).ToString() + " " + HttpStatusCode.Created; break; case Bundle.HTTPVerb.PUT: case Bundle.HTTPVerb.GET: statusString = ((int)HttpStatusCode.OK).ToString() + " " + HttpStatusCode.OK; break; case Bundle.HTTPVerb.DELETE: statusString = ((int)HttpStatusCode.NoContent).ToString() + " " + HttpStatusCode.NoContent; break; default: throw new NotImplementedException(); } resource.Response = new Bundle.ResponseComponent { Status = statusString, LastModified = r.Resource.LastModified, Etag = WeakETag.FromVersionId(r.Resource.Version).ToString(), }; return resource; })); }