test_network_keepalive.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests für Network KeepAlive.
  4. """
  5. import asyncio
  6. import pytest
  7. from datetime import datetime, timedelta
  8. from trixy_core.network.keepalive import (
  9. KeepAliveManager,
  10. KeepAliveSession,
  11. KeepAliveConfig,
  12. ConnectionState,
  13. ConnectionStats,
  14. )
  15. class TestKeepAliveConfig:
  16. """Tests für KeepAliveConfig."""
  17. def test_default_config(self):
  18. """Testet Standard-Konfiguration."""
  19. config = KeepAliveConfig()
  20. assert config.ping_interval == 30.0
  21. assert config.pong_timeout == 10.0
  22. assert config.max_missed_pongs == 3
  23. def test_custom_config(self):
  24. """Testet benutzerdefinierte Konfiguration."""
  25. config = KeepAliveConfig(
  26. ping_interval=60.0,
  27. pong_timeout=5.0,
  28. max_missed_pongs=5,
  29. )
  30. assert config.ping_interval == 60.0
  31. assert config.pong_timeout == 5.0
  32. assert config.max_missed_pongs == 5
  33. class TestConnectionStats:
  34. """Tests für ConnectionStats."""
  35. def test_initial_stats(self):
  36. """Testet initiale Statistiken."""
  37. stats = ConnectionStats()
  38. assert stats.pings_sent == 0
  39. assert stats.pongs_received == 0
  40. assert stats.missed_pongs == 0
  41. assert stats.state == ConnectionState.CONNECTED
  42. def test_state_values(self):
  43. """Testet Connection-States."""
  44. assert ConnectionState.CONNECTED.name == "CONNECTED"
  45. assert ConnectionState.DISCONNECTED.name == "DISCONNECTED"
  46. assert ConnectionState.STALE.name == "STALE"
  47. class TestKeepAliveSession:
  48. """Tests für KeepAliveSession."""
  49. @pytest.fixture
  50. def config(self):
  51. """Erstellt Test-Konfiguration."""
  52. return KeepAliveConfig(
  53. ping_interval=0.1, # Schnelle Tests
  54. pong_timeout=0.05,
  55. max_missed_pongs=2,
  56. initial_delay=0.01,
  57. )
  58. @pytest.fixture
  59. def ping_func(self):
  60. """Erstellt Mock-Ping-Funktion."""
  61. calls = []
  62. async def ping():
  63. calls.append(datetime.now())
  64. ping.calls = calls
  65. return ping
  66. @pytest.mark.asyncio
  67. async def test_session_creation(self, config, ping_func):
  68. """Testet Session-Erstellung."""
  69. session = KeepAliveSession(
  70. connection_id="test-001",
  71. ping_func=ping_func,
  72. config=config,
  73. )
  74. assert session.connection_id == "test-001"
  75. assert session.state == ConnectionState.CONNECTED
  76. assert not session.is_running
  77. @pytest.mark.asyncio
  78. async def test_session_start_stop(self, config, ping_func):
  79. """Testet Start/Stop."""
  80. session = KeepAliveSession(
  81. connection_id="test-001",
  82. ping_func=ping_func,
  83. config=config,
  84. )
  85. await session.start()
  86. assert session.is_running
  87. await asyncio.sleep(0.05)
  88. await session.stop()
  89. assert not session.is_running
  90. @pytest.mark.asyncio
  91. async def test_handle_pong(self, config, ping_func):
  92. """Testet Pong-Verarbeitung."""
  93. session = KeepAliveSession(
  94. connection_id="test-001",
  95. ping_func=ping_func,
  96. config=config,
  97. )
  98. await session.start()
  99. await asyncio.sleep(0.02)
  100. # Simuliere Ping gesendet
  101. session._waiting_for_pong = True
  102. session._ping_sent_time = datetime.now()
  103. session.stats.pings_sent = 1
  104. # Pong empfangen
  105. session.handle_pong()
  106. assert session.stats.pongs_received == 1
  107. assert not session._waiting_for_pong
  108. await session.stop()
  109. @pytest.mark.asyncio
  110. async def test_rtt_calculation(self, config, ping_func):
  111. """Testet RTT-Berechnung."""
  112. session = KeepAliveSession(
  113. connection_id="test-001",
  114. ping_func=ping_func,
  115. config=config,
  116. )
  117. # Simuliere Ping/Pong
  118. session._waiting_for_pong = True
  119. session._ping_sent_time = datetime.now() - timedelta(milliseconds=50)
  120. session.handle_pong()
  121. assert session.stats.last_rtt_ms >= 50
  122. assert session.stats.min_rtt_ms >= 50
  123. assert session.stats.max_rtt_ms >= 50
  124. class TestKeepAliveManager:
  125. """Tests für KeepAliveManager."""
  126. @pytest.fixture
  127. def config(self):
  128. """Erstellt Test-Konfiguration."""
  129. return KeepAliveConfig(
  130. ping_interval=0.1,
  131. pong_timeout=0.05,
  132. max_missed_pongs=2,
  133. initial_delay=0.01,
  134. )
  135. @pytest.fixture
  136. def manager(self, config):
  137. """Erstellt KeepAliveManager."""
  138. return KeepAliveManager(default_config=config)
  139. @pytest.mark.asyncio
  140. async def test_register_connection(self, manager):
  141. """Testet Verbindungs-Registrierung."""
  142. async def ping():
  143. pass
  144. session = await manager.register("conn-001", ping, auto_start=False)
  145. assert session is not None
  146. assert manager.session_count == 1
  147. assert "conn-001" in manager._sessions
  148. @pytest.mark.asyncio
  149. async def test_unregister_connection(self, manager):
  150. """Testet Verbindungs-Entfernung."""
  151. async def ping():
  152. pass
  153. await manager.register("conn-001", ping, auto_start=False)
  154. assert await manager.unregister("conn-001")
  155. assert manager.session_count == 0
  156. @pytest.mark.asyncio
  157. async def test_unregister_nonexistent(self, manager):
  158. """Testet Entfernung nicht existierender Verbindung."""
  159. assert not await manager.unregister("nonexistent")
  160. @pytest.mark.asyncio
  161. async def test_get_session(self, manager):
  162. """Testet Session-Abruf."""
  163. async def ping():
  164. pass
  165. await manager.register("conn-001", ping, auto_start=False)
  166. session = manager.get_session("conn-001")
  167. assert session is not None
  168. assert session.connection_id == "conn-001"
  169. @pytest.mark.asyncio
  170. async def test_handle_pong(self, manager):
  171. """Testet Pong-Verarbeitung über Manager."""
  172. async def ping():
  173. pass
  174. await manager.register("conn-001", ping, auto_start=False)
  175. # Simuliere Ping
  176. session = manager.get_session("conn-001")
  177. session._waiting_for_pong = True
  178. session._ping_sent_time = datetime.now()
  179. session.stats.pings_sent = 1
  180. manager.handle_pong("conn-001")
  181. assert session.stats.pongs_received == 1
  182. @pytest.mark.asyncio
  183. async def test_get_stats(self, manager):
  184. """Testet Statistik-Abruf."""
  185. async def ping():
  186. pass
  187. await manager.register("conn-001", ping, auto_start=False)
  188. stats = manager.get_stats("conn-001")
  189. assert stats is not None
  190. assert isinstance(stats, ConnectionStats)
  191. @pytest.mark.asyncio
  192. async def test_get_all_stats(self, manager):
  193. """Testet Abruf aller Statistiken."""
  194. async def ping():
  195. pass
  196. await manager.register("conn-001", ping, auto_start=False)
  197. await manager.register("conn-002", ping, auto_start=False)
  198. all_stats = manager.get_all_stats()
  199. assert len(all_stats) == 2
  200. assert "conn-001" in all_stats
  201. assert "conn-002" in all_stats
  202. @pytest.mark.asyncio
  203. async def test_get_summary(self, manager):
  204. """Testet Zusammenfassung."""
  205. async def ping():
  206. pass
  207. await manager.register("conn-001", ping, auto_start=False)
  208. summary = manager.get_summary()
  209. assert "total_sessions" in summary
  210. assert "active_sessions" in summary
  211. assert "states" in summary
  212. @pytest.mark.asyncio
  213. async def test_start_stop_all(self, manager):
  214. """Testet Start/Stop aller Sessions."""
  215. async def ping():
  216. pass
  217. await manager.register("conn-001", ping, auto_start=False)
  218. await manager.register("conn-002", ping, auto_start=False)
  219. assert manager.active_count == 0
  220. await manager.start_all()
  221. assert manager.active_count == 2
  222. await manager.stop_all()
  223. assert manager.active_count == 0
  224. @pytest.mark.asyncio
  225. async def test_timeout_callback(self, manager):
  226. """Testet Timeout-Callback."""
  227. timeout_received = []
  228. async def on_timeout(conn_id):
  229. timeout_received.append(conn_id)
  230. manager.on_timeout(on_timeout)
  231. async def ping():
  232. pass
  233. await manager.register("conn-001", ping, auto_start=False)
  234. # Simuliere Timeout
  235. await manager._global_timeout_handler("conn-001")
  236. assert "conn-001" in timeout_received
  237. @pytest.mark.asyncio
  238. async def test_disconnect_callback(self, manager):
  239. """Testet Disconnect-Callback."""
  240. disconnect_received = []
  241. async def on_disconnect(conn_id):
  242. disconnect_received.append(conn_id)
  243. manager.on_disconnect(on_disconnect)
  244. async def ping():
  245. pass
  246. await manager.register("conn-001", ping, auto_start=False)
  247. # Simuliere Disconnect
  248. await manager._global_disconnect_handler("conn-001")
  249. assert "conn-001" in disconnect_received
  250. @pytest.mark.asyncio
  251. async def test_get_stale_connections(self, manager):
  252. """Testet Erkennung von Stale-Verbindungen."""
  253. async def ping():
  254. pass
  255. await manager.register("conn-001", ping, auto_start=False)
  256. session = manager.get_session("conn-001")
  257. session.stats.state = ConnectionState.STALE
  258. stale = manager.get_stale_connections()
  259. assert "conn-001" in stale
  260. @pytest.mark.asyncio
  261. async def test_get_disconnected(self, manager):
  262. """Testet Erkennung getrennter Verbindungen."""
  263. async def ping():
  264. pass
  265. await manager.register("conn-001", ping, auto_start=False)
  266. session = manager.get_session("conn-001")
  267. session.stats.state = ConnectionState.DISCONNECTED
  268. disconnected = manager.get_disconnected()
  269. assert "conn-001" in disconnected
  270. @pytest.mark.asyncio
  271. async def test_cleanup(self, manager):
  272. """Testet Cleanup."""
  273. async def ping():
  274. pass
  275. await manager.register("conn-001", ping, auto_start=False)
  276. await manager.register("conn-002", ping, auto_start=False)
  277. # Markiere eine als disconnected
  278. session = manager.get_session("conn-001")
  279. session.stats.state = ConnectionState.DISCONNECTED
  280. removed = await manager.cleanup()
  281. assert removed == 1
  282. assert manager.session_count == 1