Add rate limiter lease and dialect regression tests

#13
by basyx - opened
Files changed (1) hide show
  1. tests/test_rate_limit.py +69 -0
tests/test_rate_limit.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+ from types import SimpleNamespace
5
+
6
+ import pytest
7
+
8
+ from app.security.rate_limit import APIKeyRateLimiter, RateLimitLease
9
+
10
+
11
+ class FakeLimiter:
12
+ def __init__(self) -> None:
13
+ self.releases: list[str] = []
14
+
15
+ async def release_job(self, api_key_id: str) -> None:
16
+ self.releases.append(api_key_id)
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_concurrency_lease_release_is_idempotent() -> None:
21
+ limiter = FakeLimiter()
22
+ lease = RateLimitLease(limiter, "key-1", True) # type: ignore[arg-type]
23
+
24
+ await lease.release()
25
+ await lease.release()
26
+
27
+ assert limiter.releases == ["key-1"]
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_non_job_lease_does_not_release_concurrency() -> None:
32
+ limiter = FakeLimiter()
33
+ lease = RateLimitLease(limiter, "key-1", False) # type: ignore[arg-type]
34
+
35
+ await lease.release()
36
+ await lease.release()
37
+
38
+ assert limiter.releases == []
39
+
40
+
41
+ def test_remove_one_removes_only_one_matching_reservation() -> None:
42
+ values = deque([1.0, 2.0, 2.0, 3.0])
43
+
44
+ APIKeyRateLimiter._remove_one(values, 2.0)
45
+
46
+ assert list(values) == [1.0, 2.0, 3.0]
47
+
48
+
49
+ def test_rate_limit_insert_supports_sqlite_and_postgres_dialects() -> None:
50
+ from sqlalchemy.dialects import postgresql, sqlite
51
+ from sqlalchemy.dialects.postgresql import insert as postgresql_insert
52
+ from sqlalchemy.dialects.sqlite import insert as sqlite_insert
53
+ from app.security.models import RateLimit
54
+
55
+ values = {
56
+ "api_key_id": "00000000-0000-0000-0000-000000000001",
57
+ "bucket_type": "requests_minute",
58
+ "bucket_start": __import__("datetime").datetime.now(__import__("datetime").timezone.utc),
59
+ "count": 1,
60
+ "units": 0,
61
+ "updated_at": __import__("datetime").datetime.now(__import__("datetime").timezone.utc),
62
+ }
63
+ for dialect, insert in ((sqlite.dialect(), sqlite_insert), (postgresql.dialect(), postgresql_insert)):
64
+ statement = insert(RateLimit).values(**values)
65
+ statement = statement.on_conflict_do_update(
66
+ index_elements=[RateLimit.api_key_id, RateLimit.bucket_type, RateLimit.bucket_start],
67
+ set_={"count": RateLimit.count + statement.excluded.count},
68
+ )
69
+ assert "ON CONFLICT" in str(statement.compile(dialect=dialect))