@@ -4319,113 +4319,110 @@ private static async Task GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpe
43194319 }
43204320 }
43214321
4322- [ TestMethod ]
4323- [ Owner ( "aavasthy" ) ]
4324- [ Description ( "Forces a single 404/1002 from the gateway and verifies ClientRetryPolicy adds x-ms-cosmos-hub-region-processing-only on the retry request." ) ]
4325- public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002 ( )
4326- {
4327- bool headerObservedOnRetry = false ;
4328- int requestCount = 0 ;
4329- bool shouldReturn404 = true ;
4330-
4331- // Created HTTP handler to intercept requests
4332- HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
4333- {
4334- RequestCallBack = ( request , cancellationToken ) =>
4335- {
4336- // Track all document read requests
4337- if ( request . Method == HttpMethod . Get &&
4338- request . RequestUri != null &&
4339- request . RequestUri . AbsolutePath . Contains ( "/docs/" ) )
4340- {
4341- requestCount ++ ;
4342-
4343- // Check for hub header on retry (2nd+ request)
4344- if ( requestCount > 1 &&
4345- request . Headers . TryGetValues ( HubRegionHeader , out IEnumerable < string > values ) &&
4346- values . Any ( v => v . Equals ( bool . TrueString , StringComparison . OrdinalIgnoreCase ) ) )
4347- {
4348- headerObservedOnRetry = true ;
4349- }
4350- }
4351-
4352- return Task . FromResult < HttpResponseMessage > ( null ) ;
4353- } ,
4354-
4355- ResponseIntercepter = ( response , request ) =>
4356- {
4357- if ( shouldReturn404 &&
4358- request . Method == HttpMethod . Get &&
4359- request . RequestUri != null &&
4360- request . RequestUri . AbsolutePath . Contains ( "/docs/" ) )
4361- {
4362- shouldReturn404 = false ; // Only return 404 once
4363-
4364- var errorResponse = new
4365- {
4366- code = "NotFound" ,
4367- message = "Message: {\" Errors\" :[\" Resource Not Found. Learn more: https://aka.ms/cosmosdb-tsg-not-found\" ]}\r \n ActivityId: " + Guid . NewGuid ( ) + ", Request URI: " + request . RequestUri ,
4368- additionalErrorInfo = ""
4369- } ;
4370-
4371- HttpResponseMessage notFoundResponse = new HttpResponseMessage ( HttpStatusCode . NotFound )
4372- {
4373- Content = new StringContent (
4374- JsonConvert . SerializeObject ( errorResponse ) ,
4375- Encoding . UTF8 ,
4376- "application/json"
4377- )
4378- } ;
4379-
4380- // Add the substatus header for ReadSessionNotAvailable
4381- notFoundResponse . Headers . Add ( "x-ms-substatus" , "1002" ) ;
4382- notFoundResponse . Headers . Add ( "x-ms-activity-id" , Guid . NewGuid ( ) . ToString ( ) ) ;
4383- notFoundResponse . Headers . Add ( "x-ms-request-charge" , "1.0" ) ;
4384-
4385- return Task . FromResult ( notFoundResponse ) ;
4386- }
4387-
4388- return Task . FromResult ( response ) ;
4389- }
4390- } ;
4391-
4392- CosmosClientOptions clientOptions = new CosmosClientOptions
4393- {
4394- ConnectionMode = ConnectionMode . Gateway ,
4395- ConsistencyLevel = Cosmos . ConsistencyLevel . Session ,
4396- HttpClientFactory = ( ) => new HttpClient ( httpHandler ) ,
4397- MaxRetryAttemptsOnRateLimitedRequests = 9 ,
4398- MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan . FromSeconds ( 30 )
4399- } ;
4400-
4401- using CosmosClient customClient = TestCommon . CreateCosmosClient ( clientOptions ) ;
4402-
4403- Container customContainer = customClient . GetContainer ( this . database . Id , this . Container . Id ) ;
4404-
4405- // Create a test item first
4406- ToDoActivity testItem = ToDoActivity . CreateRandomToDoActivity ( ) ;
4407- await this . Container . CreateItemAsync ( testItem , new Cosmos . PartitionKey ( testItem . pk ) ) ;
4408-
4409- try
4410- {
4411- // This should trigger 404/1002 on first attempt, then retry with hub header
4412- ItemResponse < ToDoActivity > response = await customContainer . ReadItemAsync < ToDoActivity > (
4413- testItem . id ,
4414- new Cosmos . PartitionKey ( testItem . pk ) ) ;
4415-
4416- Assert . IsNotNull ( response ) ;
4417- Assert . IsNotNull ( response . Resource ) ;
4418- }
4419- catch ( CosmosException )
4420- {
4421- // It's possible the retry also fails, but should still have seen the retry attempt
4422- }
4423-
4424- // Verifying retry happened
4425- Assert . IsTrue ( requestCount >= 2 , $ "Expected at least 2 requests (original + retry), but got { requestCount } ") ;
4426- Assert . IsTrue ( headerObservedOnRetry , $ "Expected retry request to include '{ HubRegionHeader } : true'") ;
4322+ [ TestMethod ]
4323+ [ Owner ( "aavasthy" ) ]
4324+ [ Description ( "Forces two consecutive 404/1002 responses from the gateway and verifies ClientRetryPolicy sets the hub region header flag after the first retry fails." ) ]
4325+ public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002 ( )
4326+ {
4327+ int requestCount = 0 ;
4328+ int return404Count = 0 ;
4329+ const int maxReturn404 = 2 ; // Return 404/1002 twice
4330+
4331+ // Created HTTP handler to intercept requests
4332+ HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
4333+ {
4334+ RequestCallBack = ( request , cancellationToken ) =>
4335+ {
4336+ // Track all document read requests
4337+ if ( request . Method == HttpMethod . Get &&
4338+ request . RequestUri != null &&
4339+ request . RequestUri . AbsolutePath . Contains ( "/docs/" ) )
4340+ {
4341+ requestCount ++ ;
4342+
4343+ // Header should NOT be present on first retry (2nd request)
4344+ if ( requestCount == 2 &&
4345+ request . Headers . TryGetValues ( HubRegionHeader , out IEnumerable < string > firstRetryValues ) &&
4346+ firstRetryValues . Any ( ) )
4347+ {
4348+ Assert . Fail ( "Header should NOT be present on first retry attempt." ) ;
4349+ }
4350+
4351+ // Return fake 404/1002 for first two requests
4352+ if ( return404Count < maxReturn404 )
4353+ {
4354+ return404Count ++ ;
4355+
4356+ var errorResponse = new
4357+ {
4358+ code = "NotFound" ,
4359+ message = "Message: {\" Errors\" :[\" Resource Not Found. Learn more: https://aka.ms/cosmosdb-tsg-not-found\" ]}\r \n ActivityId: " + Guid . NewGuid ( ) + ", Request URI: " + request . RequestUri ,
4360+ additionalErrorInfo = ""
4361+ } ;
4362+
4363+ HttpResponseMessage notFoundResponse = new HttpResponseMessage ( HttpStatusCode . NotFound )
4364+ {
4365+ Content = new StringContent (
4366+ JsonConvert . SerializeObject ( errorResponse ) ,
4367+ Encoding . UTF8 ,
4368+ "application/json"
4369+ )
4370+ } ;
4371+
4372+ // Add the substatus header for ReadSessionNotAvailable
4373+ notFoundResponse . Headers . Add ( "x-ms-substatus" , "1002" ) ;
4374+ notFoundResponse . Headers . Add ( "x-ms-activity-id" , Guid . NewGuid ( ) . ToString ( ) ) ;
4375+ notFoundResponse . Headers . Add ( "x-ms-request-charge" , "1.0" ) ;
4376+
4377+ return Task . FromResult ( notFoundResponse ) ;
4378+ }
4379+ }
4380+
4381+ return Task . FromResult < HttpResponseMessage > ( null ) ;
4382+ }
4383+ } ;
4384+
4385+ CosmosClientOptions clientOptions = new CosmosClientOptions
4386+ {
4387+ ConnectionMode = ConnectionMode . Gateway ,
4388+ ConsistencyLevel = Cosmos . ConsistencyLevel . Session ,
4389+ HttpClientFactory = ( ) => new HttpClient ( httpHandler ) ,
4390+ MaxRetryAttemptsOnRateLimitedRequests = 9 ,
4391+ MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan . FromSeconds ( 30 )
4392+ } ;
4393+
4394+ using CosmosClient customClient = TestCommon . CreateCosmosClient ( clientOptions ) ;
4395+
4396+ Container customContainer = customClient . GetContainer ( this . database . Id , this . Container . Id ) ;
4397+
4398+ // Create a test item first
4399+ ToDoActivity testItem = ToDoActivity . CreateRandomToDoActivity ( ) ;
4400+ await this . Container . CreateItemAsync ( testItem , new Cosmos . PartitionKey ( testItem . pk ) ) ;
4401+
4402+ try
4403+ {
4404+ // This should trigger 404/1002 twice
4405+ // In single-region emulator, after first retry fails with 404/1002, it won't retry again
4406+ ItemResponse < ToDoActivity > response = await customContainer . ReadItemAsync < ToDoActivity > (
4407+ testItem . id ,
4408+ new Cosmos . PartitionKey ( testItem . pk ) ) ;
4409+
4410+ Assert . Fail ( "Expected CosmosException due to consecutive 404/1002 failures." ) ;
4411+ }
4412+ catch ( CosmosException ex )
4413+ {
4414+ // Expected: After first retry fails with 404/1002, single master won't retry again
4415+ Assert . AreEqual ( HttpStatusCode . NotFound , ex . StatusCode ) ;
4416+ Assert . AreEqual ( ( int ) SubStatusCodes . ReadSessionNotAvailable , ex . SubStatusCode ) ;
4417+ }
4418+
4419+ // Verify the expected behavior:
4420+ // 1. Initial request (requestCount = 1) fails with 404/1002
4421+ // 2. First retry (requestCount = 2) fails with 404/1002
4422+ // 3. No more retries because single master + no additional regions
4423+ Assert . AreEqual ( 2 , requestCount , $ "Expected exactly 2 requests (initial + 1 retry) for single-region emulator, but got { requestCount } ") ;
4424+ Assert . AreEqual ( 2 , return404Count , "Both requests should have returned 404/1002" ) ;
44274425 }
4428-
44294426
44304427 private async Task < T > AutoGenerateIdPatternTest < T > ( Cosmos . PartitionKey pk , T itemWithoutId )
44314428 {
0 commit comments