query_test.rb 90.3 KB
Newer Older
1
# frozen_string_literal: true
2

3
# Redmine - project management software
jplang's avatar
jplang committed
4
# Copyright (C) 2006-2017  Jean-Philippe Lang
5 6 7 8 9
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
10
#
11 12 13 14
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
15
#
16 17 18 19
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

20
require File.expand_path('../../test_helper', __FILE__)
21

22
class QueryTest < ActiveSupport::TestCase
23 24
  include Redmine::I18n

25 26 27 28
  fixtures :projects, :enabled_modules, :users, :members,
           :member_roles, :roles, :trackers, :issue_statuses,
           :issue_categories, :enumerations, :issues,
           :watchers, :custom_fields, :custom_values, :versions,
29
           :queries,
30
           :projects_trackers,
31
           :custom_fields_trackers,
32
           :workflows, :journals,
33
           :attachments
34

35 36
  INTEGER_KLASS = RUBY_VERSION >= "2.4" ? Integer : Fixnum

37 38 39 40
  def setup
    User.current = nil
  end

41 42 43 44
  def test_query_with_roles_visibility_should_validate_roles
    set_language_if_valid 'en'
    query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
    assert !query.save
45
    assert_include "Roles cannot be blank", query.errors.full_messages
46 47 48 49 50 51 52 53 54 55 56 57 58
    query.role_ids = [1, 2]
    assert query.save
  end

  def test_changing_roles_visibility_should_clear_roles
    query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
    assert_equal 2, query.roles.count

    query.visibility = IssueQuery::VISIBILITY_PUBLIC
    query.save!
    assert_equal 0, query.roles.count
  end

59
  def test_available_filters_should_be_ordered
60
    set_language_if_valid 'en'
61 62
    query = IssueQuery.new
    assert_equal 0, query.available_filters.keys.index('status_id')
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    expected_order = [
      "Status",
      "Project",
      "Tracker",
      "Priority"
    ]
    assert_equal expected_order,
                 (query.available_filters.values.map{|v| v[:name]} & expected_order)
  end

  def test_available_filters_with_custom_fields_should_be_ordered
    set_language_if_valid 'en'
    UserCustomField.create!(
              :name => 'order test', :field_format => 'string',
              :is_for_all => true, :is_filter => true
            )
    query = IssueQuery.new
    expected_order = [
      "Searchable field",
      "Database",
      "Project's Development status",
      "Author's order test",
      "Assignee's order test"
    ]
    assert_equal expected_order,
                 (query.available_filters.values.map{|v| v[:name]} & expected_order)
89 90
  end

91
  def test_custom_fields_for_all_projects_should_be_available_in_global_queries
92
    query = IssueQuery.new(:project => nil, :name => '_')
93 94 95
    assert query.available_filters.has_key?('cf_1')
    assert !query.available_filters.has_key?('cf_3')
  end
96

97 98
  def test_system_shared_versions_should_be_available_in_global_queries
    Version.find(2).update_attribute :sharing, 'system'
99
    query = IssueQuery.new(:project => nil, :name => '_')
100
    assert query.available_filters.has_key?('fixed_version_id')
101
    assert query.available_filters['fixed_version_id'][:values].detect {|v| v[1] == '2'}
102
  end
103

104
  def test_project_filter_in_global_queries
105
    query = IssueQuery.new(:project => nil, :name => '_')
106 107 108 109 110 111
    project_filter = query.available_filters["project_id"]
    assert_not_nil project_filter
    project_ids = project_filter[:values].map{|p| p[1]}
    assert project_ids.include?("1")  #public project
    assert !project_ids.include?("2") #private project user cannot see
  end
112

jplang's avatar
jplang committed
113 114 115 116 117 118 119 120 121 122 123
  def test_available_filters_should_not_include_fields_disabled_on_all_trackers
    Tracker.all.each do |tracker|
      tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
      tracker.save!
    end

    query = IssueQuery.new(:name => '_')
    assert_include 'due_date', query.available_filters
    assert_not_include 'start_date', query.available_filters
  end

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
  def test_filter_values_without_project_should_be_arrays
    q = IssueQuery.new
    assert_nil q.project

    q.available_filters.each do |name, filter|
      values = filter.values
      assert (values.nil? || values.is_a?(Array)),
        "#values for #{name} filter returned a #{values.class.name}"
    end
  end

  def test_filter_values_with_project_should_be_arrays
    q = IssueQuery.new(:project => Project.find(1))
    assert_not_nil q.project

    q.available_filters.each do |name, filter|
      values = filter.values
      assert (values.nil? || values.is_a?(Array)),
        "#values for #{name} filter returned a #{values.class.name}"
    end
  end

146
  def find_issues_with_query(query)
147
    Issue.joins(:status, :tracker, :project, :priority).where(
148
         query.statement
149
       ).to_a
150
  end
151

152 153 154 155 156 157 158
  def assert_find_issues_with_query_is_successful(query)
    assert_nothing_raised do
      find_issues_with_query(query)
    end
  end

  def assert_query_statement_includes(query, condition)
jplang's avatar
jplang committed
159
    assert_include condition, query.statement
160
  end
161

162 163 164 165 166 167
  def assert_query_result(expected, query)
    assert_nothing_raised do
      assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
      assert_equal expected.size, query.issue_count
    end
  end
168

169 170
  def test_query_should_allow_shared_versions_for_a_project_query
    subproject_version = Version.find(4)
171
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
172 173
    filter = query.available_filters["fixed_version_id"]
    assert_not_nil filter
174
    assert_include subproject_version.id.to_s, filter[:values].map(&:second)
175
  end
176

177
  def test_query_with_multiple_custom_fields
178
    query = IssueQuery.find(1)
179
    assert query.valid?
180
    issues = find_issues_with_query(query)
181 182 183
    assert_equal 1, issues.length
    assert_equal Issue.find(3), issues.first
  end
184

185
  def test_operator_none
186
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
187 188 189 190
    query.add_filter('fixed_version_id', '!*', [''])
    query.add_filter('cf_1', '!*', [''])
    assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
    assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
191
    find_issues_with_query(query)
192
  end
193

194
  def test_operator_none_for_integer
195
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
196 197 198 199 200 201
    query.add_filter('estimated_hours', '!*', [''])
    issues = find_issues_with_query(query)
    assert !issues.empty?
    assert issues.all? {|i| !i.estimated_hours}
  end

202
  def test_operator_none_for_date
203
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
204 205 206 207 208 209
    query.add_filter('start_date', '!*', [''])
    issues = find_issues_with_query(query)
    assert !issues.empty?
    assert issues.all? {|i| i.start_date.nil?}
  end

210
  def test_operator_none_for_string_custom_field
jplang's avatar
jplang committed
211
    CustomField.find(2).update_attribute :default_value, ""
212
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
213 214 215 216 217 218 219
    query.add_filter('cf_2', '!*', [''])
    assert query.has_filter?('cf_2')
    issues = find_issues_with_query(query)
    assert !issues.empty?
    assert issues.all? {|i| i.custom_field_value(2).blank?}
  end

220 221 222 223 224 225 226 227 228 229 230 231
  def test_operator_none_for_text
    query = IssueQuery.new(:name => '_')
    query.add_filter('status_id', '*', [''])
    query.add_filter('description', '!*', [''])
    assert query.has_filter?('description')
    issues = find_issues_with_query(query)

    assert issues.any?
    assert issues.all? {|i| i.description.blank?}
    assert_equal [11, 12], issues.map(&:id).sort
  end

232
  def test_operator_all
233
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
234 235 236 237
    query.add_filter('fixed_version_id', '*', [''])
    query.add_filter('cf_1', '*', [''])
    assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
    assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
238
    find_issues_with_query(query)
239
  end
240

241
  def test_operator_all_for_date
242
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
243 244 245 246 247 248
    query.add_filter('start_date', '*', [''])
    issues = find_issues_with_query(query)
    assert !issues.empty?
    assert issues.all? {|i| i.start_date.present?}
  end

249
  def test_operator_all_for_string_custom_field
250
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
251 252 253 254 255 256 257
    query.add_filter('cf_2', '*', [''])
    assert query.has_filter?('cf_2')
    issues = find_issues_with_query(query)
    assert !issues.empty?
    assert issues.all? {|i| i.custom_field_value(2).present?}
  end

258
  def test_numeric_filter_should_not_accept_non_numeric_values
259
    query = IssueQuery.new(:name => '_')
260
    query.add_filter('estimated_hours', '=', ['a'])
261

262 263 264
    assert query.has_filter?('estimated_hours')
    assert !query.valid?
  end
265

jplang's avatar
jplang committed
266
  def test_operator_is_on_float
267
    Issue.where(:id => 2).update_all("estimated_hours = 171.2")
268
    query = IssueQuery.new(:name => '_')
jplang's avatar
jplang committed
269 270 271 272 273
    query.add_filter('estimated_hours', '=', ['171.20'])
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
  end
274

275
  def test_operator_is_on_issue_id_should_accept_comma_separated_values
jplang's avatar
jplang committed
276 277 278 279 280 281 282
    query = IssueQuery.new(:name => '_')
    query.add_filter("issue_id", '=', ['1,3'])
    issues = find_issues_with_query(query)
    assert_equal 2, issues.size
    assert_equal [1,3], issues.map(&:id).sort
  end

283 284 285 286 287 288 289 290 291 292
  def test_operator_is_on_parent_id_should_accept_comma_separated_values
    Issue.where(:id => [2,4]).update_all(:parent_id => 1)
    Issue.where(:id => 5).update_all(:parent_id => 3)
    query = IssueQuery.new(:name => '_')
    query.add_filter("parent_id", '=', ['1,3'])
    issues = find_issues_with_query(query)
    assert_equal 3, issues.size
    assert_equal [2,4,5], issues.map(&:id).sort
  end

293 294 295 296 297 298 299 300 301 302
  def test_operator_is_on_child_id_should_accept_comma_separated_values
    Issue.where(:id => [2,4]).update_all(:parent_id => 1)
    Issue.where(:id => 5).update_all(:parent_id => 3)
    query = IssueQuery.new(:name => '_')
    query.add_filter("child_id", '=', ['2,4,5'])
    issues = find_issues_with_query(query)
    assert_equal 2, issues.size
    assert_equal [1,3], issues.map(&:id).sort
  end

303 304 305 306 307 308 309 310
  def test_operator_between_on_issue_id_should_return_range
    query = IssueQuery.new(:name => '_')
    query.add_filter("issue_id", '><', ['2','3'])
    issues = find_issues_with_query(query)
    assert_equal 2, issues.size
    assert_equal [2,3], issues.map(&:id).sort
  end

311
  def test_operator_is_on_integer_custom_field
312
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
313 314 315 316
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

317
    query = IssueQuery.new(:name => '_')
318 319 320 321 322 323
    query.add_filter("cf_#{f.id}", '=', ['12'])
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
  end

324
  def test_operator_is_on_integer_custom_field_should_accept_negative_value
325
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
326 327 328 329
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

330
    query = IssueQuery.new(:name => '_')
331 332 333 334 335 336 337
    query.add_filter("cf_#{f.id}", '=', ['-12'])
    assert query.valid?
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
  end

338
  def test_operator_is_on_float_custom_field
339
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
340 341 342 343
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

344
    query = IssueQuery.new(:name => '_')
345 346 347 348 349 350
    query.add_filter("cf_#{f.id}", '=', ['12.7'])
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
  end

351
  def test_operator_is_on_float_custom_field_should_accept_negative_value
352
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
353 354 355 356
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

357
    query = IssueQuery.new(:name => '_')
358 359 360 361 362 363 364
    query.add_filter("cf_#{f.id}", '=', ['-12.7'])
    assert query.valid?
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
  end

365 366
  def test_operator_is_on_multi_list_custom_field
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
367
      :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
368 369 370 371
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')

372
    query = IssueQuery.new(:name => '_')
373 374 375 376
    query.add_filter("cf_#{f.id}", '=', ['value1'])
    issues = find_issues_with_query(query)
    assert_equal [1, 3], issues.map(&:id).sort

377
    query = IssueQuery.new(:name => '_')
378 379 380 381 382 383 384
    query.add_filter("cf_#{f.id}", '=', ['value2'])
    issues = find_issues_with_query(query)
    assert_equal [1], issues.map(&:id).sort
  end

  def test_operator_is_not_on_multi_list_custom_field
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
385
      :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
386 387 388 389
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')

390
    query = IssueQuery.new(:name => '_')
391 392 393 394 395
    query.add_filter("cf_#{f.id}", '!', ['value1'])
    issues = find_issues_with_query(query)
    assert !issues.map(&:id).include?(1)
    assert !issues.map(&:id).include?(3)

396
    query = IssueQuery.new(:name => '_')
397 398 399 400 401 402
    query.add_filter("cf_#{f.id}", '!', ['value2'])
    issues = find_issues_with_query(query)
    assert !issues.map(&:id).include?(1)
    assert issues.map(&:id).include?(3)
  end

403 404 405 406 407 408 409 410 411 412
  def test_operator_is_on_string_custom_field_with_utf8_value
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'string', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'Kiểm')

    query = IssueQuery.new(:name => '_')
    query.add_filter("cf_#{f.id}", '=', ['Kiểm'])
    issues = find_issues_with_query(query)
    assert_equal [1], issues.map(&:id).sort
  end

413 414 415 416
  def test_operator_is_on_is_private_field
    # is_private filter only available for those who can set issues private
    User.current = User.find(2)

417
    query = IssueQuery.new(:name => '_')
418 419 420 421 422 423 424 425 426 427 428 429 430 431
    assert query.available_filters.key?('is_private')

    query.add_filter("is_private", '=', ['1'])
    issues = find_issues_with_query(query)
    assert issues.any?
    assert_nil issues.detect {|issue| !issue.is_private?}
  ensure
    User.current = nil
  end

  def test_operator_is_not_on_is_private_field
    # is_private filter only available for those who can set issues private
    User.current = User.find(2)

432
    query = IssueQuery.new(:name => '_')
433 434 435 436 437 438 439 440 441 442
    assert query.available_filters.key?('is_private')

    query.add_filter("is_private", '!', ['1'])
    issues = find_issues_with_query(query)
    assert issues.any?
    assert_nil issues.detect {|issue| issue.is_private?}
  ensure
    User.current = nil
  end

443
  def test_operator_greater_than
444
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
445
    query.add_filter('done_ratio', '>=', ['40'])
jplang's avatar
jplang committed
446 447 448
    assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
    find_issues_with_query(query)
  end
449

jplang's avatar
jplang committed
450
  def test_operator_greater_than_a_float
451
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
jplang's avatar
jplang committed
452 453
    query.add_filter('estimated_hours', '>=', ['40.5'])
    assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
454
    find_issues_with_query(query)
455
  end
456

457
  def test_operator_greater_than_on_int_custom_field
458
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
459 460 461 462
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

463
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
464 465 466 467
    query.add_filter("cf_#{f.id}", '>=', ['8'])
    issues = find_issues_with_query(query)
    assert_equal 1, issues.size
    assert_equal 2, issues.first.id
468
  end
469

470
  def test_operator_lesser_than
471
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
472
    query.add_filter('done_ratio', '<=', ['30'])
jplang's avatar
jplang committed
473
    assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
474 475
    find_issues_with_query(query)
  end
476

477 478
  def test_operator_lesser_than_on_custom_field
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
479
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
480
    query.add_filter("cf_#{f.id}", '<=', ['30'])
481
    assert_match /CAST.+ <= 30\.0/, query.statement
482 483
    find_issues_with_query(query)
  end
484

485
  def test_operator_lesser_than_on_date_custom_field
486
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
487 488 489 490 491 492 493 494 495 496 497 498
    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')

    query = IssueQuery.new(:project => Project.find(1), :name => '_')
    query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
    issue_ids = find_issues_with_query(query).map(&:id)
    assert_include 1, issue_ids
    assert_not_include 2, issue_ids
    assert_not_include 3, issue_ids
  end

499
  def test_operator_between
500
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
501
    query.add_filter('done_ratio', '><', ['30', '40'])
jplang's avatar
jplang committed
502
    assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
503 504
    find_issues_with_query(query)
  end
505

506 507
  def test_operator_between_on_custom_field
    f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
508
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
509
    query.add_filter("cf_#{f.id}", '><', ['30', '40'])
510
    assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
511 512
    find_issues_with_query(query)
  end
513

jplang's avatar
jplang committed
514
  def test_date_filter_should_not_accept_non_date_values
515
    query = IssueQuery.new(:name => '_')
jplang's avatar
jplang committed
516
    query.add_filter('created_on', '=', ['a'])
517

jplang's avatar
jplang committed
518 519 520
    assert query.has_filter?('created_on')
    assert !query.valid?
  end
521

jplang's avatar
jplang committed
522
  def test_date_filter_should_not_accept_invalid_date_values
523
    query = IssueQuery.new(:name => '_')
jplang's avatar
jplang committed
524
    query.add_filter('created_on', '=', ['2011-01-34'])
525

jplang's avatar
jplang committed
526 527 528
    assert query.has_filter?('created_on')
    assert !query.valid?
  end
529

jplang's avatar
jplang committed
530
  def test_relative_date_filter_should_not_accept_non_integer_values
531
    query = IssueQuery.new(:name => '_')
jplang's avatar
jplang committed
532
    query.add_filter('created_on', '>t-', ['a'])
533

jplang's avatar
jplang committed
534 535 536
    assert query.has_filter?('created_on')
    assert !query.valid?
  end
537

538
  def test_operator_date_equals
539
    query = IssueQuery.new(:name => '_')
540
    query.add_filter('due_date', '=', ['2011-07-10'])
541 542
    assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/,
      query.statement
543 544 545 546
    find_issues_with_query(query)
  end

  def test_operator_date_lesser_than
547
    query = IssueQuery.new(:name => '_')
548
    query.add_filter('due_date', '<=', ['2011-07-10'])
549
    assert_match /issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/, query.statement
550 551 552
    find_issues_with_query(query)
  end

553 554 555
  def test_operator_date_lesser_than_with_timestamp
    query = IssueQuery.new(:name => '_')
    query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
556
    assert_match /issues\.updated_on <= '#{quoted_date "2011-07-10"} 19:13:52/, query.statement
557 558 559
    find_issues_with_query(query)
  end

560
  def test_operator_date_greater_than
561
    query = IssueQuery.new(:name => '_')
562
    query.add_filter('due_date', '>=', ['2011-07-10'])
563
    assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?'/, query.statement
564 565 566
    find_issues_with_query(query)
  end

567 568 569
  def test_operator_date_greater_than_with_timestamp
    query = IssueQuery.new(:name => '_')
    query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
570
    assert_match /issues\.updated_on > '#{quoted_date "2011-07-10"} 19:13:51(\.0+)?'/, query.statement
571 572 573
    find_issues_with_query(query)
  end

574
  def test_operator_date_between
575
    query = IssueQuery.new(:name => '_')
576
    query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
577 578
    assert_match /issues\.due_date > '#{quoted_date "2011-06-22"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?'/,
      query.statement
579 580 581
    find_issues_with_query(query)
  end

582
  def test_operator_in_more_than
583
    Issue.find(7).update_attribute(:due_date, (Date.today + 15))
584
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
585
    query.add_filter('due_date', '>t+', ['15'])
586 587 588
    issues = find_issues_with_query(query)
    assert !issues.empty?
    issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
589 590 591
  end

  def test_operator_in_less_than
592
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
593
    query.add_filter('due_date', '<t+', ['15'])
594 595
    issues = find_issues_with_query(query)
    assert !issues.empty?
596 597 598 599
    issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
  end

  def test_operator_in_the_next_days
600
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
601 602 603
    query.add_filter('due_date', '><t+', ['15'])
    issues = find_issues_with_query(query)
    assert !issues.empty?
604 605
    issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
  end
606

607 608
  def test_operator_less_than_ago
    Issue.find(7).update_attribute(:due_date, (Date.today - 3))
609
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
610 611 612
    query.add_filter('due_date', '>t-', ['3'])
    issues = find_issues_with_query(query)
    assert !issues.empty?
613 614 615 616 617
    issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
  end

  def test_operator_in_the_past_days
    Issue.find(7).update_attribute(:due_date, (Date.today - 3))
618
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
619 620 621
    query.add_filter('due_date', '><t-', ['3'])
    issues = find_issues_with_query(query)
    assert !issues.empty?
622 623
    issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
  end
624

625 626
  def test_operator_more_than_ago
    Issue.find(7).update_attribute(:due_date, (Date.today - 10))
627
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
628 629 630 631 632 633 634 635 636
    query.add_filter('due_date', '<t-', ['10'])
    assert query.statement.include?("#{Issue.table_name}.due_date <=")
    issues = find_issues_with_query(query)
    assert !issues.empty?
    issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
  end

  def test_operator_in
    Issue.find(7).update_attribute(:due_date, (Date.today + 2))
637
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
638 639 640 641 642 643 644 645
    query.add_filter('due_date', 't+', ['2'])
    issues = find_issues_with_query(query)
    assert !issues.empty?
    issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
  end

  def test_operator_ago
    Issue.find(7).update_attribute(:due_date, (Date.today - 3))
646
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
647 648 649 650
    query.add_filter('due_date', 't-', ['3'])
    issues = find_issues_with_query(query)
    assert !issues.empty?
    issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
651 652 653
  end

  def test_operator_today
654
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
655
    query.add_filter('due_date', 't', [''])
656 657 658
    issues = find_issues_with_query(query)
    assert !issues.empty?
    issues.each {|issue| assert_equal Date.today, issue.due_date}
659 660
  end

661
  def test_operator_tomorrow
maeda's avatar
maeda committed
662 663 664 665
    issue = Issue.generate!(:due_date => User.current.today.tomorrow)
    other_issues = []
    other_issues << Issue.generate!(:due_date => User.current.today.yesterday)
    other_issues << Issue.generate!(:due_date => User.current.today + 2)
666 667 668
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
    query.add_filter('due_date', 'nd', [''])
    issues = find_issues_with_query(query)
maeda's avatar
maeda committed
669 670
    assert_include issue, issues
    other_issues.each {|i| assert_not_include i, issues }
671 672
  end

jplang's avatar
jplang committed
673
  def test_operator_date_periods
674
    %w(t ld w lw l2w m lm y nd nw nm).each do |operator|
jplang's avatar
jplang committed
675 676 677 678 679
      query = IssueQuery.new(:name => '_')
      query.add_filter('due_date', operator, [''])
      assert query.valid?
      assert query.issues
    end
680 681
  end

jplang's avatar
jplang committed
682 683 684 685 686 687 688
  def test_operator_datetime_periods
    %w(t ld w lw l2w m lm y).each do |operator|
      query = IssueQuery.new(:name => '_')
      query.add_filter('created_on', operator, [''])
      assert query.valid?
      assert query.issues
    end
689 690 691
  end

  def test_operator_contains
692 693 694 695 696 697 698 699 700
    issue = Issue.generate!(:subject => 'AbCdEfG')

    query = IssueQuery.new(:name => '_')
    query.add_filter('subject', '~', ['cdeF'])
    result = find_issues_with_query(query)
    assert_include issue, result
    result.each {|issue| assert issue.subject.downcase.include?('cdef') }
  end

701 702 703 704 705 706 707 708 709 710
  def test_operator_contains_with_utf8_string
    issue = Issue.generate!(:subject => 'Subject contains Kiểm')

    query = IssueQuery.new(:name => '_')
    query.add_filter('subject', '~', ['Kiểm'])
    result = find_issues_with_query(query)
    assert_include issue, result
    assert_equal 1, result.size
  end

711 712 713 714 715
  def test_operator_does_not_contain
    issue = Issue.generate!(:subject => 'AbCdEfG')

    query = IssueQuery.new(:name => '_')
    query.add_filter('subject', '!~', ['cdeF'])
716
    result = find_issues_with_query(query)
717
    assert_not_include issue, result
718
  end
719

720 721 722
  def test_range_for_this_week_with_week_starting_on_monday
    I18n.locale = :fr
    assert_equal '1', I18n.t(:general_first_day_of_week)
723

724
    Date.stubs(:today).returns(Date.parse('2011-04-29'))
725

726
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
727
    query.add_filter('due_date', 'w', [''])
728 729
    assert_match /issues\.due_date > '#{quoted_date "2011-04-24"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-01"} 23:59:59(\.\d+)?/,
      query.statement
730 731
    I18n.locale = :en
  end
732

733 734 735
  def test_range_for_this_week_with_week_starting_on_sunday
    I18n.locale = :en
    assert_equal '7', I18n.t(:general_first_day_of_week)
736

737
    Date.stubs(:today).returns(Date.parse('2011-04-29'))
738

739
    query = IssueQuery.new(:project => Project.find(1), :name => '_')
740
    query.add_filter('due_date', 'w', [''])
741 742
    assert_match /issues\.due_date > '#{quoted_date "2011-04-23"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?/,
      query.statement
743
  end
744

745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
  def test_range_for_next_week_with_week_starting_on_monday
    I18n.locale = :fr
    assert_equal '1', I18n.t(:general_first_day_of_week)

    Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday

    query = IssueQuery.new(:project => Project.find(1), :name => '_')
    query.add_filter('due_date', 'nw', [''])
    assert_match /issues\.due_date > '#{quoted_date "2011-05-01"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-08"} 23:59:59(\.\d+)?/,
      query.statement
    I18n.locale = :en
  end

  def test_range_for_next_week_with_week_starting_on_sunday
    I18n.locale = :en
    assert_equal '7', I18n.t(:general_first_day_of_week)

    Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday

    query = IssueQuery.new(:project => Project.find(1), :name => '_')
    query.add_filter('due_date', 'nw', [''])
    assert_match /issues\.due_date > '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-07"} 23:59:59(\.\d+)?/,
      query.statement
  end

  def test_range_for_next_month
    Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday

    query = IssueQuery.new(:project => Project.find(1), :name => '_')
    query.add_filter('due_date', 'nm', [''])
    assert_match /issues\.due_date > '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-31"} 23:59:59(\.\d+)?/,
      query.statement
  end

779 780 781 782
  def test_filter_assigned_to_me
    user = User.find(2)
    group = Group.find(10)
    group.users << user
783 784 785 786
    other_group = Group.find(11)
    Member.create!(:project_id => 1, :principal => group, :role_ids => [1])
    Member.create!(:project_id => 1, :principal => other_group, :role_ids => [1])
    User.current = user
787

788
    with_settings :issue_group_assignment => '1' do
789 790 791
      i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
      i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
      i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => other_group)
792

793 794 795
      query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
      result = query.issues
      assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
796

797 798 799 800
      assert result.include?(i1)
      assert result.include?(i2)
      assert !result.include?(i3)
    end
801
  end
802

803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
  def test_filter_updated_by
    user = User.generate!
    Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
    Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
    Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')

    query = IssueQuery.new(:name => '_')
    filter_name = "updated_by"
    assert_include filter_name, query.available_filters.keys

    query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
    assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort

    query.filters = {filter_name => {:operator => '!', :values => [user.id]}}
    assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort
  end

  def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible
    user = User.generate!
    Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
    Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')

    query = IssueQuery.new(:name => '_')
    filter_name = "updated_by"
    assert_include filter_name, query.available_filters.keys

    with_current_user User.anonymous do
      query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
      assert_equal [3], find_issues_with_query(query).map(&:id).sort
    end
  end

  def test_filter_updated_by_me
    user = User.generate!
    Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')

    with_current_user user do
      query = IssueQuery.new(:name => '_')
      filter_name = "updated_by"
      assert_include filter_name, query.available_filters.keys
843

844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
      query.filters = {filter_name => {:operator => '=', :values => ['me']}}
      assert_equal [2], find_issues_with_query(query).map(&:id).sort
    end
  end

  def test_filter_last_updated_by
    user = User.generate!
    Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
    Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
    Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')

    query = IssueQuery.new(:name => '_')
    filter_name = "last_updated_by"
    assert_include filter_name, query.available_filters.keys

    query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
    assert_equal [2], find_issues_with_query(query).map(&:id).sort
  end

  def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible
    user1 = User.generate!
    user2 = User.generate!
    Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes')
    Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)

    query = IssueQuery.new(:name => '_')
    filter_name = "last_updated_by"
    assert_include filter_name, query.available_filters.keys

    with_current_user User.anonymous do
      query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
      assert_equal [2], find_issues_with_query(query).map(&:id).sort

      query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
      assert_equal [], find_issues_with_query(query).map(&:id).sort
    end

    with_current_user User.find(2) do
      query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
      assert_equal [], find_issues_with_query(query).map(&:id).sort

      query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
      assert_equal [2], find_issues_with_query(query).map(&:id).sort
    end
  end

890 891 892 893 894 895
  def test_user_custom_field_filtered_on_me
    User.current = User.find(2)
    cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
    issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
    issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})

896
    query = IssueQuery.new(:name => '_', :project => Project.find(1))
897 898 899 900 901 902 903
    filter = query.available_filters["cf_#{cf.id}"]
    assert_not_nil filter
    assert_include 'me', filter[:values].map{|v| v[1]}

    query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
    result = query.issues
    assert_equal 1, result.size
jplang's avatar
jplang committed
904
    assert_equal issue1, result.first
905 906
  end

jplang's avatar
jplang committed
907 908 909 910 911 912
  def test_filter_on_me_by_anonymous_user
    User.current = nil
    query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
    assert_equal [], query.issues
  end

913 914
  def test_filter_my_projects
    User.current = User.find(2)
915
    query = IssueQuery.new(:name => '_')
916 917 918 919 920 921 922 923 924
    filter = query.available_filters['project_id']
    assert_not_nil filter
    assert_include 'mine', filter[:values].map{|v| v[1]}

    query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
    result = query.issues
    assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
  end

925 926
  def test_filter_watched_issues
    User.current = User.find(1)
927
    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
928 929 930 931 932 933
    result = find_issues_with_query(query)
    assert_not_nil result
    assert !result.empty?
    assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
    User.current = nil
  end
934

935 936
  def test_filter_unwatched_issues
    User.current = User.find(1)
937
    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
938 939 940 941 942 943
    result = find_issues_with_query(query)
    assert_not_nil result
    assert !result.empty?
    assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
    User.current = nil
  end
944

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
  def test_filter_on_watched_issues_with_view_issue_watchers_permission
    User.current = User.find(1)
    User.current.admin = true
    assert User.current.allowed_to?(:view_issue_watchers, Project.find(1))

    Issue.find(1).add_watcher User.current
    Issue.find(3).add_watcher User.find(3)
    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
    result = find_issues_with_query(query)
    assert_includes result, Issue.find(1)
    assert_includes result, Issue.find(3)
  ensure
    User.current.reload
    User.current = nil
  end

  def test_filter_on_watched_issues_without_view_issue_watchers_permission
    User.current = User.find(1)
    User.current.admin = false
    assert !User.current.allowed_to?(:view_issue_watchers, Project.find(1))

    Issue.find(1).add_watcher User.current
    Issue.find(3).add_watcher User.find(3)
    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
    result = find_issues_with_query(query)
    assert_includes result, Issue.find(1)
    assert_not_includes result, Issue.find(3)
  ensure
    User.current.reload
    User.current = nil
  end

977
  def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
jplang's avatar
jplang committed
978
    field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
    Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
    Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})

    query = IssueQuery.new(:name => '_', :project => Project.find(1))
    query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
    assert_equal 2, find_issues_with_query(query).size

    field.project_ids = [1, 3] # Disable the field for project 4
    field.save!
    assert_equal 1, find_issues_with_query(query).size
  end

  def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
    field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
    Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
    Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})

    query = IssueQuery.new(:name => '_', :project => Project.find(1))
    query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
    assert_equal 2, find_issues_with_query(query).size

    field.tracker_ids = [1] # Disable the field for tracker 2
    field.save!
    assert_equal 1, find_issues_with_query(query).size
  end

1005 1006 1007 1008 1009
  def test_filter_on_project_custom_field
    field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
    CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')

1010
    query = IssueQuery.new(:name => '_')
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
    filter_name = "project.cf_#{field.id}"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
    assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
  end

  def test_filter_on_author_custom_field
    field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')

1021
    query = IssueQuery.new(:name => '_')
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
    filter_name = "author.cf_#{field.id}"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
    assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
  end

  def test_filter_on_assigned_to_custom_field
    field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')

1032
    query = IssueQuery.new(:name => '_')
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    filter_name = "assigned_to.cf_#{field.id}"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
    assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
  end

  def test_filter_on_fixed_version_custom_field
    field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')

1043
    query = IssueQuery.new(:name => '_')
1044 1045 1046 1047 1048 1049
    filter_name = "fixed_version.cf_#{field.id}"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
    assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
  end

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
  def test_filter_on_fixed_version_due_date
    query = IssueQuery.new(:name => '_')
    filter_name = "fixed_version.due_date"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => [20.day.from_now.to_date.to_s(:db)]}}
    issues = find_issues_with_query(query)
    assert_equal [2], issues.map(&:fixed_version_id).uniq.sort
    assert_equal [2, 12], issues.map(&:id).sort

    query = IssueQuery.new(:name => '_')
    query.filters = {filter_name => {:operator => '>=', :values => [21.day.from_now.to_date.to_s(:db)]}}
    assert_equal 0, find_issues_with_query(query).size
  end

  def test_filter_on_fixed_version_status
    query = IssueQuery.new(:name => '_')
    filter_name = "fixed_version.status"
    assert_include filter_name, query.available_filters.keys
    query.filters = {filter_name => {:operator => '=', :values => ['closed']}}
    issues = find_issues_with_query(query)

    assert_equal [1], issues.map(&:fixed_version_id).sort
    assert_equal [11], issues.map(&:id).sort

    # "is not" operator should include issues without target version
    query = IssueQuery.new(:name => '_')
    query.filters = {filter_name => {:operator => '!', :values => ['open', 'closed', 'locked']}, "project_id" => {:operator => '=', :values => [1]}}
    assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort
  end

1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
  def test_filter_on_version_custom_field
    field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
    issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => '2'})

    query = IssueQuery.new(:name => '_')
    filter_name = "cf_#{field.id}"
    assert_include filter_name, query.available_filters.keys

    query.filters = {filter_name => {:operator => '=', :values => ['2']}}
    issues = find_issues_with_query(query)
    assert_equal [issue.id], issues.map(&:id).sort
  end

  def test_filter_on_attribute_of_version_custom_field
    field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
    version = Version.generate!(:effective_date => '2017-01-14')
    issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})

    query = IssueQuery.new(:name => '_')
    filter_name = "cf_#{field.id}.due_date"
    assert_include filter_name, query.available_filters.keys

    query.filters = {filter_name => {:operator => '=', :values => ['2017-01-14']}}
    issues = find_issues_with_query(query)
    assert_equal [issue.id], issues.map(&:id).sort
  end

  def test_filter_on_custom_field_of_version_custom_field
    field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
    attr = VersionCustomField.generate!(:field_format => 'string', :is_filter => true)

    version = Version.generate!(:custom_field_values => {attr.id.to_s => 'ABC'})
    issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})

    query = IssueQuery.new(:name => '_')
    filter_name = "cf_#{field.id}.cf_#{attr.id}"
    assert_include filter_name, query.available_filters.keys

    query.filters = {filter_name => {:operator => '=', :values => ['ABC']}}
    issues = find_issues_with_query(query)
    assert_equal [issue.id], issues.map(&:id).sort
  end

1123 1124 1125 1126 1127
  def test_filter_on_relations_with_a_specific_issue
    IssueRelation.delete_all
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))

1128
    query = IssueQuery.new(:name => '_')
1129 1130 1131
    query.filters = {"relates" => {:operator => '=', :values => ['1']}}
    assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort

1132
    query = IssueQuery.new(:name => '_')
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
    query.filters = {"relates" => {:operator => '=', :values => ['2']}}
    assert_equal [1], find_issues_with_query(query).map(&:id).sort
  end

  def test_filter_on_relations_with_any_issues_in_a_project
    IssueRelation.delete_all
    with_settings :cross_project_issue_relations => '1' do
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
    end

1145
    query = IssueQuery.new(:name => '_')
1146 1147 1148
    query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
    assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort

1149
    query = IssueQuery.new(:name => '_')
1150 1151 1152
    query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
    assert_equal [1], find_issues_with_query(query).map(&:id).sort

1153
    query = IssueQuery.new(:name => '_')
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
    query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
    assert_equal [], find_issues_with_query(query).map(&:id).sort
  end

  def test_filter_on_relations_with_any_issues_not_in_a_project
    IssueRelation.delete_all
    with_settings :cross_project_issue_relations => '1' do
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
      #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
    end

1166
    query = IssueQuery.new(:name => '_')
1167 1168 1169 1170
    query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
    assert_equal [1], find_issues_with_query(query).map(&:id).sort
  end

1171 1172 1173 1174 1175 1176 1177 1178
  def test_filter_on_relations_with_no_issues_in_a_project
    IssueRelation.delete_all
    with_settings :cross_project_issue_relations => '1' do
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
      IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
      IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
    end

1179
    query = IssueQuery.new(:name => '_')
1180 1181 1182 1183 1184 1185 1186
    query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
    ids = find_issues_with_query(query).map(&:id).sort
    assert_include 2, ids
    assert_not_include 1, ids
    assert_not_include 3, ids
  end

1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
  def test_filter_on_relations_with_any_open_issues
    IssueRelation.delete_all
    # Issue 1 is blocked by 8, which is closed
    IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
    # Issue 2 is blocked by 3, which is open
    IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))

    query = IssueQuery.new(:name => '_')
    query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
    ids = find_issues_with_query(query).map(&:id)
    assert_equal [], ids & [1]
    assert_include 2, ids
  end

1201
  def test_filter_on_blocked_by_no_open_issues
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
    IssueRelation.delete_all
    # Issue 1 is blocked by 8, which is closed
    IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
    # Issue 2 is blocked by 3, which is open
    IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))

    query = IssueQuery.new(:name => '_')
    query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
    ids = find_issues_with_query(query).map(&:id)
    assert_equal [], ids & [2]
    assert_include 1, ids
  end

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
  def test_filter_on_related_with_no_open_issues
    IssueRelation.delete_all
    # Issue 1 is blocked by 8, which is closed
    IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(1), issue_to: Issue.find(8))
    # Issue 2 is blocked by 3, which is open
    IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(2), issue_to: Issue.find(3))

    query = IssueQuery.new(:name => '_')
    query.filters = { 'relates' => { operator: '!o', values: [''] } }
    ids = find_issues_with_query(query).map(&:id)
    assert_equal [], ids & [2]
    assert_include 1, ids
  end

1229 1230 1231 1232 1233
  def test_filter_on_relations_with_no_issues
    IssueRelation.delete_all
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))

1234
    query = IssueQuery.new(:name => '_')
1235 1236 1237 1238 1239 1240
    query.filters = {"relates" => {:operator => '!*', :values => ['']}}
    ids = find_issues_with_query(query).map(&:id)
    assert_equal [], ids & [1, 2, 3]
    assert_include 4, ids
  end

jplang's avatar
jplang committed
1241
  def test_filter_on_relations_with_any_issues
1242 1243 1244 1245
    IssueRelation.delete_all
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
    IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))

1246
    query = IssueQuery.new(:name => '_')
1247
    query.filters = {"relates" => {:operator => '*', :values => ['']}}
jplang's avatar
jplang committed
1248
    assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
1249 1250
  end

1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265
  def test_filter_on_relations_should_not_ignore_other_filter
    issue = Issue.generate!
    issue1 = Issue.generate!(:status_id => 1)
    issue2 = Issue.generate!(:status_id => 2)
    IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
    IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)

    query = IssueQuery.new(:name => '_')
    query.filters = {
      "status_id" => {:operator => '=', :values => ['1']},
      "relates" => {:operator => '=', :values => [issue.id.to_s]}
    }
    assert_equal [issue1], find_issues_with_query(query)
  end

1266 1267 1268
  def test_filter_on_parent
    Issue.delete_all
    parent = Issue.generate_with_descendants!
1269

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284

    query = IssueQuery.new(:name => '_')
    query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
    assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort

    query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
    assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort

    query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
    assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort

    query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
    assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
  end

1285 1286 1287 1288 1289 1290