You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1156 lines
44KB

  1. import json
  2. import logging
  3. import time
  4. import unittest
  5. import six
  6. if six.PY3:
  7. from unittest import mock
  8. else:
  9. import mock
  10. import websocket
  11. from engineio import client
  12. from engineio import exceptions
  13. from engineio import packet
  14. from engineio import payload
  15. if six.PY2:
  16. ConnectionError = OSError
  17. class TestClient(unittest.TestCase):
  18. def test_is_asyncio_based(self):
  19. c = client.Client()
  20. self.assertEqual(c.is_asyncio_based(), False)
  21. def test_create(self):
  22. c = client.Client()
  23. self.assertEqual(c.handlers, {})
  24. for attr in ['base_url', 'transports', 'sid', 'upgrades',
  25. 'ping_interval', 'ping_timeout', 'http', 'ws',
  26. 'read_loop_task', 'write_loop_task', 'queue']:
  27. self.assertIsNone(getattr(c, attr), attr + ' is not None')
  28. self.assertTrue(c.pong_received)
  29. self.assertEqual(c.state, 'disconnected')
  30. def test_custon_json(self):
  31. client.Client()
  32. self.assertEqual(packet.Packet.json, json)
  33. client.Client(json='foo')
  34. self.assertEqual(packet.Packet.json, 'foo')
  35. packet.Packet.json = json
  36. def test_logger(self):
  37. c = client.Client(logger=False)
  38. self.assertEqual(c.logger.getEffectiveLevel(), logging.ERROR)
  39. c.logger.setLevel(logging.NOTSET)
  40. c = client.Client(logger=True)
  41. self.assertEqual(c.logger.getEffectiveLevel(), logging.INFO)
  42. c.logger.setLevel(logging.WARNING)
  43. c = client.Client(logger=True)
  44. self.assertEqual(c.logger.getEffectiveLevel(), logging.WARNING)
  45. c.logger.setLevel(logging.NOTSET)
  46. my_logger = logging.Logger('foo')
  47. c = client.Client(logger=my_logger)
  48. self.assertEqual(c.logger, my_logger)
  49. def test_on_event(self):
  50. c = client.Client()
  51. @c.on('connect')
  52. def foo():
  53. pass
  54. c.on('disconnect', foo)
  55. self.assertEqual(c.handlers['connect'], foo)
  56. self.assertEqual(c.handlers['disconnect'], foo)
  57. def test_on_event_invalid(self):
  58. c = client.Client()
  59. self.assertRaises(ValueError, c.on, 'invalid')
  60. def test_already_connected(self):
  61. c = client.Client()
  62. c.state = 'connected'
  63. self.assertRaises(ValueError, c.connect, 'http://foo')
  64. def test_invalid_transports(self):
  65. c = client.Client()
  66. self.assertRaises(ValueError, c.connect, 'http://foo',
  67. transports=['foo', 'bar'])
  68. def test_some_invalid_transports(self):
  69. c = client.Client()
  70. c._connect_websocket = mock.MagicMock()
  71. c.connect('http://foo', transports=['foo', 'websocket', 'bar'])
  72. self.assertEqual(c.transports, ['websocket'])
  73. def test_connect_polling(self):
  74. c = client.Client()
  75. c._connect_polling = mock.MagicMock(return_value='foo')
  76. self.assertEqual(c.connect('http://foo'), 'foo')
  77. c._connect_polling.assert_called_once_with(
  78. 'http://foo', {}, 'engine.io')
  79. c = client.Client()
  80. c._connect_polling = mock.MagicMock(return_value='foo')
  81. self.assertEqual(c.connect('http://foo', transports=['polling']),
  82. 'foo')
  83. c._connect_polling.assert_called_once_with(
  84. 'http://foo', {}, 'engine.io')
  85. c = client.Client()
  86. c._connect_polling = mock.MagicMock(return_value='foo')
  87. self.assertEqual(c.connect('http://foo',
  88. transports=['polling', 'websocket']),
  89. 'foo')
  90. c._connect_polling.assert_called_once_with(
  91. 'http://foo', {}, 'engine.io')
  92. def test_connect_websocket(self):
  93. c = client.Client()
  94. c._connect_websocket = mock.MagicMock(return_value='foo')
  95. self.assertEqual(c.connect('http://foo', transports=['websocket']),
  96. 'foo')
  97. c._connect_websocket.assert_called_once_with(
  98. 'http://foo', {}, 'engine.io')
  99. c = client.Client()
  100. c._connect_websocket = mock.MagicMock(return_value='foo')
  101. self.assertEqual(c.connect('http://foo', transports='websocket'),
  102. 'foo')
  103. c._connect_websocket.assert_called_once_with(
  104. 'http://foo', {}, 'engine.io')
  105. def test_connect_query_string(self):
  106. c = client.Client()
  107. c._connect_polling = mock.MagicMock(return_value='foo')
  108. self.assertEqual(c.connect('http://foo?bar=baz'), 'foo')
  109. c._connect_polling.assert_called_once_with(
  110. 'http://foo?bar=baz', {}, 'engine.io')
  111. def test_connect_custom_headers(self):
  112. c = client.Client()
  113. c._connect_polling = mock.MagicMock(return_value='foo')
  114. self.assertEqual(c.connect('http://foo', headers={'Foo': 'Bar'}),
  115. 'foo')
  116. c._connect_polling.assert_called_once_with(
  117. 'http://foo', {'Foo': 'Bar'}, 'engine.io')
  118. def test_wait(self):
  119. c = client.Client()
  120. c.read_loop_task = mock.MagicMock()
  121. c.wait()
  122. c.read_loop_task.join.assert_called_once_with()
  123. def test_wait_no_task(self):
  124. c = client.Client()
  125. c.read_loop_task = None
  126. c.wait() # should not block
  127. def test_send(self):
  128. c = client.Client()
  129. saved_packets = []
  130. def fake_send_packet(pkt):
  131. saved_packets.append(pkt)
  132. c._send_packet = fake_send_packet
  133. c.send('foo')
  134. c.send('foo', binary=False)
  135. c.send(b'foo', binary=True)
  136. self.assertEqual(saved_packets[0].packet_type, packet.MESSAGE)
  137. self.assertEqual(saved_packets[0].data, 'foo')
  138. self.assertEqual(saved_packets[0].binary,
  139. False if six.PY3 else True)
  140. self.assertEqual(saved_packets[1].packet_type, packet.MESSAGE)
  141. self.assertEqual(saved_packets[1].data, 'foo')
  142. self.assertEqual(saved_packets[1].binary, False)
  143. self.assertEqual(saved_packets[2].packet_type, packet.MESSAGE)
  144. self.assertEqual(saved_packets[2].data, b'foo')
  145. self.assertEqual(saved_packets[2].binary, True)
  146. def test_disconnect_not_connected(self):
  147. c = client.Client()
  148. c.state = 'foo'
  149. c.sid = 'bar'
  150. c.disconnect()
  151. self.assertEqual(c.state, 'disconnected')
  152. self.assertIsNone(c.sid)
  153. def test_disconnect_polling(self):
  154. c = client.Client()
  155. client.connected_clients.append(c)
  156. c.state = 'connected'
  157. c.current_transport = 'polling'
  158. c.queue = mock.MagicMock()
  159. c.read_loop_task = mock.MagicMock()
  160. c.ws = mock.MagicMock()
  161. c._trigger_event = mock.MagicMock()
  162. c.disconnect()
  163. c.read_loop_task.join.assert_called_once_with()
  164. c.ws.mock.assert_not_called()
  165. self.assertNotIn(c, client.connected_clients)
  166. c._trigger_event.assert_called_once_with('disconnect',
  167. run_async=False)
  168. def test_disconnect_websocket(self):
  169. c = client.Client()
  170. client.connected_clients.append(c)
  171. c.state = 'connected'
  172. c.current_transport = 'websocket'
  173. c.queue = mock.MagicMock()
  174. c.read_loop_task = mock.MagicMock()
  175. c.ws = mock.MagicMock()
  176. c._trigger_event = mock.MagicMock()
  177. c.disconnect()
  178. c.read_loop_task.join.assert_called_once_with()
  179. c.ws.close.assert_called_once_with()
  180. self.assertNotIn(c, client.connected_clients)
  181. c._trigger_event.assert_called_once_with('disconnect',
  182. run_async=False)
  183. def test_disconnect_polling_abort(self):
  184. c = client.Client()
  185. client.connected_clients.append(c)
  186. c.state = 'connected'
  187. c.current_transport = 'polling'
  188. c.queue = mock.MagicMock()
  189. c.read_loop_task = mock.MagicMock()
  190. c.ws = mock.MagicMock()
  191. c.disconnect(abort=True)
  192. c.queue.join.assert_not_called()
  193. c.read_loop_task.join.assert_not_called()
  194. c.ws.mock.assert_not_called()
  195. self.assertNotIn(c, client.connected_clients)
  196. def test_disconnect_websocket_abort(self):
  197. c = client.Client()
  198. client.connected_clients.append(c)
  199. c.state = 'connected'
  200. c.current_transport = 'websocket'
  201. c.queue = mock.MagicMock()
  202. c.read_loop_task = mock.MagicMock()
  203. c.ws = mock.MagicMock()
  204. c.disconnect(abort=True)
  205. c.queue.join.assert_not_called()
  206. c.read_loop_task.join.assert_not_called()
  207. c.ws.mock.assert_not_called()
  208. self.assertNotIn(c, client.connected_clients)
  209. def test_current_transport(self):
  210. c = client.Client()
  211. c.current_transport = 'foo'
  212. self.assertEqual(c.transport(), 'foo')
  213. def test_background_tasks(self):
  214. flag = {}
  215. def bg_task():
  216. flag['task'] = True
  217. c = client.Client()
  218. task = c.start_background_task(bg_task)
  219. task.join()
  220. self.assertIn('task', flag)
  221. self.assertTrue(flag['task'])
  222. def test_sleep(self):
  223. c = client.Client()
  224. t = time.time()
  225. c.sleep(0.1)
  226. self.assertTrue(time.time() - t > 0.1)
  227. def test_create_queue(self):
  228. c = client.Client()
  229. q = c.create_queue()
  230. self.assertRaises(q.Empty, q.get, timeout=0.01)
  231. def test_create_event(self):
  232. c = client.Client()
  233. e = c.create_event()
  234. self.assertFalse(e.is_set())
  235. e.set()
  236. self.assertTrue(e.is_set())
  237. @mock.patch('engineio.client.time.time', return_value=123.456)
  238. @mock.patch('engineio.client.Client._send_request', return_value=None)
  239. def test_polling_connection_failed(self, _send_request, _time):
  240. c = client.Client()
  241. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
  242. headers={'Foo': 'Bar'})
  243. _send_request.assert_called_once_with(
  244. 'GET', 'http://foo/engine.io/?transport=polling&EIO=3&t=123.456',
  245. headers={'Foo': 'Bar'})
  246. @mock.patch('engineio.client.Client._send_request')
  247. def test_polling_connection_404(self, _send_request):
  248. _send_request.return_value.status_code = 404
  249. c = client.Client()
  250. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
  251. @mock.patch('engineio.client.Client._send_request')
  252. def test_polling_connection_invalid_packet(self, _send_request):
  253. _send_request.return_value.status_code = 200
  254. _send_request.return_value.content = b'foo'
  255. c = client.Client()
  256. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
  257. @mock.patch('engineio.client.Client._send_request')
  258. def test_polling_connection_no_open_packet(self, _send_request):
  259. _send_request.return_value.status_code = 200
  260. _send_request.return_value.content = payload.Payload(packets=[
  261. packet.Packet(packet.CLOSE, {
  262. 'sid': '123', 'upgrades': [], 'pingInterval': 10,
  263. 'pingTimeout': 20
  264. })
  265. ]).encode()
  266. c = client.Client()
  267. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo')
  268. @mock.patch('engineio.client.Client._send_request')
  269. def test_polling_connection_successful(self, _send_request):
  270. _send_request.return_value.status_code = 200
  271. _send_request.return_value.content = payload.Payload(packets=[
  272. packet.Packet(packet.OPEN, {
  273. 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
  274. 'pingTimeout': 2000
  275. })
  276. ]).encode()
  277. c = client.Client()
  278. c._ping_loop = mock.MagicMock()
  279. c._read_loop_polling = mock.MagicMock()
  280. c._read_loop_websocket = mock.MagicMock()
  281. c._write_loop = mock.MagicMock()
  282. on_connect = mock.MagicMock()
  283. c.on('connect', on_connect)
  284. c.connect('http://foo')
  285. time.sleep(0.1)
  286. c._ping_loop.assert_called_once_with()
  287. c._read_loop_polling.assert_called_once_with()
  288. c._read_loop_websocket.assert_not_called()
  289. c._write_loop.assert_called_once_with()
  290. on_connect.assert_called_once_with()
  291. self.assertIn(c, client.connected_clients)
  292. self.assertEqual(
  293. c.base_url,
  294. 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
  295. self.assertEqual(c.sid, '123')
  296. self.assertEqual(c.ping_interval, 1)
  297. self.assertEqual(c.ping_timeout, 2)
  298. self.assertEqual(c.upgrades, [])
  299. self.assertEqual(c.transport(), 'polling')
  300. @mock.patch('engineio.client.Client._send_request')
  301. def test_polling_connection_with_more_packets(self, _send_request):
  302. _send_request.return_value.status_code = 200
  303. _send_request.return_value.content = payload.Payload(packets=[
  304. packet.Packet(packet.OPEN, {
  305. 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
  306. 'pingTimeout': 2000
  307. }),
  308. packet.Packet(packet.NOOP)
  309. ]).encode()
  310. c = client.Client()
  311. c._ping_loop = mock.MagicMock()
  312. c._read_loop_polling = mock.MagicMock()
  313. c._read_loop_websocket = mock.MagicMock()
  314. c._write_loop = mock.MagicMock()
  315. c._receive_packet = mock.MagicMock()
  316. on_connect = mock.MagicMock()
  317. c.on('connect', on_connect)
  318. c.connect('http://foo')
  319. time.sleep(0.1)
  320. self.assertEqual(c._receive_packet.call_count, 1)
  321. self.assertEqual(
  322. c._receive_packet.call_args_list[0][0][0].packet_type,
  323. packet.NOOP)
  324. @mock.patch('engineio.client.Client._send_request')
  325. def test_polling_connection_upgraded(self, _send_request):
  326. _send_request.return_value.status_code = 200
  327. _send_request.return_value.content = payload.Payload(packets=[
  328. packet.Packet(packet.OPEN, {
  329. 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000,
  330. 'pingTimeout': 2000
  331. })
  332. ]).encode()
  333. c = client.Client()
  334. c._connect_websocket = mock.MagicMock(return_value=True)
  335. on_connect = mock.MagicMock()
  336. c.on('connect', on_connect)
  337. c.connect('http://foo')
  338. c._connect_websocket.assert_called_once_with('http://foo', {},
  339. 'engine.io')
  340. on_connect.assert_called_once_with()
  341. self.assertIn(c, client.connected_clients)
  342. self.assertEqual(
  343. c.base_url,
  344. 'http://foo/engine.io/?transport=polling&EIO=3&sid=123')
  345. self.assertEqual(c.sid, '123')
  346. self.assertEqual(c.ping_interval, 1)
  347. self.assertEqual(c.ping_timeout, 2)
  348. self.assertEqual(c.upgrades, ['websocket'])
  349. @mock.patch('engineio.client.Client._send_request')
  350. def test_polling_connection_not_upgraded(self, _send_request):
  351. _send_request.return_value.status_code = 200
  352. _send_request.return_value.content = payload.Payload(packets=[
  353. packet.Packet(packet.OPEN, {
  354. 'sid': '123', 'upgrades': ['websocket'], 'pingInterval': 1000,
  355. 'pingTimeout': 2000
  356. })
  357. ]).encode()
  358. c = client.Client()
  359. c._connect_websocket = mock.MagicMock(return_value=False)
  360. c._ping_loop = mock.MagicMock()
  361. c._read_loop_polling = mock.MagicMock()
  362. c._read_loop_websocket = mock.MagicMock()
  363. c._write_loop = mock.MagicMock()
  364. on_connect = mock.MagicMock()
  365. c.on('connect', on_connect)
  366. c.connect('http://foo')
  367. time.sleep(0.1)
  368. c._connect_websocket.assert_called_once_with('http://foo', {},
  369. 'engine.io')
  370. c._ping_loop.assert_called_once_with()
  371. c._read_loop_polling.assert_called_once_with()
  372. c._read_loop_websocket.assert_not_called()
  373. c._write_loop.assert_called_once_with()
  374. on_connect.assert_called_once_with()
  375. self.assertIn(c, client.connected_clients)
  376. @mock.patch('engineio.client.time.time', return_value=123.456)
  377. @mock.patch('engineio.client.websocket.create_connection',
  378. side_effect=[ConnectionError])
  379. def test_websocket_connection_failed(self, create_connection, _time):
  380. c = client.Client()
  381. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
  382. transports=['websocket'], headers={'Foo': 'Bar'})
  383. create_connection.assert_called_once_with(
  384. 'ws://foo/engine.io/?transport=websocket&EIO=3&t=123.456',
  385. header={'Foo': 'Bar'}, cookie=None)
  386. @mock.patch('engineio.client.time.time', return_value=123.456)
  387. @mock.patch('engineio.client.websocket.create_connection',
  388. side_effect=[ConnectionError])
  389. def test_websocket_upgrade_failed(self, create_connection, _time):
  390. c = client.Client()
  391. c.sid = '123'
  392. self.assertFalse(c.connect('http://foo', transports=['websocket']))
  393. create_connection.assert_called_once_with(
  394. 'ws://foo/engine.io/?transport=websocket&EIO=3&sid=123&t=123.456',
  395. header={}, cookie=None)
  396. @mock.patch('engineio.client.websocket.create_connection')
  397. def test_websocket_connection_no_open_packet(self, create_connection):
  398. create_connection.return_value.recv.return_value = packet.Packet(
  399. packet.CLOSE).encode()
  400. c = client.Client()
  401. self.assertRaises(exceptions.ConnectionError, c.connect, 'http://foo',
  402. transports=['websocket'])
  403. @mock.patch('engineio.client.websocket.create_connection')
  404. def test_websocket_connection_successful(self, create_connection):
  405. create_connection.return_value.recv.return_value = packet.Packet(
  406. packet.OPEN, {
  407. 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
  408. 'pingTimeout': 2000
  409. }).encode()
  410. c = client.Client()
  411. c._ping_loop = mock.MagicMock()
  412. c._read_loop_polling = mock.MagicMock()
  413. c._read_loop_websocket = mock.MagicMock()
  414. c._write_loop = mock.MagicMock()
  415. on_connect = mock.MagicMock()
  416. c.on('connect', on_connect)
  417. c.connect('ws://foo', transports=['websocket'])
  418. time.sleep(0.1)
  419. c._ping_loop.assert_called_once_with()
  420. c._read_loop_polling.assert_not_called()
  421. c._read_loop_websocket.assert_called_once_with()
  422. c._write_loop.assert_called_once_with()
  423. on_connect.assert_called_once_with()
  424. self.assertIn(c, client.connected_clients)
  425. self.assertEqual(
  426. c.base_url,
  427. 'ws://foo/engine.io/?transport=websocket&EIO=3')
  428. self.assertEqual(c.sid, '123')
  429. self.assertEqual(c.ping_interval, 1)
  430. self.assertEqual(c.ping_timeout, 2)
  431. self.assertEqual(c.upgrades, [])
  432. self.assertEqual(c.transport(), 'websocket')
  433. self.assertEqual(c.ws, create_connection.return_value)
  434. self.assertEqual(len(create_connection.call_args_list), 1)
  435. self.assertEqual(create_connection.call_args[1],
  436. {'header': {}, 'cookie': None})
  437. @mock.patch('engineio.client.websocket.create_connection')
  438. def test_websocket_connection_with_cookies(self, create_connection):
  439. create_connection.return_value.recv.return_value = packet.Packet(
  440. packet.OPEN, {
  441. 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
  442. 'pingTimeout': 2000
  443. }).encode()
  444. c = client.Client()
  445. c.http = mock.MagicMock()
  446. c.http.cookies = [mock.MagicMock(), mock.MagicMock()]
  447. c.http.cookies[0].name = 'key'
  448. c.http.cookies[0].value = 'value'
  449. c.http.cookies[1].name = 'key2'
  450. c.http.cookies[1].value = 'value2'
  451. c._ping_loop = mock.MagicMock()
  452. c._read_loop_polling = mock.MagicMock()
  453. c._read_loop_websocket = mock.MagicMock()
  454. c._write_loop = mock.MagicMock()
  455. on_connect = mock.MagicMock()
  456. c.on('connect', on_connect)
  457. c.connect('ws://foo', transports=['websocket'])
  458. time.sleep(0.1)
  459. self.assertEqual(len(create_connection.call_args_list), 1)
  460. self.assertEqual(create_connection.call_args[1],
  461. {'header': {}, 'cookie': 'key=value; key2=value2'})
  462. @mock.patch('engineio.client.websocket.create_connection')
  463. def test_websocket_upgrade_no_pong(self, create_connection):
  464. create_connection.return_value.recv.return_value = packet.Packet(
  465. packet.OPEN, {
  466. 'sid': '123', 'upgrades': [], 'pingInterval': 1000,
  467. 'pingTimeout': 2000
  468. }).encode()
  469. c = client.Client()
  470. c.sid = '123'
  471. c.current_transport = 'polling'
  472. c._ping_loop = mock.MagicMock()
  473. c._read_loop_polling = mock.MagicMock()
  474. c._read_loop_websocket = mock.MagicMock()
  475. c._write_loop = mock.MagicMock()
  476. on_connect = mock.MagicMock()
  477. c.on('connect', on_connect)
  478. self.assertFalse(c.connect('ws://foo', transports=['websocket']))
  479. c._ping_loop.assert_not_called()
  480. c._read_loop_polling.assert_not_called()
  481. c._read_loop_websocket.assert_not_called()
  482. c._write_loop.assert_not_called()
  483. on_connect.assert_not_called()
  484. self.assertEqual(c.transport(), 'polling')
  485. @mock.patch('engineio.client.websocket.create_connection')
  486. def test_websocket_upgrade_successful(self, create_connection):
  487. create_connection.return_value.recv.return_value = packet.Packet(
  488. packet.PONG, six.text_type('probe')).encode()
  489. c = client.Client()
  490. c.sid = '123'
  491. c.base_url = 'http://foo'
  492. c.current_transport = 'polling'
  493. c._ping_loop = mock.MagicMock()
  494. c._read_loop_polling = mock.MagicMock()
  495. c._read_loop_websocket = mock.MagicMock()
  496. c._write_loop = mock.MagicMock()
  497. on_connect = mock.MagicMock()
  498. c.on('connect', on_connect)
  499. self.assertTrue(c.connect('ws://foo', transports=['websocket']))
  500. time.sleep(0.1)
  501. c._ping_loop.assert_called_once_with()
  502. c._read_loop_polling.assert_not_called()
  503. c._read_loop_websocket.assert_called_once_with()
  504. c._write_loop.assert_called_once_with()
  505. on_connect.assert_not_called() # was called by polling
  506. self.assertNotIn(c, client.connected_clients) # was added by polling
  507. self.assertEqual(c.base_url, 'http://foo') # not changed
  508. self.assertEqual(c.sid, '123') # not changed
  509. self.assertEqual(c.transport(), 'websocket')
  510. self.assertEqual(c.ws, create_connection.return_value)
  511. self.assertEqual(
  512. create_connection.return_value.send.call_args_list[0],
  513. ((packet.Packet(packet.PING, six.text_type('probe')).encode(),),)
  514. ) # ping
  515. self.assertEqual(
  516. create_connection.return_value.send.call_args_list[1],
  517. ((packet.Packet(packet.UPGRADE).encode(),),)) # upgrade
  518. def test_receive_unknown_packet(self):
  519. c = client.Client()
  520. c._receive_packet(packet.Packet(encoded_packet=b'9'))
  521. # should be ignored
  522. def test_receive_noop_packet(self):
  523. c = client.Client()
  524. c._receive_packet(packet.Packet(packet.NOOP))
  525. # should be ignored
  526. def test_receive_pong_packet(self):
  527. c = client.Client()
  528. c.pong_received = False
  529. c._receive_packet(packet.Packet(packet.PONG))
  530. self.assertTrue(c.pong_received)
  531. def test_receive_message_packet(self):
  532. c = client.Client()
  533. c._trigger_event = mock.MagicMock()
  534. c._receive_packet(packet.Packet(packet.MESSAGE, {'foo': 'bar'}))
  535. c._trigger_event.assert_called_once_with('message', {'foo': 'bar'},
  536. run_async=True)
  537. def test_receive_close_packet(self):
  538. c = client.Client()
  539. c.disconnect = mock.MagicMock()
  540. c._receive_packet(packet.Packet(packet.CLOSE))
  541. c.disconnect.assert_called_once_with(abort=True)
  542. def test_send_packet_disconnected(self):
  543. c = client.Client()
  544. c.queue = c.create_queue()
  545. c.state = 'disconnected'
  546. c._send_packet(packet.Packet(packet.NOOP))
  547. self.assertTrue(c.queue.empty())
  548. def test_send_packet(self):
  549. c = client.Client()
  550. c.queue = c.create_queue()
  551. c.state = 'connected'
  552. c._send_packet(packet.Packet(packet.NOOP))
  553. self.assertFalse(c.queue.empty())
  554. pkt = c.queue.get()
  555. self.assertEqual(pkt.packet_type, packet.NOOP)
  556. def test_trigger_event(self):
  557. c = client.Client()
  558. f = {}
  559. @c.on('connect')
  560. def foo():
  561. return 'foo'
  562. @c.on('message')
  563. def bar(data):
  564. f['bar'] = data
  565. return 'bar'
  566. r = c._trigger_event('connect', run_async=False)
  567. self.assertEqual(r, 'foo')
  568. r = c._trigger_event('message', 123, run_async=True)
  569. r.join()
  570. self.assertEqual(f['bar'], 123)
  571. r = c._trigger_event('message', 321)
  572. self.assertEqual(r, 'bar')
  573. def test_trigger_unknown_event(self):
  574. c = client.Client()
  575. c._trigger_event('connect', run_async=False)
  576. c._trigger_event('message', 123, run_async=True)
  577. # should do nothing
  578. def test_trigger_event_error(self):
  579. c = client.Client()
  580. @c.on('connect')
  581. def foo():
  582. return 1 / 0
  583. @c.on('message')
  584. def bar(data):
  585. return 1 / 0
  586. r = c._trigger_event('connect', run_async=False)
  587. self.assertEqual(r, None)
  588. r = c._trigger_event('message', 123, run_async=False)
  589. self.assertEqual(r, None)
  590. def test_engineio_url(self):
  591. c = client.Client()
  592. self.assertEqual(
  593. c._get_engineio_url('http://foo', 'bar', 'polling'),
  594. 'http://foo/bar/?transport=polling&EIO=3')
  595. self.assertEqual(
  596. c._get_engineio_url('http://foo', 'bar', 'websocket'),
  597. 'ws://foo/bar/?transport=websocket&EIO=3')
  598. self.assertEqual(
  599. c._get_engineio_url('ws://foo', 'bar', 'polling'),
  600. 'http://foo/bar/?transport=polling&EIO=3')
  601. self.assertEqual(
  602. c._get_engineio_url('ws://foo', 'bar', 'websocket'),
  603. 'ws://foo/bar/?transport=websocket&EIO=3')
  604. self.assertEqual(
  605. c._get_engineio_url('https://foo', 'bar', 'polling'),
  606. 'https://foo/bar/?transport=polling&EIO=3')
  607. self.assertEqual(
  608. c._get_engineio_url('https://foo', 'bar', 'websocket'),
  609. 'wss://foo/bar/?transport=websocket&EIO=3')
  610. self.assertEqual(
  611. c._get_engineio_url('http://foo?baz=1', 'bar', 'polling'),
  612. 'http://foo/bar/?baz=1&transport=polling&EIO=3')
  613. self.assertEqual(
  614. c._get_engineio_url('http://foo#baz', 'bar', 'polling'),
  615. 'http://foo/bar/?transport=polling&EIO=3')
  616. def test_ping_loop_disconnected(self):
  617. c = client.Client()
  618. c.state = 'disconnected'
  619. c._ping_loop()
  620. # should not block
  621. def test_ping_loop_disconnect(self):
  622. c = client.Client()
  623. c.state = 'connected'
  624. c.ping_interval = 10
  625. c._send_packet = mock.MagicMock()
  626. states = [
  627. ('disconnecting', True)
  628. ]
  629. def fake_wait(timeout):
  630. self.assertEqual(timeout, 10)
  631. c.state, c.pong_received = states.pop(0)
  632. c.ping_loop_event.wait = fake_wait
  633. c._ping_loop()
  634. self.assertEqual(
  635. c._send_packet.call_args_list[0][0][0].encode(), b'2')
  636. def test_ping_loop_missing_pong(self):
  637. c = client.Client()
  638. c.state = 'connected'
  639. c.ping_interval = 10
  640. c._send_packet = mock.MagicMock()
  641. c.queue = mock.MagicMock()
  642. states = [
  643. ('connected', False)
  644. ]
  645. def fake_wait(timeout):
  646. self.assertEqual(timeout, 10)
  647. c.state, c.pong_received = states.pop(0)
  648. c.ping_loop_event.wait = fake_wait
  649. c._ping_loop()
  650. self.assertEqual(c.state, 'connected')
  651. c.queue.put.assert_called_once_with(None)
  652. def test_ping_loop_missing_pong_websocket(self):
  653. c = client.Client()
  654. c.state = 'connected'
  655. c.ping_interval = 10
  656. c._send_packet = mock.MagicMock()
  657. c.queue = mock.MagicMock()
  658. c.ws = mock.MagicMock()
  659. states = [
  660. ('connected', False)
  661. ]
  662. def fake_wait(timeout):
  663. self.assertEqual(timeout, 10)
  664. c.state, c.pong_received = states.pop(0)
  665. c.ping_loop_event.wait = fake_wait
  666. c._ping_loop()
  667. self.assertEqual(c.state, 'connected')
  668. c.queue.put.assert_called_once_with(None)
  669. c.ws.close.assert_called_once_with()
  670. def test_read_loop_polling_disconnected(self):
  671. c = client.Client()
  672. c.state = 'disconnected'
  673. c._trigger_event = mock.MagicMock()
  674. c.write_loop_task = mock.MagicMock()
  675. c.ping_loop_task = mock.MagicMock()
  676. c._read_loop_polling()
  677. c.write_loop_task.join.assert_called_once_with()
  678. c.ping_loop_task.join.assert_called_once_with()
  679. c._trigger_event.assert_not_called()
  680. @mock.patch('engineio.client.time.time', return_value=123.456)
  681. def test_read_loop_polling_no_response(self, _time):
  682. c = client.Client()
  683. c.state = 'connected'
  684. c.base_url = 'http://foo'
  685. c.queue = mock.MagicMock()
  686. c._send_request = mock.MagicMock(return_value=None)
  687. c._trigger_event = mock.MagicMock()
  688. c.write_loop_task = mock.MagicMock()
  689. c.ping_loop_task = mock.MagicMock()
  690. c._read_loop_polling()
  691. self.assertEqual(c.state, 'disconnected')
  692. c.queue.put.assert_called_once_with(None)
  693. c.write_loop_task.join.assert_called_once_with()
  694. c.ping_loop_task.join.assert_called_once_with()
  695. c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456')
  696. c._trigger_event.assert_called_once_with('disconnect',
  697. run_async=False)
  698. @mock.patch('engineio.client.time.time', return_value=123.456)
  699. def test_read_loop_polling_bad_status(self, _time):
  700. c = client.Client()
  701. c.state = 'connected'
  702. c.base_url = 'http://foo'
  703. c.queue = mock.MagicMock()
  704. c._send_request = mock.MagicMock()
  705. c._send_request.return_value.status_code = 400
  706. c.write_loop_task = mock.MagicMock()
  707. c.ping_loop_task = mock.MagicMock()
  708. c._read_loop_polling()
  709. self.assertEqual(c.state, 'disconnected')
  710. c.queue.put.assert_called_once_with(None)
  711. c.write_loop_task.join.assert_called_once_with()
  712. c.ping_loop_task.join.assert_called_once_with()
  713. c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456')
  714. @mock.patch('engineio.client.time.time', return_value=123.456)
  715. def test_read_loop_polling_bad_packet(self, _time):
  716. c = client.Client()
  717. c.state = 'connected'
  718. c.base_url = 'http://foo'
  719. c.queue = mock.MagicMock()
  720. c._send_request = mock.MagicMock()
  721. c._send_request.return_value.status_code = 200
  722. c._send_request.return_value.content = b'foo'
  723. c.write_loop_task = mock.MagicMock()
  724. c.ping_loop_task = mock.MagicMock()
  725. c._read_loop_polling()
  726. self.assertEqual(c.state, 'disconnected')
  727. c.queue.put.assert_called_once_with(None)
  728. c.write_loop_task.join.assert_called_once_with()
  729. c.ping_loop_task.join.assert_called_once_with()
  730. c._send_request.assert_called_once_with('GET', 'http://foo&t=123.456')
  731. def test_read_loop_polling(self):
  732. c = client.Client()
  733. c.state = 'connected'
  734. c.base_url = 'http://foo'
  735. c.queue = mock.MagicMock()
  736. c._send_request = mock.MagicMock()
  737. c._send_request.side_effect = [
  738. mock.MagicMock(status_code=200, content=payload.Payload(packets=[
  739. packet.Packet(packet.PING),
  740. packet.Packet(packet.NOOP)]).encode()),
  741. None
  742. ]
  743. c.write_loop_task = mock.MagicMock()
  744. c.ping_loop_task = mock.MagicMock()
  745. c._receive_packet = mock.MagicMock()
  746. c._read_loop_polling()
  747. self.assertEqual(c.state, 'disconnected')
  748. c.queue.put.assert_called_once_with(None)
  749. self.assertEqual(c._send_request.call_count, 2)
  750. self.assertEqual(c._receive_packet.call_count, 2)
  751. self.assertEqual(c._receive_packet.call_args_list[0][0][0].encode(),
  752. b'2')
  753. self.assertEqual(c._receive_packet.call_args_list[1][0][0].encode(),
  754. b'6')
  755. def test_read_loop_websocket_disconnected(self):
  756. c = client.Client()
  757. c.state = 'disconnected'
  758. c.write_loop_task = mock.MagicMock()
  759. c.ping_loop_task = mock.MagicMock()
  760. c._read_loop_websocket()
  761. c.write_loop_task.join.assert_called_once_with()
  762. c.ping_loop_task.join.assert_called_once_with()
  763. def test_read_loop_websocket_no_response(self):
  764. c = client.Client()
  765. c.state = 'connected'
  766. c.queue = mock.MagicMock()
  767. c.ws = mock.MagicMock()
  768. c.ws.recv.side_effect = websocket.WebSocketConnectionClosedException
  769. c.write_loop_task = mock.MagicMock()
  770. c.ping_loop_task = mock.MagicMock()
  771. c._read_loop_websocket()
  772. self.assertEqual(c.state, 'disconnected')
  773. c.queue.put.assert_called_once_with(None)
  774. c.write_loop_task.join.assert_called_once_with()
  775. c.ping_loop_task.join.assert_called_once_with()
  776. def test_read_loop_websocket_unexpected_error(self):
  777. c = client.Client()
  778. c.state = 'connected'
  779. c.queue = mock.MagicMock()
  780. c.ws = mock.MagicMock()
  781. c.ws.recv.side_effect = ValueError
  782. c.write_loop_task = mock.MagicMock()
  783. c.ping_loop_task = mock.MagicMock()
  784. c._read_loop_websocket()
  785. self.assertEqual(c.state, 'disconnected')
  786. c.queue.put.assert_called_once_with(None)
  787. c.write_loop_task.join.assert_called_once_with()
  788. c.ping_loop_task.join.assert_called_once_with()
  789. def test_read_loop_websocket(self):
  790. c = client.Client()
  791. c.state = 'connected'
  792. c.queue = mock.MagicMock()
  793. c.ws = mock.MagicMock()
  794. c.ws.recv.side_effect = [packet.Packet(packet.PING).encode(),
  795. ValueError]
  796. c.write_loop_task = mock.MagicMock()
  797. c.ping_loop_task = mock.MagicMock()
  798. c._receive_packet = mock.MagicMock()
  799. c._read_loop_websocket()
  800. self.assertEqual(c.state, 'disconnected')
  801. c.queue.put.assert_called_once_with(None)
  802. c.write_loop_task.join.assert_called_once_with()
  803. c.ping_loop_task.join.assert_called_once_with()
  804. self.assertEqual(c._receive_packet.call_args_list[0][0][0].encode(),
  805. b'2')
  806. def test_write_loop_disconnected(self):
  807. c = client.Client()
  808. c.state = 'disconnected'
  809. c._write_loop()
  810. # should not block
  811. def test_write_loop_no_packets(self):
  812. c = client.Client()
  813. c.state = 'connected'
  814. c.ping_interval = 1
  815. c.ping_timeout = 2
  816. c.queue = mock.MagicMock()
  817. c.queue.get.return_value = None
  818. c._write_loop()
  819. c.queue.task_done.assert_called_once_with()
  820. c.queue.get.assert_called_once_with(timeout=7)
  821. def test_write_loop_empty_queue(self):
  822. c = client.Client()
  823. c.state = 'connected'
  824. c.ping_interval = 1
  825. c.ping_timeout = 2
  826. c.queue = mock.MagicMock()
  827. c.queue.Empty = RuntimeError
  828. c.queue.get.side_effect = RuntimeError
  829. c._write_loop()
  830. c.queue.get.assert_called_once_with(timeout=7)
  831. def test_write_loop_polling_one_packet(self):
  832. c = client.Client()
  833. c.base_url = 'http://foo'
  834. c.state = 'connected'
  835. c.ping_interval = 1
  836. c.ping_timeout = 2
  837. c.current_transport = 'polling'
  838. c.queue = mock.MagicMock()
  839. c.queue.Empty = RuntimeError
  840. c.queue.get.side_effect = [
  841. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  842. RuntimeError,
  843. RuntimeError
  844. ]
  845. c._send_request = mock.MagicMock()
  846. c._send_request.return_value.status_code = 200
  847. c._write_loop()
  848. self.assertEqual(c.queue.task_done.call_count, 1)
  849. p = payload.Payload(
  850. packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
  851. c._send_request.assert_called_once_with(
  852. 'POST', 'http://foo', body=p.encode(),
  853. headers={'Content-Type': 'application/octet-stream'})
  854. def test_write_loop_polling_three_packets(self):
  855. c = client.Client()
  856. c.base_url = 'http://foo'
  857. c.state = 'connected'
  858. c.ping_interval = 1
  859. c.ping_timeout = 2
  860. c.current_transport = 'polling'
  861. c.queue = mock.MagicMock()
  862. c.queue.Empty = RuntimeError
  863. c.queue.get.side_effect = [
  864. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  865. packet.Packet(packet.PING),
  866. packet.Packet(packet.NOOP),
  867. RuntimeError,
  868. RuntimeError
  869. ]
  870. c._send_request = mock.MagicMock()
  871. c._send_request.return_value.status_code = 200
  872. c._write_loop()
  873. self.assertEqual(c.queue.task_done.call_count, 3)
  874. p = payload.Payload(packets=[
  875. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  876. packet.Packet(packet.PING),
  877. packet.Packet(packet.NOOP),
  878. ])
  879. c._send_request.assert_called_once_with(
  880. 'POST', 'http://foo', body=p.encode(),
  881. headers={'Content-Type': 'application/octet-stream'})
  882. def test_write_loop_polling_two_packets_done(self):
  883. c = client.Client()
  884. c.base_url = 'http://foo'
  885. c.state = 'connected'
  886. c.ping_interval = 1
  887. c.ping_timeout = 2
  888. c.current_transport = 'polling'
  889. c.queue = mock.MagicMock()
  890. c.queue.Empty = RuntimeError
  891. c.queue.get.side_effect = [
  892. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  893. packet.Packet(packet.PING),
  894. None,
  895. RuntimeError
  896. ]
  897. c._send_request = mock.MagicMock()
  898. c._send_request.return_value.status_code = 200
  899. c._write_loop()
  900. self.assertEqual(c.queue.task_done.call_count, 3)
  901. p = payload.Payload(packets=[
  902. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  903. packet.Packet(packet.PING),
  904. ])
  905. c._send_request.assert_called_once_with(
  906. 'POST', 'http://foo', body=p.encode(),
  907. headers={'Content-Type': 'application/octet-stream'})
  908. self.assertEqual(c.state, 'connected')
  909. def test_write_loop_polling_bad_connection(self):
  910. c = client.Client()
  911. c.base_url = 'http://foo'
  912. c.state = 'connected'
  913. c.ping_interval = 1
  914. c.ping_timeout = 2
  915. c.current_transport = 'polling'
  916. c.queue = mock.MagicMock()
  917. c.queue.Empty = RuntimeError
  918. c.queue.get.side_effect = [
  919. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  920. RuntimeError,
  921. ]
  922. c._send_request = mock.MagicMock()
  923. c._send_request.return_value = None
  924. c._write_loop()
  925. self.assertEqual(c.queue.task_done.call_count, 1)
  926. p = payload.Payload(
  927. packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
  928. c._send_request.assert_called_once_with(
  929. 'POST', 'http://foo', body=p.encode(),
  930. headers={'Content-Type': 'application/octet-stream'})
  931. self.assertEqual(c.state, 'connected')
  932. def test_write_loop_polling_bad_status(self):
  933. c = client.Client()
  934. c.base_url = 'http://foo'
  935. c.state = 'connected'
  936. c.ping_interval = 1
  937. c.ping_timeout = 2
  938. c.current_transport = 'polling'
  939. c.queue = mock.MagicMock()
  940. c.queue.Empty = RuntimeError
  941. c.queue.get.side_effect = [
  942. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  943. RuntimeError,
  944. ]
  945. c._send_request = mock.MagicMock()
  946. c._send_request.return_value.status_code = 500
  947. c._write_loop()
  948. self.assertEqual(c.queue.task_done.call_count, 1)
  949. p = payload.Payload(
  950. packets=[packet.Packet(packet.MESSAGE, {'foo': 'bar'})])
  951. c._send_request.assert_called_once_with(
  952. 'POST', 'http://foo', body=p.encode(),
  953. headers={'Content-Type': 'application/octet-stream'})
  954. self.assertEqual(c.state, 'disconnected')
  955. def test_write_loop_websocket_one_packet(self):
  956. c = client.Client()
  957. c.state = 'connected'
  958. c.ping_interval = 1
  959. c.ping_timeout = 2
  960. c.current_transport = 'websocket'
  961. c.queue = mock.MagicMock()
  962. c.queue.Empty = RuntimeError
  963. c.queue.get.side_effect = [
  964. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  965. RuntimeError,
  966. RuntimeError
  967. ]
  968. c.ws = mock.MagicMock()
  969. c._write_loop()
  970. self.assertEqual(c.queue.task_done.call_count, 1)
  971. self.assertEqual(c.ws.send.call_count, 1)
  972. self.assertEqual(c.ws.send_binary.call_count, 0)
  973. c.ws.send.assert_called_once_with('4{"foo":"bar"}')
  974. def test_write_loop_websocket_three_packets(self):
  975. c = client.Client()
  976. c.state = 'connected'
  977. c.ping_interval = 1
  978. c.ping_timeout = 2
  979. c.current_transport = 'websocket'
  980. c.queue = mock.MagicMock()
  981. c.queue.Empty = RuntimeError
  982. c.queue.get.side_effect = [
  983. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  984. packet.Packet(packet.PING),
  985. packet.Packet(packet.NOOP),
  986. RuntimeError,
  987. RuntimeError
  988. ]
  989. c.ws = mock.MagicMock()
  990. c._write_loop()
  991. self.assertEqual(c.queue.task_done.call_count, 3)
  992. self.assertEqual(c.ws.send.call_count, 3)
  993. self.assertEqual(c.ws.send_binary.call_count, 0)
  994. self.assertEqual(c.ws.send.call_args_list[0][0][0], '4{"foo":"bar"}')
  995. self.assertEqual(c.ws.send.call_args_list[1][0][0], '2')
  996. self.assertEqual(c.ws.send.call_args_list[2][0][0], '6')
  997. def test_write_loop_websocket_one_packet_binary(self):
  998. c = client.Client()
  999. c.state = 'connected'
  1000. c.ping_interval = 1
  1001. c.ping_timeout = 2
  1002. c.current_transport = 'websocket'
  1003. c.queue = mock.MagicMock()
  1004. c.queue.Empty = RuntimeError
  1005. c.queue.get.side_effect = [
  1006. packet.Packet(packet.MESSAGE, b'foo'),
  1007. RuntimeError,
  1008. RuntimeError
  1009. ]
  1010. c.ws = mock.MagicMock()
  1011. c._write_loop()
  1012. self.assertEqual(c.queue.task_done.call_count, 1)
  1013. self.assertEqual(c.ws.send.call_count, 0)
  1014. self.assertEqual(c.ws.send_binary.call_count, 1)
  1015. c.ws.send_binary.assert_called_once_with(b'\x04foo')
  1016. def test_write_loop_websocket_bad_connection(self):
  1017. c = client.Client()
  1018. c.state = 'connected'
  1019. c.ping_interval = 1
  1020. c.ping_timeout = 2
  1021. c.current_transport = 'websocket'
  1022. c.queue = mock.MagicMock()
  1023. c.queue.Empty = RuntimeError
  1024. c.queue.get.side_effect = [
  1025. packet.Packet(packet.MESSAGE, {'foo': 'bar'}),
  1026. RuntimeError,
  1027. RuntimeError
  1028. ]
  1029. c.ws = mock.MagicMock()
  1030. c.ws.send.side_effect = websocket.WebSocketConnectionClosedException
  1031. c._write_loop()
  1032. self.assertEqual(c.state, 'connected')
  1033. @mock.patch('engineio.client.original_signal_handler')
  1034. def test_signal_handler(self, original_handler):
  1035. clients = [mock.MagicMock(), mock.MagicMock()]
  1036. client.connected_clients = clients[:]
  1037. client.connected_clients[0].is_asyncio_based.return_value = False
  1038. client.connected_clients[1].is_asyncio_based.return_value = True
  1039. client.signal_handler('sig', 'frame')
  1040. clients[0].disconnect.assert_called_once_with(abort=True)
  1041. clients[1].start_background_task.assert_called_once_with(
  1042. clients[1].disconnect, abort=True)