test_network_rpc.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests für Network RPC Correlation.
  4. """
  5. import asyncio
  6. import pytest
  7. from datetime import datetime, timedelta
  8. from trixy_core.network.rpc import (
  9. CorrelationManager,
  10. CorrelationConfig,
  11. PendingRequest,
  12. RequestState,
  13. )
  14. class TestCorrelationConfig:
  15. """Tests für CorrelationConfig."""
  16. def test_default_config(self):
  17. """Testet Standard-Konfiguration."""
  18. config = CorrelationConfig()
  19. assert config.default_timeout == 30.0
  20. assert config.cleanup_interval == 60.0
  21. assert config.max_pending == 10000
  22. def test_custom_config(self):
  23. """Testet benutzerdefinierte Konfiguration."""
  24. config = CorrelationConfig(
  25. default_timeout=60.0,
  26. max_pending=5000,
  27. )
  28. assert config.default_timeout == 60.0
  29. assert config.max_pending == 5000
  30. def test_custom_id_generator(self):
  31. """Testet benutzerdefinierten ID-Generator."""
  32. counter = [0]
  33. def custom_generator():
  34. counter[0] += 1
  35. return f"custom-{counter[0]}"
  36. config = CorrelationConfig(id_generator=custom_generator)
  37. assert config.id_generator is not None
  38. class TestPendingRequest:
  39. """Tests für PendingRequest."""
  40. def test_request_creation(self):
  41. """Testet Request-Erstellung."""
  42. request = PendingRequest[str](
  43. correlation_id="test-123",
  44. timeout=30.0,
  45. )
  46. assert request.correlation_id == "test-123"
  47. assert request.state == RequestState.PENDING
  48. assert not request.is_complete
  49. def test_is_complete_states(self):
  50. """Testet is_complete für verschiedene States."""
  51. request = PendingRequest[str](correlation_id="test")
  52. request.state = RequestState.PENDING
  53. assert not request.is_complete
  54. request.state = RequestState.COMPLETED
  55. assert request.is_complete
  56. request.state = RequestState.TIMEOUT
  57. assert request.is_complete
  58. request.state = RequestState.CANCELLED
  59. assert request.is_complete
  60. request.state = RequestState.ERROR
  61. assert request.is_complete
  62. def test_elapsed(self):
  63. """Testet Zeitberechnung."""
  64. request = PendingRequest[str](
  65. correlation_id="test",
  66. created_at=datetime.now() - timedelta(seconds=5),
  67. )
  68. assert request.elapsed_seconds >= 5
  69. def test_is_expired(self):
  70. """Testet Ablaufprüfung."""
  71. request = PendingRequest[str](
  72. correlation_id="test",
  73. timeout=1.0,
  74. created_at=datetime.now() - timedelta(seconds=2),
  75. )
  76. assert request.is_expired()
  77. request2 = PendingRequest[str](
  78. correlation_id="test",
  79. timeout=10.0,
  80. )
  81. assert not request2.is_expired()
  82. class TestCorrelationManager:
  83. """Tests für CorrelationManager."""
  84. @pytest.fixture
  85. def config(self):
  86. """Erstellt Test-Konfiguration."""
  87. return CorrelationConfig(
  88. default_timeout=1.0,
  89. max_pending=100,
  90. )
  91. @pytest.fixture
  92. def manager(self, config):
  93. """Erstellt CorrelationManager."""
  94. return CorrelationManager[str](config=config)
  95. @pytest.mark.asyncio
  96. async def test_create_request(self, manager):
  97. """Testet Request-Erstellung."""
  98. cid = await manager.create_request()
  99. assert cid is not None
  100. assert len(cid) > 0
  101. assert manager.pending_count == 1
  102. assert cid in manager
  103. @pytest.mark.asyncio
  104. async def test_create_request_with_metadata(self, manager):
  105. """Testet Request-Erstellung mit Metadaten."""
  106. cid = await manager.create_request(
  107. timeout=60.0,
  108. metadata={"user": "test"},
  109. )
  110. request = manager.get(cid)
  111. assert request.timeout == 60.0
  112. assert request.metadata["user"] == "test"
  113. @pytest.mark.asyncio
  114. async def test_complete_request(self, manager):
  115. """Testet Request-Abschluss."""
  116. cid = await manager.create_request()
  117. # Task für wait_for_response
  118. async def wait_task():
  119. return await manager.wait_for_response(cid)
  120. task = asyncio.create_task(wait_task())
  121. # Kurz warten dann abschließen
  122. await asyncio.sleep(0.01)
  123. assert manager.complete(cid, "result")
  124. result = await task
  125. assert result == "result"
  126. @pytest.mark.asyncio
  127. async def test_wait_for_response_timeout(self, manager):
  128. """Testet Timeout beim Warten."""
  129. cid = await manager.create_request(timeout=0.1)
  130. with pytest.raises(TimeoutError):
  131. await manager.wait_for_response(cid)
  132. request = manager.get(cid)
  133. assert request.state == RequestState.TIMEOUT
  134. @pytest.mark.asyncio
  135. async def test_fail_request(self, manager):
  136. """Testet Request-Fehler."""
  137. cid = await manager.create_request()
  138. async def wait_task():
  139. return await manager.wait_for_response(cid)
  140. task = asyncio.create_task(wait_task())
  141. await asyncio.sleep(0.01)
  142. error = ValueError("Test error")
  143. assert manager.fail(cid, error)
  144. with pytest.raises(ValueError):
  145. await task
  146. @pytest.mark.asyncio
  147. async def test_cancel_request(self, manager):
  148. """Testet Request-Abbruch."""
  149. cid = await manager.create_request()
  150. assert manager.cancel(cid)
  151. request = manager.get(cid)
  152. assert request.state == RequestState.CANCELLED
  153. @pytest.mark.asyncio
  154. async def test_complete_unknown_request(self, manager):
  155. """Testet Abschluss unbekannter Request."""
  156. assert not manager.complete("unknown-id", "result")
  157. @pytest.mark.asyncio
  158. async def test_complete_already_complete(self, manager):
  159. """Testet doppelten Abschluss."""
  160. cid = await manager.create_request()
  161. # Simuliere Abschluss
  162. request = manager.get(cid)
  163. request.state = RequestState.COMPLETED
  164. assert not manager.complete(cid, "result")
  165. @pytest.mark.asyncio
  166. async def test_remove_request(self, manager):
  167. """Testet Request-Entfernung."""
  168. cid = await manager.create_request()
  169. assert manager.pending_count == 1
  170. removed = await manager.remove(cid)
  171. assert removed is not None
  172. assert manager.pending_count == 0
  173. @pytest.mark.asyncio
  174. async def test_has_pending(self, manager):
  175. """Testet Pending-Prüfung."""
  176. cid = await manager.create_request()
  177. assert manager.has_pending(cid)
  178. assert not manager.has_pending("unknown")
  179. @pytest.mark.asyncio
  180. async def test_cleanup_expired(self, manager):
  181. """Testet Cleanup abgelaufener Requests."""
  182. # Erstelle Request mit kurzem Timeout
  183. cid = await manager.create_request(timeout=0.01)
  184. # Warte bis abgelaufen
  185. await asyncio.sleep(0.05)
  186. removed = await manager.cleanup_expired()
  187. assert removed == 1
  188. assert manager.pending_count == 0
  189. @pytest.mark.asyncio
  190. async def test_cancel_all(self, manager):
  191. """Testet Abbruch aller Requests."""
  192. await manager.create_request()
  193. await manager.create_request()
  194. await manager.create_request()
  195. cancelled = await manager.cancel_all()
  196. assert cancelled == 3
  197. @pytest.mark.asyncio
  198. async def test_get_pending_ids(self, manager):
  199. """Testet Abruf aller Pending-IDs."""
  200. cid1 = await manager.create_request()
  201. cid2 = await manager.create_request()
  202. ids = manager.get_pending_ids()
  203. assert cid1 in ids
  204. assert cid2 in ids
  205. @pytest.mark.asyncio
  206. async def test_get_stats(self, manager):
  207. """Testet Statistik-Abruf."""
  208. await manager.create_request()
  209. await manager.create_request()
  210. stats = manager.get_stats()
  211. assert stats["total_pending"] == 2
  212. assert "states" in stats
  213. @pytest.mark.asyncio
  214. async def test_max_pending_limit(self, config):
  215. """Testet Maximum-Limit."""
  216. config.max_pending = 3
  217. manager = CorrelationManager[str](config=config)
  218. await manager.create_request()
  219. await manager.create_request()
  220. await manager.create_request()
  221. with pytest.raises(RuntimeError):
  222. await manager.create_request()
  223. @pytest.mark.asyncio
  224. async def test_unique_ids(self, manager):
  225. """Testet ID-Eindeutigkeit."""
  226. ids = set()
  227. for _ in range(100):
  228. cid = await manager.create_request()
  229. assert cid not in ids
  230. ids.add(cid)
  231. @pytest.mark.asyncio
  232. async def test_custom_id_generator(self):
  233. """Testet benutzerdefinierten ID-Generator."""
  234. counter = [0]
  235. def generator():
  236. counter[0] += 1
  237. return f"custom-{counter[0]}"
  238. config = CorrelationConfig(id_generator=generator)
  239. manager = CorrelationManager[str](config=config)
  240. cid1 = await manager.create_request()
  241. cid2 = await manager.create_request()
  242. assert cid1 == "custom-1"
  243. assert cid2 == "custom-2"
  244. @pytest.mark.asyncio
  245. async def test_cleanup_task(self, manager):
  246. """Testet Cleanup-Task."""
  247. await manager.start_cleanup()
  248. assert manager._cleanup_task is not None
  249. await manager.stop_cleanup()
  250. assert manager._cleanup_task is None
  251. @pytest.mark.asyncio
  252. async def test_len_and_contains(self, manager):
  253. """Testet __len__ und __contains__."""
  254. assert len(manager) == 0
  255. cid = await manager.create_request()
  256. assert len(manager) == 1
  257. assert cid in manager
  258. class TestRequestState:
  259. """Tests für RequestState."""
  260. def test_state_values(self):
  261. """Testet State-Werte."""
  262. assert RequestState.PENDING.name == "PENDING"
  263. assert RequestState.COMPLETED.name == "COMPLETED"
  264. assert RequestState.TIMEOUT.name == "TIMEOUT"
  265. assert RequestState.CANCELLED.name == "CANCELLED"
  266. assert RequestState.ERROR.name == "ERROR"