comparison test/unit/query_test.rb @ 909:cbb26bc654de redmine-1.3

Update to Redmine 1.3-stable branch (Redmine SVN rev 8964)
author Chris Cannam
date Fri, 24 Feb 2012 19:09:32 +0000
parents cbce1fd3b1b7
children 433d4f72a19b
comparison
equal deleted inserted replaced
908:c6c2cbd0afee 909:cbb26bc654de
3 # 3 #
4 # This program is free software; you can redistribute it and/or 4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License 5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2 6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version. 7 # of the License, or (at your option) any later version.
8 # 8 #
9 # This program is distributed in the hope that it will be useful, 9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details. 12 # GNU General Public License for more details.
13 # 13 #
14 # You should have received a copy of the GNU General Public License 14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software 15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 require File.expand_path('../../test_helper', __FILE__) 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 class QueryTest < ActiveSupport::TestCase 20 class QueryTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries 21 fixtures :projects, :enabled_modules, :users, :members,
22 :member_roles, :roles, :trackers, :issue_statuses,
23 :issue_categories, :enumerations, :issues,
24 :watchers, :custom_fields, :custom_values, :versions,
25 :queries,
26 :projects_trackers
22 27
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries 28 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
24 query = Query.new(:project => nil, :name => '_') 29 query = Query.new(:project => nil, :name => '_')
25 assert query.available_filters.has_key?('cf_1') 30 assert query.available_filters.has_key?('cf_1')
26 assert !query.available_filters.has_key?('cf_3') 31 assert !query.available_filters.has_key?('cf_3')
27 end 32 end
28 33
29 def test_system_shared_versions_should_be_available_in_global_queries 34 def test_system_shared_versions_should_be_available_in_global_queries
30 Version.find(2).update_attribute :sharing, 'system' 35 Version.find(2).update_attribute :sharing, 'system'
31 query = Query.new(:project => nil, :name => '_') 36 query = Query.new(:project => nil, :name => '_')
32 assert query.available_filters.has_key?('fixed_version_id') 37 assert query.available_filters.has_key?('fixed_version_id')
33 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} 38 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
34 end 39 end
35 40
36 def test_project_filter_in_global_queries 41 def test_project_filter_in_global_queries
37 query = Query.new(:project => nil, :name => '_') 42 query = Query.new(:project => nil, :name => '_')
38 project_filter = query.available_filters["project_id"] 43 project_filter = query.available_filters["project_id"]
39 assert_not_nil project_filter 44 assert_not_nil project_filter
40 project_ids = project_filter[:values].map{|p| p[1]} 45 project_ids = project_filter[:values].map{|p| p[1]}
41 assert project_ids.include?("1") #public project 46 assert project_ids.include?("1") #public project
42 assert !project_ids.include?("2") #private project user cannot see 47 assert !project_ids.include?("2") #private project user cannot see
43 end 48 end
44 49
45 def find_issues_with_query(query) 50 def find_issues_with_query(query)
46 Issue.find :all, 51 Issue.find :all,
47 :include => [ :assigned_to, :status, :tracker, :project, :priority ], 52 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
48 :conditions => query.statement 53 :conditions => query.statement
49 end 54 end
50 55
51 def assert_find_issues_with_query_is_successful(query) 56 def assert_find_issues_with_query_is_successful(query)
52 assert_nothing_raised do 57 assert_nothing_raised do
55 end 60 end
56 61
57 def assert_query_statement_includes(query, condition) 62 def assert_query_statement_includes(query, condition)
58 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}" 63 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
59 end 64 end
65
66 def assert_query_result(expected, query)
67 assert_nothing_raised do
68 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
69 assert_equal expected.size, query.issue_count
70 end
71 end
60 72
61 def test_query_should_allow_shared_versions_for_a_project_query 73 def test_query_should_allow_shared_versions_for_a_project_query
62 subproject_version = Version.find(4) 74 subproject_version = Version.find(4)
63 query = Query.new(:project => Project.find(1), :name => '_') 75 query = Query.new(:project => Project.find(1), :name => '_')
64 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) 76 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
65 77
66 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") 78 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
67 end 79 end
68 80
69 def test_query_with_multiple_custom_fields 81 def test_query_with_multiple_custom_fields
70 query = Query.find(1) 82 query = Query.find(1)
71 assert query.valid? 83 assert query.valid?
72 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") 84 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
73 issues = find_issues_with_query(query) 85 issues = find_issues_with_query(query)
74 assert_equal 1, issues.length 86 assert_equal 1, issues.length
75 assert_equal Issue.find(3), issues.first 87 assert_equal Issue.find(3), issues.first
76 end 88 end
77 89
78 def test_operator_none 90 def test_operator_none
79 query = Query.new(:project => Project.find(1), :name => '_') 91 query = Query.new(:project => Project.find(1), :name => '_')
80 query.add_filter('fixed_version_id', '!*', ['']) 92 query.add_filter('fixed_version_id', '!*', [''])
81 query.add_filter('cf_1', '!*', ['']) 93 query.add_filter('cf_1', '!*', [''])
82 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") 94 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
83 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''") 95 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
84 find_issues_with_query(query) 96 find_issues_with_query(query)
85 end 97 end
86 98
87 def test_operator_none_for_integer 99 def test_operator_none_for_integer
88 query = Query.new(:project => Project.find(1), :name => '_') 100 query = Query.new(:project => Project.find(1), :name => '_')
89 query.add_filter('estimated_hours', '!*', ['']) 101 query.add_filter('estimated_hours', '!*', [''])
90 issues = find_issues_with_query(query) 102 issues = find_issues_with_query(query)
91 assert !issues.empty? 103 assert !issues.empty?
92 assert issues.all? {|i| !i.estimated_hours} 104 assert issues.all? {|i| !i.estimated_hours}
105 end
106
107 def test_operator_none_for_date
108 query = Query.new(:project => Project.find(1), :name => '_')
109 query.add_filter('start_date', '!*', [''])
110 issues = find_issues_with_query(query)
111 assert !issues.empty?
112 assert issues.all? {|i| i.start_date.nil?}
93 end 113 end
94 114
95 def test_operator_all 115 def test_operator_all
96 query = Query.new(:project => Project.find(1), :name => '_') 116 query = Query.new(:project => Project.find(1), :name => '_')
97 query.add_filter('fixed_version_id', '*', ['']) 117 query.add_filter('fixed_version_id', '*', [''])
98 query.add_filter('cf_1', '*', ['']) 118 query.add_filter('cf_1', '*', [''])
99 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") 119 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
100 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''") 120 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
101 find_issues_with_query(query) 121 find_issues_with_query(query)
102 end 122 end
103 123
124 def test_operator_all_for_date
125 query = Query.new(:project => Project.find(1), :name => '_')
126 query.add_filter('start_date', '*', [''])
127 issues = find_issues_with_query(query)
128 assert !issues.empty?
129 assert issues.all? {|i| i.start_date.present?}
130 end
131
132 def test_numeric_filter_should_not_accept_non_numeric_values
133 query = Query.new(:name => '_')
134 query.add_filter('estimated_hours', '=', ['a'])
135
136 assert query.has_filter?('estimated_hours')
137 assert !query.valid?
138 end
139
140 def test_operator_is_on_float
141 Issue.update_all("estimated_hours = 171.2", "id=2")
142
143 query = Query.new(:name => '_')
144 query.add_filter('estimated_hours', '=', ['171.20'])
145 issues = find_issues_with_query(query)
146 assert_equal 1, issues.size
147 assert_equal 2, issues.first.id
148 end
149
104 def test_operator_greater_than 150 def test_operator_greater_than
105 query = Query.new(:project => Project.find(1), :name => '_') 151 query = Query.new(:project => Project.find(1), :name => '_')
106 query.add_filter('done_ratio', '>=', ['40']) 152 query.add_filter('done_ratio', '>=', ['40'])
107 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40") 153 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
154 find_issues_with_query(query)
155 end
156
157 def test_operator_greater_than_a_float
158 query = Query.new(:project => Project.find(1), :name => '_')
159 query.add_filter('estimated_hours', '>=', ['40.5'])
160 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
161 find_issues_with_query(query)
162 end
163
164 def test_operator_greater_than_on_custom_field
165 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
166 query = Query.new(:project => Project.find(1), :name => '_')
167 query.add_filter("cf_#{f.id}", '>=', ['40'])
168 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) >= 40.0")
169 find_issues_with_query(query)
170 end
171
172 def test_operator_lesser_than
173 query = Query.new(:project => Project.find(1), :name => '_')
174 query.add_filter('done_ratio', '<=', ['30'])
175 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
176 find_issues_with_query(query)
177 end
178
179 def test_operator_lesser_than_on_custom_field
180 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
181 query = Query.new(:project => Project.find(1), :name => '_')
182 query.add_filter("cf_#{f.id}", '<=', ['30'])
183 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
184 find_issues_with_query(query)
185 end
186
187 def test_operator_between
188 query = Query.new(:project => Project.find(1), :name => '_')
189 query.add_filter('done_ratio', '><', ['30', '40'])
190 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
191 find_issues_with_query(query)
192 end
193
194 def test_operator_between_on_custom_field
195 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
196 query = Query.new(:project => Project.find(1), :name => '_')
197 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
198 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
199 find_issues_with_query(query)
200 end
201
202 def test_date_filter_should_not_accept_non_date_values
203 query = Query.new(:name => '_')
204 query.add_filter('created_on', '=', ['a'])
205
206 assert query.has_filter?('created_on')
207 assert !query.valid?
208 end
209
210 def test_date_filter_should_not_accept_invalid_date_values
211 query = Query.new(:name => '_')
212 query.add_filter('created_on', '=', ['2011-01-34'])
213
214 assert query.has_filter?('created_on')
215 assert !query.valid?
216 end
217
218 def test_relative_date_filter_should_not_accept_non_integer_values
219 query = Query.new(:name => '_')
220 query.add_filter('created_on', '>t-', ['a'])
221
222 assert query.has_filter?('created_on')
223 assert !query.valid?
224 end
225
226 def test_operator_date_equals
227 query = Query.new(:name => '_')
228 query.add_filter('due_date', '=', ['2011-07-10'])
229 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
230 find_issues_with_query(query)
231 end
232
233 def test_operator_date_lesser_than
234 query = Query.new(:name => '_')
235 query.add_filter('due_date', '<=', ['2011-07-10'])
236 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
237 find_issues_with_query(query)
238 end
239
240 def test_operator_date_greater_than
241 query = Query.new(:name => '_')
242 query.add_filter('due_date', '>=', ['2011-07-10'])
243 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
244 find_issues_with_query(query)
245 end
246
247 def test_operator_date_between
248 query = Query.new(:name => '_')
249 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
250 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
108 find_issues_with_query(query) 251 find_issues_with_query(query)
109 end 252 end
110 253
111 def test_operator_in_more_than 254 def test_operator_in_more_than
112 Issue.find(7).update_attribute(:due_date, (Date.today + 15)) 255 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
122 query.add_filter('due_date', '<t+', ['15']) 265 query.add_filter('due_date', '<t+', ['15'])
123 issues = find_issues_with_query(query) 266 issues = find_issues_with_query(query)
124 assert !issues.empty? 267 assert !issues.empty?
125 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))} 268 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
126 end 269 end
127 270
128 def test_operator_less_than_ago 271 def test_operator_less_than_ago
129 Issue.find(7).update_attribute(:due_date, (Date.today - 3)) 272 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
130 query = Query.new(:project => Project.find(1), :name => '_') 273 query = Query.new(:project => Project.find(1), :name => '_')
131 query.add_filter('due_date', '>t-', ['3']) 274 query.add_filter('due_date', '>t-', ['3'])
132 issues = find_issues_with_query(query) 275 issues = find_issues_with_query(query)
133 assert !issues.empty? 276 assert !issues.empty?
134 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)} 277 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
135 end 278 end
136 279
137 def test_operator_more_than_ago 280 def test_operator_more_than_ago
138 Issue.find(7).update_attribute(:due_date, (Date.today - 10)) 281 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
139 query = Query.new(:project => Project.find(1), :name => '_') 282 query = Query.new(:project => Project.find(1), :name => '_')
140 query.add_filter('due_date', '<t-', ['10']) 283 query.add_filter('due_date', '<t-', ['10'])
141 assert query.statement.include?("#{Issue.table_name}.due_date <=") 284 assert query.statement.include?("#{Issue.table_name}.due_date <=")
188 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'") 331 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
189 result = find_issues_with_query(query) 332 result = find_issues_with_query(query)
190 assert result.empty? 333 assert result.empty?
191 result.each {|issue| assert issue.subject.downcase.include?('unable') } 334 result.each {|issue| assert issue.subject.downcase.include?('unable') }
192 end 335 end
193 336
194 def test_range_for_this_week_with_week_starting_on_monday 337 def test_range_for_this_week_with_week_starting_on_monday
195 I18n.locale = :fr 338 I18n.locale = :fr
196 assert_equal '1', I18n.t(:general_first_day_of_week) 339 assert_equal '1', I18n.t(:general_first_day_of_week)
197 340
198 Date.stubs(:today).returns(Date.parse('2011-04-29')) 341 Date.stubs(:today).returns(Date.parse('2011-04-29'))
199 342
200 query = Query.new(:project => Project.find(1), :name => '_') 343 query = Query.new(:project => Project.find(1), :name => '_')
201 query.add_filter('due_date', 'w', ['']) 344 query.add_filter('due_date', 'w', [''])
202 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}" 345 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
203 I18n.locale = :en 346 I18n.locale = :en
204 end 347 end
205 348
206 def test_range_for_this_week_with_week_starting_on_sunday 349 def test_range_for_this_week_with_week_starting_on_sunday
207 I18n.locale = :en 350 I18n.locale = :en
208 assert_equal '7', I18n.t(:general_first_day_of_week) 351 assert_equal '7', I18n.t(:general_first_day_of_week)
209 352
210 Date.stubs(:today).returns(Date.parse('2011-04-29')) 353 Date.stubs(:today).returns(Date.parse('2011-04-29'))
211 354
212 query = Query.new(:project => Project.find(1), :name => '_') 355 query = Query.new(:project => Project.find(1), :name => '_')
213 query.add_filter('due_date', 'w', ['']) 356 query.add_filter('due_date', 'w', [''])
214 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}" 357 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
215 end 358 end
216 359
217 def test_operator_does_not_contains 360 def test_operator_does_not_contains
218 query = Query.new(:project => Project.find(1), :name => '_') 361 query = Query.new(:project => Project.find(1), :name => '_')
219 query.add_filter('subject', '!~', ['uNable']) 362 query.add_filter('subject', '!~', ['uNable'])
220 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") 363 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
221 find_issues_with_query(query) 364 find_issues_with_query(query)
222 end 365 end
223 366
367 def test_filter_assigned_to_me
368 user = User.find(2)
369 group = Group.find(10)
370 User.current = user
371 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
372 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
373 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
374 group.users << user
375
376 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
377 result = query.issues
378 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
379
380 assert result.include?(i1)
381 assert result.include?(i2)
382 assert !result.include?(i3)
383 end
384
224 def test_filter_watched_issues 385 def test_filter_watched_issues
225 User.current = User.find(1) 386 User.current = User.find(1)
226 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}}) 387 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
227 result = find_issues_with_query(query) 388 result = find_issues_with_query(query)
228 assert_not_nil result 389 assert_not_nil result
229 assert !result.empty? 390 assert !result.empty?
230 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id) 391 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
231 User.current = nil 392 User.current = nil
232 end 393 end
233 394
234 def test_filter_unwatched_issues 395 def test_filter_unwatched_issues
235 User.current = User.find(1) 396 User.current = User.find(1)
236 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}}) 397 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
237 result = find_issues_with_query(query) 398 result = find_issues_with_query(query)
238 assert_not_nil result 399 assert_not_nil result
239 assert !result.empty? 400 assert !result.empty?
240 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size) 401 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
241 User.current = nil 402 User.current = nil
242 end 403 end
243 404
244 def test_statement_should_be_nil_with_no_filters 405 def test_statement_should_be_nil_with_no_filters
245 q = Query.new(:name => '_') 406 q = Query.new(:name => '_')
246 q.filters = {} 407 q.filters = {}
247 408
248 assert q.valid? 409 assert q.valid?
249 assert_nil q.statement 410 assert_nil q.statement
250 end 411 end
251 412
252 def test_default_columns 413 def test_default_columns
253 q = Query.new 414 q = Query.new
254 assert !q.columns.empty? 415 assert !q.columns.empty?
255 end 416 end
256 417
257 def test_set_column_names 418 def test_set_column_names
258 q = Query.new 419 q = Query.new
259 q.column_names = ['tracker', :subject, '', 'unknonw_column'] 420 q.column_names = ['tracker', :subject, '', 'unknonw_column']
260 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name} 421 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
261 c = q.columns.first 422 c = q.columns.first
262 assert q.has_column?(c) 423 assert q.has_column?(c)
263 end 424 end
264 425
265 def test_groupable_columns_should_include_custom_fields 426 def test_groupable_columns_should_include_custom_fields
266 q = Query.new 427 q = Query.new
267 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn} 428 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
268 end 429 end
269 430
273 assert_not_nil q.group_by_column 434 assert_not_nil q.group_by_column
274 assert_equal :status, q.group_by_column.name 435 assert_equal :status, q.group_by_column.name
275 assert_not_nil q.group_by_statement 436 assert_not_nil q.group_by_statement
276 assert_equal 'status', q.group_by_statement 437 assert_equal 'status', q.group_by_statement
277 end 438 end
278 439
279 def test_grouped_with_invalid_column 440 def test_grouped_with_invalid_column
280 q = Query.new(:group_by => 'foo') 441 q = Query.new(:group_by => 'foo')
281 assert !q.grouped? 442 assert !q.grouped?
282 assert_nil q.group_by_column 443 assert_nil q.group_by_column
283 assert_nil q.group_by_statement 444 assert_nil q.group_by_statement
284 end 445 end
285 446
447 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
448 with_settings :user_format => 'lastname_coma_firstname' do
449 q = Query.new
450 assert q.sortable_columns.has_key?('assigned_to')
451 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
452 end
453 end
454
455 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
456 with_settings :user_format => 'lastname_coma_firstname' do
457 q = Query.new
458 assert q.sortable_columns.has_key?('author')
459 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
460 end
461 end
462
286 def test_default_sort 463 def test_default_sort
287 q = Query.new 464 q = Query.new
288 assert_equal [], q.sort_criteria 465 assert_equal [], q.sort_criteria
289 end 466 end
290 467
291 def test_set_sort_criteria_with_hash 468 def test_set_sort_criteria_with_hash
292 q = Query.new 469 q = Query.new
293 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} 470 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
294 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria 471 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
295 end 472 end
296 473
297 def test_set_sort_criteria_with_array 474 def test_set_sort_criteria_with_array
298 q = Query.new 475 q = Query.new
299 q.sort_criteria = [['priority', 'desc'], 'tracker'] 476 q.sort_criteria = [['priority', 'desc'], 'tracker']
300 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria 477 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
301 end 478 end
302 479
303 def test_create_query_with_sort 480 def test_create_query_with_sort
304 q = Query.new(:name => 'Sorted') 481 q = Query.new(:name => 'Sorted')
305 q.sort_criteria = [['priority', 'desc'], 'tracker'] 482 q.sort_criteria = [['priority', 'desc'], 'tracker']
306 assert q.save 483 assert q.save
307 q.reload 484 q.reload
308 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria 485 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
309 end 486 end
310 487
311 def test_sort_by_string_custom_field_asc 488 def test_sort_by_string_custom_field_asc
312 q = Query.new 489 q = Query.new
313 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } 490 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
314 assert c 491 assert c
315 assert c.sortable 492 assert c.sortable
316 issues = Issue.find :all, 493 issues = Issue.find :all,
317 :include => [ :assigned_to, :status, :tracker, :project, :priority ], 494 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
318 :conditions => q.statement, 495 :conditions => q.statement,
319 :order => "#{c.sortable} ASC" 496 :order => "#{c.sortable} ASC"
320 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} 497 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
321 assert !values.empty? 498 assert !values.empty?
322 assert_equal values.sort, values 499 assert_equal values.sort, values
323 end 500 end
324 501
325 def test_sort_by_string_custom_field_desc 502 def test_sort_by_string_custom_field_desc
326 q = Query.new 503 q = Query.new
327 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } 504 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
328 assert c 505 assert c
329 assert c.sortable 506 assert c.sortable
330 issues = Issue.find :all, 507 issues = Issue.find :all,
331 :include => [ :assigned_to, :status, :tracker, :project, :priority ], 508 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
332 :conditions => q.statement, 509 :conditions => q.statement,
333 :order => "#{c.sortable} DESC" 510 :order => "#{c.sortable} DESC"
334 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s} 511 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
335 assert !values.empty? 512 assert !values.empty?
336 assert_equal values.sort.reverse, values 513 assert_equal values.sort.reverse, values
337 end 514 end
338 515
339 def test_sort_by_float_custom_field_asc 516 def test_sort_by_float_custom_field_asc
340 q = Query.new 517 q = Query.new
341 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } 518 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
342 assert c 519 assert c
343 assert c.sortable 520 assert c.sortable
344 issues = Issue.find :all, 521 issues = Issue.find :all,
345 :include => [ :assigned_to, :status, :tracker, :project, :priority ], 522 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
346 :conditions => q.statement, 523 :conditions => q.statement,
347 :order => "#{c.sortable} ASC" 524 :order => "#{c.sortable} ASC"
348 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact 525 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
349 assert !values.empty? 526 assert !values.empty?
350 assert_equal values.sort, values 527 assert_equal values.sort, values
351 end 528 end
352 529
353 def test_invalid_query_should_raise_query_statement_invalid_error 530 def test_invalid_query_should_raise_query_statement_invalid_error
354 q = Query.new 531 q = Query.new
355 assert_raise Query::StatementInvalid do 532 assert_raise Query::StatementInvalid do
356 q.issues(:conditions => "foo = 1") 533 q.issues(:conditions => "foo = 1")
357 end 534 end
358 end 535 end
359 536
537 def test_issue_count
538 q = Query.new(:name => '_')
539 issue_count = q.issue_count
540 assert_equal q.issues.size, issue_count
541 end
542
543 def test_issue_count_with_archived_issues
544 p = Project.generate!( :status => Project::STATUS_ARCHIVED )
545 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
546 assert !i.visible?
547
548 test_issue_count
549 end
550
360 def test_issue_count_by_association_group 551 def test_issue_count_by_association_group
361 q = Query.new(:name => '_', :group_by => 'assigned_to') 552 q = Query.new(:name => '_', :group_by => 'assigned_to')
362 count_by_group = q.issue_count_by_group 553 count_by_group = q.issue_count_by_group
363 assert_kind_of Hash, count_by_group 554 assert_kind_of Hash, count_by_group
364 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort 555 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
372 assert_kind_of Hash, count_by_group 563 assert_kind_of Hash, count_by_group
373 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort 564 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
374 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq 565 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
375 assert count_by_group.has_key?('MySQL') 566 assert count_by_group.has_key?('MySQL')
376 end 567 end
377 568
378 def test_issue_count_by_date_custom_field_group 569 def test_issue_count_by_date_custom_field_group
379 q = Query.new(:name => '_', :group_by => 'cf_8') 570 q = Query.new(:name => '_', :group_by => 'cf_8')
380 count_by_group = q.issue_count_by_group 571 count_by_group = q.issue_count_by_group
381 assert_kind_of Hash, count_by_group 572 assert_kind_of Hash, count_by_group
382 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort 573 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
383 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq 574 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
384 end 575 end
385 576
386 def test_label_for 577 def test_label_for
387 q = Query.new 578 q = Query.new
388 assert_equal 'assigned_to', q.label_for('assigned_to_id') 579 assert_equal 'assigned_to', q.label_for('assigned_to_id')
389 end 580 end
390 581
391 def test_editable_by 582 def test_editable_by
392 admin = User.find(1) 583 admin = User.find(1)
393 manager = User.find(2) 584 manager = User.find(2)
394 developer = User.find(3) 585 developer = User.find(3)
395 586
396 # Public query on project 1 587 # Public query on project 1
397 q = Query.find(1) 588 q = Query.find(1)
398 assert q.editable_by?(admin) 589 assert q.editable_by?(admin)
399 assert q.editable_by?(manager) 590 assert q.editable_by?(manager)
400 assert !q.editable_by?(developer) 591 assert !q.editable_by?(developer)
416 assert q.editable_by?(admin) 607 assert q.editable_by?(admin)
417 assert !q.editable_by?(manager) 608 assert !q.editable_by?(manager)
418 assert !q.editable_by?(developer) 609 assert !q.editable_by?(developer)
419 end 610 end
420 611
612 def test_visible_scope
613 query_ids = Query.visible(User.anonymous).map(&:id)
614
615 assert query_ids.include?(1), 'public query on public project was not visible'
616 assert query_ids.include?(4), 'public query for all projects was not visible'
617 assert !query_ids.include?(2), 'private query on public project was visible'
618 assert !query_ids.include?(3), 'private query for all projects was visible'
619 assert !query_ids.include?(7), 'public query on private project was visible'
620 end
621
421 context "#available_filters" do 622 context "#available_filters" do
422 setup do 623 setup do
423 @query = Query.new(:name => "_") 624 @query = Query.new(:name => "_")
424 end 625 end
425 626
426 should "include users of visible projects in cross-project view" do 627 should "include users of visible projects in cross-project view" do
427 users = @query.available_filters["assigned_to_id"] 628 users = @query.available_filters["assigned_to_id"]
428 assert_not_nil users 629 assert_not_nil users
429 assert users[:values].map{|u|u[1]}.include?("3") 630 assert users[:values].map{|u|u[1]}.include?("3")
430 end 631 end
437 638
438 context "'member_of_group' filter" do 639 context "'member_of_group' filter" do
439 should "be present" do 640 should "be present" do
440 assert @query.available_filters.keys.include?("member_of_group") 641 assert @query.available_filters.keys.include?("member_of_group")
441 end 642 end
442 643
443 should "be an optional list" do 644 should "be an optional list" do
444 assert_equal :list_optional, @query.available_filters["member_of_group"][:type] 645 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
445 end 646 end
446 647
447 should "have a list of the groups as values" do 648 should "have a list of the groups as values" do
448 Group.destroy_all # No fixtures 649 Group.destroy_all # No fixtures
449 group1 = Group.generate!.reload 650 group1 = Group.generate!.reload
450 group2 = Group.generate!.reload 651 group2 = Group.generate!.reload
451 652
460 661
461 context "'assigned_to_role' filter" do 662 context "'assigned_to_role' filter" do
462 should "be present" do 663 should "be present" do
463 assert @query.available_filters.keys.include?("assigned_to_role") 664 assert @query.available_filters.keys.include?("assigned_to_role")
464 end 665 end
465 666
466 should "be an optional list" do 667 should "be an optional list" do
467 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type] 668 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
468 end 669 end
469 670
470 should "have a list of the Roles as values" do 671 should "have a list of the Roles as values" do
471 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) 672 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
472 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) 673 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
473 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) 674 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
474 end 675 end
488 Group.destroy_all # No fixtures 689 Group.destroy_all # No fixtures
489 @user_in_group = User.generate! 690 @user_in_group = User.generate!
490 @second_user_in_group = User.generate! 691 @second_user_in_group = User.generate!
491 @user_in_group2 = User.generate! 692 @user_in_group2 = User.generate!
492 @user_not_in_group = User.generate! 693 @user_not_in_group = User.generate!
493 694
494 @group = Group.generate!.reload 695 @group = Group.generate!.reload
495 @group.users << @user_in_group 696 @group.users << @user_in_group
496 @group.users << @second_user_in_group 697 @group.users << @second_user_in_group
497 698
498 @group2 = Group.generate!.reload 699 @group2 = Group.generate!.reload
499 @group2.users << @user_in_group2 700 @group2.users << @user_in_group2
500 701
501 end 702 end
502 703
503 should "search assigned to for users in the group" do 704 should "search assigned to for users in the group" do
504 @query = Query.new(:name => '_') 705 @query = Query.new(:name => '_')
505 @query.add_filter('member_of_group', '=', [@group.id.to_s]) 706 @query.add_filter('member_of_group', '=', [@group.id.to_s])
506 707
507 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')" 708 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
523 724
524 # Only users in a group 725 # Only users in a group
525 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" 726 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
526 assert_find_issues_with_query_is_successful @query 727 assert_find_issues_with_query_is_successful @query
527 end 728 end
528 729
529 should "return an empty set with = empty group" do 730 should "return an empty set with = empty group" do
530 @empty_group = Group.generate! 731 @empty_group = Group.generate!
531 @query = Query.new(:name => '_') 732 @query = Query.new(:name => '_')
532 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) 733 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
533 734
534 assert_equal [], find_issues_with_query(@query) 735 assert_equal [], find_issues_with_query(@query)
535 end 736 end
536 737
537 should "return issues with ! empty group" do 738 should "return issues with ! empty group" do
538 @empty_group = Group.generate! 739 @empty_group = Group.generate!
539 @query = Query.new(:name => '_') 740 @query = Query.new(:name => '_')
540 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) 741 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
541 742
542 assert_find_issues_with_query_is_successful @query 743 assert_find_issues_with_query_is_successful @query
543 end 744 end
544 end 745 end
545 746
546 context "with 'assigned_to_role' filter" do 747 context "with 'assigned_to_role' filter" do
547 setup do 748 setup do
548 # No fixtures 749 @manager_role = Role.find_by_name('Manager')
549 MemberRole.delete_all 750 @developer_role = Role.find_by_name('Developer')
550 Member.delete_all
551 Role.delete_all
552
553 @manager_role = Role.generate!(:name => 'Manager')
554 @developer_role = Role.generate!(:name => 'Developer')
555 751
556 @project = Project.generate! 752 @project = Project.generate!
557 @manager = User.generate! 753 @manager = User.generate!
558 @developer = User.generate! 754 @developer = User.generate!
559 @boss = User.generate! 755 @boss = User.generate!
756 @guest = User.generate!
560 User.add_to_project(@manager, @project, @manager_role) 757 User.add_to_project(@manager, @project, @manager_role)
561 User.add_to_project(@developer, @project, @developer_role) 758 User.add_to_project(@developer, @project, @developer_role)
562 User.add_to_project(@boss, @project, [@manager_role, @developer_role]) 759 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
563 end 760
564 761 @issue1 = Issue.generate_for_project!(@project, :assigned_to_id => @manager.id)
762 @issue2 = Issue.generate_for_project!(@project, :assigned_to_id => @developer.id)
763 @issue3 = Issue.generate_for_project!(@project, :assigned_to_id => @boss.id)
764 @issue4 = Issue.generate_for_project!(@project, :assigned_to_id => @guest.id)
765 @issue5 = Issue.generate_for_project!(@project)
766 end
767
565 should "search assigned to for users with the Role" do 768 should "search assigned to for users with the Role" do
566 @query = Query.new(:name => '_') 769 @query = Query.new(:name => '_', :project => @project)
567 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) 770 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
568 771
569 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')" 772 assert_query_result [@issue1, @issue3], @query
570 assert_find_issues_with_query_is_successful @query 773 end
571 end 774
572 775 should "search assigned to for users with the Role on the issue project" do
573 should "search assigned to for users not assigned to any Role (none)" do 776 other_project = Project.generate!
574 @query = Query.new(:name => '_') 777 User.add_to_project(@developer, other_project, @manager_role)
575 @query.add_filter('assigned_to_role', '!*', ['']) 778
576 779 @query = Query.new(:name => '_', :project => @project)
577 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')" 780 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
578 assert_find_issues_with_query_is_successful @query 781
579 end 782 assert_query_result [@issue1, @issue3], @query
580 783 end
581 should "search assigned to for users assigned to any Role (all)" do 784
582 @query = Query.new(:name => '_')
583 @query.add_filter('assigned_to_role', '*', [''])
584
585 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
586 assert_find_issues_with_query_is_successful @query
587 end
588
589 should "return an empty set with empty role" do 785 should "return an empty set with empty role" do
590 @empty_role = Role.generate! 786 @empty_role = Role.generate!
591 @query = Query.new(:name => '_') 787 @query = Query.new(:name => '_', :project => @project)
592 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) 788 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
593 789
594 assert_equal [], find_issues_with_query(@query) 790 assert_query_result [], @query
595 end 791 end
596 792
793 should "search assigned to for users without the Role" do
794 @query = Query.new(:name => '_', :project => @project)
795 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
796
797 assert_query_result [@issue2, @issue4, @issue5], @query
798 end
799
800 should "search assigned to for users not assigned to any Role (none)" do
801 @query = Query.new(:name => '_', :project => @project)
802 @query.add_filter('assigned_to_role', '!*', [''])
803
804 assert_query_result [@issue4, @issue5], @query
805 end
806
807 should "search assigned to for users assigned to any Role (all)" do
808 @query = Query.new(:name => '_', :project => @project)
809 @query.add_filter('assigned_to_role', '*', [''])
810
811 assert_query_result [@issue1, @issue2, @issue3], @query
812 end
813
597 should "return issues with ! empty role" do 814 should "return issues with ! empty role" do
598 @empty_role = Role.generate! 815 @empty_role = Role.generate!
599 @query = Query.new(:name => '_') 816 @query = Query.new(:name => '_', :project => @project)
600 @query.add_filter('member_of_group', '!', [@empty_role.id.to_s]) 817 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
601 818
602 assert_find_issues_with_query_is_successful @query 819 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
603 end 820 end
604 end 821 end
605 end 822 end
606 823
607 end 824 end