mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-22 20:50:22 +00:00
Replaces the pass-through skeleton with a lazily rolled fixed window: admit() charges one message against the current epoch, resets the counter once epochPeriodSec has elapsed, and rejects with OverBudget when messagesPerEpoch is exhausted. Disabled or non-positive configurations admit everything, so the default-constructed MessagingClientConf (enabled = false) keeps today's behaviour. Parking of over-budget messages stays with the SendService scheduler (NextRoundRetry); the manager only answers whether one more transmission fits. The queue / dequeueReady stubs that anticipated manager-side parking are removed accordingly. Extends tests/messaging/test_rate_limit_manager.nim with budget boundary, epoch rollover, resetEpoch, and degenerate-config cases, replacing the enabled-pass-through placeholder test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.1 KiB
Nim
57 lines
2.1 KiB
Nim
{.used.}
|
|
|
|
import chronos, testutils/unittests, stew/byteutils
|
|
|
|
import logos_delivery/messaging/rate_limit_manager/rate_limit_manager
|
|
|
|
suite "RateLimitManager - admission":
|
|
asyncTest "admit is a pass-through when disabled":
|
|
let rl = RateLimitManager.new(
|
|
RateLimitConfig(enabled: false, epochPeriodSec: 600, messagesPerEpoch: 1)
|
|
)
|
|
for _ in 0 ..< 10:
|
|
let res = await rl.admit("payload".toBytes())
|
|
check res.isOk()
|
|
|
|
asyncTest "admits up to the budget then rejects with OverBudget":
|
|
let rl = RateLimitManager.new(
|
|
RateLimitConfig(enabled: true, epochPeriodSec: 600, messagesPerEpoch: 3)
|
|
)
|
|
for i in 0 ..< 3:
|
|
check (await rl.admit(("msg" & $i).toBytes())).isOk()
|
|
let res = await rl.admit("over".toBytes())
|
|
check:
|
|
res.isErr()
|
|
res.error == RateLimitError.OverBudget
|
|
|
|
asyncTest "budget frees when the epoch rolls over":
|
|
let rl = RateLimitManager.new(
|
|
RateLimitConfig(enabled: true, epochPeriodSec: 1, messagesPerEpoch: 1)
|
|
)
|
|
check (await rl.admit("first".toBytes())).isOk()
|
|
check (await rl.admit("second".toBytes())).isErr()
|
|
await sleepAsync(1100.milliseconds)
|
|
check (await rl.admit("third".toBytes())).isOk()
|
|
check (await rl.admit("fourth".toBytes())).isErr()
|
|
|
|
asyncTest "resetEpoch forces a fresh budget":
|
|
let rl = RateLimitManager.new(
|
|
RateLimitConfig(enabled: true, epochPeriodSec: 600, messagesPerEpoch: 1)
|
|
)
|
|
check (await rl.admit("first".toBytes())).isOk()
|
|
check (await rl.admit("second".toBytes())).isErr()
|
|
rl.resetEpoch()
|
|
check (await rl.admit("third".toBytes())).isOk()
|
|
|
|
asyncTest "non-positive budget or period is treated as disabled":
|
|
let zeroBudget = RateLimitManager.new(
|
|
RateLimitConfig(enabled: true, epochPeriodSec: 600, messagesPerEpoch: 0)
|
|
)
|
|
check (await zeroBudget.admit("a".toBytes())).isOk()
|
|
|
|
let zeroPeriod = RateLimitManager.new(
|
|
RateLimitConfig(enabled: true, epochPeriodSec: 0, messagesPerEpoch: 1)
|
|
)
|
|
check (await zeroPeriod.admit("a".toBytes())).isOk()
|
|
check (await zeroPeriod.admit("b".toBytes())).isOk()
|