test_network.py 10 KB

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