test_webapp.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. # pylint: disable=missing-module-docstring
  3. import logging
  4. import json
  5. from urllib.parse import ParseResult
  6. import babel
  7. from mock import Mock
  8. from searx.results import Timing
  9. import searx.search.processors
  10. from searx.search import Search
  11. from searx.preferences import Preferences
  12. from tests import SearxTestCase
  13. class ViewsTestCase(SearxTestCase): # pylint: disable=missing-class-docstring, too-many-public-methods
  14. def setUp(self):
  15. # skip init function (no external HTTP request)
  16. def dummy(*args, **kwargs): # pylint: disable=unused-argument
  17. pass
  18. self.setattr4test(searx.search.processors, 'initialize_processor', dummy)
  19. log = logging.getLogger("searx")
  20. log_lev = log.level
  21. log.setLevel(logging.ERROR)
  22. from searx import webapp # pylint: disable=import-outside-toplevel
  23. log.setLevel(log_lev)
  24. webapp.app.config['TESTING'] = True # to get better error messages
  25. self.app = webapp.app.test_client()
  26. # remove sha for the static file
  27. # so the tests don't have to care about the changing URLs
  28. for k in webapp.static_files:
  29. webapp.static_files[k] = None
  30. # set some defaults
  31. test_results = [
  32. {
  33. 'content': 'first test content',
  34. 'title': 'First Test',
  35. 'url': 'http://first.test.xyz',
  36. 'engines': ['youtube', 'startpage'],
  37. 'engine': 'startpage',
  38. 'parsed_url': ParseResult(
  39. scheme='http', netloc='first.test.xyz', path='/', params='', query='', fragment=''
  40. ),
  41. 'template': 'default.html',
  42. },
  43. {
  44. 'content': 'second test content',
  45. 'title': 'Second Test',
  46. 'url': 'http://second.test.xyz',
  47. 'engines': ['youtube', 'startpage'],
  48. 'engine': 'youtube',
  49. 'parsed_url': ParseResult(
  50. scheme='http', netloc='second.test.xyz', path='/', params='', query='', fragment=''
  51. ),
  52. 'template': 'default.html',
  53. },
  54. ]
  55. timings = [
  56. Timing(engine='startpage', total=0.8, load=0.7),
  57. Timing(engine='youtube', total=0.9, load=0.6),
  58. ]
  59. def search_mock(search_self, *args): # pylint: disable=unused-argument
  60. search_self.result_container = Mock(
  61. get_ordered_results=lambda: test_results,
  62. answers={},
  63. corrections=set(),
  64. suggestions=set(),
  65. infoboxes=[],
  66. unresponsive_engines=set(),
  67. results=test_results,
  68. number_of_results=3,
  69. results_length=lambda: len(test_results),
  70. get_timings=lambda: timings,
  71. redirect_url=None,
  72. engine_data={},
  73. )
  74. search_self.search_query.locale = babel.Locale.parse("en-US", sep='-')
  75. self.setattr4test(Search, 'search', search_mock)
  76. original_preferences_get_value = Preferences.get_value
  77. def preferences_get_value(preferences_self, user_setting_name: str):
  78. if user_setting_name == 'theme':
  79. return 'simple'
  80. return original_preferences_get_value(preferences_self, user_setting_name)
  81. self.setattr4test(Preferences, 'get_value', preferences_get_value)
  82. # to see full diffs
  83. self.maxDiff = None # pylint: disable=invalid-name
  84. def test_index_empty(self):
  85. result = self.app.post('/')
  86. self.assertEqual(result.status_code, 200)
  87. self.assertIn(
  88. b'<div class="title"><h1>SearXNG</h1></div>',
  89. result.data,
  90. )
  91. def test_index_html_post(self):
  92. result = self.app.post('/', data={'q': 'test'})
  93. self.assertEqual(result.status_code, 308)
  94. self.assertEqual(result.location, '/search')
  95. def test_index_html_get(self):
  96. result = self.app.post('/?q=test')
  97. self.assertEqual(result.status_code, 308)
  98. self.assertEqual(result.location, '/search?q=test')
  99. def test_search_empty_html(self):
  100. result = self.app.post('/search', data={'q': ''})
  101. self.assertEqual(result.status_code, 200)
  102. self.assertIn(b'<div class="title"><h1>SearXNG</h1></div>', result.data)
  103. def test_search_empty_json(self):
  104. result = self.app.post('/search', data={'q': '', 'format': 'json'})
  105. self.assertEqual(result.status_code, 400)
  106. def test_search_empty_csv(self):
  107. result = self.app.post('/search', data={'q': '', 'format': 'csv'})
  108. self.assertEqual(result.status_code, 400)
  109. def test_search_empty_rss(self):
  110. result = self.app.post('/search', data={'q': '', 'format': 'rss'})
  111. self.assertEqual(result.status_code, 400)
  112. def test_search_html(self):
  113. result = self.app.post('/search', data={'q': 'test'})
  114. self.assertIn(
  115. b'<span class="url_o1"><span class="url_i1">http://second.test.xyz</span></span>',
  116. result.data,
  117. )
  118. self.assertIn(
  119. b'<p class="content">\n second <span class="highlight">test</span> ',
  120. result.data,
  121. )
  122. def test_index_json(self):
  123. result = self.app.post('/', data={'q': 'test', 'format': 'json'})
  124. self.assertEqual(result.status_code, 308)
  125. def test_search_json(self):
  126. result = self.app.post('/search', data={'q': 'test', 'format': 'json'})
  127. result_dict = json.loads(result.data.decode())
  128. self.assertEqual('test', result_dict['query'])
  129. self.assertEqual(len(result_dict['results']), 2)
  130. self.assertEqual(result_dict['results'][0]['content'], 'first test content')
  131. self.assertEqual(result_dict['results'][0]['url'], 'http://first.test.xyz')
  132. def test_index_csv(self):
  133. result = self.app.post('/', data={'q': 'test', 'format': 'csv'})
  134. self.assertEqual(result.status_code, 308)
  135. def test_search_csv(self):
  136. result = self.app.post('/search', data={'q': 'test', 'format': 'csv'})
  137. self.assertEqual(
  138. b'title,url,content,host,engine,score,type\r\n'
  139. b'First Test,http://first.test.xyz,first test content,first.test.xyz,startpage,,result\r\n' # noqa
  140. b'Second Test,http://second.test.xyz,second test content,second.test.xyz,youtube,,result\r\n', # noqa
  141. result.data,
  142. )
  143. def test_index_rss(self):
  144. result = self.app.post('/', data={'q': 'test', 'format': 'rss'})
  145. self.assertEqual(result.status_code, 308)
  146. def test_search_rss(self):
  147. result = self.app.post('/search', data={'q': 'test', 'format': 'rss'})
  148. self.assertIn(b'<description>Search results for "test" - searx</description>', result.data)
  149. self.assertIn(b'<opensearch:totalResults>3</opensearch:totalResults>', result.data)
  150. self.assertIn(b'<title>First Test</title>', result.data)
  151. self.assertIn(b'<link>http://first.test.xyz</link>', result.data)
  152. self.assertIn(b'<description>first test content</description>', result.data)
  153. def test_redirect_about(self):
  154. result = self.app.get('/about')
  155. self.assertEqual(result.status_code, 302)
  156. def test_info_page(self):
  157. result = self.app.get('/info/en/search-syntax')
  158. self.assertEqual(result.status_code, 200)
  159. self.assertIn(b'<h1>Search syntax</h1>', result.data)
  160. def test_health(self):
  161. result = self.app.get('/healthz')
  162. self.assertEqual(result.status_code, 200)
  163. self.assertIn(b'OK', result.data)
  164. def test_preferences(self):
  165. result = self.app.get('/preferences')
  166. self.assertEqual(result.status_code, 200)
  167. self.assertIn(b'<form id="search_form" method="post" action="/preferences"', result.data)
  168. self.assertIn(b'<div id="categories_container">', result.data)
  169. self.assertIn(b'<legend id="pref_ui_locale">Interface language</legend>', result.data)
  170. def test_browser_locale(self):
  171. result = self.app.get('/preferences', headers={'Accept-Language': 'zh-tw;q=0.8'})
  172. self.assertEqual(result.status_code, 200)
  173. self.assertIn(
  174. b'<option value="zh-Hant-TW" selected="selected">',
  175. result.data,
  176. 'Interface locale ignored browser preference.',
  177. )
  178. self.assertIn(
  179. b'<option value="zh-Hant-TW" selected="selected">',
  180. result.data,
  181. 'Search language ignored browser preference.',
  182. )
  183. def test_browser_empty_locale(self):
  184. result = self.app.get('/preferences', headers={'Accept-Language': ''})
  185. self.assertEqual(result.status_code, 200)
  186. self.assertIn(
  187. b'<option value="en" selected="selected">', result.data, 'Interface locale ignored browser preference.'
  188. )
  189. def test_locale_occitan(self):
  190. result = self.app.get('/preferences?locale=oc')
  191. self.assertEqual(result.status_code, 200)
  192. self.assertIn(
  193. b'<option value="oc" selected="selected">', result.data, 'Interface locale ignored browser preference.'
  194. )
  195. def test_stats(self):
  196. result = self.app.get('/stats')
  197. self.assertEqual(result.status_code, 200)
  198. self.assertIn(b'<h1>Engine stats</h1>', result.data)
  199. def test_robots_txt(self):
  200. result = self.app.get('/robots.txt')
  201. self.assertEqual(result.status_code, 200)
  202. self.assertIn(b'Allow: /', result.data)
  203. def test_opensearch_xml(self):
  204. result = self.app.get('/opensearch.xml')
  205. self.assertEqual(result.status_code, 200)
  206. self.assertIn(
  207. b'<Description>SearXNG is a metasearch engine that respects your privacy.</Description>', result.data
  208. )
  209. def test_favicon(self):
  210. result = self.app.get('/favicon.ico')
  211. self.assertEqual(result.status_code, 200)
  212. def test_config(self):
  213. result = self.app.get('/config')
  214. self.assertEqual(result.status_code, 200)
  215. json_result = result.get_json()
  216. self.assertTrue(json_result)