You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

search_controller_test.rb 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2021 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. require File.expand_path('../../test_helper', __FILE__)
  19. class SearchControllerTest < Redmine::ControllerTest
  20. fixtures :projects, :projects_trackers,
  21. :enabled_modules, :roles, :users, :members, :member_roles,
  22. :issues, :trackers, :issue_statuses, :enumerations,
  23. :workflows,
  24. :custom_fields, :custom_values,
  25. :custom_fields_projects, :custom_fields_trackers,
  26. :repositories, :changesets
  27. def setup
  28. User.current = nil
  29. end
  30. def test_search_without_q_should_display_search_form
  31. get :index
  32. assert_response :success
  33. assert_select '#content input[name=q]'
  34. end
  35. def test_search_for_projects
  36. get :index, :params => {:q => "cook"}
  37. assert_response :success
  38. assert_select '#search-results dt.project a', :text => /eCookbook/
  39. end
  40. def test_search_on_archived_project_should_return_403
  41. Project.find(3).archive
  42. get :index, :params => {:id => 3}
  43. assert_response 403
  44. end
  45. def test_search_on_invisible_project_by_user_should_be_denied
  46. @request.session[:user_id] = 7
  47. get :index, :params => {:id => 2}
  48. assert_response 403
  49. end
  50. def test_search_on_invisible_project_by_anonymous_user_should_redirect
  51. get :index, :params => {:id => 2}
  52. assert_response 302
  53. end
  54. def test_search_on_private_project_by_member_should_succeed
  55. @request.session[:user_id] = 2
  56. get :index, :params => {:id => 2}
  57. assert_response :success
  58. end
  59. def test_search_all_projects
  60. with_settings :default_language => 'en' do
  61. get :index, :params => {:q => 'recipe subproject commit', :all_words => ''}
  62. end
  63. assert_response :success
  64. assert_select '#search-results' do
  65. assert_select 'dt.issue a', :text => /Feature request #2/
  66. assert_select 'dt.issue a', :text => /Bug #5/
  67. assert_select 'dt.changeset a', :text => /Revision 1/
  68. assert_select 'dt.issue a', :text => /Add ingredients categories/
  69. assert_select 'dd', :text => /should be classified by categories/
  70. end
  71. assert_select '#search-results-counts' do
  72. assert_select 'a', :text => 'Changesets (5)'
  73. end
  74. end
  75. def test_search_issues
  76. get :index, :params => {:q => 'issue', :issues => 1}
  77. assert_response :success
  78. assert_select 'input[name=all_words][checked=checked]'
  79. assert_select 'input[name=titles_only]:not([checked])'
  80. assert_select '#search-results' do
  81. assert_select 'dt.issue a', :text => /Bug #5/
  82. assert_select 'dt.issue-closed a', :text => /Bug #8 \(Closed\)/
  83. end
  84. end
  85. def test_search_issues_should_search_notes
  86. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  87. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  88. assert_response :success
  89. assert_select '#search-results' do
  90. assert_select 'dt.issue a', :text => /Feature request #2/
  91. end
  92. end
  93. def test_search_issues_with_multiple_matches_in_journals_should_return_issue_once
  94. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  95. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  96. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  97. assert_response :success
  98. assert_select '#search-results' do
  99. assert_select 'dt.issue a', :text => /Feature request #2/
  100. assert_select 'dt', 1
  101. end
  102. end
  103. def test_search_issues_should_search_private_notes_with_permission_only
  104. Journal.create!(:journalized => Issue.find(2), :notes => 'Private notes with searchkeyword', :private_notes => true)
  105. @request.session[:user_id] = 2
  106. Role.find(1).add_permission! :view_private_notes
  107. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  108. assert_response :success
  109. assert_select '#search-results' do
  110. assert_select 'dt.issue a', :text => /Feature request #2/
  111. end
  112. Role.find(1).remove_permission! :view_private_notes
  113. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  114. assert_response :success
  115. assert_select '#search-results' do
  116. assert_select 'dt', :text => /Feature request #2/, :count => 0
  117. end
  118. end
  119. def test_search_all_projects_with_scope_param
  120. get :index, :params => {:q => 'issue', :scope => 'all'}
  121. assert_response :success
  122. assert_select '#search-results dt'
  123. end
  124. def test_search_my_projects
  125. @request.session[:user_id] = 2
  126. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''}
  127. assert_response :success
  128. assert_select '#search-results' do
  129. assert_select 'dt.issue', :text => /Bug #1/
  130. assert_select 'dt', :text => /Bug #5/, :count => 0
  131. end
  132. end
  133. def test_search_my_projects_without_memberships
  134. # anonymous user has no memberships
  135. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''}
  136. assert_response :success
  137. assert_select '#search-results' do
  138. assert_select 'dt', 0
  139. end
  140. end
  141. def test_search_project_and_subprojects
  142. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'subprojects', :all_words => ''}
  143. assert_response :success
  144. assert_select '#search-results' do
  145. assert_select 'dt.issue', :text => /Bug #1/
  146. assert_select 'dt.issue', :text => /Bug #5/
  147. end
  148. end
  149. def test_search_without_searchable_custom_fields
  150. CustomField.update_all :searchable => false
  151. get :index, :params => {:id => 1}
  152. assert_response :success
  153. get :index, :params => {:id => 1, :q => "can"}
  154. assert_response :success
  155. end
  156. def test_search_with_searchable_custom_fields
  157. get :index, :params => {:id => 1, :q => "stringforcustomfield"}
  158. assert_response :success
  159. assert_select '#search-results' do
  160. assert_select 'dt.issue', :text => /#7/
  161. assert_select 'dt', 1
  162. end
  163. end
  164. def test_search_without_attachments
  165. issue = Issue.generate! :subject => 'search_attachments'
  166. attachment = Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  167. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => '0'}
  168. assert_response :success
  169. assert_select '#search-results' do
  170. assert_select 'dt.issue', :text => /##{issue.id}/
  171. assert_select 'dt', 1
  172. end
  173. end
  174. def test_search_attachments_only
  175. issue = Issue.generate! :subject => 'search_attachments'
  176. attachment = Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  177. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => 'only'}
  178. assert_response :success
  179. assert_select '#search-results' do
  180. assert_select 'dt.issue', :text => / #1 /
  181. assert_select 'dt', 1
  182. end
  183. end
  184. def test_search_with_attachments
  185. issue = Issue.generate! :subject => 'search_attachments'
  186. Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  187. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => '1'}
  188. assert_response :success
  189. assert_select '#search-results' do
  190. assert_select 'dt.issue', :text => / #1 /
  191. assert_select 'dt.issue', :text => / ##{issue.id} /
  192. assert_select 'dt', 2
  193. end
  194. end
  195. def test_search_open_issues
  196. Issue.generate! :subject => 'search_open'
  197. Issue.generate! :subject => 'search_open', :status_id => 5
  198. get :index, :params => {:id => 1, :q => 'search_open', :open_issues => '1'}
  199. assert_response :success
  200. assert_select '#search-results' do
  201. assert_select 'dt', 1
  202. end
  203. end
  204. def test_search_all_words
  205. # 'all words' is on by default
  206. get :index, :params => {:id => 1, :q => 'recipe updating saving', :all_words => '1'}
  207. assert_response :success
  208. assert_select 'input[name=all_words][checked=checked]'
  209. assert_select '#search-results' do
  210. assert_select 'dt.issue', :text => / #3 /
  211. assert_select 'dt', 1
  212. end
  213. end
  214. def test_search_one_of_the_words
  215. get :index, :params => {:id => 1, :q => 'recipe updating saving', :all_words => ''}
  216. assert_response :success
  217. assert_select 'input[name=all_words]:not([checked])'
  218. assert_select '#search-results' do
  219. assert_select 'dt.issue', :text => / #3 /
  220. assert_select 'dt', 3
  221. end
  222. end
  223. def test_search_titles_only_without_result
  224. get :index, :params => {:id => 1, :q => 'recipe updating saving', :titles_only => '1'}
  225. assert_response :success
  226. assert_select 'input[name=titles_only][checked=checked]'
  227. assert_select '#search-results' do
  228. assert_select 'dt', 0
  229. end
  230. end
  231. def test_search_titles_only
  232. get :index, :params => {:id => 1, :q => 'recipe', :titles_only => '1'}
  233. assert_response :success
  234. assert_select 'input[name=titles_only][checked=checked]'
  235. assert_select '#search-results' do
  236. assert_select 'dt', 2
  237. end
  238. end
  239. def test_search_content
  240. Issue.where(:id => 1).update_all("description = 'This is a searchkeywordinthecontent'")
  241. get :index, :params => {:id => 1, :q => 'searchkeywordinthecontent', :titles_only => ''}
  242. assert_response :success
  243. assert_select 'input[name=titles_only]:not([checked])'
  244. assert_select '#search-results' do
  245. assert_select 'dt.issue', :text => / #1 /
  246. assert_select 'dt', 1
  247. end
  248. end
  249. def test_search_with_pagination
  250. issues = (0..24).map {Issue.generate! :subject => 'search_with_limited_results'}.reverse
  251. get :index, :params => {:q => 'search_with_limited_results'}
  252. assert_response :success
  253. issues[0..9].each do |issue|
  254. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  255. end
  256. get :index, :params => {:q => 'search_with_limited_results', :page => 2}
  257. assert_response :success
  258. issues[10..19].each do |issue|
  259. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  260. end
  261. get :index, :params => {:q => 'search_with_limited_results', :page => 3}
  262. assert_response :success
  263. issues[20..24].each do |issue|
  264. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  265. end
  266. get :index, :params => {:q => 'search_with_limited_results', :page => 4}
  267. assert_response :success
  268. assert_select '#search-results dt', 0
  269. end
  270. def test_search_with_invalid_project_id
  271. get :index, :params => {:id => 195, :q => 'recipe'}
  272. assert_response 404
  273. end
  274. def test_search_should_include_closed_projects
  275. @request.session[:user_id] = 1
  276. project = Project.find(5)
  277. project.close
  278. project.save
  279. # scope all
  280. get :index, :params => {:q => 'Issue of a private subproject', :scope => 'all'}
  281. assert_response :success
  282. assert_select '#search-results' do
  283. assert_select 'dt.issue', :text => /Bug #6/
  284. end
  285. # scope my_projects
  286. get :index, :params => {:q => 'Issue of a private subproject', :scope => 'my_projects'}
  287. assert_response :success
  288. assert_select '#search-results' do
  289. assert_select 'dt.issue', :text => /Bug #6/
  290. end
  291. # scope subprojects
  292. get :index, :params => {:id => 1, :q => 'Issue of a private subproject', :scope => 'subprojects'}
  293. assert_response :success
  294. assert_select '#search-results' do
  295. assert_select 'dt.issue', :text => /Bug #6/
  296. end
  297. # scope project
  298. get :index, :params => {:id => 5, :q => 'Issue of a private subproject'}
  299. assert_response :success
  300. assert_select '#search-results' do
  301. assert_select 'dt.issue', :text => /Bug #6/
  302. end
  303. end
  304. def test_quick_jump_to_issue
  305. # issue of a public project
  306. get :index, :params => {:q => "3"}
  307. assert_redirected_to '/issues/3'
  308. # issue of a private project
  309. get :index, :params => {:q => "4"}
  310. assert_response :success
  311. end
  312. def test_large_integer
  313. get :index, :params => {:q => '4615713488'}
  314. assert_response :success
  315. end
  316. def test_tokens_with_quotes
  317. issue1 = Issue.generate! :subject => 'say hello'
  318. issue2 = Issue.generate! :subject => 'say good bye'
  319. issue3 = Issue.generate! :subject => 'say goodbye'
  320. get :index, :params => {:q => '"good bye" hello "bye bye"', :all_words => ''}
  321. assert_response :success
  322. assert_select '#search-results' do
  323. assert_select 'dt.issue a', :text => / ##{issue1.id} /
  324. assert_select 'dt.issue a', :text => / ##{issue2.id} /
  325. assert_select 'dt.issue a', :text => / ##{issue3.id} /, :count => 0
  326. end
  327. end
  328. def test_results_should_be_escaped_once
  329. assert Issue.find(1).update(:subject => '<subject> escaped_once', :description => '<description> escaped_once')
  330. get :index, :params => {:q => 'escaped_once'}
  331. assert_response :success
  332. assert_select '#search-results' do
  333. assert_select 'dt.issue a', :text => /<subject>/
  334. assert_select 'dd', :text => /<description>/
  335. end
  336. end
  337. def test_keywords_should_be_highlighted
  338. assert Issue.find(1).update(:subject => 'subject highlighted', :description => 'description highlighted')
  339. get :index, :params => {:q => 'highlighted'}
  340. assert_response :success
  341. assert_select '#search-results' do
  342. assert_select 'dt.issue a span.highlight', :text => 'highlighted'
  343. assert_select 'dd span.highlight', :text => 'highlighted'
  344. end
  345. end
  346. def test_search_should_exclude_empty_modules_params
  347. @request.session[:user_id] = 1
  348. get :index, params: {
  349. q: "private",
  350. scope: "all",
  351. issues: "1",
  352. projects: nil
  353. }
  354. assert_response :success
  355. assert_select '#search-results dt.project', 0
  356. end
  357. end