##// END OF EJS Templates
Additional tests for SearchController and handle my_projects scope without memberships....
Jean-Philippe Lang -
r8146:bb0cd34f4e2e
parent child
Show More
@@ -1,162 +1,202
1 1 require File.expand_path('../../test_helper', __FILE__)
2 2 require 'search_controller'
3 3
4 4 # Re-raise errors caught by the controller.
5 5 class SearchController; def rescue_action(e) raise e end; end
6 6
7 7 class SearchControllerTest < ActionController::TestCase
8 8 fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles,
9 9 :issues, :trackers, :issue_statuses,
10 10 :custom_fields, :custom_values,
11 11 :repositories, :changesets
12 12
13 13 def setup
14 14 @controller = SearchController.new
15 15 @request = ActionController::TestRequest.new
16 16 @response = ActionController::TestResponse.new
17 17 User.current = nil
18 18 end
19 19
20 20 def test_search_for_projects
21 21 get :index
22 22 assert_response :success
23 23 assert_template 'index'
24 24
25 25 get :index, :q => "cook"
26 26 assert_response :success
27 27 assert_template 'index'
28 28 assert assigns(:results).include?(Project.find(1))
29 29 end
30 30
31 31 def test_search_all_projects
32 32 get :index, :q => 'recipe subproject commit', :all_words => ''
33 33 assert_response :success
34 34 assert_template 'index'
35 35
36 36 assert assigns(:results).include?(Issue.find(2))
37 37 assert assigns(:results).include?(Issue.find(5))
38 38 assert assigns(:results).include?(Changeset.find(101))
39 39 assert_tag :dt, :attributes => { :class => /issue/ },
40 40 :child => { :tag => 'a', :content => /Add ingredients categories/ },
41 41 :sibling => { :tag => 'dd', :content => /should be classified by categories/ }
42 42
43 43 assert assigns(:results_by_type).is_a?(Hash)
44 44 assert_equal 5, assigns(:results_by_type)['changesets']
45 45 assert_tag :a, :content => 'Changesets (5)'
46 46 end
47 47
48 48 def test_search_issues
49 49 get :index, :q => 'issue', :issues => 1
50 50 assert_response :success
51 51 assert_template 'index'
52 52
53 53 assert_equal true, assigns(:all_words)
54 54 assert_equal false, assigns(:titles_only)
55 55 assert assigns(:results).include?(Issue.find(8))
56 56 assert assigns(:results).include?(Issue.find(5))
57 57 assert_tag :dt, :attributes => { :class => /issue closed/ },
58 58 :child => { :tag => 'a', :content => /Closed/ }
59 59 end
60 60
61 def test_search_all_projects_with_scope_param
62 get :index, :q => 'issue', :scope => 'all'
63 assert_response :success
64 assert_template 'index'
65 assert assigns(:results).present?
66 end
67
68 def test_search_my_projects
69 @request.session[:user_id] = 2
70 get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''
71 assert_response :success
72 assert_template 'index'
73 assert assigns(:results).include?(Issue.find(1))
74 assert !assigns(:results).include?(Issue.find(5))
75 end
76
77 def test_search_my_projects_without_memberships
78 # anonymous user has no memberships
79 get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => ''
80 assert_response :success
81 assert_template 'index'
82 assert assigns(:results).empty?
83 end
84
61 85 def test_search_project_and_subprojects
62 86 get :index, :id => 1, :q => 'recipe subproject', :scope => 'subprojects', :all_words => ''
63 87 assert_response :success
64 88 assert_template 'index'
65 89 assert assigns(:results).include?(Issue.find(1))
66 90 assert assigns(:results).include?(Issue.find(5))
67 91 end
68 92
69 93 def test_search_without_searchable_custom_fields
70 94 CustomField.update_all "searchable = #{ActiveRecord::Base.connection.quoted_false}"
71 95
72 96 get :index, :id => 1
73 97 assert_response :success
74 98 assert_template 'index'
75 99 assert_not_nil assigns(:project)
76 100
77 101 get :index, :id => 1, :q => "can"
78 102 assert_response :success
79 103 assert_template 'index'
80 104 end
81 105
82 106 def test_search_with_searchable_custom_fields
83 107 get :index, :id => 1, :q => "stringforcustomfield"
84 108 assert_response :success
85 109 results = assigns(:results)
86 110 assert_not_nil results
87 111 assert_equal 1, results.size
88 112 assert results.include?(Issue.find(7))
89 113 end
90 114
91 115 def test_search_all_words
92 116 # 'all words' is on by default
93 117 get :index, :id => 1, :q => 'recipe updating saving', :all_words => '1'
94 118 assert_equal true, assigns(:all_words)
95 119 results = assigns(:results)
96 120 assert_not_nil results
97 121 assert_equal 1, results.size
98 122 assert results.include?(Issue.find(3))
99 123 end
100 124
101 125 def test_search_one_of_the_words
102 126 get :index, :id => 1, :q => 'recipe updating saving', :all_words => ''
103 127 assert_equal false, assigns(:all_words)
104 128 results = assigns(:results)
105 129 assert_not_nil results
106 130 assert_equal 3, results.size
107 131 assert results.include?(Issue.find(3))
108 132 end
109 133
110 134 def test_search_titles_only_without_result
111 135 get :index, :id => 1, :q => 'recipe updating saving', :titles_only => '1'
112 136 results = assigns(:results)
113 137 assert_not_nil results
114 138 assert_equal 0, results.size
115 139 end
116 140
117 141 def test_search_titles_only
118 142 get :index, :id => 1, :q => 'recipe', :titles_only => '1'
119 143 assert_equal true, assigns(:titles_only)
120 144 results = assigns(:results)
121 145 assert_not_nil results
122 146 assert_equal 2, results.size
123 147 end
124 148
125 149 def test_search_content
126 150 Issue.update_all("description = 'This is a searchkeywordinthecontent'", "id=1")
127 151
128 152 get :index, :id => 1, :q => 'searchkeywordinthecontent', :titles_only => ''
129 153 assert_equal false, assigns(:titles_only)
130 154 results = assigns(:results)
131 155 assert_not_nil results
132 156 assert_equal 1, results.size
133 157 end
134 158
159 def test_search_with_offset
160 get :index, :q => 'coo', :offset => '20080806073000'
161 assert_response :success
162 results = assigns(:results)
163 assert results.any?
164 assert results.map(&:event_datetime).max < '20080806T073000'.to_time
165 end
166
167 def test_search_previous_with_offset
168 get :index, :q => 'coo', :offset => '20080806073000', :previous => '1'
169 assert_response :success
170 results = assigns(:results)
171 assert results.any?
172 assert results.map(&:event_datetime).min >= '20080806T073000'.to_time
173 end
174
135 175 def test_search_with_invalid_project_id
136 176 get :index, :id => 195, :q => 'recipe'
137 177 assert_response 404
138 178 assert_nil assigns(:results)
139 179 end
140 180
141 181 def test_quick_jump_to_issue
142 182 # issue of a public project
143 183 get :index, :q => "3"
144 184 assert_redirected_to '/issues/3'
145 185
146 186 # issue of a private project
147 187 get :index, :q => "4"
148 188 assert_response :success
149 189 assert_template 'index'
150 190 end
151 191
152 192 def test_large_integer
153 193 get :index, :q => '4615713488'
154 194 assert_response :success
155 195 assert_template 'index'
156 196 end
157 197
158 198 def test_tokens_with_quotes
159 199 get :index, :id => 1, :q => '"good bye" hello "bye bye"'
160 200 assert_equal ["good bye", "hello", "bye bye"], assigns(:tokens)
161 201 end
162 202 end
@@ -1,130 +1,135
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Acts
20 20 module Searchable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 # Options:
27 27 # * :columns - a column or an array of columns to search
28 28 # * :project_key - project foreign key (default to project_id)
29 29 # * :date_column - name of the datetime column (default to created_on)
30 30 # * :sort_order - name of the column used to sort results (default to :date_column or created_on)
31 31 # * :permission - permission required to search the model (default to :view_"objects")
32 32 def acts_as_searchable(options = {})
33 33 return if self.included_modules.include?(Redmine::Acts::Searchable::InstanceMethods)
34 34
35 35 cattr_accessor :searchable_options
36 36 self.searchable_options = options
37 37
38 38 if searchable_options[:columns].nil?
39 39 raise 'No searchable column defined.'
40 40 elsif !searchable_options[:columns].is_a?(Array)
41 41 searchable_options[:columns] = [] << searchable_options[:columns]
42 42 end
43 43
44 44 searchable_options[:project_key] ||= "#{table_name}.project_id"
45 45 searchable_options[:date_column] ||= "#{table_name}.created_on"
46 46 searchable_options[:order_column] ||= searchable_options[:date_column]
47 47
48 48 # Should we search custom fields on this model ?
49 49 searchable_options[:search_custom_fields] = !reflect_on_association(:custom_values).nil?
50 50
51 51 send :include, Redmine::Acts::Searchable::InstanceMethods
52 52 end
53 53 end
54 54
55 55 module InstanceMethods
56 56 def self.included(base)
57 57 base.extend ClassMethods
58 58 end
59 59
60 60 module ClassMethods
61 61 # Searches the model for the given tokens
62 62 # projects argument can be either nil (will search all projects), a project or an array of projects
63 63 # Returns the results and the results count
64 64 def search(tokens, projects=nil, options={})
65 if projects.is_a?(Array) && projects.empty?
66 # no results
67 return [[], 0]
68 end
69
65 70 # TODO: make user an argument
66 71 user = User.current
67 72 tokens = [] << tokens unless tokens.is_a?(Array)
68 73 projects = [] << projects unless projects.nil? || projects.is_a?(Array)
69 74
70 75 find_options = {:include => searchable_options[:include]}
71 76 find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC')
72 77
73 78 limit_options = {}
74 79 limit_options[:limit] = options[:limit] if options[:limit]
75 80 if options[:offset]
76 81 limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')"
77 82 end
78 83
79 84 columns = searchable_options[:columns]
80 85 columns = columns[0..0] if options[:titles_only]
81 86
82 87 token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"}
83 88
84 89 if !options[:titles_only] && searchable_options[:search_custom_fields]
85 90 searchable_custom_field_ids = CustomField.find(:all,
86 91 :select => 'id',
87 92 :conditions => { :type => "#{self.name}CustomField",
88 93 :searchable => true }).collect(&:id)
89 94 if searchable_custom_field_ids.any?
90 95 custom_field_sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" +
91 96 " WHERE customized_type='#{self.name}' AND customized_id=#{table_name}.id AND LOWER(value) LIKE ?" +
92 97 " AND #{CustomValue.table_name}.custom_field_id IN (#{searchable_custom_field_ids.join(',')}))"
93 98 token_clauses << custom_field_sql
94 99 end
95 100 end
96 101
97 102 sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ')
98 103
99 104 find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
100 105
101 106 scope = self
102 107 project_conditions = []
103 108 if searchable_options.has_key?(:permission)
104 109 project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project)
105 110 elsif respond_to?(:visible)
106 111 scope = scope.visible(user)
107 112 else
108 113 ActiveSupport::Deprecation.warn "acts_as_searchable with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option."
109 114 project_conditions << Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym)
110 115 end
111 116 # TODO: use visible scope options instead
112 117 project_conditions << "#{searchable_options[:project_key]} IN (#{projects.collect(&:id).join(',')})" unless projects.nil?
113 118 project_conditions = project_conditions.empty? ? nil : project_conditions.join(' AND ')
114 119
115 120 results = []
116 121 results_count = 0
117 122
118 123 with_scope(:find => {:conditions => project_conditions}) do
119 124 with_scope(:find => find_options) do
120 125 results_count = scope.count(:all)
121 126 results = scope.find(:all, limit_options)
122 127 end
123 128 end
124 129 [results, results_count]
125 130 end
126 131 end
127 132 end
128 133 end
129 134 end
130 135 end
General Comments 0
You need to be logged in to leave comments. Login now