public async Task AddPartitionKeyRangeToContinuationTokenOnNotNullBackendContinuation() { ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" }, null); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <bool>() )).Returns(Task.FromResult <IReadOnlyList <PartitionKeyRange> >(null)).Verifiable(); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); DictionaryNameValueCollection headers = new DictionaryNameValueCollection(); headers.Add(HttpConstants.HttpHeaders.Continuation, "something"); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, null, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, RntdbEnumerationDirection.Reverse ); Assert.IsTrue(true); routingMapProvider.Verify(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <bool>() ), Times.Never); }
public DefaultDocumentQueryExecutionContext( IDocumentQueryClient client, ResourceType resourceTypeEnum, Type resourceType, Expression expression, FeedOptions feedOptions, string resourceLink, bool isContinuationExpected, Guid correlatedActivityId) : base( client, resourceTypeEnum, resourceType, expression, feedOptions, resourceLink, false, correlatedActivityId) { this.isContinuationExpected = isContinuationExpected; this.fetchSchedulingMetrics = new SchedulingStopwatch(); this.fetchSchedulingMetrics.Ready(); this.providedRangesCache = new Dictionary <string, IReadOnlyList <Range <string> > >(); this.fetchExecutionRangeAccumulator = new FetchExecutionRangeAccumulator(singlePartitionKeyId); this.retries = -1; this.partitionRoutingHelper = new PartitionRoutingHelper(); }
public PartitionKeyRangeHandler(CosmosClient client, PartitionRoutingHelper partitionRoutingHelper = null) { if (client == null) { throw new ArgumentNullException(nameof(client)); } this.client = client; this.partitionRoutingHelper = partitionRoutingHelper ?? new PartitionRoutingHelper(); }
public async Task GetTargetRangeFromContinuationTokenNonExistentContainer() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, isMinInclusive: true, isMaxInclusive: false) }; //Not empty Range <string> range = new Range <string>("A", "B", true, false); List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken> { new CompositeContinuationToken { Range = range } }; IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" } }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider .SetupSequence(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == range.Min), It.IsAny <ITrace>(), It.IsAny <bool>())) .Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Skip(1).ToList())) .Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)null)); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); ResolvedRangeInfo resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, range, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); Assert.IsNotNull(resolvedRangeInfo); Assert.IsNull(resolvedRangeInfo.ResolvedRange); Assert.IsNull(resolvedRangeInfo.ContinuationTokens); }
public async Task AddPartitionKeyRangeToContinuationTokenOnSplit() { const string BackendToken = "backendToken"; RequestNameValueCollection headers = new(); List <CompositeContinuationToken> compositeContinuationTokensFromSplit = new List <CompositeContinuationToken> { new CompositeContinuationToken { Token = "someToken", Range = new Range <string>("A", "B", true, false) }, new CompositeContinuationToken { Token = "anotherToken", Range = new Range <string>("B", "C", true, false) } }; PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); //With backend header headers.Add(HttpConstants.HttpHeaders.Continuation, BackendToken); ResolvedRangeInfo resolvedRangeInfo = new ResolvedRangeInfo(new PartitionKeyRange(), new List <CompositeContinuationToken>(compositeContinuationTokensFromSplit)); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, null, null, null, resolvedRangeInfo, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); List <CompositeContinuationToken> compositeContinuationTokens = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(headers.Get(HttpConstants.HttpHeaders.Continuation)); Assert.IsTrue(result); Assert.AreEqual(compositeContinuationTokensFromSplit.Count, compositeContinuationTokens.Count); Assert.AreEqual(BackendToken, compositeContinuationTokens.First().Token); Assert.AreNotEqual(BackendToken, compositeContinuationTokens.Last().Token); //Without backend header headers.Remove(HttpConstants.HttpHeaders.Continuation); resolvedRangeInfo = new ResolvedRangeInfo(new PartitionKeyRange(), new List <CompositeContinuationToken>(compositeContinuationTokensFromSplit)); result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, null, null, null, resolvedRangeInfo, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); compositeContinuationTokens = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(headers.Get(HttpConstants.HttpHeaders.Continuation)); Assert.IsTrue(result); Assert.IsTrue(compositeContinuationTokens.Count == compositeContinuationTokensFromSplit.Count - 1); Assert.AreEqual(compositeContinuationTokensFromSplit.Last().Token, compositeContinuationTokens.First().Token); }
public DefaultDocumentQueryExecutionContext( DocumentQueryExecutionContextBase.InitParams constructorParams, bool isContinuationExpected) : base(constructorParams) { this.isContinuationExpected = isContinuationExpected; this.fetchSchedulingMetrics = new SchedulingStopwatch(); this.fetchSchedulingMetrics.Ready(); this.fetchExecutionRangeAccumulator = new FetchExecutionRangeAccumulator(); this.providedRangesCache = new Dictionary <string, IReadOnlyList <Range <string> > >(); this.retries = -1; this.partitionRoutingHelper = new PartitionRoutingHelper(); }
public async Task GetTargetRangeFromContinuationTokenWhenNotEmpty() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, isMinInclusive: true, isMaxInclusive: false) }; //Not empty Range <string> range = new Range <string>("A", "B", true, false); List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken> { new CompositeContinuationToken { Range = range } }; IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" } }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == range.Min), It.IsAny <bool>() )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); ResolvedRangeInfo resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, range, suppliedTokens, RntdbEnumerationDirection.Reverse); Assert.AreEqual(overlappingRanges.First().Id, resolvedRangeInfo.ResolvedRange.Id); CollectionAssert.AreEqual(suppliedTokens, resolvedRangeInfo.ContinuationTokens); routingMapProvider.Verify(); }
public CosmosGatewayQueryExecutionContext( CosmosQueryContext cosmosQueryContext) { if (cosmosQueryContext == null) { throw new ArgumentNullException(nameof(cosmosQueryContext)); } this.queryContext = cosmosQueryContext; this.fetchSchedulingMetrics = new SchedulingStopwatch(); this.fetchSchedulingMetrics.Ready(); this.fetchExecutionRangeAccumulator = new FetchExecutionRangeAccumulator(); this.retries = -1; this.partitionRoutingHelper = new PartitionRoutingHelper(); }
public void CompositeContinuationTokenIsNotPassedToBackend() { Range <string> expectedRange = new Range <string>("A", "B", true, false); string expectedToken = "someToken"; CompositeContinuationToken compositeContinuationToken = new CompositeContinuationToken { Range = expectedRange, Token = expectedToken }; string continuation = JsonConvert.SerializeObject(compositeContinuationToken); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); DictionaryNameValueCollection headers = new DictionaryNameValueCollection(); headers.Add(HttpConstants.HttpHeaders.Continuation, continuation); Range <string> range = partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(headers, out List <CompositeContinuationToken> compositeContinuationTokens); Assert.IsTrue(expectedRange.Equals(range)); Assert.AreEqual(expectedToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); //not a composite token }
private async Task <Tuple <PartitionRoutingHelper.ResolvedRangeInfo, IReadOnlyList <Range <string> > > > TryGetTargetPartitionKeyRangeAsync( DocumentServiceRequest request, ContainerProperties collection, QueryPartitionProvider queryPartitionProvider, IRoutingMapProvider routingMapProvider, Range <string> rangeFromContinuationToken, List <CompositeContinuationToken> suppliedTokens) { string version = request.Headers[HttpConstants.HttpHeaders.Version]; version = string.IsNullOrEmpty(version) ? HttpConstants.Versions.CurrentVersion : version; bool enableCrossPartitionQuery = false; string enableCrossPartitionQueryHeader = request.Headers[HttpConstants.HttpHeaders.EnableCrossPartitionQuery]; if (enableCrossPartitionQueryHeader != null) { if (!bool.TryParse(enableCrossPartitionQueryHeader, out enableCrossPartitionQuery)) { throw new BadRequestException( string.Format( CultureInfo.InvariantCulture, RMResources.InvalidHeaderValue, enableCrossPartitionQueryHeader, HttpConstants.HttpHeaders.EnableCrossPartitionQuery)); } } IReadOnlyList <Range <string> > providedRanges; if (!this.providedRangesCache.TryGetValue(collection.ResourceId, out providedRanges)) { if (this.ShouldExecuteQueryRequest) { FeedOptions feedOptions = this.GetFeedOptions(null); PartitionKeyDefinition partitionKeyDefinition; if ((feedOptions.Properties != null) && feedOptions.Properties.TryGetValue( DefaultDocumentQueryExecutionContext.InternalPartitionKeyDefinitionProperty, out object partitionKeyDefinitionObject)) { if (partitionKeyDefinitionObject is PartitionKeyDefinition definition) { partitionKeyDefinition = definition; } else { throw new ArgumentException( "partitionkeydefinition has invalid type", nameof(partitionKeyDefinitionObject)); } } else { partitionKeyDefinition = collection.PartitionKey; } providedRanges = PartitionRoutingHelper.GetProvidedPartitionKeyRanges( (errorMessage) => new BadRequestException(errorMessage), this.QuerySpec, enableCrossPartitionQuery, false, this.isContinuationExpected, false, //haslogicalpartitionkey partitionKeyDefinition, queryPartitionProvider, version, out QueryInfo queryInfo); } else if (request.Properties != null && request.Properties.TryGetValue( WFConstants.BackendHeaders.EffectivePartitionKeyString, out object effectivePartitionKey)) { if (effectivePartitionKey is string effectivePartitionKeyString) { providedRanges = new List <Range <string> >() { Range <string> .GetPointRange(effectivePartitionKeyString), }; } else { throw new ArgumentException( "EffectivePartitionKey must be a string", WFConstants.BackendHeaders.EffectivePartitionKeyString); } } else { providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, true, false) }; } this.providedRangesCache[collection.ResourceId] = providedRanges; } PartitionRoutingHelper.ResolvedRangeInfo resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider, collection.ResourceId, rangeFromContinuationToken, suppliedTokens); if (resolvedRangeInfo.ResolvedRange == null) { return(null); } else { return(Tuple.Create(resolvedRangeInfo, providedRanges)); } }
public PartitionRoutingHelperTest() { this.partitionRoutingHelper = new PartitionRoutingHelper(); }
public async Task AddPartitionKeyRangeToContinuationTokenOnBoundry() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( "A", "D", isMinInclusive: true, isMaxInclusive: false) }; //Reverse ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, null); IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max), It.IsAny <ITrace>(), It.Is <bool>(x => x == false) )).Returns(Task.FromResult(overlappingRanges)).Verifiable(); PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); RequestNameValueCollection headers = new(); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse ); Assert.IsTrue(result); routingMapProvider.Verify(); string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.First().ToRange(), }); Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation)); //Forward currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "D" }, null); overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "D" }, }.AsReadOnly(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.IsAny <Range <string> >(), It.IsAny <ITrace>(), It.IsAny <bool>() )).Returns(Task.FromResult(overlappingRanges)); headers = new RequestNameValueCollection(); result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, NoOpTrace.Singleton, RntdbEnumerationDirection.Forward ); Assert.IsTrue(result); routingMapProvider.Verify(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(e => e.IsMaxInclusive), It.IsAny <ITrace>(), It.IsAny <bool>() ), Times.Never); expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.Last().ToRange(), }); Assert.IsNull(headers.Get(HttpConstants.HttpHeaders.Continuation)); }
public async Task GetTargetRangeFromContinuationTokenOnSplit() { const string Token = "token"; List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, isMinInclusive: true, isMaxInclusive: false) }; Range <string> rangeFromContinuationToken = new Range <string>("A", "C", true, false); List <CompositeContinuationToken> suppliedTokens = new List <CompositeContinuationToken> { new CompositeContinuationToken { Token = Token, Range = rangeFromContinuationToken } }; IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "2", MinInclusive = "A", MaxExclusive = "B" }, new PartitionKeyRange { Id = "3", MinInclusive = "B", MaxExclusive = "C" }, new PartitionKeyRange { Id = "1", MinInclusive = "C", MaxExclusive = "D" } }.AsReadOnly(); IReadOnlyList <PartitionKeyRange> replacedRanges = overlappingRanges.Take(2).ToList().AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min), It.IsAny <ITrace>(), It.Is <bool>(x => x == false) )).Returns(Task.FromResult((IReadOnlyList <PartitionKeyRange>)overlappingRanges.Take(1).ToList())).Verifiable(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == rangeFromContinuationToken.Min && x.Max == rangeFromContinuationToken.Max), It.IsAny <ITrace>(), It.Is <bool>(x => x == true) )).Returns(Task.FromResult(replacedRanges)).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); ResolvedRangeInfo resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, rangeFromContinuationToken, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Reverse); routingMapProvider.Verify(); Assert.IsTrue(replacedRanges.Last().Equals(resolvedRangeInfo.ResolvedRange)); List <PartitionKeyRange> reversedReplacedRanges = new List <PartitionKeyRange>(replacedRanges); reversedReplacedRanges.Reverse(); Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count); Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token); for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++) { Assert.IsTrue(reversedReplacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range)); } //Forward partitionRoutingHelper = new PartitionRoutingHelper(); resolvedRangeInfo = await partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync( providedRanges, routingMapProvider.Object, CollectionId, rangeFromContinuationToken, suppliedTokens, NoOpTrace.Singleton, RntdbEnumerationDirection.Forward); routingMapProvider.Verify(); Assert.IsTrue(replacedRanges.First().Equals(resolvedRangeInfo.ResolvedRange)); Assert.AreEqual(replacedRanges.Count, resolvedRangeInfo.ContinuationTokens.Count); Assert.AreEqual(resolvedRangeInfo.ContinuationTokens[0].Token, Token); for (int i = 0; i < resolvedRangeInfo.ContinuationTokens.Count; i++) { Assert.IsTrue(replacedRanges[i].ToRange().Equals(resolvedRangeInfo.ContinuationTokens[i].Range)); } }
private async Task <Tuple <PartitionRoutingHelper.ResolvedRangeInfo, IReadOnlyList <Range <string> > > > TryGetTargetPartitionKeyRangeAsync( DocumentServiceRequest request, CosmosContainerSettings collection, QueryPartitionProvider queryPartitionProvider, IRoutingMapProvider routingMapProvider, Range <string> rangeFromContinuationToken, List <CompositeContinuationToken> suppliedTokens) { string version = request.Headers[HttpConstants.HttpHeaders.Version]; version = string.IsNullOrEmpty(version) ? HttpConstants.Versions.CurrentVersion : version; bool enableCrossPartitionQuery = false; string enableCrossPartitionQueryHeader = request.Headers[HttpConstants.HttpHeaders.EnableCrossPartitionQuery]; if (enableCrossPartitionQueryHeader != null) { if (!bool.TryParse(enableCrossPartitionQueryHeader, out enableCrossPartitionQuery)) { throw new BadRequestException( string.Format( CultureInfo.InvariantCulture, RMResources.InvalidHeaderValue, enableCrossPartitionQueryHeader, HttpConstants.HttpHeaders.EnableCrossPartitionQuery)); } } IReadOnlyList <Range <string> > providedRanges; if (!this.providedRangesCache.TryGetValue(collection.ResourceId, out providedRanges)) { if (this.ShouldExecuteQueryRequest) { QueryInfo queryInfo; providedRanges = PartitionRoutingHelper.GetProvidedPartitionKeyRanges( this.QuerySpec, enableCrossPartitionQuery, false, isContinuationExpected, collection.PartitionKey, queryPartitionProvider, version, out queryInfo); } else { providedRanges = new List <Range <string> > { new Range <string>( PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey, PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey, true, false) }; } this.providedRangesCache[collection.ResourceId] = providedRanges; } PartitionRoutingHelper.ResolvedRangeInfo resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRange( providedRanges, routingMapProvider, collection.ResourceId, rangeFromContinuationToken, suppliedTokens); if (resolvedRangeInfo.ResolvedRange == null) { return(null); } else { return(Tuple.Create(resolvedRangeInfo, providedRanges)); } }
public async Task AddPartitionKeyRangeToContinuationTokenOnNullBackendContinuation() { List <Range <string> > providedRanges = new List <Range <string> > { new Range <string>( "A", "D", isMinInclusive: true, isMaxInclusive: false) }; ResolvedRangeInfo currentPartitionKeyRange = new ResolvedRangeInfo(new PartitionKeyRange { Id = "1", MinInclusive = "B", MaxExclusive = "C" }, null); IReadOnlyList <PartitionKeyRange> overlappingRanges = new List <PartitionKeyRange> { new PartitionKeyRange { Id = "0", MinInclusive = "A", MaxExclusive = "B" }, currentPartitionKeyRange.ResolvedRange, new PartitionKeyRange { Id = "3", MinInclusive = "C", MaxExclusive = "D" } }.AsReadOnly(); Mock <IRoutingMapProvider> routingMapProvider = new Mock <IRoutingMapProvider>(); routingMapProvider.Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny <string>(), It.Is <Range <string> >(x => x.Min == providedRanges.Single().Min&& x.Max == providedRanges.Single().Max), It.Is <bool>(x => x == false) )).Returns(Task.FromResult(overlappingRanges)).Verifiable(); //Reverse PartitionRoutingHelper partitionRoutingHelper = new PartitionRoutingHelper(); StringKeyValueCollection headers = new StringKeyValueCollection(); bool result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, RntdbEnumerationDirection.Reverse ); Assert.IsTrue(result); routingMapProvider.Verify(); string expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.First().ToRange(), }); Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); //Forward routingMapProvider.Setup(m => m.TryGetRangeByEffectivePartitionKey( It.IsAny <string>(), It.Is <string>(x => x == currentPartitionKeyRange.ResolvedRange.MaxExclusive) )).Returns(Task.FromResult(overlappingRanges.Last())).Verifiable(); headers = new StringKeyValueCollection(); result = await partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync( headers, providedRanges, routingMapProvider.Object, CollectionId, currentPartitionKeyRange, RntdbEnumerationDirection.Forward ); Assert.IsTrue(result); routingMapProvider.Verify(); expectedContinuationToken = JsonConvert.SerializeObject(new CompositeContinuationToken { Token = null, Range = overlappingRanges.Last().ToRange(), }); Assert.AreEqual(expectedContinuationToken, headers.Get(HttpConstants.HttpHeaders.Continuation)); }