@@ -288,16 +288,16 @@ func TestRuleShadowMode(test *testing.T) {
288288 t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [1 ]).Return (limits [1 ])
289289 t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
290290 []* pb.RateLimitResponse_DescriptorStatus {
291- {Code : pb .RateLimitResponse_OK , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
292- {Code : pb .RateLimitResponse_OK , CurrentLimit : nil , LimitRemaining : 0 },
291+ {Code : pb .RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
292+ {Code : pb .RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [ 1 ]. Limit , LimitRemaining : 0 },
293293 })
294294 response , err := service .ShouldRateLimit (context .Background (), request )
295295 t .assert .Equal (
296296 & pb.RateLimitResponse {
297297 OverallCode : pb .RateLimitResponse_OK ,
298298 Statuses : []* pb.RateLimitResponse_DescriptorStatus {
299299 {Code : pb .RateLimitResponse_OK , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
300- {Code : pb .RateLimitResponse_OK , CurrentLimit : nil , LimitRemaining : 0 },
300+ {Code : pb .RateLimitResponse_OK , CurrentLimit : limits [ 1 ]. Limit , LimitRemaining : 0 },
301301 },
302302 },
303303 response )
@@ -319,16 +319,10 @@ func TestMixedRuleShadowMode(test *testing.T) {
319319 }
320320 t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [0 ]).Return (limits [0 ])
321321 t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [1 ]).Return (limits [1 ])
322- testResults := []pb.RateLimitResponse_Code {pb .RateLimitResponse_OVER_LIMIT , pb .RateLimitResponse_OVER_LIMIT }
323- for i := 0 ; i < len (limits ); i ++ {
324- if limits [i ].ShadowMode {
325- testResults [i ] = pb .RateLimitResponse_OK
326- }
327- }
328322 t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
329323 []* pb.RateLimitResponse_DescriptorStatus {
330- {Code : testResults [ 0 ] , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
331- {Code : testResults [ 1 ] , CurrentLimit : nil , LimitRemaining : 0 },
324+ {Code : pb . RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
325+ {Code : pb . RateLimitResponse_OVER_LIMIT , CurrentLimit : nil , LimitRemaining : 0 },
332326 })
333327 response , err := service .ShouldRateLimit (context .Background (), request )
334328 t .assert .Equal (
@@ -345,6 +339,115 @@ func TestMixedRuleShadowMode(test *testing.T) {
345339 t .assert .EqualValues (0 , t .statStore .NewCounter ("global_shadow_mode" ).Value ())
346340}
347341
342+ func TestShadowModeExceededHeader (test * testing.T ) {
343+ os .Setenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" , "true" )
344+ defer os .Unsetenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" )
345+
346+ t := commonSetup (test )
347+ defer t .controller .Finish ()
348+ service := t .setupBasicService ()
349+
350+ // Test 1: Shadow mode descriptor exceeded - header should be true
351+ request := common .NewRateLimitRequest (
352+ "different-domain" , [][][2 ]string {{{"foo" , "bar" }}}, 1 )
353+ limits := []* config.RateLimit {
354+ config .NewRateLimit (10 , pb .RateLimitResponse_RateLimit_MINUTE , t .statsManager .NewStats ("key" ), false , true , false , "" , nil , false ),
355+ }
356+ t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [0 ]).Return (limits [0 ])
357+ t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
358+ []* pb.RateLimitResponse_DescriptorStatus {
359+ {Code : pb .RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
360+ })
361+ response , err := service .ShouldRateLimit (context .Background (), request )
362+ t .assert .Nil (err )
363+ t .assert .Equal (pb .RateLimitResponse_OK , response .OverallCode )
364+ t .assert .Equal (pb .RateLimitResponse_OK , response .Statuses [0 ].Code )
365+ t .assert .Equal (1 , len (response .RequestHeadersToAdd ))
366+ t .assert .Equal ("x-ratelimit-exceeded-shadow-mode" , response .RequestHeadersToAdd [0 ].Key )
367+ t .assert .Equal ("true" , response .RequestHeadersToAdd [0 ].Value )
368+ }
369+
370+ func TestShadowModeExceededHeaderNotExceeded (test * testing.T ) {
371+ os .Setenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" , "true" )
372+ defer os .Unsetenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" )
373+
374+ t := commonSetup (test )
375+ defer t .controller .Finish ()
376+ service := t .setupBasicService ()
377+
378+ // Test: Shadow mode descriptor NOT exceeded - header should be false
379+ request := common .NewRateLimitRequest (
380+ "different-domain" , [][][2 ]string {{{"foo" , "bar" }}}, 1 )
381+ limits := []* config.RateLimit {
382+ config .NewRateLimit (10 , pb .RateLimitResponse_RateLimit_MINUTE , t .statsManager .NewStats ("key" ), false , true , false , "" , nil , false ),
383+ }
384+ t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [0 ]).Return (limits [0 ])
385+ t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
386+ []* pb.RateLimitResponse_DescriptorStatus {
387+ {Code : pb .RateLimitResponse_OK , CurrentLimit : limits [0 ].Limit , LimitRemaining : 5 },
388+ })
389+ response , err := service .ShouldRateLimit (context .Background (), request )
390+ t .assert .Nil (err )
391+ t .assert .Equal (pb .RateLimitResponse_OK , response .OverallCode )
392+ t .assert .Equal (1 , len (response .RequestHeadersToAdd ))
393+ t .assert .Equal ("x-ratelimit-exceeded-shadow-mode" , response .RequestHeadersToAdd [0 ].Key )
394+ t .assert .Equal ("false" , response .RequestHeadersToAdd [0 ].Value )
395+ }
396+
397+ func TestShadowModeExceededHeaderDisabled (test * testing.T ) {
398+ // Feature flag not set, so no header should be added
399+ t := commonSetup (test )
400+ defer t .controller .Finish ()
401+ service := t .setupBasicService ()
402+
403+ request := common .NewRateLimitRequest (
404+ "different-domain" , [][][2 ]string {{{"foo" , "bar" }}}, 1 )
405+ limits := []* config.RateLimit {
406+ config .NewRateLimit (10 , pb .RateLimitResponse_RateLimit_MINUTE , t .statsManager .NewStats ("key" ), false , true , false , "" , nil , false ),
407+ }
408+ t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [0 ]).Return (limits [0 ])
409+ t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
410+ []* pb.RateLimitResponse_DescriptorStatus {
411+ {Code : pb .RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
412+ })
413+ response , err := service .ShouldRateLimit (context .Background (), request )
414+ t .assert .Nil (err )
415+ t .assert .Equal (pb .RateLimitResponse_OK , response .OverallCode )
416+ t .assert .Nil (response .RequestHeadersToAdd )
417+ }
418+
419+ func TestShadowModeExceededHeaderGlobalShadowMode (test * testing.T ) {
420+ os .Setenv ("SHADOW_MODE" , "true" )
421+ os .Setenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" , "true" )
422+ defer func () {
423+ os .Unsetenv ("SHADOW_MODE" )
424+ os .Unsetenv ("SHADOW_MODE_EXCEEDED_HEADER_ENABLED" )
425+ }()
426+
427+ t := commonSetup (test )
428+ defer t .controller .Finish ()
429+ service := t .setupBasicService ()
430+
431+ // Non-shadow-mode descriptor that is over limit, but global shadow mode converts it
432+ request := common .NewRateLimitRequest (
433+ "different-domain" , [][][2 ]string {{{"foo" , "bar" }}}, 1 )
434+ limits := []* config.RateLimit {
435+ config .NewRateLimit (10 , pb .RateLimitResponse_RateLimit_MINUTE , t .statsManager .NewStats ("key" ), false , false , false , "" , nil , false ),
436+ }
437+ t .config .EXPECT ().GetLimit (context .Background (), "different-domain" , request .Descriptors [0 ]).Return (limits [0 ])
438+ t .cache .EXPECT ().DoLimit (context .Background (), request , limits ).Return (
439+ []* pb.RateLimitResponse_DescriptorStatus {
440+ {Code : pb .RateLimitResponse_OVER_LIMIT , CurrentLimit : limits [0 ].Limit , LimitRemaining : 0 },
441+ })
442+ response , err := service .ShouldRateLimit (context .Background (), request )
443+ t .assert .Nil (err )
444+ t .assert .Equal (pb .RateLimitResponse_OK , response .OverallCode )
445+ t .assert .Equal (1 , len (response .RequestHeadersToAdd ))
446+ t .assert .Equal ("x-ratelimit-exceeded-shadow-mode" , response .RequestHeadersToAdd [0 ].Key )
447+ t .assert .Equal ("true" , response .RequestHeadersToAdd [0 ].Value )
448+ t .assert .EqualValues (1 , t .statStore .NewCounter ("global_shadow_mode" ).Value ())
449+ }
450+
348451func TestServiceWithCustomRatelimitHeaders (test * testing.T ) {
349452 os .Setenv ("LIMIT_RESPONSE_HEADERS_ENABLED" , "true" )
350453 os .Setenv ("LIMIT_LIMIT_HEADER" , "A-Ratelimit-Limit" )
0 commit comments