test_network.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. # pylint: disable=missing-module-docstring, protected-access
  3. from mock import patch
  4. import httpx
  5. from searx.network.network import Network, NETWORKS, initialize
  6. from tests import SearxTestCase
  7. class TestNetwork(SearxTestCase): # pylint: disable=missing-class-docstring
  8. def setUp(self):
  9. initialize()
  10. def test_simple(self):
  11. network = Network()
  12. self.assertEqual(next(network._local_addresses_cycle), None)
  13. self.assertEqual(next(network._proxies_cycle), ())
  14. def test_ipaddress_cycle(self):
  15. network = NETWORKS['ipv6']
  16. self.assertEqual(next(network._local_addresses_cycle), '::')
  17. self.assertEqual(next(network._local_addresses_cycle), '::')
  18. network = NETWORKS['ipv4']
  19. self.assertEqual(next(network._local_addresses_cycle), '0.0.0.0')
  20. self.assertEqual(next(network._local_addresses_cycle), '0.0.0.0')
  21. network = Network(local_addresses=['192.168.0.1', '192.168.0.2'])
  22. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.1')
  23. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.2')
  24. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.1')
  25. network = Network(local_addresses=['192.168.0.0/30'])
  26. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.1')
  27. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.2')
  28. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.1')
  29. self.assertEqual(next(network._local_addresses_cycle), '192.168.0.2')
  30. network = Network(local_addresses=['fe80::/10'])
  31. self.assertEqual(next(network._local_addresses_cycle), 'fe80::1')
  32. self.assertEqual(next(network._local_addresses_cycle), 'fe80::2')
  33. self.assertEqual(next(network._local_addresses_cycle), 'fe80::3')
  34. with self.assertRaises(ValueError):
  35. Network(local_addresses=['not_an_ip_address'])
  36. def test_proxy_cycles(self):
  37. network = Network(proxies='http://localhost:1337')
  38. self.assertEqual(next(network._proxies_cycle), (('all://', 'http://localhost:1337'),))
  39. network = Network(proxies={'https': 'http://localhost:1337', 'http': 'http://localhost:1338'})
  40. self.assertEqual(
  41. next(network._proxies_cycle), (('https://', 'http://localhost:1337'), ('http://', 'http://localhost:1338'))
  42. )
  43. self.assertEqual(
  44. next(network._proxies_cycle), (('https://', 'http://localhost:1337'), ('http://', 'http://localhost:1338'))
  45. )
  46. network = Network(
  47. proxies={'https': ['http://localhost:1337', 'http://localhost:1339'], 'http': 'http://localhost:1338'}
  48. )
  49. self.assertEqual(
  50. next(network._proxies_cycle), (('https://', 'http://localhost:1337'), ('http://', 'http://localhost:1338'))
  51. )
  52. self.assertEqual(
  53. next(network._proxies_cycle), (('https://', 'http://localhost:1339'), ('http://', 'http://localhost:1338'))
  54. )
  55. with self.assertRaises(ValueError):
  56. Network(proxies=1)
  57. def test_get_kwargs_clients(self):
  58. kwargs = {
  59. 'verify': True,
  60. 'max_redirects': 5,
  61. 'timeout': 2,
  62. 'allow_redirects': True,
  63. }
  64. kwargs_client = Network.extract_kwargs_clients(kwargs)
  65. self.assertEqual(len(kwargs_client), 2)
  66. self.assertEqual(len(kwargs), 2)
  67. self.assertEqual(kwargs['timeout'], 2)
  68. self.assertEqual(kwargs['follow_redirects'], True)
  69. self.assertTrue(kwargs_client['verify'])
  70. self.assertEqual(kwargs_client['max_redirects'], 5)
  71. async def test_get_client(self):
  72. network = Network(verify=True)
  73. client1 = await network.get_client()
  74. client2 = await network.get_client(verify=True)
  75. client3 = await network.get_client(max_redirects=10)
  76. client4 = await network.get_client(verify=True)
  77. client5 = await network.get_client(verify=False)
  78. client6 = await network.get_client(max_redirects=10)
  79. self.assertEqual(client1, client2)
  80. self.assertEqual(client1, client4)
  81. self.assertNotEqual(client1, client3)
  82. self.assertNotEqual(client1, client5)
  83. self.assertEqual(client3, client6)
  84. await network.aclose()
  85. async def test_aclose(self):
  86. network = Network(verify=True)
  87. await network.get_client()
  88. await network.aclose()
  89. async def test_request(self):
  90. a_text = 'Lorem Ipsum'
  91. response = httpx.Response(status_code=200, text=a_text)
  92. with patch.object(httpx.AsyncClient, 'request', return_value=response):
  93. network = Network(enable_http=True)
  94. response = await network.request('GET', 'https://example.com/')
  95. self.assertEqual(response.text, a_text)
  96. await network.aclose()
  97. class TestNetworkRequestRetries(SearxTestCase): # pylint: disable=missing-class-docstring
  98. TEXT = 'Lorem Ipsum'
  99. @classmethod
  100. def get_response_404_then_200(cls):
  101. first = True
  102. async def get_response(*args, **kwargs): # pylint: disable=unused-argument
  103. nonlocal first
  104. if first:
  105. first = False
  106. return httpx.Response(status_code=403, text=TestNetworkRequestRetries.TEXT)
  107. return httpx.Response(status_code=200, text=TestNetworkRequestRetries.TEXT)
  108. return get_response
  109. async def test_retries_ok(self):
  110. with patch.object(httpx.AsyncClient, 'request', new=TestNetworkRequestRetries.get_response_404_then_200()):
  111. network = Network(enable_http=True, retries=1, retry_on_http_error=403)
  112. response = await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  113. self.assertEqual(response.text, TestNetworkRequestRetries.TEXT)
  114. await network.aclose()
  115. async def test_retries_fail_int(self):
  116. with patch.object(httpx.AsyncClient, 'request', new=TestNetworkRequestRetries.get_response_404_then_200()):
  117. network = Network(enable_http=True, retries=0, retry_on_http_error=403)
  118. response = await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  119. self.assertEqual(response.status_code, 403)
  120. await network.aclose()
  121. async def test_retries_fail_list(self):
  122. with patch.object(httpx.AsyncClient, 'request', new=TestNetworkRequestRetries.get_response_404_then_200()):
  123. network = Network(enable_http=True, retries=0, retry_on_http_error=[403, 429])
  124. response = await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  125. self.assertEqual(response.status_code, 403)
  126. await network.aclose()
  127. async def test_retries_fail_bool(self):
  128. with patch.object(httpx.AsyncClient, 'request', new=TestNetworkRequestRetries.get_response_404_then_200()):
  129. network = Network(enable_http=True, retries=0, retry_on_http_error=True)
  130. response = await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  131. self.assertEqual(response.status_code, 403)
  132. await network.aclose()
  133. async def test_retries_exception_then_200(self):
  134. request_count = 0
  135. async def get_response(*args, **kwargs): # pylint: disable=unused-argument
  136. nonlocal request_count
  137. request_count += 1
  138. if request_count < 3:
  139. raise httpx.RequestError('fake exception', request=None)
  140. return httpx.Response(status_code=200, text=TestNetworkRequestRetries.TEXT)
  141. with patch.object(httpx.AsyncClient, 'request', new=get_response):
  142. network = Network(enable_http=True, retries=2)
  143. response = await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  144. self.assertEqual(response.status_code, 200)
  145. self.assertEqual(response.text, TestNetworkRequestRetries.TEXT)
  146. await network.aclose()
  147. async def test_retries_exception(self):
  148. async def get_response(*args, **kwargs):
  149. raise httpx.RequestError('fake exception', request=None)
  150. with patch.object(httpx.AsyncClient, 'request', new=get_response):
  151. network = Network(enable_http=True, retries=0)
  152. with self.assertRaises(httpx.RequestError):
  153. await network.request('GET', 'https://example.com/', raise_for_httperror=False)
  154. await network.aclose()
  155. class TestNetworkStreamRetries(SearxTestCase): # pylint: disable=missing-class-docstring
  156. TEXT = 'Lorem Ipsum'
  157. @classmethod
  158. def get_response_exception_then_200(cls):
  159. first = True
  160. def stream(*args, **kwargs): # pylint: disable=unused-argument
  161. nonlocal first
  162. if first:
  163. first = False
  164. raise httpx.RequestError('fake exception', request=None)
  165. return httpx.Response(status_code=200, text=TestNetworkStreamRetries.TEXT)
  166. return stream
  167. async def test_retries_ok(self):
  168. with patch.object(httpx.AsyncClient, 'stream', new=TestNetworkStreamRetries.get_response_exception_then_200()):
  169. network = Network(enable_http=True, retries=1, retry_on_http_error=403)
  170. response = await network.stream('GET', 'https://example.com/')
  171. self.assertEqual(response.text, TestNetworkStreamRetries.TEXT)
  172. await network.aclose()
  173. async def test_retries_fail(self):
  174. with patch.object(httpx.AsyncClient, 'stream', new=TestNetworkStreamRetries.get_response_exception_then_200()):
  175. network = Network(enable_http=True, retries=0, retry_on_http_error=403)
  176. with self.assertRaises(httpx.RequestError):
  177. await network.stream('GET', 'https://example.com/')
  178. await network.aclose()
  179. async def test_retries_exception(self):
  180. first = True
  181. def stream(*args, **kwargs): # pylint: disable=unused-argument
  182. nonlocal first
  183. if first:
  184. first = False
  185. return httpx.Response(status_code=403, text=TestNetworkRequestRetries.TEXT)
  186. return httpx.Response(status_code=200, text=TestNetworkRequestRetries.TEXT)
  187. with patch.object(httpx.AsyncClient, 'stream', new=stream):
  188. network = Network(enable_http=True, retries=0, retry_on_http_error=403)
  189. response = await network.stream('GET', 'https://example.com/', raise_for_httperror=False)
  190. self.assertEqual(response.status_code, 403)
  191. await network.aclose()