"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/test/unit/query_test.rb" (6 Apr 2020, 100553 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "query_test.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  Jean-Philippe Lang
    5 #
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the terms of the GNU General Public License
    8 # as published by the Free Software Foundation; either version 2
    9 # of the License, or (at your option) any later version.
   10 #
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with this program; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 require File.expand_path('../../test_helper', __FILE__)
   21 
   22 class QueryTest < ActiveSupport::TestCase
   23   include Redmine::I18n
   24 
   25   fixtures :projects, :enabled_modules, :users, :user_preferences, :members,
   26            :member_roles, :roles, :trackers, :issue_statuses,
   27            :issue_categories, :enumerations, :issues,
   28            :watchers, :custom_fields, :custom_values, :versions,
   29            :queries,
   30            :projects_trackers,
   31            :custom_fields_trackers,
   32            :workflows, :journals,
   33            :attachments, :time_entries
   34 
   35   INTEGER_KLASS = RUBY_VERSION >= "2.4" ? Integer : Fixnum
   36 
   37   def setup
   38     User.current = nil
   39   end
   40 
   41   def test_query_with_roles_visibility_should_validate_roles
   42     set_language_if_valid 'en'
   43     query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
   44     assert !query.save
   45     assert_include "Roles cannot be blank", query.errors.full_messages
   46     query.role_ids = [1, 2]
   47     assert query.save
   48   end
   49 
   50   def test_changing_roles_visibility_should_clear_roles
   51     query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
   52     assert_equal 2, query.roles.count
   53 
   54     query.visibility = IssueQuery::VISIBILITY_PUBLIC
   55     query.save!
   56     assert_equal 0, query.roles.count
   57   end
   58 
   59   def test_available_filters_should_be_ordered
   60     set_language_if_valid 'en'
   61     query = IssueQuery.new
   62     assert_equal 0, query.available_filters.keys.index('status_id')
   63     expected_order = [
   64       "Status",
   65       "Project",
   66       "Tracker",
   67       "Priority"
   68     ]
   69     assert_equal expected_order,
   70                  (query.available_filters.values.map{|v| v[:name]} & expected_order)
   71   end
   72 
   73   def test_available_filters_with_custom_fields_should_be_ordered
   74     set_language_if_valid 'en'
   75     UserCustomField.create!(
   76               :name => 'order test', :field_format => 'string',
   77               :is_for_all => true, :is_filter => true
   78             )
   79     query = IssueQuery.new
   80     expected_order = [
   81       "Searchable field",
   82       "Database",
   83       "Project's Development status",
   84       "Author's order test",
   85       "Assignee's order test"
   86     ]
   87     assert_equal expected_order,
   88                  (query.available_filters.values.map{|v| v[:name]} & expected_order)
   89   end
   90 
   91   def test_custom_fields_for_all_projects_should_be_available_in_global_queries
   92     query = IssueQuery.new(:project => nil, :name => '_')
   93     assert query.available_filters.has_key?('cf_1')
   94     assert !query.available_filters.has_key?('cf_3')
   95   end
   96 
   97   def test_system_shared_versions_should_be_available_in_global_queries
   98     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     project_filter = query.available_filters["project_id"]
  107     assert_not_nil project_filter
  108     project_ids = project_filter[:values].map{|p| p[1]}
  109     assert project_ids.include?("1")  # public project
  110     assert !project_ids.include?("2") # private project user cannot see
  111   end
  112 
  113   def test_available_filters_should_not_include_fields_disabled_on_all_trackers
  114     Tracker.all.each do |tracker|
  115       tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
  116       tracker.save!
  117     end
  118 
  119     query = IssueQuery.new(:name => '_')
  120     assert_include 'due_date', query.available_filters
  121     assert_not_include 'start_date', query.available_filters
  122   end
  123 
  124   def test_filter_values_without_project_should_be_arrays
  125     q = IssueQuery.new
  126     assert_nil q.project
  127 
  128     q.available_filters.each do |name, filter|
  129       values = filter.values
  130       assert (values.nil? || values.is_a?(Array)),
  131              "#values for #{name} filter returned a #{values.class.name}"
  132     end
  133   end
  134 
  135   def test_filter_values_with_project_should_be_arrays
  136     q = IssueQuery.new(:project => Project.find(1))
  137     assert_not_nil q.project
  138 
  139     q.available_filters.each do |name, filter|
  140       values = filter.values
  141       assert (values.nil? || values.is_a?(Array)),
  142              "#values for #{name} filter returned a #{values.class.name}"
  143     end
  144   end
  145 
  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   def assert_find_issues_with_query_is_successful(query)
  153     assert_nothing_raised do
  154       find_issues_with_query(query)
  155     end
  156   end
  157 
  158   def assert_query_statement_includes(query, condition)
  159     assert_include condition, query.statement
  160   end
  161 
  162   def assert_query_result(expected, query)
  163     assert_nothing_raised do
  164       assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
  165       assert_equal expected.size, query.issue_count
  166     end
  167   end
  168 
  169   def test_query_should_allow_shared_versions_for_a_project_query
  170     subproject_version = Version.find(4)
  171     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  172     filter = query.available_filters["fixed_version_id"]
  173     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     assert_equal 1, issues.length
  182     assert_equal Issue.find(3), issues.first
  183   end
  184 
  185   def test_operator_none
  186     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  187     query.add_filter('fixed_version_id', '!*', [''])
  188     query.add_filter('cf_1', '!*', [''])
  189     assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
  190     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     query.add_filter('estimated_hours', '!*', [''])
  197     issues = find_issues_with_query(query)
  198     assert !issues.empty?
  199     assert issues.all? {|i| !i.estimated_hours}
  200   end
  201 
  202   def test_operator_none_for_date
  203     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  204     query.add_filter('start_date', '!*', [''])
  205     issues = find_issues_with_query(query)
  206     assert !issues.empty?
  207     assert issues.all? {|i| i.start_date.nil?}
  208   end
  209 
  210   def test_operator_none_for_string_custom_field
  211     CustomField.find(2).update_attribute :default_value, ""
  212     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  213     query.add_filter('cf_2', '!*', [''])
  214     assert query.has_filter?('cf_2')
  215     issues = find_issues_with_query(query)
  216     assert !issues.empty?
  217     assert issues.all? {|i| i.custom_field_value(2).blank?}
  218   end
  219 
  220   def test_operator_none_for_text
  221     query = IssueQuery.new(:name => '_')
  222     query.add_filter('status_id', '*', [''])
  223     query.add_filter('description', '!*', [''])
  224     assert query.has_filter?('description')
  225     issues = find_issues_with_query(query)
  226 
  227     assert issues.any?
  228     assert issues.all? {|i| i.description.blank?}
  229     assert_equal [11, 12], issues.map(&:id).sort
  230   end
  231 
  232   def test_operator_all
  233     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  234     query.add_filter('fixed_version_id', '*', [''])
  235     query.add_filter('cf_1', '*', [''])
  236     assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
  237     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     query.add_filter('start_date', '*', [''])
  244     issues = find_issues_with_query(query)
  245     assert !issues.empty?
  246     assert issues.all? {|i| i.start_date.present?}
  247   end
  248 
  249   def test_operator_all_for_string_custom_field
  250     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  251     query.add_filter('cf_2', '*', [''])
  252     assert query.has_filter?('cf_2')
  253     issues = find_issues_with_query(query)
  254     assert !issues.empty?
  255     assert issues.all? {|i| i.custom_field_value(2).present?}
  256   end
  257 
  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     assert query.has_filter?('estimated_hours')
  263     assert !query.valid?
  264   end
  265 
  266   def test_operator_is_on_float
  267     Issue.where(:id => 2).update_all("estimated_hours = 171.2")
  268     query = IssueQuery.new(:name => '_')
  269     query.add_filter('estimated_hours', '=', ['171.20'])
  270     issues = find_issues_with_query(query)
  271     assert_equal 1, issues.size
  272     assert_equal 2, issues.first.id
  273   end
  274 
  275   def test_operator_is_on_issue_id_should_accept_comma_separated_values
  276     query = IssueQuery.new(:name => '_')
  277     query.add_filter("issue_id", '=', ['1,3'])
  278     issues = find_issues_with_query(query)
  279     assert_equal 2, issues.size
  280     assert_equal [1,3], issues.map(&:id).sort
  281   end
  282 
  283   def test_operator_is_on_parent_id_should_accept_comma_separated_values
  284     Issue.where(:id => [2,4]).update_all(:parent_id => 1)
  285     Issue.where(:id => 5).update_all(:parent_id => 3)
  286     query = IssueQuery.new(:name => '_')
  287     query.add_filter("parent_id", '=', ['1,3'])
  288     issues = find_issues_with_query(query)
  289     assert_equal 3, issues.size
  290     assert_equal [2,4,5], issues.map(&:id).sort
  291   end
  292 
  293   def test_operator_is_on_child_id_should_accept_comma_separated_values
  294     Issue.where(:id => [2,4]).update_all(:parent_id => 1)
  295     Issue.where(:id => 5).update_all(:parent_id => 3)
  296     query = IssueQuery.new(:name => '_')
  297     query.add_filter("child_id", '=', ['2,4,5'])
  298     issues = find_issues_with_query(query)
  299     assert_equal 2, issues.size
  300     assert_equal [1,3], issues.map(&:id).sort
  301   end
  302 
  303   def test_operator_between_on_issue_id_should_return_range
  304     query = IssueQuery.new(:name => '_')
  305     query.add_filter("issue_id", '><', ['2','3'])
  306     issues = find_issues_with_query(query)
  307     assert_equal 2, issues.size
  308     assert_equal [2,3], issues.map(&:id).sort
  309   end
  310 
  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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  314     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
  315     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  316 
  317     query = IssueQuery.new(:name => '_')
  318     query.add_filter("cf_#{f.id}", '=', ['12'])
  319     issues = find_issues_with_query(query)
  320     assert_equal 1, issues.size
  321     assert_equal 2, issues.first.id
  322   end
  323 
  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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  327     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
  328     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  329 
  330     query = IssueQuery.new(:name => '_')
  331     query.add_filter("cf_#{f.id}", '=', ['-12'])
  332     assert query.valid?
  333     issues = find_issues_with_query(query)
  334     assert_equal 1, issues.size
  335     assert_equal 2, issues.first.id
  336   end
  337 
  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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
  341     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
  342     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  343 
  344     query = IssueQuery.new(:name => '_')
  345     query.add_filter("cf_#{f.id}", '=', ['12.7'])
  346     issues = find_issues_with_query(query)
  347     assert_equal 1, issues.size
  348     assert_equal 2, issues.first.id
  349   end
  350 
  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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
  354     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
  355     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  356 
  357     query = IssueQuery.new(:name => '_')
  358     query.add_filter("cf_#{f.id}", '=', ['-12.7'])
  359     assert query.valid?
  360     issues = find_issues_with_query(query)
  361     assert_equal 1, issues.size
  362     assert_equal 2, issues.first.id
  363   end
  364 
  365   def test_operator_is_on_multi_list_custom_field
  366     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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
  369     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
  370     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
  371 
  372     query = IssueQuery.new(:name => '_')
  373     query.add_filter("cf_#{f.id}", '=', ['value1'])
  374     issues = find_issues_with_query(query)
  375     assert_equal [1, 3], issues.map(&:id).sort
  376 
  377     query = IssueQuery.new(:name => '_')
  378     query.add_filter("cf_#{f.id}", '=', ['value2'])
  379     issues = find_issues_with_query(query)
  380     assert_equal [1], issues.map(&:id).sort
  381   end
  382 
  383   def test_operator_is_not_on_multi_list_custom_field
  384     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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
  387     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
  388     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
  389 
  390     query = IssueQuery.new(:name => '_')
  391     query.add_filter("cf_#{f.id}", '!', ['value1'])
  392     issues = find_issues_with_query(query)
  393     assert !issues.map(&:id).include?(1)
  394     assert !issues.map(&:id).include?(3)
  395 
  396     query = IssueQuery.new(:name => '_')
  397     query.add_filter("cf_#{f.id}", '!', ['value2'])
  398     issues = find_issues_with_query(query)
  399     assert !issues.map(&:id).include?(1)
  400     assert issues.map(&:id).include?(3)
  401   end
  402 
  403   def test_operator_is_on_string_custom_field_with_utf8_value
  404     f = IssueCustomField.create!(:name => 'filter', :field_format => 'string', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  405     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'Kiểm')
  406 
  407     query = IssueQuery.new(:name => '_')
  408     query.add_filter("cf_#{f.id}", '=', ['Kiểm'])
  409     issues = find_issues_with_query(query)
  410     assert_equal [1], issues.map(&:id).sort
  411   end
  412 
  413   def test_operator_is_on_is_private_field
  414     # is_private filter only available for those who can set issues private
  415     User.current = User.find(2)
  416 
  417     query = IssueQuery.new(:name => '_')
  418     assert query.available_filters.key?('is_private')
  419 
  420     query.add_filter("is_private", '=', ['1'])
  421     issues = find_issues_with_query(query)
  422     assert issues.any?
  423     assert_nil issues.detect {|issue| !issue.is_private?}
  424   ensure
  425     User.current = nil
  426   end
  427 
  428   def test_operator_is_not_on_is_private_field
  429     # is_private filter only available for those who can set issues private
  430     User.current = User.find(2)
  431 
  432     query = IssueQuery.new(:name => '_')
  433     assert query.available_filters.key?('is_private')
  434 
  435     query.add_filter("is_private", '!', ['1'])
  436     issues = find_issues_with_query(query)
  437     assert issues.any?
  438     assert_nil issues.detect {|issue| issue.is_private?}
  439   ensure
  440     User.current = nil
  441   end
  442 
  443   def test_operator_greater_than
  444     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  445     query.add_filter('done_ratio', '>=', ['40'])
  446     assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
  447     find_issues_with_query(query)
  448   end
  449 
  450   def test_operator_greater_than_a_float
  451     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  452     query.add_filter('estimated_hours', '>=', ['40.5'])
  453     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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  460     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
  461     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  462 
  463     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  464     query.add_filter("cf_#{f.id}", '>=', ['8'])
  465     issues = find_issues_with_query(query)
  466     assert_equal 1, issues.size
  467     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'])
  473     assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
  474     find_issues_with_query(query)
  475   end
  476 
  477   def test_operator_lesser_than_on_custom_field
  478     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     find_issues_with_query(query)
  483   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     CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
  488     CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
  489     CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  490 
  491     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  492     query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
  493     issue_ids = find_issues_with_query(query).map(&:id)
  494     assert_include 1, issue_ids
  495     assert_not_include 2, issue_ids
  496     assert_not_include 3, issue_ids
  497   end
  498 
  499   def test_operator_between
  500     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  501     query.add_filter('done_ratio', '><', ['30', '40'])
  502     assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
  503     find_issues_with_query(query)
  504   end
  505 
  506   def test_operator_between_on_custom_field
  507     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     find_issues_with_query(query)
  512   end
  513 
  514   def test_date_filter_should_not_accept_non_date_values
  515     query = IssueQuery.new(:name => '_')
  516     query.add_filter('created_on', '=', ['a'])
  517 
  518     assert query.has_filter?('created_on')
  519     assert !query.valid?
  520   end
  521 
  522   def test_date_filter_should_not_accept_invalid_date_values
  523     query = IssueQuery.new(:name => '_')
  524     query.add_filter('created_on', '=', ['2011-01-34'])
  525 
  526     assert query.has_filter?('created_on')
  527     assert !query.valid?
  528   end
  529 
  530   def test_relative_date_filter_should_not_accept_non_integer_values
  531     query = IssueQuery.new(:name => '_')
  532     query.add_filter('created_on', '>t-', ['a'])
  533 
  534     assert query.has_filter?('created_on')
  535     assert !query.valid?
  536   end
  537 
  538   def test_operator_date_equals
  539     query = IssueQuery.new(:name => '_')
  540     query.add_filter('due_date', '=', ['2011-07-10'])
  541     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+)?/,
  542                  query.statement
  543     find_issues_with_query(query)
  544   end
  545 
  546   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     find_issues_with_query(query)
  551   end
  552 
  553   def test_operator_date_lesser_than_with_timestamp
  554     query = IssueQuery.new(:name => '_')
  555     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     find_issues_with_query(query)
  558   end
  559 
  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     find_issues_with_query(query)
  565   end
  566 
  567   def test_operator_date_greater_than_with_timestamp
  568     query = IssueQuery.new(:name => '_')
  569     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     find_issues_with_query(query)
  572   end
  573 
  574   def test_operator_date_between
  575     query = IssueQuery.new(:name => '_')
  576     query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
  577     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+)?'/,
  578                  query.statement
  579     find_issues_with_query(query)
  580   end
  581 
  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     issues = find_issues_with_query(query)
  587     assert !issues.empty?
  588     issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
  589   end
  590 
  591   def test_operator_in_less_than
  592     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  593     query.add_filter('due_date', '<t+', ['15'])
  594     issues = find_issues_with_query(query)
  595     assert !issues.empty?
  596     issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
  597   end
  598 
  599   def test_operator_in_the_next_days
  600     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  601     query.add_filter('due_date', '><t+', ['15'])
  602     issues = find_issues_with_query(query)
  603     assert !issues.empty?
  604     issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
  605   end
  606 
  607   def test_operator_less_than_ago
  608     Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  609     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  610     query.add_filter('due_date', '>t-', ['3'])
  611     issues = find_issues_with_query(query)
  612     assert !issues.empty?
  613     issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
  614   end
  615 
  616   def test_operator_in_the_past_days
  617     Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  618     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  619     query.add_filter('due_date', '><t-', ['3'])
  620     issues = find_issues_with_query(query)
  621     assert !issues.empty?
  622     issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
  623   end
  624 
  625   def test_operator_more_than_ago
  626     Issue.find(7).update_attribute(:due_date, (Date.today - 10))
  627     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  628     query.add_filter('due_date', '<t-', ['10'])
  629     assert query.statement.include?("#{Issue.table_name}.due_date <=")
  630     issues = find_issues_with_query(query)
  631     assert !issues.empty?
  632     issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
  633   end
  634 
  635   def test_operator_in
  636     Issue.find(7).update_attribute(:due_date, (Date.today + 2))
  637     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  638     query.add_filter('due_date', 't+', ['2'])
  639     issues = find_issues_with_query(query)
  640     assert !issues.empty?
  641     issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
  642   end
  643 
  644   def test_operator_ago
  645     Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  646     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  647     query.add_filter('due_date', 't-', ['3'])
  648     issues = find_issues_with_query(query)
  649     assert !issues.empty?
  650     issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
  651   end
  652 
  653   def test_operator_today
  654     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  655     query.add_filter('due_date', 't', [''])
  656     issues = find_issues_with_query(query)
  657     assert !issues.empty?
  658     issues.each {|issue| assert_equal Date.today, issue.due_date}
  659   end
  660 
  661   def test_operator_tomorrow
  662     issue = Issue.generate!(:due_date => User.current.today.tomorrow)
  663     other_issues = []
  664     other_issues << Issue.generate!(:due_date => User.current.today.yesterday)
  665     other_issues << Issue.generate!(:due_date => User.current.today + 2)
  666     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  667     query.add_filter('due_date', 'nd', [''])
  668     issues = find_issues_with_query(query)
  669     assert_include issue, issues
  670     other_issues.each {|i| assert_not_include i, issues }
  671   end
  672 
  673   def test_operator_date_periods
  674     %w(t ld w lw l2w m lm y nd nw nm).each do |operator|
  675       query = IssueQuery.new(:name => '_')
  676       query.add_filter('due_date', operator, [''])
  677       assert query.valid?
  678       assert query.issues
  679     end
  680   end
  681 
  682   def test_operator_datetime_periods
  683     %w(t ld w lw l2w m lm y).each do |operator|
  684       query = IssueQuery.new(:name => '_')
  685       query.add_filter('created_on', operator, [''])
  686       assert query.valid?
  687       assert query.issues
  688     end
  689   end
  690 
  691   def test_operator_contains
  692     issue = Issue.generate!(:subject => 'AbCdEfG')
  693 
  694     query = IssueQuery.new(:name => '_')
  695     query.add_filter('subject', '~', ['cdeF'])
  696     result = find_issues_with_query(query)
  697     assert_include issue, result
  698     result.each {|issue| assert issue.subject.downcase.include?('cdef') }
  699   end
  700 
  701   def test_operator_contains_with_utf8_string
  702     issue = Issue.generate!(:subject => 'Subject contains Kiểm')
  703 
  704     query = IssueQuery.new(:name => '_')
  705     query.add_filter('subject', '~', ['Kiểm'])
  706     result = find_issues_with_query(query)
  707     assert_include issue, result
  708     assert_equal 1, result.size
  709   end
  710 
  711   def test_operator_does_not_contain
  712     issue = Issue.generate!(:subject => 'AbCdEfG')
  713 
  714     query = IssueQuery.new(:name => '_')
  715     query.add_filter('subject', '!~', ['cdeF'])
  716     result = find_issues_with_query(query)
  717     assert_not_include issue, result
  718   end
  719 
  720   def test_range_for_this_week_with_week_starting_on_monday
  721     I18n.locale = :fr
  722     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     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+)?/,
  729                  query.statement
  730     I18n.locale = :en
  731   end
  732 
  733   def test_range_for_this_week_with_week_starting_on_sunday
  734     I18n.locale = :en
  735     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     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+)?/,
  742                  query.statement
  743   end
  744 
  745   def test_range_for_next_week_with_week_starting_on_monday
  746     I18n.locale = :fr
  747     assert_equal '1', I18n.t(:general_first_day_of_week)
  748 
  749     Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday
  750 
  751     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  752     query.add_filter('due_date', 'nw', [''])
  753     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+)?/,
  754                  query.statement
  755     I18n.locale = :en
  756   end
  757 
  758   def test_range_for_next_week_with_week_starting_on_sunday
  759     I18n.locale = :en
  760     assert_equal '7', I18n.t(:general_first_day_of_week)
  761 
  762     Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday
  763 
  764     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  765     query.add_filter('due_date', 'nw', [''])
  766     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+)?/,
  767                  query.statement
  768   end
  769 
  770   def test_range_for_next_month
  771     Date.stubs(:today).returns(Date.parse('2011-04-29')) # Friday
  772 
  773     query = IssueQuery.new(:project => Project.find(1), :name => '_')
  774     query.add_filter('due_date', 'nm', [''])
  775     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+)?/,
  776                  query.statement
  777   end
  778 
  779   def test_filter_assigned_to_me
  780     user = User.find(2)
  781     group = Group.find(10)
  782     group.users << user
  783     other_group = Group.find(11)
  784     Member.create!(:project_id => 1, :principal => group, :role_ids => [1])
  785     Member.create!(:project_id => 1, :principal => other_group, :role_ids => [1])
  786     User.current = user
  787 
  788     with_settings :issue_group_assignment => '1' do
  789       i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
  790       i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
  791       i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => other_group)
  792 
  793       query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
  794       result = query.issues
  795       assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
  796 
  797       assert result.include?(i1)
  798       assert result.include?(i2)
  799       assert !result.include?(i3)
  800     end
  801   end
  802 
  803   def test_filter_updated_by
  804     user = User.generate!
  805     Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  806     Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  807     Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
  808 
  809     query = IssueQuery.new(:name => '_')
  810     filter_name = "updated_by"
  811     assert_include filter_name, query.available_filters.keys
  812 
  813     query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  814     assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
  815 
  816     query.filters = {filter_name => {:operator => '!', :values => [user.id]}}
  817     assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort
  818   end
  819 
  820   def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible
  821     user = User.generate!
  822     Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
  823     Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  824 
  825     query = IssueQuery.new(:name => '_')
  826     filter_name = "updated_by"
  827     assert_include filter_name, query.available_filters.keys
  828 
  829     with_current_user User.anonymous do
  830       query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  831       assert_equal [3], find_issues_with_query(query).map(&:id).sort
  832     end
  833   end
  834 
  835   def test_filter_updated_by_me
  836     user = User.generate!
  837     Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  838 
  839     with_current_user user do
  840       query = IssueQuery.new(:name => '_')
  841       filter_name = "updated_by"
  842       assert_include filter_name, query.available_filters.keys
  843 
  844       query.filters = {filter_name => {:operator => '=', :values => ['me']}}
  845       assert_equal [2], find_issues_with_query(query).map(&:id).sort
  846     end
  847   end
  848 
  849   def test_filter_last_updated_by
  850     user = User.generate!
  851     Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  852     Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  853     Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
  854 
  855     query = IssueQuery.new(:name => '_')
  856     filter_name = "last_updated_by"
  857     assert_include filter_name, query.available_filters.keys
  858 
  859     query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  860     assert_equal [2], find_issues_with_query(query).map(&:id).sort
  861   end
  862 
  863   def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible
  864     user1 = User.generate!
  865     user2 = User.generate!
  866     Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes')
  867     Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
  868 
  869     query = IssueQuery.new(:name => '_')
  870     filter_name = "last_updated_by"
  871     assert_include filter_name, query.available_filters.keys
  872 
  873     with_current_user User.anonymous do
  874       query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
  875       assert_equal [2], find_issues_with_query(query).map(&:id).sort
  876 
  877       query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
  878       assert_equal [], find_issues_with_query(query).map(&:id).sort
  879     end
  880 
  881     with_current_user User.find(2) do
  882       query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
  883       assert_equal [], find_issues_with_query(query).map(&:id).sort
  884 
  885       query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
  886       assert_equal [2], find_issues_with_query(query).map(&:id).sort
  887     end
  888   end
  889 
  890   def test_user_custom_field_filtered_on_me
  891     User.current = User.find(2)
  892     cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
  893     issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
  894     issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
  895 
  896     query = IssueQuery.new(:name => '_', :project => Project.find(1))
  897     filter = query.available_filters["cf_#{cf.id}"]
  898     assert_not_nil filter
  899     assert_include 'me', filter[:values].map{|v| v[1]}
  900 
  901     query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
  902     result = query.issues
  903     assert_equal 1, result.size
  904     assert_equal issue1, result.first
  905   end
  906 
  907   def test_filter_on_me_by_anonymous_user
  908     User.current = nil
  909     query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
  910     assert_equal [], query.issues
  911   end
  912 
  913   def test_filter_my_projects
  914     User.current = User.find(2)
  915     query = IssueQuery.new(:name => '_')
  916     filter = query.available_filters['project_id']
  917     assert_not_nil filter
  918     assert_include 'mine', filter[:values].map{|v| v[1]}
  919 
  920     query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
  921     result = query.issues
  922     assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
  923   end
  924 
  925   def test_filter_my_bookmarks
  926     User.current = User.find(1)
  927     query = ProjectQuery.new(:name => '_')
  928     filter = query.available_filters['id']
  929     assert_not_nil filter
  930     assert_include 'bookmarks', filter[:values].map{|v| v[1]}
  931 
  932     query.filters = { 'id' => {:operator => '=', :values => ['bookmarks']}}
  933     result = query.results_scope
  934 
  935     assert_equal [1,5], result.map(&:id).sort
  936   end
  937 
  938   def test_filter_my_bookmarks_for_user_without_bookmarked_projects
  939     User.current = User.find(2)
  940     query = ProjectQuery.new(:name => '_')
  941     filter = query.available_filters['id']
  942 
  943     assert_not_include 'bookmarks', filter[:values].map{|v| v[1]}
  944   end
  945 
  946   def test_filter_project_parent_id_with_my_projects
  947     User.current = User.find(1)
  948     query = ProjectQuery.new(:name => '_')
  949     filter = query.available_filters['parent_id']
  950     assert_not_nil filter
  951     assert_include 'mine', filter[:values].map{|v| v[1]}
  952 
  953     query.filters = { 'parent_id' => {:operator => '=', :values => ['mine']}}
  954     result = query.results_scope
  955 
  956     my_projects = User.current.memberships.map(&:project_id)
  957     assert_equal Project.where(parent_id: my_projects).ids, result.map(&:id).sort
  958   end
  959 
  960   def test_filter_project_parent_id_with_my_bookmarks
  961     User.current = User.find(1)
  962     query = ProjectQuery.new(:name => '_')
  963     filter = query.available_filters['parent_id']
  964     assert_not_nil filter
  965     assert_include 'bookmarks', filter[:values].map{|v| v[1]}
  966 
  967     query.filters = { 'parent_id' => {:operator => '=', :values => ['bookmarks']}}
  968     result = query.results_scope
  969 
  970     bookmarks = User.current.bookmarked_project_ids
  971     assert_equal Project.where(parent_id: bookmarks).ids, result.map(&:id).sort
  972   end
  973 
  974   def test_filter_watched_issues
  975     User.current = User.find(1)
  976     query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
  977     result = find_issues_with_query(query)
  978     assert_not_nil result
  979     assert !result.empty?
  980     assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
  981     User.current = nil
  982   end
  983 
  984   def test_filter_unwatched_issues
  985     User.current = User.find(1)
  986     query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
  987     result = find_issues_with_query(query)
  988     assert_not_nil result
  989     assert !result.empty?
  990     assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
  991     User.current = nil
  992   end
  993 
  994   def test_filter_on_watched_issues_with_view_issue_watchers_permission
  995     User.current = User.find(1)
  996     User.current.admin = true
  997     assert User.current.allowed_to?(:view_issue_watchers, Project.find(1))
  998 
  999     Issue.find(1).add_watcher User.current
 1000     Issue.find(3).add_watcher User.find(3)
 1001     query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
 1002     result = find_issues_with_query(query)
 1003     assert_includes result, Issue.find(1)
 1004     assert_includes result, Issue.find(3)
 1005   ensure
 1006     User.current.reload
 1007     User.current = nil
 1008   end
 1009 
 1010   def test_filter_on_watched_issues_without_view_issue_watchers_permission
 1011     User.current = User.find(1)
 1012     User.current.admin = false
 1013     assert !User.current.allowed_to?(:view_issue_watchers, Project.find(1))
 1014 
 1015     Issue.find(1).add_watcher User.current
 1016     Issue.find(3).add_watcher User.find(3)
 1017     query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
 1018     result = find_issues_with_query(query)
 1019     assert_includes result, Issue.find(1)
 1020     assert_not_includes result, Issue.find(3)
 1021   ensure
 1022     User.current.reload
 1023     User.current = nil
 1024   end
 1025 
 1026   def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
 1027     field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
 1028     Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
 1029     Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
 1030 
 1031     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 1032     query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
 1033     assert_equal 2, find_issues_with_query(query).size
 1034 
 1035     field.project_ids = [1, 3] # Disable the field for project 4
 1036     field.save!
 1037     assert_equal 1, find_issues_with_query(query).size
 1038   end
 1039 
 1040   def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
 1041     field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
 1042     Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
 1043     Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
 1044 
 1045     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 1046     query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
 1047     assert_equal 2, find_issues_with_query(query).size
 1048 
 1049     field.tracker_ids = [1] # Disable the field for tracker 2
 1050     field.save!
 1051     assert_equal 1, find_issues_with_query(query).size
 1052   end
 1053 
 1054   def test_filter_on_project_custom_field
 1055     field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
 1056     CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
 1057     CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
 1058 
 1059     query = IssueQuery.new(:name => '_')
 1060     filter_name = "project.cf_#{field.id}"
 1061     assert_include filter_name, query.available_filters.keys
 1062     query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
 1063     assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
 1064   end
 1065 
 1066   def test_filter_on_author_custom_field
 1067     field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
 1068     CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
 1069 
 1070     query = IssueQuery.new(:name => '_')
 1071     filter_name = "author.cf_#{field.id}"
 1072     assert_include filter_name, query.available_filters.keys
 1073     query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
 1074     assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
 1075   end
 1076 
 1077   def test_filter_on_assigned_to_custom_field
 1078     field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
 1079     CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
 1080 
 1081     query = IssueQuery.new(:name => '_')
 1082     filter_name = "assigned_to.cf_#{field.id}"
 1083     assert_include filter_name, query.available_filters.keys
 1084     query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
 1085     assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
 1086   end
 1087 
 1088   def test_filter_on_fixed_version_custom_field
 1089     field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
 1090     CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
 1091 
 1092     query = IssueQuery.new(:name => '_')
 1093     filter_name = "fixed_version.cf_#{field.id}"
 1094     assert_include filter_name, query.available_filters.keys
 1095     query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
 1096     assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
 1097   end
 1098 
 1099   def test_filter_on_fixed_version_due_date
 1100     query = IssueQuery.new(:name => '_')
 1101     filter_name = "fixed_version.due_date"
 1102     assert_include filter_name, query.available_filters.keys
 1103     query.filters = {filter_name => {:operator => '=', :values => [20.day.from_now.to_date.to_s(:db)]}}
 1104     issues = find_issues_with_query(query)
 1105     assert_equal [2], issues.map(&:fixed_version_id).uniq.sort
 1106     assert_equal [2, 12], issues.map(&:id).sort
 1107 
 1108     query = IssueQuery.new(:name => '_')
 1109     query.filters = {filter_name => {:operator => '>=', :values => [21.day.from_now.to_date.to_s(:db)]}}
 1110     assert_equal 0, find_issues_with_query(query).size
 1111   end
 1112 
 1113   def test_filter_on_fixed_version_status
 1114     query = IssueQuery.new(:name => '_')
 1115     filter_name = "fixed_version.status"
 1116     assert_include filter_name, query.available_filters.keys
 1117     query.filters = {filter_name => {:operator => '=', :values => ['closed']}}
 1118     issues = find_issues_with_query(query)
 1119 
 1120     assert_equal [1], issues.map(&:fixed_version_id).sort
 1121     assert_equal [11], issues.map(&:id).sort
 1122 
 1123     # "is not" operator should include issues without target version
 1124     query = IssueQuery.new(:name => '_')
 1125     query.filters = {filter_name => {:operator => '!', :values => ['open', 'closed', 'locked']}, "project_id" => {:operator => '=', :values => [1]}}
 1126     assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort
 1127   end
 1128 
 1129   def test_filter_on_version_custom_field
 1130     field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
 1131     issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => '2'})
 1132 
 1133     query = IssueQuery.new(:name => '_')
 1134     filter_name = "cf_#{field.id}"
 1135     assert_include filter_name, query.available_filters.keys
 1136 
 1137     query.filters = {filter_name => {:operator => '=', :values => ['2']}}
 1138     issues = find_issues_with_query(query)
 1139     assert_equal [issue.id], issues.map(&:id).sort
 1140   end
 1141 
 1142   def test_filter_on_attribute_of_version_custom_field
 1143     field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
 1144     version = Version.generate!(:effective_date => '2017-01-14')
 1145     issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
 1146 
 1147     query = IssueQuery.new(:name => '_')
 1148     filter_name = "cf_#{field.id}.due_date"
 1149     assert_include filter_name, query.available_filters.keys
 1150 
 1151     query.filters = {filter_name => {:operator => '=', :values => ['2017-01-14']}}
 1152     issues = find_issues_with_query(query)
 1153     assert_equal [issue.id], issues.map(&:id).sort
 1154   end
 1155 
 1156   def test_filter_on_custom_field_of_version_custom_field
 1157     field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
 1158     attr = VersionCustomField.generate!(:field_format => 'string', :is_filter => true)
 1159 
 1160     version = Version.generate!(:custom_field_values => {attr.id.to_s => 'ABC'})
 1161     issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
 1162 
 1163     query = IssueQuery.new(:name => '_')
 1164     filter_name = "cf_#{field.id}.cf_#{attr.id}"
 1165     assert_include filter_name, query.available_filters.keys
 1166 
 1167     query.filters = {filter_name => {:operator => '=', :values => ['ABC']}}
 1168     issues = find_issues_with_query(query)
 1169     assert_equal [issue.id], issues.map(&:id).sort
 1170   end
 1171 
 1172   def test_filter_on_relations_with_a_specific_issue
 1173     IssueRelation.delete_all
 1174     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
 1175     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
 1176 
 1177     query = IssueQuery.new(:name => '_')
 1178     query.filters = {"relates" => {:operator => '=', :values => ['1']}}
 1179     assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
 1180 
 1181     query = IssueQuery.new(:name => '_')
 1182     query.filters = {"relates" => {:operator => '=', :values => ['2']}}
 1183     assert_equal [1], find_issues_with_query(query).map(&:id).sort
 1184 
 1185     query = IssueQuery.new(:name => '_')
 1186     query.filters = {"relates" => {:operator => '!', :values => ['1']}}
 1187     assert_equal Issue.where.not(:id => [2, 3]).order(:id).ids, find_issues_with_query(query).map(&:id).sort
 1188   end
 1189 
 1190   def test_filter_on_relations_with_any_issues_in_a_project
 1191     IssueRelation.delete_all
 1192     with_settings :cross_project_issue_relations => '1' do
 1193       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
 1194       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
 1195       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
 1196     end
 1197 
 1198     query = IssueQuery.new(:name => '_')
 1199     query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
 1200     assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
 1201 
 1202     query = IssueQuery.new(:name => '_')
 1203     query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
 1204     assert_equal [1], find_issues_with_query(query).map(&:id).sort
 1205 
 1206     query = IssueQuery.new(:name => '_')
 1207     query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
 1208     assert_equal [], find_issues_with_query(query).map(&:id).sort
 1209   end
 1210 
 1211   def test_filter_on_relations_with_any_issues_not_in_a_project
 1212     IssueRelation.delete_all
 1213     with_settings :cross_project_issue_relations => '1' do
 1214       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
 1215       # IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
 1216       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
 1217     end
 1218 
 1219     query = IssueQuery.new(:name => '_')
 1220     query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
 1221     assert_equal [1], find_issues_with_query(query).map(&:id).sort
 1222   end
 1223 
 1224   def test_filter_on_relations_with_no_issues_in_a_project
 1225     IssueRelation.delete_all
 1226     with_settings :cross_project_issue_relations => '1' do
 1227       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
 1228       IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
 1229       IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
 1230     end
 1231 
 1232     query = IssueQuery.new(:name => '_')
 1233     query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
 1234     ids = find_issues_with_query(query).map(&:id).sort
 1235     assert_include 2, ids
 1236     assert_not_include 1, ids
 1237     assert_not_include 3, ids
 1238   end
 1239 
 1240   def test_filter_on_relations_with_any_open_issues
 1241     IssueRelation.delete_all
 1242     # Issue 1 is blocked by 8, which is closed
 1243     IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
 1244     # Issue 2 is blocked by 3, which is open
 1245     IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
 1246 
 1247     query = IssueQuery.new(:name => '_')
 1248     query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
 1249     ids = find_issues_with_query(query).map(&:id)
 1250     assert_equal [], ids & [1]
 1251     assert_include 2, ids
 1252   end
 1253 
 1254   def test_filter_on_blocked_by_no_open_issues
 1255     IssueRelation.delete_all
 1256     # Issue 1 is blocked by 8, which is closed
 1257     IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
 1258     # Issue 2 is blocked by 3, which is open
 1259     IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
 1260 
 1261     query = IssueQuery.new(:name => '_')
 1262     query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
 1263     ids = find_issues_with_query(query).map(&:id)
 1264     assert_equal [], ids & [2]
 1265     assert_include 1, ids
 1266   end
 1267 
 1268   def test_filter_on_related_with_no_open_issues
 1269     IssueRelation.delete_all
 1270     # Issue 1 is blocked by 8, which is closed
 1271     IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(1), issue_to: Issue.find(8))
 1272     # Issue 2 is blocked by 3, which is open
 1273     IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(2), issue_to: Issue.find(3))
 1274 
 1275     query = IssueQuery.new(:name => '_')
 1276     query.filters = { 'relates' => { operator: '!o', values: [''] } }
 1277     ids = find_issues_with_query(query).map(&:id)
 1278     assert_equal [], ids & [2]
 1279     assert_include 1, ids
 1280   end
 1281 
 1282   def test_filter_on_relations_with_no_issues
 1283     IssueRelation.delete_all
 1284     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
 1285     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
 1286 
 1287     query = IssueQuery.new(:name => '_')
 1288     query.filters = {"relates" => {:operator => '!*', :values => ['']}}
 1289     ids = find_issues_with_query(query).map(&:id)
 1290     assert_equal [], ids & [1, 2, 3]
 1291     assert_include 4, ids
 1292   end
 1293 
 1294   def test_filter_on_relations_with_any_issues
 1295     IssueRelation.delete_all
 1296     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
 1297     IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
 1298 
 1299     query = IssueQuery.new(:name => '_')
 1300     query.filters = {"relates" => {:operator => '*', :values => ['']}}
 1301     assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
 1302   end
 1303 
 1304   def test_filter_on_relations_should_not_ignore_other_filter
 1305     issue = Issue.generate!
 1306     issue1 = Issue.generate!(:status_id => 1)
 1307     issue2 = Issue.generate!(:status_id => 2)
 1308     IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
 1309     IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
 1310 
 1311     query = IssueQuery.new(:name => '_')
 1312     query.filters = {
 1313       "status_id" => {:operator => '=', :values => ['1']},
 1314       "relates" => {:operator => '=', :values => [issue.id.to_s]}
 1315     }
 1316     assert_equal [issue1], find_issues_with_query(query)
 1317   end
 1318 
 1319   def test_filter_on_parent
 1320     Issue.delete_all
 1321     parent = Issue.generate_with_descendants!
 1322 
 1323     query = IssueQuery.new(:name => '_')
 1324     query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
 1325     assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1326 
 1327     query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
 1328     assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1329 
 1330     query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
 1331     assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1332 
 1333     query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
 1334     assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
 1335   end
 1336 
 1337   def test_filter_on_invalid_parent_should_return_no_results
 1338     query = IssueQuery.new(:name => '_')
 1339     query.filters = {"parent_id" => {:operator => '=', :values => '99999999999'}}
 1340     assert_equal [], find_issues_with_query(query).map(&:id).sort
 1341 
 1342     query.filters = {"parent_id" => {:operator => '~', :values => '99999999999'}}
 1343     assert_equal [], find_issues_with_query(query)
 1344   end
 1345 
 1346   def test_filter_on_child
 1347     Issue.delete_all
 1348     parent = Issue.generate_with_descendants!
 1349     child, leaf = parent.children.sort_by(&:id)
 1350     grandchild = child.children.first
 1351 
 1352     query = IssueQuery.new(:name => '_')
 1353     query.filters = {"child_id" => {:operator => '=', :values => [grandchild.id.to_s]}}
 1354     assert_equal [child.id], find_issues_with_query(query).map(&:id).sort
 1355 
 1356     query.filters = {"child_id" => {:operator => '~', :values => [grandchild.id.to_s]}}
 1357     assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1358 
 1359     query.filters = {"child_id" => {:operator => '*', :values => ['']}}
 1360     assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1361 
 1362     query.filters = {"child_id" => {:operator => '!*', :values => ['']}}
 1363     assert_equal [grandchild, leaf].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
 1364   end
 1365 
 1366   def test_filter_on_invalid_child_should_return_no_results
 1367     query = IssueQuery.new(:name => '_')
 1368     query.filters = {"child_id" => {:operator => '=', :values =>  '99999999999'}}
 1369     assert_equal [], find_issues_with_query(query)
 1370 
 1371     query.filters = {"child_id" => {:operator => '~', :values =>  '99999999999'}}
 1372     assert_equal [].map(&:id).sort, find_issues_with_query(query)
 1373   end
 1374 
 1375   def test_filter_on_attachment_any
 1376     query = IssueQuery.new(:name => '_')
 1377     query.filters = {"attachment" => {:operator => '*', :values =>  ['']}}
 1378     issues = find_issues_with_query(query)
 1379     assert issues.any?
 1380     assert_nil issues.detect {|issue| issue.attachments.empty?}
 1381   end
 1382 
 1383   def test_filter_on_attachment_none
 1384     query = IssueQuery.new(:name => '_')
 1385     query.filters = {"attachment" => {:operator => '!*', :values =>  ['']}}
 1386     issues = find_issues_with_query(query)
 1387     assert issues.any?
 1388     assert_nil issues.detect {|issue| issue.attachments.any?}
 1389   end
 1390 
 1391   def test_filter_on_attachment_contains
 1392     query = IssueQuery.new(:name => '_')
 1393     query.filters = {"attachment" => {:operator => '~', :values =>  ['error281']}}
 1394     issues = find_issues_with_query(query)
 1395     assert issues.any?
 1396     assert_nil issues.detect {|issue| ! issue.attachments.any? {|attachment| attachment.filename.include?('error281')}}
 1397   end
 1398 
 1399   def test_filter_on_attachment_not_contains
 1400     query = IssueQuery.new(:name => '_')
 1401     query.filters = {"attachment" => {:operator => '!~', :values =>  ['error281']}}
 1402     issues = find_issues_with_query(query)
 1403     assert issues.any?
 1404     assert_nil issues.detect {|issue| issue.attachments.any? {|attachment| attachment.filename.include?('error281')}}
 1405   end
 1406 
 1407   def test_filter_on_attachment_when_starts_with
 1408     query = IssueQuery.new(:name => '_')
 1409     query.filters = {"attachment" => {:operator => '^', :values =>  ['testfile']}}
 1410     issues = find_issues_with_query(query)
 1411     assert_equal [14], issues.collect(&:id).sort
 1412   end
 1413 
 1414   def test_filter_on_attachment_when_ends_with
 1415     query = IssueQuery.new(:name => '_')
 1416     query.filters = {"attachment" => {:operator => '$', :values =>  ['zip']}}
 1417     issues = find_issues_with_query(query)
 1418     assert_equal [3, 4], issues.collect(&:id).sort
 1419   end
 1420 
 1421   def test_filter_on_subject_when_starts_with
 1422     query = IssueQuery.new(:name => '_')
 1423     query.filters = {'subject' => {:operator => '^', :values => ['issue']}}
 1424     issues = find_issues_with_query(query)
 1425     assert_equal [4, 6, 7, 10], issues.collect(&:id).sort
 1426   end
 1427 
 1428   def test_filter_on_subject_when_ends_with
 1429     query = IssueQuery.new(:name => '_')
 1430     query.filters = {'subject' => {:operator => '$', :values => ['issue']}}
 1431     issues = find_issues_with_query(query)
 1432     assert_equal [5, 8, 9], issues.collect(&:id).sort
 1433   end
 1434 
 1435   def test_statement_should_be_nil_with_no_filters
 1436     q = IssueQuery.new(:name => '_')
 1437     q.filters = {}
 1438 
 1439     assert q.valid?
 1440     assert_nil q.statement
 1441   end
 1442 
 1443   def test_available_filters_as_json_should_include_missing_assigned_to_id_values
 1444     user = User.generate!
 1445     with_current_user User.find(1) do
 1446       q = IssueQuery.new
 1447       q.filters = {"assigned_to_id" => {:operator => '=', :values => user.id.to_s}}
 1448 
 1449       filters = q.available_filters_as_json
 1450       assert_include [user.name, user.id.to_s], filters['assigned_to_id']['values']
 1451     end
 1452   end
 1453 
 1454   def test_available_filters_as_json_should_include_missing_author_id_values
 1455     user = User.generate!
 1456     with_current_user User.find(1) do
 1457       q = IssueQuery.new
 1458       q.filters = {"author_id" => {:operator => '=', :values => user.id.to_s}}
 1459 
 1460       filters = q.available_filters_as_json
 1461       assert_include [user.name, user.id.to_s], filters['author_id']['values']
 1462     end
 1463   end
 1464 
 1465   def test_default_columns
 1466     q = IssueQuery.new
 1467     assert q.columns.any?
 1468     assert q.inline_columns.any?
 1469     assert q.block_columns.empty?
 1470   end
 1471 
 1472   def test_set_column_names
 1473     q = IssueQuery.new
 1474     q.column_names = ['tracker', :subject, '', 'unknonw_column']
 1475     assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
 1476   end
 1477 
 1478   def test_has_column_should_accept_a_column_name
 1479     q = IssueQuery.new
 1480     q.column_names = ['tracker', :subject]
 1481     assert q.has_column?(:tracker)
 1482     assert !q.has_column?(:category)
 1483   end
 1484 
 1485   def test_has_column_should_accept_a_column
 1486     q = IssueQuery.new
 1487     q.column_names = ['tracker', :subject]
 1488 
 1489     tracker_column = q.available_columns.detect {|c| c.name==:tracker}
 1490     assert_kind_of QueryColumn, tracker_column
 1491     category_column = q.available_columns.detect {|c| c.name==:category}
 1492     assert_kind_of QueryColumn, category_column
 1493 
 1494     assert q.has_column?(tracker_column)
 1495     assert !q.has_column?(category_column)
 1496   end
 1497 
 1498   def test_has_column_should_return_true_for_default_column
 1499     with_settings :issue_list_default_columns => %w(tracker subject) do
 1500       q = IssueQuery.new
 1501       assert q.has_column?(:tracker)
 1502       assert !q.has_column?(:category)
 1503     end
 1504   end
 1505 
 1506   def test_inline_and_block_columns
 1507     q = IssueQuery.new
 1508     q.column_names = ['subject', 'description', 'tracker', 'last_notes']
 1509 
 1510     assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
 1511     assert_equal [:description, :last_notes], q.block_columns.map(&:name)
 1512   end
 1513 
 1514   def test_custom_field_columns_should_be_inline
 1515     q = IssueQuery.new
 1516     columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
 1517     assert columns.any?
 1518     assert_nil columns.detect {|column| !column.inline?}
 1519   end
 1520 
 1521   def test_query_should_preload_spent_hours
 1522     q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
 1523     assert q.has_column?(:spent_hours)
 1524     issues = q.issues
 1525     assert_not_nil issues.first.instance_variable_get("@spent_hours")
 1526   end
 1527 
 1528   def test_query_should_preload_last_updated_by
 1529     with_current_user User.find(2) do
 1530       q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_updated_by])
 1531       q.filters = {"issue_id" => {:operator => '=', :values => ['1,2,3']}}
 1532       assert q.has_column?(:last_updated_by)
 1533 
 1534       issues = q.issues.sort_by(&:id)
 1535       assert issues.all? {|issue| !issue.instance_variable_get("@last_updated_by").nil?}
 1536       assert_equal ["User", "User", "NilClass"], issues.map { |i| i.last_updated_by.class.name}
 1537       assert_equal ["John Smith", "John Smith", ""], issues.map { |i| i.last_updated_by.to_s }
 1538     end
 1539   end
 1540 
 1541   def test_query_should_preload_last_notes
 1542     q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_notes])
 1543     assert q.has_column?(:last_notes)
 1544     issues = q.issues
 1545     assert_not_nil issues.first.instance_variable_get("@last_notes")
 1546   end
 1547 
 1548   def test_groupable_columns_should_include_custom_fields
 1549     q = IssueQuery.new
 1550     column = q.groupable_columns.detect {|c| c.name == :cf_1}
 1551     assert_not_nil column
 1552     assert_kind_of QueryCustomFieldColumn, column
 1553   end
 1554 
 1555   def test_groupable_columns_should_not_include_multi_custom_fields
 1556     field = CustomField.find(1)
 1557     field.update_attribute :multiple, true
 1558 
 1559     q = IssueQuery.new
 1560     column = q.groupable_columns.detect {|c| c.name == :cf_1}
 1561     assert_nil column
 1562   end
 1563 
 1564   def test_groupable_columns_should_include_user_custom_fields
 1565     cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
 1566 
 1567     q = IssueQuery.new
 1568     assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
 1569   end
 1570 
 1571   def test_groupable_columns_should_include_version_custom_fields
 1572     cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
 1573 
 1574     q = IssueQuery.new
 1575     assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
 1576   end
 1577 
 1578   def test_grouped_with_valid_column
 1579     q = IssueQuery.new(:group_by => 'status')
 1580     assert q.grouped?
 1581     assert_not_nil q.group_by_column
 1582     assert_equal :status, q.group_by_column.name
 1583     assert_not_nil q.group_by_statement
 1584     assert_equal 'status', q.group_by_statement
 1585   end
 1586 
 1587   def test_grouped_with_invalid_column
 1588     q = IssueQuery.new(:group_by => 'foo')
 1589     assert !q.grouped?
 1590     assert_nil q.group_by_column
 1591     assert_nil q.group_by_statement
 1592   end
 1593 
 1594   def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
 1595     with_settings :user_format => 'lastname_comma_firstname' do
 1596       q = IssueQuery.new
 1597       assert q.sortable_columns.has_key?('assigned_to')
 1598       assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
 1599     end
 1600   end
 1601 
 1602   def test_sortable_columns_should_sort_authors_according_to_user_format_setting
 1603     with_settings :user_format => 'lastname_comma_firstname' do
 1604       q = IssueQuery.new
 1605       assert q.sortable_columns.has_key?('author')
 1606       assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
 1607     end
 1608   end
 1609 
 1610   def test_sortable_columns_should_sort_last_updated_by_according_to_user_format_setting
 1611     with_settings :user_format => 'lastname_comma_firstname' do
 1612       q = IssueQuery.new
 1613       q.sort_criteria = [['last_updated_by', 'desc']]
 1614 
 1615       assert q.sortable_columns.has_key?('last_updated_by')
 1616       assert_equal %w(last_journal_user.lastname last_journal_user.firstname last_journal_user.id), q.sortable_columns['last_updated_by']
 1617     end
 1618   end
 1619 
 1620   def test_sortable_columns_should_include_custom_field
 1621     q = IssueQuery.new
 1622     assert q.sortable_columns['cf_1']
 1623   end
 1624 
 1625   def test_sortable_columns_should_not_include_multi_custom_field
 1626     field = CustomField.find(1)
 1627     field.update_attribute :multiple, true
 1628 
 1629     q = IssueQuery.new
 1630     assert !q.sortable_columns['cf_1']
 1631   end
 1632 
 1633   def test_default_sort
 1634     q = IssueQuery.new
 1635     assert_equal [['id', 'desc']], q.sort_criteria
 1636   end
 1637 
 1638   def test_sort_criteria_should_have_only_first_three_elements
 1639     q = IssueQuery.new
 1640     q.sort_criteria = [['priority', 'desc'], ['tracker', 'asc'], ['priority', 'asc'], ['id', 'asc'], ['project', 'asc'], ['subject', 'asc']]
 1641     assert_equal [['priority', 'desc'], ['tracker', 'asc'], ['id', 'asc']], q.sort_criteria
 1642   end
 1643 
 1644   def test_sort_criteria_should_remove_blank_or_duplicate_keys
 1645     q = IssueQuery.new
 1646     q.sort_criteria = [['priority', 'desc'], [nil, 'desc'], ['', 'asc'], ['priority', 'asc'], ['project', 'asc']]
 1647     assert_equal [['priority', 'desc'], ['project', 'asc']], q.sort_criteria
 1648   end
 1649 
 1650   def test_set_sort_criteria_with_hash
 1651     q = IssueQuery.new
 1652     q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
 1653     assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
 1654   end
 1655 
 1656   def test_set_sort_criteria_with_array
 1657     q = IssueQuery.new
 1658     q.sort_criteria = [['priority', 'desc'], 'tracker']
 1659     assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
 1660   end
 1661 
 1662   def test_create_query_with_sort
 1663     q = IssueQuery.new(:name => 'Sorted')
 1664     q.sort_criteria = [['priority', 'desc'], 'tracker']
 1665     assert q.save
 1666     q.reload
 1667     assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
 1668   end
 1669 
 1670   def test_sort_by_string_custom_field_asc
 1671     q = IssueQuery.new
 1672     c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
 1673     assert c
 1674     assert c.sortable
 1675     q.sort_criteria = [[c.name.to_s, 'asc']]
 1676     issues = q.issues
 1677     values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
 1678     assert !values.empty?
 1679     assert_equal values.sort, values
 1680   end
 1681 
 1682   def test_sort_by_string_custom_field_desc
 1683     q = IssueQuery.new
 1684     c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
 1685     assert c
 1686     assert c.sortable
 1687     q.sort_criteria = [[c.name.to_s, 'desc']]
 1688     issues = q.issues
 1689     values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
 1690     assert !values.empty?
 1691     assert_equal values.sort.reverse, values
 1692   end
 1693 
 1694   def test_sort_by_float_custom_field_asc
 1695     q = IssueQuery.new
 1696     c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
 1697     assert c
 1698     assert c.sortable
 1699     q.sort_criteria = [[c.name.to_s, 'asc']]
 1700     issues = q.issues
 1701     values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
 1702     assert !values.empty?
 1703     assert_equal values.sort, values
 1704   end
 1705 
 1706   def test_sort_with_group_by_timestamp_query_column_should_sort_after_date_value
 1707     User.current = User.find(1)
 1708 
 1709     # Touch Issue#10 in order to be the last updated issue
 1710     Issue.find(10).update_attribute(:updated_on, Issue.find(10).updated_on + 1)
 1711 
 1712     q = IssueQuery.new(
 1713       :name => '_',
 1714       :filters => { 'updated_on' => {:operator => 't', :values => ['']} },
 1715       :group_by => 'updated_on',
 1716       :sort_criteria => [['subject', 'asc']]
 1717     )
 1718 
 1719     # The following 3 issues are updated today (ordered by updated_on):
 1720     #   Issue#10: Issue Doing the Blocking
 1721     #   Issue#9: Blocked Issue
 1722     #   Issue#6: Issue of a private subproject
 1723 
 1724     # When we group by a timestamp query column, all the issues in the group have the same date value (today)
 1725     # and the time of the value should not be taken into consideration when sorting
 1726     #
 1727     # For the same issues after subject ascending should return the following:
 1728     # Issue#9: Blocked Issue
 1729     # Issue#10: Issue Doing the Blocking
 1730     # Issue#6: Issue of a private subproject
 1731     assert_equal [9, 10, 6], q.issues.map(&:id)
 1732   end
 1733 
 1734   def test_sort_by_total_for_estimated_hours
 1735     # Prepare issues
 1736     parent = issues(:issues_001)
 1737     child = issues(:issues_002)
 1738     private_child = issues(:issues_003)
 1739     other = issues(:issues_007)
 1740 
 1741     User.current = users(:users_001)
 1742 
 1743     parent.safe_attributes         = {:estimated_hours => 1}
 1744     child.safe_attributes          = {:estimated_hours => 2, :parent_issue_id => 1}
 1745     private_child.safe_attributes  = {:estimated_hours => 4, :parent_issue_id => 1, :is_private => true}
 1746     other.safe_attributes          = {:estimated_hours => 5}
 1747 
 1748     [parent, child, private_child, other].each(&:save!)
 1749 
 1750     q = IssueQuery.new(
 1751       :name => '_',
 1752       :filters => { 'issue_id' => {:operator => '=', :values => ['1,7']} },
 1753       :sort_criteria => [['total_estimated_hours', 'asc']]
 1754     )
 1755 
 1756     # With private_child, `parent' is "bigger" than `other'
 1757     ids = q.issue_ids
 1758     assert_equal [7, 1], ids, "Private issue was not used to calculate sort order"
 1759 
 1760     # Without the invisible private_child, `other' is "bigger" than `parent'
 1761     User.current = User.anonymous
 1762     ids = q.issue_ids
 1763     assert_equal [1, 7], ids, "Private issue was used to calculate sort order"
 1764   end
 1765 
 1766   def test_set_totalable_names
 1767     q = IssueQuery.new
 1768     q.totalable_names = ['estimated_hours', :spent_hours, '']
 1769     assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
 1770   end
 1771 
 1772   def test_totalable_columns_should_default_to_settings
 1773     with_settings :issue_list_default_totals => ['estimated_hours'] do
 1774       q = IssueQuery.new
 1775       assert_equal [:estimated_hours], q.totalable_columns.map(&:name)
 1776     end
 1777   end
 1778 
 1779   def test_available_totalable_columns_should_include_estimated_hours
 1780     q = IssueQuery.new
 1781     assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
 1782   end
 1783 
 1784   def test_available_totalable_columns_should_include_spent_hours
 1785     User.current = User.find(1)
 1786 
 1787     q = IssueQuery.new
 1788     assert_include :spent_hours, q.available_totalable_columns.map(&:name)
 1789   end
 1790 
 1791   def test_available_totalable_columns_should_include_int_custom_field
 1792     field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
 1793     q = IssueQuery.new
 1794     assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
 1795   end
 1796 
 1797   def test_available_totalable_columns_should_include_float_custom_field
 1798     field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
 1799     q = IssueQuery.new
 1800     assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
 1801   end
 1802 
 1803   def test_total_for_estimated_hours
 1804     Issue.delete_all
 1805     Issue.generate!(:estimated_hours => 5.5)
 1806     Issue.generate!(:estimated_hours => 1.1)
 1807     Issue.generate!
 1808 
 1809     q = IssueQuery.new
 1810     assert_equal 6.6, q.total_for(:estimated_hours)
 1811   end
 1812 
 1813   def test_total_by_group_for_estimated_hours
 1814     Issue.delete_all
 1815     Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2)
 1816     Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3)
 1817     Issue.generate!(:estimated_hours => 3.5)
 1818 
 1819     q = IssueQuery.new(:group_by => 'assigned_to')
 1820     assert_equal(
 1821       {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
 1822       q.total_by_group_for(:estimated_hours)
 1823     )
 1824   end
 1825 
 1826   def test_total_for_spent_hours
 1827     TimeEntry.delete_all
 1828     TimeEntry.generate!(:hours => 5.5)
 1829     TimeEntry.generate!(:hours => 1.1)
 1830 
 1831     q = IssueQuery.new
 1832     assert_equal 6.6, q.total_for(:spent_hours)
 1833   end
 1834 
 1835   def test_total_by_group_for_spent_hours
 1836     TimeEntry.delete_all
 1837     TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
 1838     TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
 1839     Issue.where(:id => 1).update_all(:assigned_to_id => 2)
 1840     Issue.where(:id => 2).update_all(:assigned_to_id => 3)
 1841 
 1842     q = IssueQuery.new(:group_by => 'assigned_to')
 1843     assert_equal(
 1844       {User.find(2) => 5.5, User.find(3) => 1.1},
 1845       q.total_by_group_for(:spent_hours)
 1846     )
 1847   end
 1848 
 1849   def test_total_by_project_group_for_spent_hours
 1850     TimeEntry.delete_all
 1851     TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
 1852     TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
 1853     Issue.where(:id => 1).update_all(:assigned_to_id => 2)
 1854     Issue.where(:id => 2).update_all(:assigned_to_id => 3)
 1855 
 1856     q = IssueQuery.new(:group_by => 'project')
 1857     assert_equal(
 1858       {Project.find(1) => 6.6},
 1859       q.total_by_group_for(:spent_hours)
 1860     )
 1861   end
 1862 
 1863   def test_total_for_int_custom_field
 1864     field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
 1865     CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
 1866     CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
 1867     CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
 1868 
 1869     q = IssueQuery.new
 1870     assert_equal 9, q.total_for("cf_#{field.id}")
 1871   end
 1872 
 1873   def test_total_by_group_for_int_custom_field
 1874     field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
 1875     CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
 1876     CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
 1877     Issue.where(:id => 1).update_all(:assigned_to_id => 2)
 1878     Issue.where(:id => 2).update_all(:assigned_to_id => 3)
 1879 
 1880     q = IssueQuery.new(:group_by => 'assigned_to')
 1881     assert_equal(
 1882       {User.find(2) => 2, User.find(3) => 7},
 1883       q.total_by_group_for("cf_#{field.id}")
 1884     )
 1885   end
 1886 
 1887   def test_total_for_float_custom_field
 1888     field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
 1889     CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3')
 1890     CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
 1891     CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
 1892 
 1893     q = IssueQuery.new
 1894     assert_equal 9.3, q.total_for("cf_#{field.id}")
 1895   end
 1896 
 1897   def test_invalid_query_should_raise_query_statement_invalid_error
 1898     q = IssueQuery.new
 1899     assert_raise Query::StatementInvalid do
 1900       q.issues(:conditions => "foo = 1")
 1901     end
 1902   end
 1903 
 1904   def test_issue_count
 1905     q = IssueQuery.new(:name => '_')
 1906     issue_count = q.issue_count
 1907     assert_equal q.issues.size, issue_count
 1908   end
 1909 
 1910   def test_issue_count_with_archived_issues
 1911     p = Project.generate! do |project|
 1912       project.status = Project::STATUS_ARCHIVED
 1913     end
 1914     i = Issue.generate!( :project => p, :tracker => p.trackers.first )
 1915     assert !i.visible?
 1916 
 1917     test_issue_count
 1918   end
 1919 
 1920   def test_issue_count_by_association_group
 1921     q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
 1922     count_by_group = q.result_count_by_group
 1923     assert_kind_of Hash, count_by_group
 1924     assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
 1925     assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
 1926     assert count_by_group.has_key?(User.find(3))
 1927   end
 1928 
 1929   def test_issue_count_by_list_custom_field_group
 1930     q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
 1931     count_by_group = q.result_count_by_group
 1932     assert_kind_of Hash, count_by_group
 1933     assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
 1934     assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
 1935     assert count_by_group.has_key?('MySQL')
 1936   end
 1937 
 1938   def test_issue_count_by_date_custom_field_group
 1939     q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
 1940     count_by_group = q.result_count_by_group
 1941     assert_kind_of Hash, count_by_group
 1942     assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
 1943     assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
 1944   end
 1945 
 1946   def test_issue_count_with_nil_group_only
 1947     Issue.update_all("assigned_to_id = NULL")
 1948 
 1949     q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
 1950     count_by_group = q.result_count_by_group
 1951     assert_kind_of Hash, count_by_group
 1952     assert_equal 1, count_by_group.keys.size
 1953     assert_nil count_by_group.keys.first
 1954   end
 1955 
 1956   def test_issue_ids
 1957     q = IssueQuery.new(:name => '_')
 1958     q.sort_criteria = ['subject', 'id']
 1959     issues = q.issues
 1960     assert_equal issues.map(&:id), q.issue_ids
 1961   end
 1962 
 1963   def test_label_for
 1964     set_language_if_valid 'en'
 1965     q = IssueQuery.new
 1966     assert_equal 'Assignee', q.label_for('assigned_to_id')
 1967   end
 1968 
 1969   def test_label_for_fr
 1970     set_language_if_valid 'fr'
 1971     q = IssueQuery.new
 1972     assert_equal 'Assigné à', q.label_for('assigned_to_id')
 1973   end
 1974 
 1975   def test_editable_by
 1976     admin = User.find(1)
 1977     manager = User.find(2)
 1978     developer = User.find(3)
 1979 
 1980     # Public query on project 1
 1981     q = IssueQuery.find(1)
 1982     assert q.editable_by?(admin)
 1983     assert q.editable_by?(manager)
 1984     assert !q.editable_by?(developer)
 1985 
 1986     # Private query on project 1
 1987     q = IssueQuery.find(2)
 1988     assert q.editable_by?(admin)
 1989     assert !q.editable_by?(manager)
 1990     assert q.editable_by?(developer)
 1991 
 1992     # Private query for all projects
 1993     q = IssueQuery.find(3)
 1994     assert q.editable_by?(admin)
 1995     assert !q.editable_by?(manager)
 1996     assert q.editable_by?(developer)
 1997   end
 1998 
 1999   def test_editable_by_for_global_query
 2000     admin = User.find(1)
 2001     manager = User.find(2)
 2002     developer = User.find(3)
 2003 
 2004     q = IssueQuery.find(4)
 2005 
 2006     assert q.editable_by?(admin)
 2007     assert !q.editable_by?(manager)
 2008     assert !q.editable_by?(developer)
 2009   end
 2010 
 2011   def test_editable_by_for_global_query_with_project_set
 2012     admin = User.find(1)
 2013     manager = User.find(2)
 2014     developer = User.find(3)
 2015 
 2016     q = IssueQuery.find(4)
 2017     q.project = Project.find(1)
 2018 
 2019     assert q.editable_by?(admin)
 2020     assert !q.editable_by?(manager)
 2021     assert !q.editable_by?(developer)
 2022   end
 2023 
 2024   def test_visible_scope
 2025     query_ids = IssueQuery.visible(User.anonymous).map(&:id)
 2026 
 2027     assert query_ids.include?(1), 'public query on public project was not visible'
 2028     assert query_ids.include?(4), 'public query for all projects was not visible'
 2029     assert !query_ids.include?(2), 'private query on public project was visible'
 2030     assert !query_ids.include?(3), 'private query for all projects was visible'
 2031     assert !query_ids.include?(7), 'public query on private project was visible'
 2032   end
 2033 
 2034   def test_query_with_public_visibility_should_be_visible_to_anyone
 2035     q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
 2036 
 2037     assert q.visible?(User.anonymous)
 2038     assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
 2039 
 2040     assert q.visible?(User.find(7))
 2041     assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
 2042 
 2043     assert q.visible?(User.find(2))
 2044     assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
 2045 
 2046     assert q.visible?(User.find(1))
 2047     assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
 2048   end
 2049 
 2050   def test_query_with_roles_visibility_should_be_visible_to_user_with_role
 2051     q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
 2052 
 2053     assert !q.visible?(User.anonymous)
 2054     assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
 2055 
 2056     assert !q.visible?(User.find(7))
 2057     assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
 2058 
 2059     assert q.visible?(User.find(2))
 2060     assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
 2061 
 2062     assert q.visible?(User.find(1))
 2063     assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
 2064 
 2065     # Should ignore archived project memberships
 2066     Project.find(1).archive
 2067     assert !q.visible?(User.find(3))
 2068     assert_nil IssueQuery.visible(User.find(3)).find_by_id(q.id)
 2069   end
 2070 
 2071   def test_query_with_private_visibility_should_be_visible_to_owner
 2072     q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
 2073 
 2074     assert !q.visible?(User.anonymous)
 2075     assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
 2076 
 2077     assert q.visible?(User.find(7))
 2078     assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
 2079 
 2080     assert !q.visible?(User.find(2))
 2081     assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
 2082 
 2083     assert q.visible?(User.find(1))
 2084     assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
 2085   end
 2086 
 2087   def test_build_from_params_should_not_update_query_with_nil_param_values
 2088     q = IssueQuery.create!(:name => 'Query',
 2089                            :type => "IssueQuery",
 2090                            :user => User.find(7),
 2091                            :filters => {"status_id" => {:values => ["1"], :operator => "o"}},
 2092                            :column_names => [:tracker, :status],
 2093                            :sort_criteria => ['id', 'asc'],
 2094                            :group_by => "project",
 2095                            :options => { :totalable_names=>[:estimated_hours], :draw_relations => '1', :draw_progress_line => '1' }
 2096                             )
 2097     old_attributes = q.attributes
 2098     q.build_from_params({})
 2099     assert_equal old_attributes, q.attributes
 2100   end
 2101 
 2102   test "#available_filters should include users of visible projects in cross-project view" do
 2103     users = IssueQuery.new.available_filters["assigned_to_id"]
 2104     assert_not_nil users
 2105     assert users[:values].map{|u| u[1]}.include?("3")
 2106   end
 2107 
 2108   test "#available_filters should include users of subprojects" do
 2109     user1 = User.generate!
 2110     user2 = User.generate!
 2111     project = Project.find(1)
 2112     Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
 2113     users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
 2114     assert_not_nil users
 2115     assert users[:values].map{|u| u[1]}.include?(user1.id.to_s)
 2116     assert !users[:values].map{|u| u[1]}.include?(user2.id.to_s)
 2117   end
 2118 
 2119   test "#available_filters should include visible projects in cross-project view" do
 2120     projects = IssueQuery.new.available_filters["project_id"]
 2121     assert_not_nil projects
 2122     assert projects[:values].map{|u| u[1]}.include?("1")
 2123   end
 2124 
 2125   test "#available_filters should include 'member_of_group' filter" do
 2126     query = IssueQuery.new
 2127     assert query.available_filters.key?("member_of_group")
 2128     assert_equal :list_optional, query.available_filters["member_of_group"][:type]
 2129     assert query.available_filters["member_of_group"][:values].present?
 2130     assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
 2131                  query.available_filters["member_of_group"][:values].sort
 2132   end
 2133 
 2134   test "#available_filters should include 'assigned_to_role' filter" do
 2135     query = IssueQuery.new
 2136     assert query.available_filters.key?("assigned_to_role")
 2137     assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
 2138 
 2139     assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
 2140     assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
 2141     assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
 2142 
 2143     assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
 2144     assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
 2145   end
 2146 
 2147   def test_available_filters_should_include_custom_field_according_to_user_visibility
 2148     visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
 2149     hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
 2150 
 2151     with_current_user User.find(3) do
 2152       query = IssueQuery.new
 2153       assert_include "cf_#{visible_field.id}", query.available_filters.keys
 2154       assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
 2155     end
 2156   end
 2157 
 2158   def test_available_columns_should_include_custom_field_according_to_user_visibility
 2159     visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
 2160     hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
 2161 
 2162     with_current_user User.find(3) do
 2163       query = IssueQuery.new
 2164       assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
 2165       assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
 2166     end
 2167   end
 2168 
 2169   def test_available_columns_should_not_include_total_estimated_hours_when_trackers_disabled_estimated_hours
 2170     Tracker.visible.each do |tracker|
 2171       tracker.core_fields = tracker.core_fields.reject{|field| field == 'estimated_hours'}
 2172       tracker.save!
 2173     end
 2174     query = IssueQuery.new
 2175     available_columns = query.available_columns.map(&:name)
 2176     assert_not_include :estimated_hours, available_columns
 2177     assert_not_include :total_estimated_hours, available_columns
 2178 
 2179     tracker = Tracker.visible.first
 2180     tracker.core_fields = ['estimated_hours']
 2181     tracker.save!
 2182     query = IssueQuery.new
 2183     available_columns = query.available_columns.map(&:name)
 2184     assert_include :estimated_hours, available_columns
 2185     assert_include :total_estimated_hours, available_columns
 2186   end
 2187 
 2188   def setup_member_of_group
 2189     Group.destroy_all # No fixtures
 2190     @user_in_group = User.generate!
 2191     @second_user_in_group = User.generate!
 2192     @user_in_group2 = User.generate!
 2193     @user_not_in_group = User.generate!
 2194 
 2195     @group = Group.generate!.reload
 2196     @group.users << @user_in_group
 2197     @group.users << @second_user_in_group
 2198 
 2199     @group2 = Group.generate!.reload
 2200     @group2.users << @user_in_group2
 2201 
 2202     @query = IssueQuery.new(:name => '_')
 2203   end
 2204 
 2205   test "member_of_group filter should search assigned to for users in the group" do
 2206     setup_member_of_group
 2207     @query.add_filter('member_of_group', '=', [@group.id.to_s])
 2208 
 2209     assert_find_issues_with_query_is_successful @query
 2210   end
 2211 
 2212   test "member_of_group filter should search not assigned to any group member (none)" do
 2213     setup_member_of_group
 2214     @query.add_filter('member_of_group', '!*', [''])
 2215 
 2216     assert_find_issues_with_query_is_successful @query
 2217   end
 2218 
 2219   test "member_of_group filter should search assigned to any group member (all)" do
 2220     setup_member_of_group
 2221     @query.add_filter('member_of_group', '*', [''])
 2222 
 2223     assert_find_issues_with_query_is_successful @query
 2224   end
 2225 
 2226   test "member_of_group filter should return an empty set with = empty group" do
 2227     setup_member_of_group
 2228     @empty_group = Group.generate!
 2229     @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
 2230 
 2231     assert_equal [], find_issues_with_query(@query)
 2232   end
 2233 
 2234   test "member_of_group filter should return issues with ! empty group" do
 2235     setup_member_of_group
 2236     @empty_group = Group.generate!
 2237     @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
 2238 
 2239     assert_find_issues_with_query_is_successful @query
 2240   end
 2241 
 2242   def setup_assigned_to_role
 2243     @manager_role = Role.find_by_name('Manager')
 2244     @developer_role = Role.find_by_name('Developer')
 2245 
 2246     @project = Project.generate!
 2247     @manager = User.generate!
 2248     @developer = User.generate!
 2249     @boss = User.generate!
 2250     @guest = User.generate!
 2251     User.add_to_project(@manager, @project, @manager_role)
 2252     User.add_to_project(@developer, @project, @developer_role)
 2253     User.add_to_project(@boss, @project, [@manager_role, @developer_role])
 2254 
 2255     @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
 2256     @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
 2257     @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
 2258     @issue4 = Issue.generate!(:project => @project, :author_id => @guest.id, :assigned_to_id => @guest.id)
 2259     @issue5 = Issue.generate!(:project => @project)
 2260 
 2261     @query = IssueQuery.new(:name => '_', :project => @project)
 2262   end
 2263 
 2264   test "assigned_to_role filter should search assigned to for users with the Role" do
 2265     setup_assigned_to_role
 2266     @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
 2267 
 2268     assert_query_result [@issue1, @issue3], @query
 2269   end
 2270 
 2271   test "assigned_to_role filter should search assigned to for users with the Role on the issue project" do
 2272     setup_assigned_to_role
 2273     other_project = Project.generate!
 2274     User.add_to_project(@developer, other_project, @manager_role)
 2275     @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
 2276 
 2277     assert_query_result [@issue1, @issue3], @query
 2278   end
 2279 
 2280   test "assigned_to_role filter should return an empty set with empty role" do
 2281     setup_assigned_to_role
 2282     @empty_role = Role.generate!
 2283     @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
 2284 
 2285     assert_query_result [], @query
 2286   end
 2287 
 2288   test "assigned_to_role filter should search assigned to for users without the Role" do
 2289     setup_assigned_to_role
 2290     @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
 2291 
 2292     assert_query_result [@issue2, @issue4, @issue5], @query
 2293   end
 2294 
 2295   test "assigned_to_role filter should search assigned to for users not assigned to any Role (none)" do
 2296     setup_assigned_to_role
 2297     @query.add_filter('assigned_to_role', '!*', [''])
 2298 
 2299     assert_query_result [@issue4, @issue5], @query
 2300   end
 2301 
 2302   test "assigned_to_role filter should search assigned to for users assigned to any Role (all)" do
 2303     setup_assigned_to_role
 2304     @query.add_filter('assigned_to_role', '*', [''])
 2305 
 2306     assert_query_result [@issue1, @issue2, @issue3], @query
 2307   end
 2308 
 2309   test "assigned_to_role filter should return issues with ! empty role" do
 2310     setup_assigned_to_role
 2311     @empty_role = Role.generate!
 2312     @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
 2313 
 2314     assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
 2315   end
 2316 
 2317   def test_query_column_should_accept_a_symbol_as_caption
 2318     set_language_if_valid 'en'
 2319     c = QueryColumn.new('foo', :caption => :general_text_Yes)
 2320     assert_equal 'Yes', c.caption
 2321   end
 2322 
 2323   def test_query_column_should_accept_a_proc_as_caption
 2324     c = QueryColumn.new('foo', :caption => lambda {'Foo'})
 2325     assert_equal 'Foo', c.caption
 2326   end
 2327 
 2328   def test_date_clause_should_respect_user_time_zone_with_local_default
 2329     @query = IssueQuery.new(:name => '_')
 2330 
 2331     # user is in Hawaii (-10)
 2332     User.current = users(:users_001)
 2333     User.current.pref.update_attribute :time_zone, 'Hawaii'
 2334 
 2335     # assume timestamps are stored in server local time
 2336     local_zone = Time.zone
 2337 
 2338     from = Date.parse '2016-03-20'
 2339     to = Date.parse '2016-03-22'
 2340     assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
 2341 
 2342     # the dates should have been interpreted in the user's time zone and
 2343     # converted to local time
 2344     # what we get exactly in the sql depends on the local time zone, therefore
 2345     # it's computed here.
 2346     f = User.current.time_zone.local(from.year, from.month, from.day).yesterday.end_of_day.in_time_zone(local_zone)
 2347     t = User.current.time_zone.local(to.year, to.month, to.day).end_of_day.in_time_zone(local_zone)
 2348     assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
 2349   end
 2350 
 2351   def test_date_clause_should_respect_user_time_zone_with_utc_default
 2352     @query = IssueQuery.new(:name => '_')
 2353 
 2354     # user is in Hawaii (-10)
 2355     User.current = users(:users_001)
 2356     User.current.pref.update_attribute :time_zone, 'Hawaii'
 2357 
 2358     # assume timestamps are stored as utc
 2359     ActiveRecord::Base.default_timezone = :utc
 2360 
 2361     from = Date.parse '2016-03-20'
 2362     to = Date.parse '2016-03-22'
 2363     assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
 2364     # the dates should have been interpreted in the user's time zone and
 2365     # converted to utc. March 20 in Hawaii begins at 10am UTC.
 2366     f = Time.new(2016, 3, 20, 9, 59, 59, 0).end_of_hour
 2367     t = Time.new(2016, 3, 23, 9, 59, 59, 0).end_of_hour
 2368     assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
 2369   ensure
 2370     ActiveRecord::Base.default_timezone = :local # restore Redmine default
 2371   end
 2372 
 2373   def test_filter_on_subprojects
 2374     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 2375     filter_name = "subproject_id"
 2376     assert_include filter_name, query.available_filters.keys
 2377 
 2378     # "is" operator should include issues of parent project + issues of the selected subproject
 2379     query.filters = {filter_name => {:operator => '=', :values => ['3']}}
 2380     issues = find_issues_with_query(query)
 2381     assert_equal [1, 2, 3, 5, 7, 8, 11, 12, 13, 14], issues.map(&:id).sort
 2382 
 2383     # "is not" operator should include issues of parent project + issues of all active subprojects - issues of the selected subprojects
 2384     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 2385     query.filters = {filter_name => {:operator => '!', :values => ['3']}}
 2386     issues = find_issues_with_query(query)
 2387     assert_equal [1, 2, 3, 6, 7, 8, 9, 10, 11, 12], issues.map(&:id).sort
 2388   end
 2389 
 2390   def test_filter_updated_on_none_should_return_issues_with_updated_on_equal_with_created_on
 2391     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 2392 
 2393     query.filters = {'updated_on' => {:operator => '!*', :values => ['']}}
 2394     issues = find_issues_with_query(query)
 2395     assert_equal [3, 6, 7, 8, 9, 10, 14], issues.map(&:id).sort
 2396   end
 2397 
 2398   def test_filter_updated_on_any_should_return_issues_with_updated_on_greater_than_created_on
 2399     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 2400 
 2401     query.filters = {'updated_on' => {:operator => '*', :values => ['']}}
 2402     issues = find_issues_with_query(query)
 2403     assert_equal [1, 2, 5, 11, 12, 13], issues.map(&:id).sort
 2404   end
 2405 
 2406   def test_issue_statuses_should_return_only_statuses_used_by_that_project
 2407     query = IssueQuery.new(:name => '_', :project => Project.find(1))
 2408     query.filters = {'status_id' => {:operator => '=', :values => []}}
 2409 
 2410     WorkflowTransition.delete_all
 2411     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
 2412     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
 2413     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
 2414     WorkflowTransition.create(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3)
 2415 
 2416     assert_equal ['1','2','3','4'], query.available_filters['status_id'][:values].map(&:second)
 2417   end
 2418 
 2419   def test_issue_statuses_without_project_should_return_all_statuses
 2420     query = IssueQuery.new(:name => '_')
 2421     query.filters = {'status_id' => {:operator => '=', :values => []}}
 2422 
 2423     WorkflowTransition.delete_all
 2424     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
 2425     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
 2426     WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
 2427     WorkflowTransition.create(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3)
 2428 
 2429     assert_equal ['1','2','3','4','5','6'], query.available_filters['status_id'][:values].map(&:second)
 2430   end
 2431 
 2432   def test_project_status_filter_should_be_available_in_global_queries
 2433     query = IssueQuery.new(:project => nil, :name => '_')
 2434     assert query.available_filters.has_key?('project.status')
 2435   end
 2436 
 2437   def test_project_status_filter_should_be_available_when_project_has_subprojects
 2438     query = IssueQuery.new(:project => Project.find(1), :name => '_')
 2439     assert query.available_filters.has_key?('project.status')
 2440   end
 2441 
 2442   def test_project_status_filter_should_not_be_available_when_project_is_leaf
 2443     query = IssueQuery.new(:project => Project.find(2), :name => '_')
 2444     assert !query.available_filters.has_key?('project.status')
 2445   end
 2446 
 2447   def test_project_statuses_values_should_return_only_active_and_closed_statuses
 2448     set_language_if_valid 'en'
 2449     query = IssueQuery.new(:project => nil, :name => '_')
 2450     project_status_filter = query.available_filters['project.status']
 2451     assert_not_nil project_status_filter
 2452 
 2453     assert_equal [["active", "1"], ["closed", "5"]], project_status_filter[:values]
 2454   end
 2455 
 2456   def test_as_params_should_serialize_query
 2457     query = IssueQuery.new(name: "_")
 2458     query.add_filter('subject', '!~', ['asdf'])
 2459     query.group_by = 'tracker'
 2460     query.totalable_names = %w(estimated_hours)
 2461     query.column_names = %w(id subject estimated_hours)
 2462     assert hsh = query.as_params
 2463 
 2464     new_query = IssueQuery.build_from_params(hsh)
 2465     assert_equal query.filters, new_query.filters
 2466     assert_equal query.group_by, new_query.group_by
 2467     assert_equal query.column_names, new_query.column_names
 2468     assert_equal query.totalable_names, new_query.totalable_names
 2469   end
 2470 
 2471   def test_issue_query_filter_by_spent_time
 2472     query = IssueQuery.new(:name => '_')
 2473 
 2474     query.filters = {'spent_time' => {:operator => '*', :values => ['']}}
 2475     assert_equal [3, 1], query.issues.pluck(:id)
 2476 
 2477     query.filters = {'spent_time' => {:operator => '!*', :values => ['']}}
 2478     assert_equal [13, 12, 11, 8, 7, 5, 2], query.issues.pluck(:id)
 2479 
 2480     query.filters = {'spent_time' => {:operator => '>=', :values => ['10']}}
 2481     assert_equal [1], query.issues.pluck(:id)
 2482 
 2483     query.filters = {'spent_time' => {:operator => '<=', :values => ['10']}}
 2484     assert_equal [13, 12, 11, 8, 7, 5, 3, 2], query.issues.pluck(:id)
 2485 
 2486     query.filters = {'spent_time' => {:operator => '><', :values => ['1', '2']}}
 2487     assert_equal [3], query.issues.pluck(:id)
 2488   end
 2489 
 2490   def test_issues_should_be_in_the_same_order_when_paginating
 2491     q = IssueQuery.new
 2492     q.sort_criteria = {'0' => ['priority', 'desc']}
 2493     issue_ids = q.issues.pluck(:id)
 2494     paginated_issue_ids = []
 2495     # Test with a maximum of 2 records per page.
 2496     ((q.issue_count / 2) + 1).times do |i|
 2497       paginated_issue_ids += q.issues(:offset => (i * 2), :limit => 2).pluck(:id)
 2498     end
 2499 
 2500     # Non-paginated issue ids and paginated issue ids should be in the same order.
 2501     assert_equal issue_ids, paginated_issue_ids
 2502   end
 2503 end