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()