Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

search_controller_test.rb 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2023 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_relative '../test_helper'
  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. :issue_categories, :workflows,
  24. :custom_fields, :custom_values,
  25. :custom_fields_projects, :custom_fields_trackers,
  26. :repositories, :changesets,
  27. :user_preferences
  28. def setup
  29. User.current = nil
  30. end
  31. def test_search_without_q_should_display_search_form
  32. get :index
  33. assert_response :success
  34. assert_select '#content input[name=q]'
  35. end
  36. def test_search_for_projects
  37. get :index, :params => {:q => "cook"}
  38. assert_response :success
  39. assert_select '#search-results dt.project a', :text => /eCookbook/
  40. end
  41. def test_search_on_archived_project_should_return_403
  42. Project.find(3).archive
  43. get :index, :params => {:id => 3}
  44. assert_response 403
  45. end
  46. def test_search_on_invisible_project_by_user_should_be_denied
  47. @request.session[:user_id] = 7
  48. get :index, :params => {:id => 2}
  49. assert_response 403
  50. end
  51. def test_search_on_invisible_project_by_anonymous_user_should_redirect
  52. get :index, :params => {:id => 2}
  53. assert_response 302
  54. end
  55. def test_search_on_private_project_by_member_should_succeed
  56. @request.session[:user_id] = 2
  57. get :index, :params => {:id => 2}
  58. assert_response :success
  59. end
  60. def test_search_all_projects
  61. with_settings :default_language => 'en' do
  62. get :index, :params => {:q => 'recipe subproject commit', :all_words => ''}
  63. end
  64. assert_response :success
  65. assert_select '#search-results' do
  66. assert_select 'dt.issue a', :text => /Feature request #2/
  67. assert_select 'dt.issue a', :text => /Bug #5/
  68. assert_select 'dt.changeset a', :text => /Revision 1/
  69. assert_select 'dt.issue a', :text => /Add ingredients categories/
  70. assert_select 'dd', :text => /should be classified by categories/
  71. end
  72. assert_select '#search-results-counts' do
  73. assert_select 'a', :text => 'Changesets (5)'
  74. end
  75. end
  76. def test_search_issues
  77. get :index, :params => {:q => 'issue', :issues => 1}
  78. assert_response :success
  79. assert_select 'input[name=all_words][checked=checked]'
  80. assert_select 'input[name=titles_only]:not([checked])'
  81. assert_select 'p.buttons a', :text => 'Apply issues filter'
  82. assert_select '#search-results' do
  83. assert_select 'dt.issue a', :text => /Bug #5/
  84. assert_select 'dt.issue-closed a', :text => /Bug #8 \(Closed\)/
  85. end
  86. end
  87. def test_search_issues_should_search_notes
  88. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  89. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  90. assert_response :success
  91. assert_select '#search-results' do
  92. assert_select 'dt.issue a', :text => /Feature request #2/
  93. end
  94. end
  95. def test_search_issues_with_multiple_matches_in_journals_should_return_issue_once
  96. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  97. Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword')
  98. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  99. assert_response :success
  100. assert_select '#search-results' do
  101. assert_select 'dt.issue a', :text => /Feature request #2/
  102. assert_select 'dt', 1
  103. end
  104. end
  105. def test_search_issues_should_search_private_notes_with_permission_only
  106. Journal.create!(:journalized => Issue.find(2), :notes => 'Private notes with searchkeyword', :private_notes => true)
  107. @request.session[:user_id] = 2
  108. Role.find(1).add_permission! :view_private_notes
  109. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  110. assert_response :success
  111. assert_select '#search-results' do
  112. assert_select 'dt.issue a', :text => /Feature request #2/
  113. end
  114. Role.find(1).remove_permission! :view_private_notes
  115. get :index, :params => {:q => 'searchkeyword', :issues => 1}
  116. assert_response :success
  117. assert_select '#search-results' do
  118. assert_select 'dt', :text => /Feature request #2/, :count => 0
  119. end
  120. end
  121. def test_search_all_projects_with_scope_param
  122. get :index, :params => {:q => 'issue', :scope => 'all'}
  123. assert_response :success
  124. assert_select '#search-results dt'
  125. end
  126. def test_search_my_projects
  127. @request.session[:user_id] = 2
  128. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''}
  129. assert_response :success
  130. assert_select '#search-results' do
  131. assert_select 'dt.issue', :text => /Bug #1/
  132. assert_select 'dt', :text => /Bug #5/, :count => 0
  133. end
  134. end
  135. def test_search_my_projects_without_memberships
  136. # anonymous user has no memberships
  137. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''}
  138. assert_response :success
  139. assert_select '#search-results' do
  140. assert_select 'dt', 0
  141. end
  142. end
  143. def test_search_my_bookmarks
  144. @request.session[:user_id] = 1
  145. get :index, :params => {:q => 'project', :scope => 'bookmarks', :all_words => ''}
  146. assert_response :success
  147. assert_select '#search-results' do
  148. assert_select 'dt.issue', :count => 1
  149. assert_select 'dt.issue', :text => /Bug #6/
  150. assert_select 'dt.changeset', :count => 1
  151. assert_select 'dt.changeset', :text => /Revision 4/
  152. end
  153. end
  154. def test_search_project_and_subprojects
  155. get :index, :params => {:id => 1, :q => 'recipe subproject', :scope => 'subprojects', :all_words => ''}
  156. assert_response :success
  157. assert_select '#search-results' do
  158. assert_select 'dt.issue', :text => /Bug #1/
  159. assert_select 'dt.issue', :text => /Bug #5/
  160. end
  161. end
  162. def test_search_without_searchable_custom_fields
  163. CustomField.update_all :searchable => false
  164. get :index, :params => {:id => 1}
  165. assert_response :success
  166. get :index, :params => {:id => 1, :q => "can"}
  167. assert_response :success
  168. end
  169. def test_search_with_searchable_custom_fields
  170. get :index, :params => {:id => 1, :q => "stringforcustomfield"}
  171. assert_response :success
  172. assert_select '#search-results' do
  173. assert_select 'dt.issue', :text => /#7/
  174. assert_select 'dt', 1
  175. end
  176. end
  177. def test_search_without_attachments
  178. issue = Issue.generate! :subject => 'search_attachments'
  179. attachment = Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  180. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => '0'}
  181. assert_response :success
  182. assert_select '#search-results' do
  183. assert_select 'dt.issue', :text => /##{issue.id}/
  184. assert_select 'dt', 1
  185. end
  186. end
  187. def test_search_attachments_only
  188. issue = Issue.generate! :subject => 'search_attachments'
  189. attachment = Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  190. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => 'only'}
  191. assert_response :success
  192. assert_select '#search-results' do
  193. assert_select 'dt.issue', :text => / #1 /
  194. assert_select 'dt', 1
  195. end
  196. end
  197. def test_search_with_attachments
  198. issue = Issue.generate! :subject => 'search_attachments'
  199. Attachment.generate! :container => Issue.find(1), :filename => 'search_attachments.patch'
  200. get :index, :params => {:id => 1, :q => 'search_attachments', :attachments => '1'}
  201. assert_response :success
  202. assert_select '#search-results' do
  203. assert_select 'dt.issue', :text => / #1 /
  204. assert_select 'dt.issue', :text => / ##{issue.id} /
  205. assert_select 'dt', 2
  206. end
  207. end
  208. def test_search_open_issues
  209. Issue.generate! :subject => 'search_open'
  210. Issue.generate! :subject => 'search_open', :status_id => 5
  211. get :index, :params => {:id => 1, :q => 'search_open', :open_issues => '1'}
  212. assert_response :success
  213. assert_select '#search-results' do
  214. assert_select 'dt', 1
  215. end
  216. end
  217. def test_search_all_words
  218. # 'all words' is on by default
  219. get :index, :params => {:id => 1, :q => 'recipe updating saving', :all_words => '1'}
  220. assert_response :success
  221. assert_select 'input[name=all_words][checked=checked]'
  222. assert_select '#search-results' do
  223. assert_select 'dt.issue', :text => / #3 /
  224. assert_select 'dt', 1
  225. end
  226. end
  227. def test_search_one_of_the_words
  228. get :index, :params => {:id => 1, :q => 'recipe updating saving', :all_words => ''}
  229. assert_response :success
  230. assert_select 'input[name=all_words]:not([checked])'
  231. assert_select '#search-results' do
  232. assert_select 'dt.issue', :text => / #3 /
  233. assert_select 'dt', 3
  234. end
  235. end
  236. def test_search_titles_only_without_result
  237. get :index, :params => {:id => 1, :q => 'recipe updating saving', :titles_only => '1'}
  238. assert_response :success
  239. assert_select 'input[name=titles_only][checked=checked]'
  240. assert_select '#search-results' do
  241. assert_select 'dt', 0
  242. end
  243. end
  244. def test_search_titles_only
  245. get :index, :params => {:id => 1, :q => 'recipe', :titles_only => '1'}
  246. assert_response :success
  247. assert_select 'input[name=titles_only][checked=checked]'
  248. assert_select '#search-results' do
  249. assert_select 'dt', 2
  250. end
  251. end
  252. def test_search_content
  253. Issue.where(:id => 1).update_all("description = 'This is a searchkeywordinthecontent'")
  254. get :index, :params => {:id => 1, :q => 'searchkeywordinthecontent', :titles_only => ''}
  255. assert_response :success
  256. assert_select 'input[name=titles_only]:not([checked])'
  257. assert_select '#search-results' do
  258. assert_select 'dt.issue', :text => / #1 /
  259. assert_select 'dt', 1
  260. end
  261. end
  262. def test_search_with_pagination
  263. issues = (0..24).map {Issue.generate! :subject => 'search_with_limited_results'}.reverse
  264. get :index, :params => {:q => 'search_with_limited_results'}
  265. assert_response :success
  266. issues[0..9].each do |issue|
  267. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  268. end
  269. get :index, :params => {:q => 'search_with_limited_results', :page => 2}
  270. assert_response :success
  271. issues[10..19].each do |issue|
  272. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  273. end
  274. get :index, :params => {:q => 'search_with_limited_results', :page => 3}
  275. assert_response :success
  276. issues[20..24].each do |issue|
  277. assert_select '#search-results dt.issue', :text => / ##{issue.id} /
  278. end
  279. get :index, :params => {:q => 'search_with_limited_results', :page => 4}
  280. assert_response :success
  281. assert_select '#search-results dt', 0
  282. end
  283. def test_search_with_invalid_project_id
  284. get :index, :params => {:id => 195, :q => 'recipe'}
  285. assert_response 404
  286. end
  287. def test_search_should_include_closed_projects
  288. @request.session[:user_id] = 1
  289. project = Project.find(5)
  290. project.close
  291. project.save
  292. # scope all
  293. get :index, :params => {:q => 'Issue of a private subproject', :scope => 'all'}
  294. assert_response :success
  295. assert_select '#search-results' do
  296. assert_select 'dt.issue', :text => /Bug #6/
  297. end
  298. # scope my_projects
  299. get :index, :params => {:q => 'Issue of a private subproject', :scope => 'my_projects'}
  300. assert_response :success
  301. assert_select '#search-results' do
  302. assert_select 'dt.issue', :text => /Bug #6/
  303. end
  304. # scope subprojects
  305. get :index, :params => {:id => 1, :q => 'Issue of a private subproject', :scope => 'subprojects'}
  306. assert_response :success
  307. assert_select '#search-results' do
  308. assert_select 'dt.issue', :text => /Bug #6/
  309. end
  310. # scope project
  311. get :index, :params => {:id => 5, :q => 'Issue of a private subproject'}
  312. assert_response :success
  313. assert_select '#search-results' do
  314. assert_select 'dt.issue', :text => /Bug #6/
  315. end
  316. end
  317. def test_quick_jump_to_issue
  318. # issue of a public project
  319. get :index, :params => {:q => "3"}
  320. assert_redirected_to '/issues/3'
  321. # issue of a private project
  322. get :index, :params => {:q => "4"}
  323. assert_response :success
  324. end
  325. def test_large_integer
  326. get :index, :params => {:q => '4615713488'}
  327. assert_response :success
  328. end
  329. def test_tokens_with_quotes
  330. issue1 = Issue.generate! :subject => 'say hello'
  331. issue2 = Issue.generate! :subject => 'say good bye'
  332. issue3 = Issue.generate! :subject => 'say goodbye'
  333. get :index, :params => {:q => '"good bye" hello "bye bye"', :all_words => ''}
  334. assert_response :success
  335. assert_select '#search-results' do
  336. assert_select 'dt.issue a', :text => / ##{issue1.id} /
  337. assert_select 'dt.issue a', :text => / ##{issue2.id} /
  338. assert_select 'dt.issue a', :text => / ##{issue3.id} /, :count => 0
  339. end
  340. end
  341. def test_results_should_be_escaped_once
  342. assert Issue.find(1).update(:subject => '<subject> escaped_once', :description => '<description> escaped_once')
  343. get :index, :params => {:q => 'escaped_once'}
  344. assert_response :success
  345. assert_select '#search-results' do
  346. assert_select 'dt.issue a', :text => /<subject>/
  347. assert_select 'dd', :text => /<description>/
  348. end
  349. end
  350. def test_keywords_should_be_highlighted
  351. assert Issue.find(1).update(:subject => 'subject highlighted', :description => 'description highlighted')
  352. get :index, :params => {:q => 'highlighted'}
  353. assert_response :success
  354. assert_select '#search-results' do
  355. assert_select 'dt.issue a span.highlight', :text => 'highlighted'
  356. assert_select 'dd span.highlight', :text => 'highlighted'
  357. end
  358. end
  359. def test_search_should_exclude_empty_modules_params
  360. @request.session[:user_id] = 1
  361. get :index, params: {
  362. q: "private",
  363. scope: "all",
  364. issues: "1",
  365. projects: nil
  366. }
  367. assert_response :success
  368. assert_select '#search-results dt.project', 0
  369. end
  370. def test_search_should_not_show_apply_issues_filter_button_if_no_issues_found
  371. get :index, :params => {:q => 'commits'}
  372. assert_response :success
  373. assert_select 'p.buttons a', :text => 'Apply issues filter', :count => 0
  374. assert_select '#search-results' do
  375. assert_select 'dt.issue', :count => 0
  376. assert_select 'dt.issue-closed', :count => 0
  377. end
  378. end
  379. end