@@ -4361,6 +4361,83 @@ async fn test_request_trace_id_isolation() {
43614361 ) ;
43624362}
43634363
4364+ /// Verifies that `RateLimitError.retryAfterMs` is a positive number when the
4365+ /// global (untraced) budget is exhausted.
4366+ #[ tokio:: test]
4367+ #[ serial]
4368+ async fn test_rate_limit_retry_after_ms ( ) {
4369+ init_otel ( ) ;
4370+
4371+ // The budget in rate-limit-main is 10. Send 11 requests; the last one
4372+ // should come back as 429 with a positive retryAfterMs value.
4373+ const BUDGET : usize = 10 ;
4374+
4375+ integration_test_with_server_flag ! (
4376+ ServerFlags {
4377+ rate_limit_cleanup_interval_sec: 60 ,
4378+ request_wait_timeout_ms: Some ( 30_000 ) ,
4379+ ..Default :: default ( )
4380+ } ,
4381+ "./test_cases/rate-limit-main" ,
4382+ NON_SECURE_PORT ,
4383+ "rate-limit-retry-after" ,
4384+ None ,
4385+ None :: <reqwest:: RequestBuilder >,
4386+ None :: <Tls >,
4387+ (
4388+ |( port, _url, _req_builder, _event_rx, _metric_src) | async move {
4389+ let client = reqwest:: Client :: new( ) ;
4390+ let url = format!( "http://localhost:{}/rate-limit-retry-after" , port) ;
4391+ let server_url = format!( "http://localhost:{}" , port) ;
4392+
4393+ // Exhaust the budget.
4394+ for _ in 0 ..BUDGET {
4395+ let resp = client
4396+ . get( & url)
4397+ . header( "x-test-server-url" , & server_url)
4398+ . send( )
4399+ . await
4400+ . unwrap( ) ;
4401+ // Each of these may itself trigger an inner fetch that is counted
4402+ // against the budget; we only care about the final one below.
4403+ let _ = resp;
4404+ }
4405+
4406+ // This request should be rate-limited.
4407+ let resp = client
4408+ . get( & url)
4409+ . header( "x-test-server-url" , & server_url)
4410+ . send( )
4411+ . await ;
4412+
4413+ Some ( resp)
4414+ } ,
4415+ |resp| async move {
4416+ let res = resp. unwrap( ) ;
4417+ assert_eq!(
4418+ res. status( ) . as_u16( ) ,
4419+ 429 ,
4420+ "expected 429 from rate-limited request"
4421+ ) ;
4422+ let body: serde_json:: Value = res. json( ) . await . unwrap( ) ;
4423+ assert_eq!(
4424+ body[ "name" ] . as_str( ) . unwrap_or( "" ) ,
4425+ "RateLimitError" ,
4426+ "expected RateLimitError in body, got: {body}"
4427+ ) ;
4428+ let retry_after_ms = body[ "retryAfterMs" ]
4429+ . as_u64( )
4430+ . expect( "retryAfterMs should be a non-null number" ) ;
4431+ assert!(
4432+ retry_after_ms > 0 ,
4433+ "retryAfterMs should be positive, got {retry_after_ms}"
4434+ ) ;
4435+ }
4436+ ) ,
4437+ TerminationToken :: new( )
4438+ ) ;
4439+ }
4440+
43644441#[ derive( Deserialize ) ]
43654442struct ErrorResponsePayload {
43664443 msg : String ,
0 commit comments