test_utils.py 9.3 KB

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