File tree Expand file tree Collapse file tree
tests/unit/infrastructure Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1010Global, not per caller. Per-caller rate limits bound one IP; this bounds
1111the bill and the analyzer's headroom.
1212
13- Without Redis the cap is unenforced, matching every other cache here.
13+ Unlike the caches here, it fails closed. It shares its Redis with the
14+ expander's result cache, so an outage removes the cache that absorbs
15+ lookups and the cap that bounds them at the same moment. Unable to count
16+ means unable to spend.
1417"""
1518
1619from __future__ import annotations
@@ -39,18 +42,18 @@ def __init__(
3942 self ._prefix = prefix
4043
4144 async def take (self ) -> bool :
42- """Claim one lookup. False once the day's cap is spent."""
45+ """Claim one lookup. False once the day's cap is spent, and false
46+ whenever the count is unavailable."""
4347 if self ._redis is None :
44- return True
48+ return False
4549 key = f"{ self ._prefix } :{ datetime .now (timezone .utc ):%Y-%m-%d} "
4650 try :
4751 used = await self ._redis .incr (key )
4852 if used == 1 :
4953 await self ._redis .expire (key , _KEY_TTL_SECONDS )
5054 except Exception as exc :
51- # A broken counter must not take the feature down with it.
5255 log .warning ("web_risk_budget_error" , error = str (exc ))
53- return True
56+ return False
5457 if used == self ._limit + 1 :
5558 log .warning ("web_risk_budget_exhausted" , limit = self ._limit )
5659 return used <= self ._limit
Original file line number Diff line number Diff line change @@ -44,12 +44,16 @@ async def test_the_daily_key_expires_so_it_self_clears():
4444
4545
4646@pytest .mark .asyncio
47- async def test_without_redis_the_cap_is_unenforced ():
48- assert await WebRiskBudget (None , limit = 0 ).take () is True
47+ async def test_without_redis_nothing_is_spent ():
48+ assert await WebRiskBudget (None , limit = 1_000 ).take () is False
4949
5050
5151@pytest .mark .asyncio
52- async def test_a_broken_counter_does_not_take_the_feature_down ():
52+ async def test_a_broken_counter_refuses_rather_than_waving_calls_through ():
53+ """The counter shares its Redis with the expander's result cache, so an
54+ outage drops the cache and the cap together. Unable to count means
55+ unable to spend, or the quota goes exactly when it is least affordable.
56+ """
5357 redis = AsyncMock ()
5458 redis .incr = AsyncMock (side_effect = RuntimeError ("redis down" ))
55- assert await WebRiskBudget (redis , limit = 1 ).take () is True
59+ assert await WebRiskBudget (redis , limit = 1_000 ).take () is False
You can’t perform that action at this time.
0 commit comments