test_utils.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. # pylint: disable=missing-module-docstring, invalid-name
  3. import random
  4. import string
  5. import lxml.etree
  6. from lxml import html
  7. from parameterized.parameterized import parameterized
  8. from searx.exceptions import SearxXPathSyntaxException, SearxEngineXPathException
  9. from searx import utils
  10. from tests import SearxTestCase
  11. def random_string(length, choices=string.ascii_letters):
  12. return ''.join(random.choice(choices) for _ in range(length))
  13. class TestUtils(SearxTestCase): # pylint: disable=missing-class-docstring
  14. def test_gen_useragent(self):
  15. self.assertIsInstance(utils.gen_useragent(), str)
  16. self.assertIsNotNone(utils.gen_useragent())
  17. self.assertTrue(utils.gen_useragent().startswith('Mozilla'))
  18. def test_searx_useragent(self):
  19. self.assertIsInstance(utils.searx_useragent(), str)
  20. self.assertIsNotNone(utils.searx_useragent())
  21. self.assertTrue(utils.searx_useragent().startswith('searx'))
  22. def test_html_to_text(self):
  23. html_str = """
  24. <a href="/testlink" class="link_access_account">
  25. <style>
  26. .toto {
  27. color: red;
  28. }
  29. </style>
  30. <span class="toto">
  31. <span>
  32. <img src="test.jpg" />
  33. </span>
  34. </span>
  35. <span class="titi">
  36. Test text
  37. </span>
  38. <script>value='dummy';</script>
  39. </a>
  40. """
  41. self.assertIsInstance(utils.html_to_text(html_str), str)
  42. self.assertIsNotNone(utils.html_to_text(html_str))
  43. self.assertEqual(utils.html_to_text(html_str), "Test text")
  44. self.assertEqual(utils.html_to_text(r"regexp: (?<![a-zA-Z]"), "regexp: (?<![a-zA-Z]")
  45. def test_extract_text(self):
  46. html_str = """
  47. <a href="/testlink" class="link_access_account">
  48. <span class="toto">
  49. <span>
  50. <img src="test.jpg" />
  51. </span>
  52. </span>
  53. <span class="titi">
  54. Test text
  55. </span>
  56. </a>
  57. """
  58. dom = html.fromstring(html_str)
  59. self.assertEqual(utils.extract_text(dom), 'Test text')
  60. self.assertEqual(utils.extract_text(dom.xpath('//span')), 'Test text')
  61. self.assertEqual(utils.extract_text(dom.xpath('//span/text()')), 'Test text')
  62. self.assertEqual(utils.extract_text(dom.xpath('count(//span)')), '3.0')
  63. self.assertEqual(utils.extract_text(dom.xpath('boolean(//span)')), 'True')
  64. self.assertEqual(utils.extract_text(dom.xpath('//img/@src')), 'test.jpg')
  65. self.assertEqual(utils.extract_text(dom.xpath('//unexistingtag')), '')
  66. def test_extract_text_allow_none(self):
  67. self.assertEqual(utils.extract_text(None, allow_none=True), None)
  68. def test_extract_text_error_none(self):
  69. with self.assertRaises(ValueError):
  70. utils.extract_text(None)
  71. def test_extract_text_error_empty(self):
  72. with self.assertRaises(ValueError):
  73. utils.extract_text({})
  74. def test_extract_url(self):
  75. def f(html_str, search_url):
  76. return utils.extract_url(html.fromstring(html_str), search_url)
  77. self.assertEqual(f('<span id="42">https://example.com</span>', 'http://example.com/'), 'https://example.com/')
  78. self.assertEqual(f('https://example.com', 'http://example.com/'), 'https://example.com/')
  79. self.assertEqual(f('//example.com', 'http://example.com/'), 'http://example.com/')
  80. self.assertEqual(f('//example.com', 'https://example.com/'), 'https://example.com/')
  81. self.assertEqual(f('/path?a=1', 'https://example.com'), 'https://example.com/path?a=1')
  82. with self.assertRaises(lxml.etree.ParserError):
  83. f('', 'https://example.com')
  84. with self.assertRaises(Exception):
  85. utils.extract_url([], 'https://example.com')
  86. def test_html_to_text_invalid(self):
  87. _html = '<p><b>Lorem ipsum</i>dolor sit amet</p>'
  88. self.assertEqual(utils.html_to_text(_html), "Lorem ipsum")
  89. def test_ecma_unscape(self):
  90. self.assertEqual(utils.ecma_unescape('text%20with%20space'), 'text with space')
  91. self.assertEqual(utils.ecma_unescape('text using %xx: %F3'), 'text using %xx: ó')
  92. self.assertEqual(utils.ecma_unescape('text using %u: %u5409, %u4E16%u754c'), 'text using %u: 吉, 世界')
  93. class TestHTMLTextExtractor(SearxTestCase): # pylint: disable=missing-class-docstring
  94. def setUp(self):
  95. self.html_text_extractor = utils._HTMLTextExtractor() # pylint: disable=protected-access
  96. def test__init__(self):
  97. self.assertEqual(self.html_text_extractor.result, [])
  98. @parameterized.expand(
  99. [
  100. ('xF', '\x0f'),
  101. ('XF', '\x0f'),
  102. ('97', 'a'),
  103. ]
  104. )
  105. def test_handle_charref(self, charref: str, expected: str):
  106. self.html_text_extractor.handle_charref(charref)
  107. self.assertIn(expected, self.html_text_extractor.result)
  108. def test_handle_entityref(self):
  109. entity = 'test'
  110. self.html_text_extractor.handle_entityref(entity)
  111. self.assertIn(entity, self.html_text_extractor.result)
  112. def test_invalid_html(self):
  113. text = '<p><b>Lorem ipsum</i>dolor sit amet</p>'
  114. with self.assertRaises(utils._HTMLTextExtractorException): # pylint: disable=protected-access
  115. self.html_text_extractor.feed(text)
  116. class TestXPathUtils(SearxTestCase): # pylint: disable=missing-class-docstring
  117. TEST_DOC = """<ul>
  118. <li>Text in <b>bold</b> and <i>italic</i> </li>
  119. <li>Another <b>text</b> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="></li>
  120. </ul>"""
  121. def test_get_xpath_cache(self):
  122. xp1 = utils.get_xpath('//a')
  123. xp2 = utils.get_xpath('//div')
  124. xp3 = utils.get_xpath('//a')
  125. self.assertEqual(id(xp1), id(xp3))
  126. self.assertNotEqual(id(xp1), id(xp2))
  127. def test_get_xpath_type(self):
  128. utils.get_xpath(lxml.etree.XPath('//a'))
  129. with self.assertRaises(TypeError):
  130. utils.get_xpath([])
  131. def test_get_xpath_invalid(self):
  132. invalid_xpath = '//a[0].text'
  133. with self.assertRaises(SearxXPathSyntaxException) as context:
  134. utils.get_xpath(invalid_xpath)
  135. self.assertEqual(context.exception.message, 'Invalid expression')
  136. self.assertEqual(context.exception.xpath_str, invalid_xpath)
  137. def test_eval_xpath_unregistered_function(self):
  138. doc = html.fromstring(TestXPathUtils.TEST_DOC)
  139. invalid_function_xpath = 'int(//a)'
  140. with self.assertRaises(SearxEngineXPathException) as context:
  141. utils.eval_xpath(doc, invalid_function_xpath)
  142. self.assertEqual(context.exception.message, 'Unregistered function')
  143. self.assertEqual(context.exception.xpath_str, invalid_function_xpath)
  144. def test_eval_xpath(self):
  145. doc = html.fromstring(TestXPathUtils.TEST_DOC)
  146. self.assertEqual(utils.eval_xpath(doc, '//p'), [])
  147. self.assertEqual(utils.eval_xpath(doc, '//i/text()'), ['italic'])
  148. self.assertEqual(utils.eval_xpath(doc, 'count(//i)'), 1.0)
  149. def test_eval_xpath_list(self):
  150. doc = html.fromstring(TestXPathUtils.TEST_DOC)
  151. # check a not empty list
  152. self.assertEqual(utils.eval_xpath_list(doc, '//i/text()'), ['italic'])
  153. # check min_len parameter
  154. with self.assertRaises(SearxEngineXPathException) as context:
  155. utils.eval_xpath_list(doc, '//p', min_len=1)
  156. self.assertEqual(context.exception.message, 'len(xpath_str) < 1')
  157. self.assertEqual(context.exception.xpath_str, '//p')
  158. def test_eval_xpath_getindex(self):
  159. doc = html.fromstring(TestXPathUtils.TEST_DOC)
  160. # check index 0
  161. self.assertEqual(utils.eval_xpath_getindex(doc, '//i/text()', 0), 'italic')
  162. # default is 'something'
  163. self.assertEqual(utils.eval_xpath_getindex(doc, '//i/text()', 1, default='something'), 'something')
  164. # default is None
  165. self.assertIsNone(utils.eval_xpath_getindex(doc, '//i/text()', 1, default=None))
  166. # index not found
  167. with self.assertRaises(SearxEngineXPathException) as context:
  168. utils.eval_xpath_getindex(doc, '//i/text()', 1)
  169. self.assertEqual(context.exception.message, 'index 1 not found')
  170. # not a list
  171. with self.assertRaises(SearxEngineXPathException) as context:
  172. utils.eval_xpath_getindex(doc, 'count(//i)', 1)
  173. self.assertEqual(context.exception.message, 'the result is not a list')
  174. def test_detect_language(self):
  175. # make sure new line are not an issue
  176. # fasttext.predict('') does not accept new line.
  177. l = utils.detect_language('The quick brown fox jumps over\nthe lazy dog')
  178. self.assertEqual(l, 'en')
  179. l = utils.detect_language(
  180. 'いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす'
  181. )
  182. self.assertEqual(l, 'ja')
  183. l = utils.detect_language('Pijamalı hasta yağız şoföre çabucak güvendi.')
  184. self.assertEqual(l, 'tr')
  185. l = utils.detect_language('')
  186. self.assertIsNone(l)
  187. # mix languages --> None
  188. l = utils.detect_language('The いろはにほへと Pijamalı')
  189. self.assertIsNone(l)
  190. with self.assertRaises(ValueError):
  191. utils.detect_language(None) # type: ignore