diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/cluster/valkey/LiveValkeyIntegrationTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/cluster/valkey/LiveValkeyIntegrationTest.java index c26528a715..0e6cb2f4de 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/cluster/valkey/LiveValkeyIntegrationTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/cluster/valkey/LiveValkeyIntegrationTest.java @@ -298,24 +298,44 @@ class LiveValkeyIntegrationTest { ValkeyRateLimitStore store = newRateLimitStore(factoryA); String key = "boundary-" + java.util.UUID.randomUUID(); long capacity = 5; - Duration window = Duration.ofMillis(500); + // refillGreedy tops the bucket up continuously, one token every window/capacity. A 500ms + // window left the drain loop only 100ms before a 6th token appeared, so a slow Valkey + // round-trip broke the count; 4s spaces refills 800ms apart, clear of any burst. + Duration window = Duration.ofSeconds(4); + long refillIntervalMs = window.toMillis() / capacity; + long drainStart = System.nanoTime(); int firstAllowed = 0; for (int i = 0; i < 10; i++) { if (store.tryConsume(key, capacity, window).allowed()) firstAllowed++; } - assertEquals(capacity, firstAllowed, "must allow exactly capacity tokens initially"); + long drainMs = (System.nanoTime() - drainStart) / 1_000_000; + // Refill never pauses, so a slow drain earns extra tokens honestly - allow exactly the + // number the elapsed time can have produced and no more. + long earned = drainMs / refillIntervalMs; + assertTrue( + firstAllowed >= capacity && firstAllowed <= capacity + earned, + "initial burst must be capacity (" + + capacity + + ") plus at most the " + + earned + + " token(s) refilled during a " + + drainMs + + "ms drain, got " + + firstAllowed); - Thread.sleep(window.toMillis() + 50); + // A fixed-window limiter would hand back a whole fresh capacity at the boundary; a token + // bucket hands back one token per refill interval. + Thread.sleep(refillIntervalMs + 200); int secondAllowed = 0; - long start = System.nanoTime(); - for (int i = 0; i < 20 && (System.nanoTime() - start) < 20_000_000L; i++) { + for (int i = 0; i < 10; i++) { if (store.tryConsume(key, capacity, window).allowed()) secondAllowed++; } assertTrue( - secondAllowed <= capacity, - "token-bucket must not let a fresh full capacity be consumed instantly across" - + " the boundary; got " + secondAllowed >= 1 && secondAllowed < capacity, + "one refill interval must yield about one token, not a fresh full window of " + + capacity + + "; got " + secondAllowed); }