89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
import unittest
|
|
|
|
from agent.customer_agent.session import AnonymousSessionService, SessionOwnershipError
|
|
|
|
|
|
class FakeRedis:
|
|
def __init__(self):
|
|
self.values = {}
|
|
self.lists = {}
|
|
self.sorted_sets = {}
|
|
self.expirations = {}
|
|
|
|
async def set(self, key, value, ex=None):
|
|
self.values[key] = value
|
|
if ex is not None:
|
|
self.expirations[key] = ex
|
|
|
|
async def exists(self, key):
|
|
return int(key in self.values or key in self.lists)
|
|
|
|
async def rpush(self, key, value):
|
|
self.lists.setdefault(key, []).append(value)
|
|
return len(self.lists[key])
|
|
|
|
async def expire(self, key, seconds):
|
|
self.expirations[key] = seconds
|
|
|
|
async def zremrangebyscore(self, key, minimum, maximum):
|
|
self.sorted_sets[key] = [item for item in self.sorted_sets.get(key, []) if item[0] > maximum]
|
|
|
|
async def zadd(self, key, mapping):
|
|
self.sorted_sets.setdefault(key, []).extend((score, member) for member, score in mapping.items())
|
|
|
|
async def zcard(self, key):
|
|
return len(self.sorted_sets.get(key, []))
|
|
|
|
|
|
class SessionTests(unittest.IsolatedAsyncioTestCase):
|
|
async def test_create_initializes_anonymous_session_and_message_list(self):
|
|
redis = FakeRedis()
|
|
service = AnonymousSessionService(redis, config_getter={"agent.customer.session.ttl": "120"}.get)
|
|
|
|
session_id = await service.create_session()
|
|
|
|
self.assertEqual(len(session_id), 32)
|
|
self.assertIn(f"session:{session_id}", redis.values)
|
|
self.assertIn(f"session:{session_id}:messages", redis.lists)
|
|
self.assertEqual(redis.expirations[f"session:{session_id}"], 120)
|
|
|
|
async def test_missing_session_raises_not_found(self):
|
|
service = AnonymousSessionService(FakeRedis(), config_getter={}.get)
|
|
|
|
with self.assertRaises(SessionOwnershipError) as ctx:
|
|
await service.verify_session_ownership("missing")
|
|
|
|
self.assertEqual(ctx.exception.code, 404)
|
|
|
|
async def test_authenticated_owner_mismatch_raises_forbidden(self):
|
|
redis = FakeRedis()
|
|
service = AnonymousSessionService(redis, config_getter={}.get)
|
|
session_id = await service.create_session()
|
|
|
|
with self.assertRaises(SessionOwnershipError) as ctx:
|
|
await service.verify_session_ownership(session_id, customer_id="customer-1", session_customer_id="customer-2")
|
|
|
|
self.assertEqual(ctx.exception.code, 403)
|
|
|
|
async def test_rate_limit_uses_sliding_window_and_returns_retry_after(self):
|
|
redis = FakeRedis()
|
|
service = AnonymousSessionService(
|
|
redis,
|
|
config_getter={
|
|
"agent.customer.rate_limit.window_sec": "60",
|
|
"agent.customer.rate_limit.max_requests": "1",
|
|
"agent.customer.session.ttl": "120",
|
|
}.get,
|
|
clock=lambda: 100.0,
|
|
)
|
|
await service.create_session()
|
|
|
|
self.assertEqual(await service.consume_chat_quota("s"), None)
|
|
retry_after = await service.consume_chat_quota("s")
|
|
|
|
self.assertEqual(retry_after, 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|